diff --git a/package.nls.json b/package.nls.json index eecc47c..2e12aa4 100644 --- a/package.nls.json +++ b/package.nls.json @@ -109,6 +109,7 @@ "treeView.tooltip.folder": "$(folder) Folder {0}\n\n", "commit": "commit", "branch": "branch", + "remoteBranch": "remote branch", "tag": "tag", "commitHash": "commit hash", "treeView.tooltip.error": "$(error) Detached from the git version\n\n", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index d160d08..2849717 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -109,6 +109,7 @@ "treeView.tooltip.folder": "$(folder) 路径 {0}\n\n", "commit": "提交", "branch": "分支", + "remoteBranch": "远程分支", "tag": "标记", "commitHash": "提交hash", "treeView.tooltip.error": "$(error) 已从 git 版本中分离\n\n", diff --git a/src/lib/commands.ts b/src/lib/commands.ts index 26ba2e0..d7b9cd7 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands.ts @@ -567,13 +567,13 @@ const searchAllWorktreeCmd = () => { }; const pushWorkTreeCmd = (item?: WorkTreeItem) => { - if (!item) return; - pullOrPushAction('push', item.name, item.path); + if (!item?.remoteRef) return; + pullOrPushAction('push', item.remoteRef, item.path); }; const pullWorkTreeCmd = (item?: WorkTreeItem) => { - if (!item) return; - pullOrPushAction('pull', item.name, item.path); + if (!item?.remoteRef) return; + pullOrPushAction('pull', item.remoteRef, item.path); }; const loadAllTreeDataCmd = (item?: ILoadMoreItem) => { diff --git a/src/lib/quickPick.ts b/src/lib/quickPick.ts index 82cffbb..a19fb45 100644 --- a/src/lib/quickPick.ts +++ b/src/lib/quickPick.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { getBranchList, getRemoteBranchList, getTagList, formatTime, getWorkTreeList, checkGitValid } from '@/utils'; +import { formatTime, getWorkTreeList, checkGitValid, getAllRefList } from '@/utils'; import { GlobalState } from '@/lib/globalState'; import { IWorkTreeCacheItem } from '@/types'; import localize from '@/localize'; @@ -42,22 +42,32 @@ export const pickBranch = async ( }); quickPick.show(); quickPick.busy = true; - // TODO 使用 git for-each-ref 获取所有分支和tag - const [branchList, remoteBranchList, tagList] = await Promise.all([ - getBranchList(['refname:short', 'objectname:short', 'worktreepath', 'authordate', 'HEAD'], cwd), - getRemoteBranchList(['refname:short', 'objectname:short'], cwd), - getTagList(['refname:short', 'objectname:short'], cwd), - ]); - + // 使用 git for-each-ref 获取所有分支和tag + const allRefList = await getAllRefList( + ['refname', 'objectname:short', 'worktreepath', 'authordate', 'HEAD'], + cwd, + ); + type RefList = typeof allRefList; + let branchList: RefList = []; + let remoteBranchList: RefList = []; + let tagList: RefList = []; + allRefList.forEach((item) => { + if (item.refname.startsWith('refs/heads/')) { + branchList.push(item); + } else if (item.refname.startsWith('refs/remotes/')) { + remoteBranchList.push(item); + } else if (item.refname.startsWith('refs/tags/')) { + tagList.push(item); + } + }); if (!branchList) { quickPick.hide(); return; } - const branchItems: BranchForWorkTree[] = branchList .filter((i) => !i.worktreepath && i.HEAD !== '*') .map((item) => { - const shortRefName = item['refname:short'].replace(/^heads\//, ''); + const shortRefName = item['refname'].replace('refs/heads/', ''); return { label: shortRefName, description: `$(git-commit) ${item['objectname:short']} $(circle-small-filled) ${formatTime( @@ -84,14 +94,15 @@ export const pickBranch = async ( ...branchList .filter((i) => i.worktreepath) .map((item) => { + const shortName = item['refname'].replace('refs/heads/', ''); return { - label: item['refname:short'], + label: shortName, description: `$(git-commit) ${item['objectname:short']} $(circle-small-filled) ${formatTime( item.authordate, )}`, iconPath: new vscode.ThemeIcon('source-control'), hash: item['objectname:short'], - branch: item['refname:short'], + branch: shortName, }; }), { @@ -102,18 +113,18 @@ export const pickBranch = async ( const remoteBranchItems: BranchForWorkTree[] = remoteBranchList.map((item) => { return { - label: item['refname:short'].replace(/^remotes\//, ''), + label: item['refname'].replace('refs/remotes/', ''), iconPath: new vscode.ThemeIcon('cloud'), - description: item['objectname:short'], + description: item['objectname:short'] + ' ' + localize('remoteBranch'), hash: item['objectname:short'], }; }); const tagItems: BranchForWorkTree[] = tagList.map((item) => { return { - label: item['refname:short'].replace(/^tags\//, ''), + label: item['refname'].replace('refs/tags/', ''), iconPath: new vscode.ThemeIcon('tag'), - description: item['objectname:short'], + description: item['objectname:short'] + ' ' + localize('tag'), hash: item['objectname:short'], }; }); diff --git a/src/lib/treeView.ts b/src/lib/treeView.ts index 466cdc9..c0a5446 100644 --- a/src/lib/treeView.ts +++ b/src/lib/treeView.ts @@ -23,6 +23,7 @@ export class WorkTreeItem extends vscode.TreeItem { name: string; type = TreeItemKind.worktree; parent?: GitFolderItem; + remoteRef?: string; constructor(item: IWorkTreeDetail, collapsible: vscode.TreeItemCollapsibleState, parent?: GitFolderItem) { let finalName = item.folderName ? `${item.name} ⇄ ${item.folderName}` : item.name; super(finalName, collapsible); @@ -54,6 +55,7 @@ export class WorkTreeItem extends vscode.TreeItem { this.path = item.path; this.name = item.name; + this.remoteRef = item.remoteRef; this.tooltip = new vscode.MarkdownString('', true); this.tooltip.appendMarkdown(localize('treeView.tooltip.folder', item.path)); diff --git a/src/types.ts b/src/types.ts index 79efb2e..6aea49e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,7 @@ export interface IWorkTreeDetail { folderName?: string; ahead?: number; behind?: number; + remoteRef?: string; } export interface IWorkTreeOutputItem { diff --git a/src/utils.ts b/src/utils.ts index 225d86b..e71a372 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -133,16 +133,11 @@ export async function getWorkTreeList(root?: string, skipRemote?: boolean): Prom let detailList = await Promise.all( (list as unknown as IWorkTreeOutputItem[]).map(async (item) => { const branchName = item.branch?.replace('refs/heads/', '') || ''; + const remoteBranchName = `${remoteName}/${branchName}`; + const hasRemote = remoteBranchMap.has(`refs/remotes/${remoteBranchName}`); const [aheadBehind, nameRev] = await Promise.all([ - !skipRemote && - branchName && - remoteName && - remoteBranchMap.has(`refs/remotes/${remoteName}/${branchName}`) - ? getAheadBehindCommitCount( - branchName, - `refs/remotes/${remoteName}/${branchName}`, - item.worktree, - ) + !skipRemote && branchName && remoteName && hasRemote + ? getAheadBehindCommitCount(branchName, `refs/remotes/${remoteBranchName}`, item.worktree) : Promise.resolve(void 0), !branchName ? getNameRev(item.worktree) : Promise.resolve(''), ]); @@ -170,6 +165,7 @@ export async function getWorkTreeList(root?: string, skipRemote?: boolean): Prom ahead: aheadBehind?.ahead, behind: aheadBehind?.behind, hash: item.HEAD, + remoteRef: hasRemote ? remoteBranchName : void 0, }; }), ); @@ -199,52 +195,12 @@ export function parseOutput(output: string, keyList: T[]): Rec return workTrees; } -export async function getBranchList(keys: T[], cwd?: string) { - try { - let output = await executeGitCommandAuto(cwd, [ - 'branch', - `--format=${formatQuery(keys)}`, - '--sort=-committerdate', - ]); - return parseOutput(output, keys); - } catch { - return []; - } -} - -export async function getRemoteBranchList(keys: T[], cwd?: string) { - try { - let output = await executeGitCommandAuto(cwd, [ - 'branch', - '-r', - `--format=${formatQuery(keys)}`, - '--sort=-committerdate', - ]); - return parseOutput(output, keys); - } catch { - return []; - } -} - -export async function getTagList(keys: T[], cwd?: string) { - try { - let output = await executeGitCommandAuto(cwd, [ - 'tag', - '--list', - `--format=${formatQuery(keys)}`, - '--sort=-committerdate', - ]); - return parseOutput(output, keys); - } catch { - return []; - } -} - export async function getAllRefList(keys: T[], cwd?: string) { try { let output = await executeGitCommandAuto(cwd, [ 'for-each-ref', `--format=${formatQuery(keys)}`, + '--sort=-refname:lstrip=2', '--sort=-committerdate', ]); return parseOutput(output, keys); @@ -351,21 +307,21 @@ export const checkoutBranch = (cwd: string, branchName: string, ...args: string[ return executeGitCommandAuto(cwd, ['switch', '--ignore-other-worktrees', ...list]); }; -export const pullBranch = (remoteName: string, branchName: string, remoteBranchName: string, cwd?: string) => { +export const pullBranch = (remoteName: string, branchName: string, cwd?: string) => { const token = new vscode.CancellationTokenSource(); actionProgressWrapper( localize('cmd.pullWorkTree'), - () => executeGitCommandAuto(cwd, ['pull', remoteName, `${remoteBranchName}:${branchName}`], token.token), + () => executeGitCommandAuto(cwd, ['pull', remoteName, `${branchName}:${branchName}`], token.token), updateTreeDataEvent.fire.bind(updateTreeDataEvent), token, ); }; -export const pushBranch = (remoteName: string, localBranchName: string, remoteBranchName: string, cwd?: string) => { +export const pushBranch = (remoteName: string, branchName: string, cwd?: string) => { const token = new vscode.CancellationTokenSource(); actionProgressWrapper( localize('cmd.pushWorkTree'), - () => executeGitCommandAuto(cwd, ['push', remoteName, `${localBranchName}:${remoteBranchName}`], token.token), + () => executeGitCommandAuto(cwd, ['push', remoteName, `${branchName}:${branchName}`], token.token), updateTreeDataEvent.fire.bind(updateTreeDataEvent), token, ); @@ -393,18 +349,8 @@ export const checkExist = (path: string) => { .catch(() => false); }; -export const pullOrPushAction = async (action: 'pull' | 'push', branchName: string, cwd: string) => { - const remoteBranchList = await getRemoteBranchList(['refname:short'], cwd); - const item = remoteBranchList.find((row) => { - const [remoteName, ...remoteBranchNameArgs] = row['refname:short'].replace(/^remotes\//, '').split('/'); - return remoteBranchNameArgs.join('/').toLowerCase() === branchName.toLowerCase(); - }); - if (!item) { - return false; - } - const [remoteName, ...remoteBranchNameArgs] = item['refname:short'].replace(/^remotes\//, '').split('/'); - const remoteBranchName = remoteBranchNameArgs.join('/'); - return action === 'pull' - ? pullBranch(remoteName, branchName, remoteBranchName, cwd) - : pushBranch(remoteName, branchName, remoteBranchName, cwd); +export const pullOrPushAction = async (action: 'pull' | 'push', refName: string, cwd: string) => { + const [remoteName, ...remoteBranchNameArgs] = refName.split('/'); + const branchName = remoteBranchNameArgs.join('/'); + return action === 'pull' ? pullBranch(remoteName, branchName, cwd) : pushBranch(remoteName, branchName, cwd); };