From 741925db115a299de23a05ec29c3bb26d0a4ed36 Mon Sep 17 00:00:00 2001 From: tomerstav <7940187+tomerstav@users.noreply.github.com> Date: Mon, 19 Oct 2020 04:13:52 +0000 Subject: [PATCH 1/4] Implemented rebase onto --- extensions/git/package.json | 12 +++++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 37 ++++++++++++++++++++++++++++++++ extensions/git/src/git.ts | 18 ++++++++++++++++ extensions/git/src/repository.ts | 13 +++++++++++ 5 files changed, 81 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index dbb65d6c7c706..5879f9dd3fba7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -298,6 +298,11 @@ "title": "%command.merge%", "category": "Git" }, + { + "command": "git.rebaseOnto", + "title": "%command.rebaseOnto%", + "category": "Git" + }, { "command": "git.createTag", "title": "%command.createTag%", @@ -703,6 +708,10 @@ "command": "git.merge", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.rebaseOnto", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.createTag", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1433,6 +1442,9 @@ { "command": "git.merge" }, + { + "command": "git.rebaseOnto" + }, { "command": "git.branch" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7148193f0798c..45e16cee3536b 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -50,6 +50,7 @@ "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", "command.merge": "Merge Branch...", + "command.rebaseOnto": "Rebase Branch Onto...", "command.createTag": "Create Tag", "command.deleteTag": "Delete Tag", "command.fetch": "Fetch", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index fb66583c3d447..f3f0fcd8e0ad4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -99,6 +99,18 @@ class MergeItem implements QuickPickItem { } } +class RebaseOntoItem implements QuickPickItem { + + get label(): string { return this.ref.name || ''; } + get description(): string { return this.ref.name || ''; } + + constructor(protected ref: Ref) { } + + async run(repository: Repository): Promise { + await repository.rebaseOnto(repository.HEAD, this.ref); + } +} + class CreateBranchItem implements QuickPickItem { constructor(private cc: CommandCenter) { } @@ -1848,6 +1860,31 @@ export class CommandCenter { await choice.run(repository); } + @command('git.rebaseOnto', { repository: true }) + async rebaseOnto(repository: Repository): Promise { + const config = workspace.getConfiguration('git'); + const checkoutType = config.get('checkoutType') || 'all'; + const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; + + const heads = repository.refs.filter(ref => ref.type === RefType.Head) + .filter(ref => ref.name || ref.commit) + .map(ref => new RebaseOntoItem(ref as Branch)); + + const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) + .filter(ref => ref.name || ref.commit) + .map(ref => new RebaseOntoItem(ref as Branch)); + + const picks = [...heads, ...remoteHeads]; + const placeHolder = localize('select a branch to rebase onto', 'Select a branch to rebase onto'); + const choice = await window.showQuickPick(picks, { placeHolder }); + + if (!choice) { + return; + } + + await choice.run(repository); + } + @command('git.createTag', { repository: true }) async createTag(repository: Repository): Promise { const inputTagName = await window.showInputBox({ diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d3c574c74b13b..8725d85a5bedc 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1608,6 +1608,24 @@ export class Repository { } } + async rebase(branch: string, options: PullOptions = {}): Promise { + const args = ['rebase']; + + args.push(branch); + + try { + await this.run(args, options); + } catch (err) { + if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) { + err.gitErrorCode = GitErrorCodes.Conflict; + } else if (/cannot rebase onto multiple branches/i.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.CantRebaseMultipleBranches; + } + + throw err; + } + } + async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise { const args = ['push']; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 5250ab4920af4..af3d02b8cf1a6 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -300,6 +300,7 @@ export const enum Operation { RenameBranch = 'RenameBranch', DeleteRef = 'DeleteRef', Merge = 'Merge', + Rebase = 'Rebase', Ignore = 'Ignore', Tag = 'Tag', DeleteTag = 'DeleteTag', @@ -1067,6 +1068,10 @@ export class Repository implements Disposable { await this.run(Operation.Merge, () => this.repository.merge(ref)); } + async rebase(branch: string): Promise { + await this.run(Operation.Rebase, () => this.repository.rebase(branch)); + } + async tag(name: string, message?: string): Promise { await this.run(Operation.Tag, () => this.repository.tag(name, message)); } @@ -1143,6 +1148,14 @@ export class Repository implements Disposable { return this.pullFrom(true, remote, branch); } + @throttle + async rebaseOnto(head: Branch | undefined, branch: Branch | undefined): Promise { + if (head?.name && branch?.name) { + await this.checkout(branch.name); + await this.rebase(head.name); + } + } + @throttle async pull(head?: Branch, unshallow?: boolean): Promise { let remote: string | undefined; From 5a76fea0a83d52c1c12b4359ee00b9c3d7fa4392 Mon Sep 17 00:00:00 2001 From: tomerstav <7940187+tomerstav@users.noreply.github.com> Date: Mon, 19 Oct 2020 23:24:48 +0000 Subject: [PATCH 2/4] Rewrote logic --- extensions/git/src/commands.ts | 4 +++- extensions/git/src/repository.ts | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f3f0fcd8e0ad4..f6fcd0f446729 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -107,7 +107,9 @@ class RebaseOntoItem implements QuickPickItem { constructor(protected ref: Ref) { } async run(repository: Repository): Promise { - await repository.rebaseOnto(repository.HEAD, this.ref); + if (this.ref?.name) { + await repository.rebase(this.ref.name); + } } } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index af3d02b8cf1a6..6fc49c1259235 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1148,14 +1148,6 @@ export class Repository implements Disposable { return this.pullFrom(true, remote, branch); } - @throttle - async rebaseOnto(head: Branch | undefined, branch: Branch | undefined): Promise { - if (head?.name && branch?.name) { - await this.checkout(branch.name); - await this.rebase(head.name); - } - } - @throttle async pull(head?: Branch, unshallow?: boolean): Promise { let remote: string | undefined; From 7b936983f6e91ddf78ff35d6a3e9d9e6709983ec Mon Sep 17 00:00:00 2001 From: tomerstav <7940187+tomerstav@users.noreply.github.com> Date: Fri, 23 Oct 2020 03:35:27 +0000 Subject: [PATCH 3/4] Renamed rebaseOnto to rebase --- extensions/git/package.json | 8 ++++---- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 5879f9dd3fba7..7540ba9405094 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -299,8 +299,8 @@ "category": "Git" }, { - "command": "git.rebaseOnto", - "title": "%command.rebaseOnto%", + "command": "git.rebase", + "title": "%command.rebase%", "category": "Git" }, { @@ -709,7 +709,7 @@ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { - "command": "git.rebaseOnto", + "command": "git.rebase", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { @@ -1443,7 +1443,7 @@ "command": "git.merge" }, { - "command": "git.rebaseOnto" + "command": "git.rebase" }, { "command": "git.branch" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 45e16cee3536b..e7a8fbe0d5cad 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -50,7 +50,7 @@ "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", "command.merge": "Merge Branch...", - "command.rebaseOnto": "Rebase Branch Onto...", + "command.rebase": "Rebase Branch...", "command.createTag": "Create Tag", "command.deleteTag": "Delete Tag", "command.fetch": "Fetch", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f6fcd0f446729..e504d24682aa9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -99,7 +99,7 @@ class MergeItem implements QuickPickItem { } } -class RebaseOntoItem implements QuickPickItem { +class RebaseItem implements QuickPickItem { get label(): string { return this.ref.name || ''; } get description(): string { return this.ref.name || ''; } @@ -1862,23 +1862,23 @@ export class CommandCenter { await choice.run(repository); } - @command('git.rebaseOnto', { repository: true }) - async rebaseOnto(repository: Repository): Promise { + @command('git.rebase', { repository: true }) + async rebase(repository: Repository): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; const heads = repository.refs.filter(ref => ref.type === RefType.Head) .filter(ref => ref.name || ref.commit) - .map(ref => new RebaseOntoItem(ref as Branch)); + .map(ref => new RebaseItem(ref as Branch)); const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) .filter(ref => ref.name || ref.commit) - .map(ref => new RebaseOntoItem(ref as Branch)); + .map(ref => new RebaseItem(ref as Branch)); const picks = [...heads, ...remoteHeads]; const placeHolder = localize('select a branch to rebase onto', 'Select a branch to rebase onto'); - const choice = await window.showQuickPick(picks, { placeHolder }); + const choice = await window.showQuickPick(picks, { placeHolder }); if (!choice) { return; From 039f26db561a9de9e49862d404cbc16497425f77 Mon Sep 17 00:00:00 2001 From: tomerstav <7940187+tomerstav@users.noreply.github.com> Date: Fri, 23 Oct 2020 04:36:17 +0000 Subject: [PATCH 4/4] Highlight branchs upstream if defined by placing item first in picker --- extensions/git/src/commands.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e504d24682aa9..c03f292a44131 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1869,14 +1869,16 @@ export class CommandCenter { const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; const heads = repository.refs.filter(ref => ref.type === RefType.Head) - .filter(ref => ref.name || ref.commit) - .map(ref => new RebaseItem(ref as Branch)); + .filter(ref => ref.name || ref.commit); const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit) - .map(ref => new RebaseItem(ref as Branch)); + .filter(ref => ref.name || ref.commit); + + // set upstream branch as first + const upstreamName = repository?.HEAD?.upstream?.name; + const upstreamRemote = repository?.HEAD?.upstream?.remote; + const picks = [...heads, ...remoteHeads].sort(ref => ref.name === `${upstreamRemote}/${upstreamName}` && ref.remote === upstreamRemote ? -1 : 0).map(ref => new RebaseItem(ref as Branch)); - const picks = [...heads, ...remoteHeads]; const placeHolder = localize('select a branch to rebase onto', 'Select a branch to rebase onto'); const choice = await window.showQuickPick(picks, { placeHolder });