Skip to content

Commit e045147

Browse files
committed
improve conflicts during git rebase flow
1 parent be1ef8a commit e045147

4 files changed

Lines changed: 100 additions & 40 deletions

File tree

extensions/git/src/commands.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,16 +1273,7 @@ export class CommandCenter {
12731273
return;
12741274
}
12751275

1276-
try {
1277-
await choice.run(repository);
1278-
} catch (err) {
1279-
if (err.gitErrorCode !== GitErrorCodes.Conflict) {
1280-
throw err;
1281-
}
1282-
1283-
const message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
1284-
await window.showWarningMessage(message);
1285-
}
1276+
await choice.run(repository);
12861277
}
12871278

12881279
@command('git.createTag', { repository: true })
@@ -1655,10 +1646,11 @@ export class CommandCenter {
16551646

16561647
return result.catch(async err => {
16571648
const options: MessageOptions = {
1658-
modal: err.gitErrorCode === GitErrorCodes.DirtyWorkTree
1649+
modal: true
16591650
};
16601651

16611652
let message: string;
1653+
let type: 'error' | 'warning' = 'error';
16621654

16631655
switch (err.gitErrorCode) {
16641656
case GitErrorCodes.DirtyWorkTree:
@@ -1667,6 +1659,11 @@ export class CommandCenter {
16671659
case GitErrorCodes.PushRejected:
16681660
message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes.");
16691661
break;
1662+
case GitErrorCodes.Conflict:
1663+
message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
1664+
type = 'warning';
1665+
options.modal = false;
1666+
break;
16701667
default:
16711668
const hint = (err.stderr || err.message || String(err))
16721669
.replace(/^error: /mi, '')
@@ -1687,11 +1684,11 @@ export class CommandCenter {
16871684
return;
16881685
}
16891686

1690-
options.modal = true;
1691-
16921687
const outputChannel = this.outputChannel as OutputChannel;
16931688
const openOutputChannelChoice = localize('open git log', "Open Git Log");
1694-
const choice = await window.showErrorMessage(message, options, openOutputChannelChoice);
1689+
const choice = type === 'error'
1690+
? await window.showErrorMessage(message, options, openOutputChannelChoice)
1691+
: await window.showWarningMessage(message, options, openOutputChannelChoice);
16951692

16961693
if (choice === openOutputChannelChoice) {
16971694
outputChannel.show();

extensions/git/src/git.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ export const GitErrorCodes = {
336336
NoStashFound: 'NoStashFound',
337337
LocalChangesOverwritten: 'LocalChangesOverwritten',
338338
NoUpstreamBranch: 'NoUpstreamBranch',
339-
IsInSubmodule: 'IsInSubmodule'
339+
IsInSubmodule: 'IsInSubmodule',
340340
};
341341

342342
function getGitErrorCode(stderr: string): string | undefined {
@@ -876,27 +876,41 @@ export class Repository {
876876
try {
877877
await this.run(args, { input: message || '' });
878878
} catch (commitErr) {
879-
if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) {
880-
commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges;
881-
throw commitErr;
882-
}
879+
await this.handleCommitError(commitErr);
880+
}
881+
}
883882

884-
try {
885-
await this.run(['config', '--get-all', 'user.name']);
886-
} catch (err) {
887-
err.gitErrorCode = GitErrorCodes.NoUserNameConfigured;
888-
throw err;
889-
}
883+
async rebaseContinue(): Promise<void> {
884+
const args = ['rebase', '--continue'];
890885

891-
try {
892-
await this.run(['config', '--get-all', 'user.email']);
893-
} catch (err) {
894-
err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured;
895-
throw err;
896-
}
886+
try {
887+
await this.run(args);
888+
} catch (commitErr) {
889+
await this.handleCommitError(commitErr);
890+
}
891+
}
897892

893+
private async handleCommitError(commitErr: any): Promise<void> {
894+
if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) {
895+
commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges;
898896
throw commitErr;
899897
}
898+
899+
try {
900+
await this.run(['config', '--get-all', 'user.name']);
901+
} catch (err) {
902+
err.gitErrorCode = GitErrorCodes.NoUserNameConfigured;
903+
throw err;
904+
}
905+
906+
try {
907+
await this.run(['config', '--get-all', 'user.email']);
908+
} catch (err) {
909+
err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured;
910+
throw err;
911+
}
912+
913+
throw commitErr;
900914
}
901915

902916
async branch(name: string, checkout: boolean): Promise<void> {

extensions/git/src/repository.ts

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ export enum Operation {
299299
Stash = 'Stash',
300300
CheckIgnore = 'CheckIgnore',
301301
LSTree = 'LSTree',
302-
SubmoduleUpdate = 'SubmoduleUpdate'
302+
SubmoduleUpdate = 'SubmoduleUpdate',
303+
RebaseContinue = 'RebaseContinue',
303304
}
304305

305306
function isReadOnly(operation: Operation): boolean {
@@ -479,6 +480,22 @@ export class Repository implements Disposable {
479480
return this._submodules;
480481
}
481482

483+
private _rebaseCommit: Commit | undefined = undefined;
484+
485+
set rebaseCommit(rebaseCommit: Commit | undefined) {
486+
if (this._rebaseCommit && !rebaseCommit) {
487+
this.inputBox.value = '';
488+
} else if (rebaseCommit && (!this._rebaseCommit || this._rebaseCommit.hash !== rebaseCommit.hash)) {
489+
this.inputBox.value = rebaseCommit.message;
490+
}
491+
492+
this._rebaseCommit = rebaseCommit;
493+
}
494+
495+
get rebaseCommit(): Commit | undefined {
496+
return this._rebaseCommit;
497+
}
498+
482499
private _operations = new OperationsImpl();
483500
get operations(): Operations { return this._operations; }
484501

@@ -553,6 +570,15 @@ export class Repository implements Disposable {
553570
}
554571

555572
validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
573+
if (this.rebaseCommit) {
574+
if (this.rebaseCommit.message !== text) {
575+
return {
576+
message: localize('commit in rebase', "It's not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead."),
577+
type: SourceControlInputBoxValidationType.Warning
578+
};
579+
}
580+
}
581+
556582
const config = workspace.getConfiguration('git');
557583
const setting = config.get<'always' | 'warn' | 'off'>('inputValidation');
558584

@@ -636,13 +662,23 @@ export class Repository implements Disposable {
636662
}
637663

638664
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
639-
await this.run(Operation.Commit, async () => {
640-
if (opts.all) {
641-
await this.repository.add([]);
642-
}
665+
if (this.rebaseCommit) {
666+
await this.run(Operation.RebaseContinue, async () => {
667+
if (opts.all) {
668+
await this.repository.add([]);
669+
}
643670

644-
await this.repository.commit(message, opts);
645-
});
671+
await this.repository.rebaseContinue();
672+
});
673+
} else {
674+
await this.run(Operation.Commit, async () => {
675+
if (opts.all) {
676+
await this.repository.add([]);
677+
}
678+
679+
await this.repository.commit(message, opts);
680+
});
681+
}
646682
}
647683

648684
async clean(resources: Uri[]): Promise<void> {
@@ -1025,12 +1061,13 @@ export class Repository implements Disposable {
10251061
// noop
10261062
}
10271063

1028-
const [refs, remotes, submodules] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules()]);
1064+
const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]);
10291065

10301066
this._HEAD = HEAD;
10311067
this._refs = refs;
10321068
this._remotes = remotes;
10331069
this._submodules = submodules;
1070+
this.rebaseCommit = rebaseCommit;
10341071

10351072
const index: Resource[] = [];
10361073
const workingTree: Resource[] = [];
@@ -1089,6 +1126,17 @@ export class Repository implements Disposable {
10891126
this._onDidChangeStatus.fire();
10901127
}
10911128

1129+
private async getRebaseCommit(): Promise<Commit | undefined> {
1130+
const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD');
1131+
1132+
try {
1133+
const rebaseHead = await new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)));
1134+
return await this.getCommit(rebaseHead.trim());
1135+
} catch (err) {
1136+
return undefined;
1137+
}
1138+
}
1139+
10921140
private onFSChange(uri: Uri): void {
10931141
const config = workspace.getConfiguration('git');
10941142
const autorefresh = config.get<boolean>('autorefresh');

extensions/git/src/statusbar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class CheckoutStatusBar {
2424
}
2525

2626
get command(): Command | undefined {
27-
const title = `$(git-branch) ${this.repository.headLabel}`;
27+
const rebasing = !!this.repository.rebaseCommit;
28+
const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`;
2829

2930
return {
3031
command: 'git.checkout',

0 commit comments

Comments
 (0)