diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index 259fa11..8549b4b 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -12,7 +12,6 @@ import { GitContributor, GitLog, GitReference, - GitRemote, GitRevision, GitRevisionReference, GitStash, @@ -23,7 +22,7 @@ import { Repository, TagSortOptions, } from '../git/models'; -import { RemoteProvider, RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/remotes/provider'; import { SearchPattern } from '../git/search'; import { BranchQuickPickItem, @@ -57,6 +56,7 @@ import { GitCommandQuickPickItem, OpenChangedFilesCommandQuickPickItem, OpenRemoteResourceCommandQuickPickItem, + QuickPickSeparator, ReferencesQuickPickItem, RefQuickPickItem, RepositoryQuickPickItem, @@ -227,54 +227,79 @@ export async function getBranchesAndOrTags( if ((branches == null || branches.length === 0) && (tags == null || tags.length === 0)) return []; if (branches != null && branches.length !== 0 && (tags == null || tags.length === 0)) { - return Promise.all( - branches.map(b => - BranchQuickPickItem.create( - b, - picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), - { - buttons: buttons, - current: singleRepo ? 'checkmark' : false, - ref: singleRepo, - status: singleRepo, - type: 'remote', - }, - ), - ), - ); + return [ + QuickPickSeparator.create('Branches'), + ...(await Promise.all( + branches + .filter(b => !b.remote) + .map(b => + BranchQuickPickItem.create( + b, + picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), + { + buttons: buttons, + current: singleRepo ? 'checkmark' : false, + ref: singleRepo, + status: singleRepo, + type: 'remote', + }, + ), + ), + )), + QuickPickSeparator.create('Remote Branches'), + ...(await Promise.all( + branches + .filter(b => b.remote) + .map(b => + BranchQuickPickItem.create( + b, + picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), + { + buttons: buttons, + current: singleRepo ? 'checkmark' : false, + ref: singleRepo, + status: singleRepo, + type: 'remote', + }, + ), + ), + )), + ] as BranchQuickPickItem[]; } if (tags != null && tags.length !== 0 && (branches == null || branches.length === 0)) { - return Promise.all( - tags.map(t => - TagQuickPickItem.create( - t, - picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)), - { - buttons: buttons, - message: false, //singleRepo, - ref: singleRepo, - }, - ), + return tags.map(t => + TagQuickPickItem.create( + t, + picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)), + { + buttons: buttons, + message: false, //singleRepo, + ref: singleRepo, + }, ), ); } - return Promise.all([ - ...branches! - .filter(b => !b.remote) - .map(b => - BranchQuickPickItem.create( - b, - picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), - { - buttons: buttons, - current: singleRepo ? 'checkmark' : false, - ref: singleRepo, - status: singleRepo, - }, + return [ + QuickPickSeparator.create('Branches'), + ...(await Promise.all( + branches! + .filter(b => !b.remote) + .map(b => + BranchQuickPickItem.create( + b, + picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), + { + buttons: buttons, + current: singleRepo ? 'checkmark' : false, + ref: singleRepo, + status: singleRepo, + }, + ), ), - ), + )), + QuickPickSeparator.create('Tags'), ...tags!.map(t => TagQuickPickItem.create( t, @@ -287,22 +312,25 @@ export async function getBranchesAndOrTags( }, ), ), - ...branches! - .filter(b => b.remote) - .map(b => - BranchQuickPickItem.create( - b, - picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), - { - buttons: buttons, - current: singleRepo ? 'checkmark' : false, - ref: singleRepo, - status: singleRepo, - type: 'remote', - }, + QuickPickSeparator.create('Remote Branches'), + ...(await Promise.all( + branches! + .filter(b => b.remote) + .map(b => + BranchQuickPickItem.create( + b, + picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)), + { + buttons: buttons, + current: singleRepo ? 'checkmark' : false, + ref: singleRepo, + status: singleRepo, + type: 'remote', + }, + ), ), - ), - ]); + )), + ] as (BranchQuickPickItem | TagQuickPickItem)[]; } export function getValidateGitReferenceFn( @@ -1386,10 +1414,10 @@ export async function* showCommitOrStashStep< placeholder: GitReference.toString(state.reference, { capitalize: true, icon: false }), ignoreFocusOut: true, items: await getShowCommitOrStashStepItems(state), - additionalButtons: GitReference.isStash(state.reference) - ? [QuickCommandButtons.RevealInSideBar] - : [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], - onDidClickButton: (quickpick, button) => { + // additionalButtons: GitReference.isStash(state.reference) + // ? [QuickCommandButtons.RevealInSideBar] + // : [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], + onDidClickItemButton: (quickpick, button, _item) => { if (button === QuickCommandButtons.SearchInSideBar) { void Container.instance.searchAndCompareView.search( state.repo.path, @@ -1436,19 +1464,19 @@ export async function* showCommitOrStashStep< async function getShowCommitOrStashStepItems< State extends PartialStepState & { repo: Repository; reference: GitCommit | GitStashCommit }, ->(state: State) { - const items: CommandQuickPickItem[] = [new CommitFilesQuickPickItem(state.reference)]; +>(state: State): Promise { + const items: (CommandQuickPickItem | QuickPickSeparator)[] = [new CommitFilesQuickPickItem(state.reference)]; - const branch = await Container.instance.git.getBranch(state.repo.path); - let remotes: GitRemote[] | undefined; - - let isStash = false; if (GitCommit.isStash(state.reference)) { - isStash = true; - - items.push(new RevealInSideBarQuickPickItem(state.reference)); + items.push( + QuickPickSeparator.create(), + new RevealInSideBarQuickPickItem(state.reference), + QuickPickSeparator.create(), + new CommitCopyMessageQuickPickItem(state.reference), + ); items.push( + QuickPickSeparator.create('Actions'), new GitCommandQuickPickItem('Apply Stash...', { command: 'stash', state: { @@ -1468,13 +1496,34 @@ async function getShowCommitOrStashStepItems< }), ); } else { - remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true }); - items.push( + QuickPickSeparator.create(), new RevealInSideBarQuickPickItem(state.reference), new SearchForCommitQuickPickItem(state.reference), ); + const remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true }); + if (remotes?.length) { + items.push( + QuickPickSeparator.create(), + new OpenRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Commit, + sha: state.reference.sha, + }), + new CopyRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Commit, + sha: state.reference.sha, + }), + ); + } + + items.push( + QuickPickSeparator.create(), + new CommitCopyIdQuickPickItem(state.reference), + new CommitCopyMessageQuickPickItem(state.reference), + ); + + const branch = await Container.instance.git.getBranch(state.repo.path); const branches = branch != null ? await Container.instance.git.getCommitBranches(state.repo.path, state.reference.ref, { @@ -1482,8 +1531,26 @@ async function getShowCommitOrStashStepItems< commitDate: GitCommit.is(state.reference) ? state.reference.committer.date : undefined, }) : undefined; + const commitOnCurrentBranch = Boolean(branches?.length); + + if (commitOnCurrentBranch) { + items.push(QuickPickSeparator.create('Actions')); + + const unpublished = !branch?.remote && branch?.upstream && !(await state.reference.isPushed()); + if (unpublished) { + // TODO@eamodio Add Undo commit, if HEAD & unpushed + + items.push( + new GitCommandQuickPickItem('Push to Commit...', { + command: 'push', + state: { + repos: state.repo, + reference: state.reference, + }, + }), + ); + } - if (branches?.length) { items.push( new GitCommandQuickPickItem('Revert Commit...', { command: 'revert', @@ -1492,17 +1559,6 @@ async function getShowCommitOrStashStepItems< references: [state.reference], }, }), - new GitCommandQuickPickItem('Reset Commit...', { - command: 'reset', - state: { - repo: state.repo, - reference: GitReference.create(`${state.reference.ref}^`, state.reference.repoPath, { - refType: 'revision', - name: `${state.reference.name}^`, - message: state.reference.message, - }), - }, - }), new GitCommandQuickPickItem(`Reset ${branch?.name ?? 'Current Branch'} to Commit...`, { command: 'reset', state: { @@ -1510,11 +1566,15 @@ async function getShowCommitOrStashStepItems< reference: state.reference, }, }), - new GitCommandQuickPickItem('Push to Commit...', { - command: 'push', + new GitCommandQuickPickItem(`Reset ${branch?.name ?? 'Current Branch'} to Previous Commit...`, { + command: 'reset', state: { - repos: state.repo, - reference: state.reference, + repo: state.repo, + reference: GitReference.create(`${state.reference.ref}^`, state.reference.repoPath, { + refType: 'revision', + name: `${state.reference.name}^`, + message: state.reference.message, + }), }, }), ); @@ -1545,6 +1605,10 @@ async function getShowCommitOrStashStepItems< reference: state.reference, }, }), + ); + + items.push( + QuickPickSeparator.create(), new GitCommandQuickPickItem('Create Branch at Commit...', { command: 'branch', state: { @@ -1565,27 +1629,29 @@ async function getShowCommitOrStashStepItems< } items.push( + QuickPickSeparator.create('Open Changes'), new CommitOpenAllChangesCommandQuickPickItem(state.reference), - new CommitOpenAllChangesWithDiffToolCommandQuickPickItem(state.reference), new CommitOpenAllChangesWithWorkingCommandQuickPickItem(state.reference), - - new CommitOpenDirectoryCompareCommandQuickPickItem(state.reference), - new CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem(state.reference), - + new CommitOpenAllChangesWithDiffToolCommandQuickPickItem(state.reference), + QuickPickSeparator.create(), new CommitOpenFilesCommandQuickPickItem(state.reference), new CommitOpenRevisionsCommandQuickPickItem(state.reference), ); - if (remotes?.length) { - items.push( - new OpenRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Commit, - sha: state.reference.sha, - }), - ); - } + items.push( + QuickPickSeparator.create('Compare'), + new CommitCompareWithHEADCommandQuickPickItem(state.reference), + new CommitCompareWithWorkingCommandQuickPickItem(state.reference), + ); items.push( + QuickPickSeparator.create(), + new CommitOpenDirectoryCompareCommandQuickPickItem(state.reference), + new CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem(state.reference), + ); + + items.push( + QuickPickSeparator.create('Browse'), new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: false }), new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { before: true, @@ -1596,26 +1662,9 @@ async function getShowCommitOrStashStepItems< before: true, openInNewWindow: true, }), - - new CommitCompareWithHEADCommandQuickPickItem(state.reference), - new CommitCompareWithWorkingCommandQuickPickItem(state.reference), ); - if (!isStash) { - items.push(new CommitCopyIdQuickPickItem(state.reference)); - } - items.push(new CommitCopyMessageQuickPickItem(state.reference)); - - if (remotes?.length) { - items.push( - new CopyRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Commit, - sha: state.reference.sha, - }), - ); - } - - return items; + return items as CommandQuickPickItem[]; } export function* showCommitOrStashFilesStep< @@ -1652,8 +1701,8 @@ export function* showCommitOrStashFilesStep< ) ?? []), ], matchOnDescription: true, - additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], - onDidClickButton: (quickpick, button) => { + // additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], + onDidClickItemButton: (quickpick, button, _item) => { if (button === QuickCommandButtons.SearchInSideBar) { void Container.instance.searchAndCompareView.search( state.repo.path, @@ -1724,8 +1773,8 @@ export async function* showCommitOrStashFileStep< ignoreFocusOut: true, items: await getShowCommitOrStashFileStepItems(state), matchOnDescription: true, - additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], - onDidClickButton: (quickpick, button) => { + // additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], + onDidClickItemButton: (quickpick, button, _item) => { if (button === QuickCommandButtons.SearchInSideBar) { void Container.instance.searchAndCompareView.search( state.repo.path, @@ -1780,33 +1829,70 @@ async function getShowCommitOrStashFileStepItems< const file = await state.reference.findFile(state.fileName); if (file == null) return []; - const items: CommandQuickPickItem[] = [ + const items: (CommandQuickPickItem | QuickPickSeparator)[] = [ new CommitFilesQuickPickItem(state.reference, undefined, GitUri.getFormattedFileName(state.fileName)), ]; - let remotes: GitRemote[] | undefined; - - let isStash = false; - if (GitCommit.is(state.reference)) { - isStash = true; - - items.push(new RevealInSideBarQuickPickItem(state.reference)); + if (GitCommit.isStash(state.reference)) { + items.push( + QuickPickSeparator.create(), + new RevealInSideBarQuickPickItem(state.reference), + QuickPickSeparator.create(), + new CommitCopyMessageQuickPickItem(state.reference), + QuickPickSeparator.create('Actions'), + ); } else { - remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true }); - items.push( + QuickPickSeparator.create(), new RevealInSideBarQuickPickItem(state.reference), new SearchForCommitQuickPickItem(state.reference), + ); + + const remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true }); + if (remotes?.length) { + items.push( + QuickPickSeparator.create(), + new OpenRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Revision, + fileName: state.fileName, + commit: state.reference, + }), + new OpenRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Commit, + sha: state.reference.ref, + }), + QuickPickSeparator.create(), + new CopyRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Revision, + fileName: state.fileName, + commit: state.reference, + }), + new CopyRemoteResourceCommandQuickPickItem(remotes, { + type: RemoteResourceType.Commit, + sha: state.reference.sha, + }), + ); + } - new CommitApplyFileChangesCommandQuickPickItem(state.reference, file), - new CommitRestoreFileChangesCommandQuickPickItem(state.reference, file), + items.push( + QuickPickSeparator.create(), + new CommitCopyIdQuickPickItem(state.reference), + new CommitCopyMessageQuickPickItem(state.reference), + QuickPickSeparator.create('Actions'), ); } items.push( + new CommitApplyFileChangesCommandQuickPickItem(state.reference, file), + new CommitRestoreFileChangesCommandQuickPickItem(state.reference, file), + ); + + items.push( + QuickPickSeparator.create('Open Changes'), new CommitOpenChangesCommandQuickPickItem(state.reference, state.fileName), - new CommitOpenChangesWithDiffToolCommandQuickPickItem(state.reference, state.fileName), new CommitOpenChangesWithWorkingCommandQuickPickItem(state.reference, state.fileName), + new CommitOpenChangesWithDiffToolCommandQuickPickItem(state.reference, state.fileName), + QuickPickSeparator.create(), ); if (file.status !== 'D') { @@ -1814,21 +1900,14 @@ async function getShowCommitOrStashFileStepItems< } items.push(new CommitOpenRevisionCommandQuickPickItem(state.reference, file)); - if (remotes?.length) { - items.push( - new OpenRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Revision, - fileName: state.fileName, - commit: state.reference, - }), - new OpenRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Commit, - sha: state.reference.ref, - }), - ); - } + items.push( + QuickPickSeparator.create('Compare'), + new CommitCompareWithHEADCommandQuickPickItem(state.reference), + new CommitCompareWithWorkingCommandQuickPickItem(state.reference), + ); items.push( + QuickPickSeparator.create('Browse'), new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: false }), new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { before: true, @@ -1839,31 +1918,9 @@ async function getShowCommitOrStashFileStepItems< before: true, openInNewWindow: true, }), - - new CommitCompareWithHEADCommandQuickPickItem(state.reference), - new CommitCompareWithWorkingCommandQuickPickItem(state.reference), ); - if (!isStash) { - items.push(new CommitCopyIdQuickPickItem(state.reference)); - } - items.push(new CommitCopyMessageQuickPickItem(state.reference)); - - if (remotes?.length) { - items.push( - new CopyRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Commit, - sha: state.reference.sha, - }), - new CopyRemoteResourceCommandQuickPickItem(remotes, { - type: RemoteResourceType.Revision, - fileName: state.fileName, - commit: state.reference, - }), - ); - } - - return items; + return items as CommandQuickPickItem[]; } export function* showRepositoryStatusStep< diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index b1af3ab..bf3975a 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -75,11 +75,15 @@ function defaultExceptionHandler(ex: Error, cwd: string | undefined, start?: [nu throw ex; } +type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly: true }; + export class Git { // A map of running git commands -- avoids running duplicate overlaping commands private readonly pendingCommands = new Map>(); - async git(options: GitCommandOptions, ...args: any[]): Promise { + async git(options: ExitCodeOnlyGitCommandOptions, ...args: any[]): Promise; + async git(options: GitCommandOptions, ...args: any[]): Promise; + async git(options: GitCommandOptions, ...args: any[]): Promise { const start = hrtime(); const { configs, correlationKey, errors: errorHandling, encoding, ...opts } = options; @@ -123,7 +127,7 @@ export class Git { args.splice(0, 0, '-c', 'core.longpaths=true'); } - promise = run(await this.path(), args, encoding ?? 'utf8', runOpts); + promise = run(await this.path(), args, encoding ?? 'utf8', runOpts); this.pendingCommands.set(command, promise); } else { @@ -133,14 +137,14 @@ export class Git { let exception: Error | undefined; try { - return (await promise) as TOut; + return (await promise) as T; } catch (ex) { exception = ex; switch (errorHandling) { case GitErrorHandling.Ignore: exception = undefined; - return '' as TOut; + return '' as T; case GitErrorHandling.Throw: throw ex; @@ -148,7 +152,7 @@ export class Git { default: { const result = defaultExceptionHandler(ex, options.cwd, start); exception = undefined; - return result as TOut; + return result as T; } } } finally { @@ -1035,15 +1039,21 @@ export class Git { return data.length === 0 ? undefined : data.trim(); } - merge_base(repoPath: string, ref1: string, ref2: string, { forkPoint }: { forkPoint?: boolean } = {}) { + merge_base(repoPath: string, ref1: string, ref2: string, options?: { forkPoint?: boolean }) { const params = ['merge-base']; - if (forkPoint) { + if (options?.forkPoint) { params.push('--fork-point'); } return this.git({ cwd: repoPath }, ...params, ref1, ref2); } + async merge_base__is_ancestor(repoPath: string, ref1: string, ref2: string): Promise { + const params = ['merge-base', '--is-ancestor']; + const exitCode = await this.git({ cwd: repoPath, exitCodeOnly: true }, ...params, ref1, ref2); + return exitCode === 0; + } + reflog( repoPath: string, { diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 8d8e17d..b32282d 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -3261,6 +3261,13 @@ export class LocalGitProvider implements GitProvider, Disposable { return branches.length !== 0 || tags.length !== 0; } + @log() + async hasCommitBeenPushed(repoPath: string, ref: string): Promise { + if (repoPath == null) return false; + + return this.git.merge_base__is_ancestor(repoPath, ref, '@{u}'); + } + isTrackable(uri: Uri): boolean { return this.supportedSchemes.has(uri.scheme); } diff --git a/src/env/node/git/shell.ts b/src/env/node/git/shell.ts index bc7dc9e..58ef5a5 100644 --- a/src/env/node/git/shell.ts +++ b/src/env/node/git/shell.ts @@ -170,16 +170,36 @@ export class RunError extends Error { } } -export function run( +type ExitCodeOnlyRunOptions = RunOptions & { exitCodeOnly: true }; + +export function run( + command: string, + args: any[], + encoding: BufferEncoding | 'buffer' | string, + options: ExitCodeOnlyRunOptions, +): Promise; +export function run( command: string, args: any[], encoding: BufferEncoding | 'buffer' | string, - options: RunOptions = {}, -): Promise { + options?: RunOptions, +): Promise; +export function run( + command: string, + args: any[], + encoding: BufferEncoding | 'buffer' | string, + options?: RunOptions & { exitCodeOnly?: boolean }, +): Promise { const { stdin, stdinEncoding, ...opts }: RunOptions = { maxBuffer: 100 * 1024 * 1024, ...options }; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const proc = execFile(command, args, opts, (error: ExecException | null, stdout, stderr) => { + if (options?.exitCodeOnly) { + resolve((error?.code ?? proc.exitCode) as T); + + return; + } + if (error != null) { if (bufferExceededRegex.test(error.message)) { error.message = `Command output exceeded the allocated stdout buffer. Set 'options.maxBuffer' to a larger value than ${opts.maxBuffer} bytes`; @@ -206,8 +226,8 @@ export function run( resolve( encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer' - ? (stdout as TOut) - : (decode(Buffer.from(stdout, 'binary'), encoding) as TOut), + ? (stdout as T) + : (decode(Buffer.from(stdout, 'binary'), encoding) as T), ); }); diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 6f59573..dda935b 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -360,6 +360,7 @@ export interface GitProvider extends Disposable { }, ): Promise; + hasCommitBeenPushed(repoPath: string, ref: string): Promise; isTrackable(uri: Uri): boolean; getDiffTool(repoPath?: string): Promise; diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index 4a5c6ae..509ac45 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -1777,6 +1777,14 @@ export class GitProviderService implements Disposable { return provider.hasBranchOrTag(path, options); } + @log({ args: { 1: false } }) + async hasCommitBeenPushed(repoPath: string | Uri, ref: string): Promise { + if (repoPath == null) return false; + + const { provider, path } = this.getProvider(repoPath); + return provider.hasCommitBeenPushed(path, ref); + } + @log() async hasRemotes(repoPath: string | Uri | undefined): Promise { if (repoPath == null) return false; diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts index fbd85c9..b60be69 100644 --- a/src/git/models/commit.ts +++ b/src/git/models/commit.ts @@ -411,6 +411,10 @@ export class GitCommit implements GitRevisionReference { : Promise.resolve(undefined); } + async isPushed(): Promise { + return this.container.git.hasCommitBeenPushed(this.repoPath, this.ref); + } + with(changes: { sha?: string; parents?: string[]; diff --git a/src/premium/github/githubGitProvider.ts b/src/premium/github/githubGitProvider.ts index 88c4fda..2a3def7 100644 --- a/src/premium/github/githubGitProvider.ts +++ b/src/premium/github/githubGitProvider.ts @@ -1928,6 +1928,12 @@ export class GitHubGitProvider implements GitProvider, Disposable { return branches.length !== 0 || tags.length !== 0; } + @log() + async hasCommitBeenPushed(_repoPath: string, _ref: string): Promise { + // In this env we can't have unpushed commits + return true; + } + isTrackable(uri: Uri): boolean { return this.supportedSchemes.has(uri.scheme); } diff --git a/src/quickpicks/commitQuickPickItems.ts b/src/quickpicks/commitQuickPickItems.ts index 9d3ec30..7ece22f 100644 --- a/src/quickpicks/commitQuickPickItems.ts +++ b/src/quickpicks/commitQuickPickItems.ts @@ -1,5 +1,6 @@ import { QuickPickItem, window } from 'vscode'; import { Commands, GitActions, OpenChangedFilesCommandArgs } from '../commands'; +import { QuickCommandButtons } from '../commands/quickCommand.buttons'; import { GlyphChars } from '../constants'; import { Container } from '../container'; import { CommitFormatter } from '../git/formatters'; @@ -20,7 +21,11 @@ export class CommitFilesQuickPickItem extends CommandQuickPickItem { separator: ', ', empty: 'No files changed', })}${fileName ? `${Strings.pad(GlyphChars.Dot, 2, 2)}${fileName}` : ''}`, + alwaysShow: true, picked: picked, + buttons: GitCommit.isStash(commit) + ? [QuickCommandButtons.RevealInSideBar] + : [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar], }, undefined, undefined, @@ -117,7 +122,7 @@ export class CommitCompareWithWorkingCommandQuickPickItem extends CommandQuickPi export class CommitCopyIdQuickPickItem extends CommandQuickPickItem { constructor(private readonly commit: GitCommit, item?: QuickPickItem) { - super(item ?? '$(clippy) Copy SHA'); + super(item ?? '$(copy) Copy SHA'); } override execute(): Promise { @@ -132,7 +137,7 @@ export class CommitCopyIdQuickPickItem extends CommandQuickPickItem { export class CommitCopyMessageQuickPickItem extends CommandQuickPickItem { constructor(private readonly commit: GitCommit, item?: QuickPickItem) { - super(item ?? '$(clippy) Copy Message'); + super(item ?? '$(copy) Copy Message'); } override execute(): Promise { diff --git a/src/quickpicks/gitQuickPickItems.ts b/src/quickpicks/gitQuickPickItems.ts index a6ed3c4..c77d20e 100644 --- a/src/quickpicks/gitQuickPickItems.ts +++ b/src/quickpicks/gitQuickPickItems.ts @@ -39,7 +39,7 @@ export namespace BranchQuickPickItem { export async function create( branch: GitBranch, picked?: boolean, - options: { + options?: { alwaysShow?: boolean; buttons?: QuickInputButton[]; checked?: boolean; @@ -47,28 +47,28 @@ export namespace BranchQuickPickItem { ref?: boolean; status?: boolean; type?: boolean | 'remote'; - } = {}, - ) { + }, + ): Promise { let description = ''; - if (options.type === true) { - if (options.current === true && branch.current) { + if (options?.type === true) { + if (options?.current === true && branch.current) { description = 'current branch'; } else { description = 'branch'; } - } else if (options.type === 'remote') { + } else if (options?.type === 'remote') { if (branch.remote) { description = 'remote branch'; } - } else if (options.current === true && branch.current) { + } else if (options?.current === true && branch.current) { description = 'current branch'; } - if (options.status && !branch.remote && branch.upstream != null) { + if (options?.status && !branch.remote && branch.upstream != null) { let arrows = GlyphChars.Dash; - const remote = await branch.getRemote(); if (!branch.upstream.missing) { + const remote = await branch.getRemote(); if (remote != null) { let left; let right; @@ -102,7 +102,7 @@ export namespace BranchQuickPickItem { description = `${description ? `${description}${GlyphChars.Space.repeat(2)}${status}` : status}`; } - if (options.ref) { + if (options?.ref) { if (branch.sha) { description = description ? `${description}${pad('$(git-commit)', 2, 2)}${GitRevision.shorten(branch.sha)}` @@ -117,14 +117,14 @@ export namespace BranchQuickPickItem { } const checked = - options.checked || (options.checked == null && options.current === 'checkmark' && branch.current); + options?.checked || (options?.checked == null && options?.current === 'checkmark' && branch.current); const item: BranchQuickPickItem = { label: `${pad('$(git-branch)', 0, 2)}${branch.starred ? '$(star-full) ' : ''}${branch.name}${ checked ? `${GlyphChars.Space.repeat(2)}$(check)${GlyphChars.Space}` : '' }`, description: description, - alwaysShow: options.alwaysShow, - buttons: options.buttons, + alwaysShow: options?.alwaysShow, + buttons: options?.buttons, picked: picked ?? branch.current, item: branch, current: branch.current, diff --git a/src/quickpicks/quickPicksItems.ts b/src/quickpicks/quickPicksItems.ts index 67a1b0d..1d5273c 100644 --- a/src/quickpicks/quickPicksItems.ts +++ b/src/quickpicks/quickPicksItems.ts @@ -1,4 +1,4 @@ -import { commands, QuickPickItem } from 'vscode'; +import { commands, QuickPickItem, QuickPickItemKind } from 'vscode'; import { Commands, GitActions } from '../commands'; import { Container } from '../container'; import { GitCommit, GitReference, GitRevisionReference } from '../git/models'; @@ -12,6 +12,16 @@ declare module 'vscode' { } } +export interface QuickPickSeparator extends QuickPickItem { + kind: QuickPickItemKind.Separator; +} + +export namespace QuickPickSeparator { + export function create(label?: string): QuickPickSeparator { + return { kind: QuickPickItemKind.Separator, label: label ?? '' }; + } +} + export interface QuickPickItemOfT extends QuickPickItem { readonly item: T; } diff --git a/src/quickpicks/remoteProviderPicker.ts b/src/quickpicks/remoteProviderPicker.ts index 6e67497..a308b67 100644 --- a/src/quickpicks/remoteProviderPicker.ts +++ b/src/quickpicks/remoteProviderPicker.ts @@ -98,7 +98,7 @@ export class CopyRemoteResourceCommandQuickPickItem extends CommandQuickPickItem clipboard: true, }; super( - `$(clippy) Copy ${providers?.length ? providers[0].name : 'Remote'} ${getNameFromRemoteResource( + `$(copy) Copy ${providers?.length ? providers[0].name : 'Remote'} ${getNameFromRemoteResource( resource, )} Url${providers?.length === 1 ? '' : GlyphChars.Ellipsis}`, Commands.OpenOnRemote,