diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index afa4ad1bcaf..8168b7201fd 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -177,6 +177,10 @@ export class ApiRepository implements Repository { return this.repository.getBranches(query, cancellationToken); } + getBranchBase(name: string): Promise { + return this.repository.getBranchBase(name); + } + setBranchUpstream(name: string, upstream: string): Promise { return this.repository.setBranchUpstream(name, upstream); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index d972ce59f7e..8dd2b7addb3 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -219,6 +219,7 @@ export interface Repository { deleteBranch(name: string, force?: boolean): Promise; getBranch(name: string): Promise; getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; setBranchUpstream(name: string, upstream: string): Promise; getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 129a8045fef..ad49a9ec42c 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1083,6 +1083,17 @@ export class Repository { return parseGitCommits(result.stdout); } + async reflog(ref: string, pattern: string): Promise { + const args = ['reflog', ref, `--grep-reflog=${pattern}`]; + const result = await this.exec(args); + if (result.exitCode) { + return []; + } + + return result.stdout.split('\n') + .filter(entry => !!entry); + } + async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise { const stdout = await this.buffer(object); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 8ae0143f1f2..b50f5932eff 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1452,14 +1452,66 @@ export class Repository implements Disposable { async getBranchBase(ref: string): Promise { const branch = await this.getBranch(ref); + const branchMergeBaseConfigKey = `branch.${branch.name}.vscode-merge-base`; // Upstream if (branch.upstream) { - return this.getBranch(`refs/remotes/${branch.upstream.remote}/${branch.upstream.name}`); + return await this.getBranch(`refs/remotes/${branch.upstream.remote}/${branch.upstream.name}`); + } + + // Git config + try { + const mergeBase = await this.getConfig(branchMergeBaseConfigKey); + if (mergeBase) { + return await this.getBranch(mergeBase); + } + } catch (err) { } + + // Reflog + const branchFromReflog = await this.getBranchBaseFromReflog(ref); + if (branchFromReflog) { + await this.setConfig(branchMergeBaseConfigKey, branchFromReflog.name!); + return branchFromReflog; } // Default branch - return await this.getDefaultBranch(); + const defaultBranch = await this.getDefaultBranch(); + if (defaultBranch) { + await this.setConfig(branchMergeBaseConfigKey, defaultBranch.name!); + return defaultBranch; + } + + return undefined; + } + + private async getBranchBaseFromReflog(ref: string): Promise { + try { + const reflogEntries = await this.repository.reflog(ref, 'branch: Created from *.'); + if (reflogEntries.length !== 1) { + return undefined; + } + + // Branch created from an explicit branch + const match = reflogEntries[0].match(/branch: Created from (?.*)$/); + if (match && match.length === 2 && match[1] !== 'HEAD') { + return await this.getBranch(match[1]); + } + + // Branch created from HEAD + const headReflogEntries = await this.repository.reflog('HEAD', `checkout: moving from .* to ${ref.replace('refs/heads/', '')}`); + if (headReflogEntries.length === 0) { + return undefined; + } + + const match2 = headReflogEntries[headReflogEntries.length - 1].match(/checkout: moving from ([^\s]+)\s/); + if (match2 && match2.length === 2) { + return await this.getBranch(match2[1]); + } + + } + catch (err) { } + + return undefined; } private async getDefaultBranch(): Promise {