Git - improve heuristics for determining branch base (#193986)

* Add getBranchBaseFromReflog

* Read/store branch merge base in the git config

* Add getBranchBase() extension api
pull/192260/head
Ladislau Szomoru 2023-09-25 16:12:10 +02:00 committed by GitHub
parent 70a9d8f4ea
commit 8e80e950a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 2 deletions

View File

@ -177,6 +177,10 @@ export class ApiRepository implements Repository {
return this.repository.getBranches(query, cancellationToken);
}
getBranchBase(name: string): Promise<Branch | undefined> {
return this.repository.getBranchBase(name);
}
setBranchUpstream(name: string, upstream: string): Promise<void> {
return this.repository.setBranchUpstream(name, upstream);
}

View File

@ -219,6 +219,7 @@ export interface Repository {
deleteBranch(name: string, force?: boolean): Promise<void>;
getBranch(name: string): Promise<Branch>;
getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise<Ref[]>;
getBranchBase(name: string): Promise<Branch | undefined>;
setBranchUpstream(name: string, upstream: string): Promise<void>;
getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise<Ref[]>;

View File

@ -1083,6 +1083,17 @@ export class Repository {
return parseGitCommits(result.stdout);
}
async reflog(ref: string, pattern: string): Promise<string[]> {
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<string> {
const stdout = await this.buffer(object);

View File

@ -1452,14 +1452,66 @@ export class Repository implements Disposable {
async getBranchBase(ref: string): Promise<Branch | undefined> {
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<Branch | undefined> {
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 (?<name>.*)$/);
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<Branch | undefined> {