Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git - Add the ability to fetch a branch before it is checked out #160181

Merged
merged 7 commits into from Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions extensions/git/package.json
Expand Up @@ -2340,6 +2340,12 @@
"default": false,
"description": "%config.rebaseWhenSync%"
},
"git.fetchBeforeCheckout": {
"type": "boolean",
"scope": "resource",
"default": false,
"description": "%config.fetchBeforeCheckout%"
},
"git.fetchOnPull": {
"type": "boolean",
"scope": "resource",
Expand Down
1 change: 1 addition & 0 deletions extensions/git/package.nls.json
Expand Up @@ -200,6 +200,7 @@
"config.rebaseWhenSync": "Force git to use rebase when running the sync command.",
"config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.",
"config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.",
"config.fetchBeforeCheckout": "Controls whether a branch that does not have outgoing commits is fast-forwarded before it is checked out.",
"config.pullTags": "Fetch all tags when pulling.",
"config.pruneOnFetch": "Prune when fetching.",
"config.autoStash": "Stash any changes before pulling and restore them after successful pull.",
Expand Down
3 changes: 2 additions & 1 deletion extensions/git/src/api/git.d.ts
Expand Up @@ -350,5 +350,6 @@ export const enum GitErrorCodes {
PatchDoesNotApply = 'PatchDoesNotApply',
NoPathFound = 'NoPathFound',
UnknownPath = 'UnknownPath',
EmptyCommitMessage = 'EmptyCommitMessage'
EmptyCommitMessage = 'EmptyCommitMessage',
BranchFastForwardRejected = 'BranchFastForwardRejected'
}
21 changes: 17 additions & 4 deletions extensions/git/src/commands.ts
Expand Up @@ -33,13 +33,18 @@ class CheckoutItem implements QuickPickItem {
constructor(protected repository: Repository, protected ref: Ref) { }

async run(opts?: { detached?: boolean }): Promise<void> {
const ref = this.ref.name;

if (!ref) {
if (!this.ref.name) {
return;
}

await this.repository.checkout(ref, opts);
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const fetchBeforeCheckout = config.get<boolean>('fetchBeforeCheckout', false) === true;

if (fetchBeforeCheckout) {
await this.repository.fastForwardBranch(this.ref.name!);
}

await this.repository.checkout(this.ref.name, opts);
}
}

Expand All @@ -49,6 +54,14 @@ class CheckoutTagItem extends CheckoutItem {
override get description(): string {
return localize('tag at', "Tag at {0}", this.shortCommit);
}

override async run(opts?: { detached?: boolean }): Promise<void> {
if (!this.ref.name) {
return;
}

await this.repository.checkout(this.ref.name, opts);
}
}

class CheckoutRemoteHeadItem extends CheckoutItem {
Expand Down
3 changes: 3 additions & 0 deletions extensions/git/src/git.ts
Expand Up @@ -1708,6 +1708,9 @@ export class Repository {
err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified;
} else if (/Could not read from remote repository/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.RemoteConnectionError;
} else if (/! \[rejected\].*\(non-fast-forward\)/m.test(err.stderr || '')) {
// The local branch has outgoing changes and it cannot be fast-forwarded.
err.gitErrorCode = GitErrorCodes.BranchFastForwardRejected;
}

throw err;
Expand Down
21 changes: 21 additions & 0 deletions extensions/git/src/repository.ts
Expand Up @@ -1363,6 +1363,27 @@ export class Repository implements Disposable {
await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
}

@throttle
async fastForwardBranch(name: string): Promise<void> {
// Get branch details
const branch = await this.getBranch(name);
lszomoru marked this conversation as resolved.
Show resolved Hide resolved
if (!branch.upstream?.remote || !branch.upstream?.name || !branch.name) {
return;
}

try {
// Fast-forward the branch if possible
const options = { remote: branch.upstream.remote, ref: `${branch.upstream.name}:${branch.name}` };
await this.run(Operation.Fetch, async () => this.repository.fetch(options));
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.BranchFastForwardRejected) {
return;
}

throw err;
}
}

async cherryPick(commitHash: string): Promise<void> {
await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash));
}
Expand Down