diff --git a/extensions/git/package.json b/extensions/git/package.json index dbb65d6c7c7..5879f9dd3fb 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 7148193f079..45e16cee353 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 fb66583c3d4..f3f0fcd8e0a 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 d3c574c74b1..8725d85a5be 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 5250ab4920a..af3d02b8cf1 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;