Skip to content

Commit a5bc795

Browse files
committed
mhutchie#390 Display a new dialog when creating a branch with a name that already exists, allowing the user to force-create the branch.
1 parent b83a808 commit a5bc795

File tree

5 files changed

+104
-23
lines changed

5 files changed

+104
-23
lines changed

src/dataSource.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -815,15 +815,26 @@ export class DataSource extends Disposable {
815815
* @param branchName The name of the branch.
816816
* @param commitHash The hash of the commit the branch should be created at.
817817
* @param checkout Check out the branch after it is created.
818-
* @returns The ErrorInfo from the executed command.
818+
* @param force Force create the branch, replacing an existing branch with the same name (if it exists).
819+
* @returns The ErrorInfo's from the executed command(s).
819820
*/
820-
public createBranch(repo: string, branchName: string, commitHash: string, checkout: boolean) {
821-
let args = [];
822-
if (checkout) args.push('checkout', '-b');
823-
else args.push('branch');
821+
public async createBranch(repo: string, branchName: string, commitHash: string, checkout: boolean, force: boolean) {
822+
const args = [];
823+
if (checkout && !force) {
824+
args.push('checkout', '-b');
825+
} else {
826+
args.push('branch');
827+
if (force) {
828+
args.push('-f');
829+
}
830+
}
824831
args.push(branchName, commitHash);
825832

826-
return this.runGitCommand(args, repo);
833+
const statuses = [await this.runGitCommand(args, repo)];
834+
if (statuses[0] === null && checkout && force) {
835+
statuses.push(await this.checkoutBranch(repo, branchName, null));
836+
}
837+
return statuses;
827838
}
828839

829840
/**

src/gitGraphView.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ export class GitGraphView extends Disposable {
278278
case 'createBranch':
279279
this.sendMessage({
280280
command: 'createBranch',
281-
error: await this.dataSource.createBranch(msg.repo, msg.branchName, msg.commitHash, msg.checkout)
281+
errors: await this.dataSource.createBranch(msg.repo, msg.branchName, msg.commitHash, msg.checkout, msg.force)
282282
});
283283
break;
284284
case 'createPullRequest':

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,8 +699,9 @@ export interface RequestCreateBranch extends RepoRequest {
699699
readonly commitHash: string;
700700
readonly branchName: string;
701701
readonly checkout: boolean;
702+
readonly force: boolean;
702703
}
703-
export interface ResponseCreateBranch extends ResponseWithErrorInfo {
704+
export interface ResponseCreateBranch extends ResponseWithMultiErrorInfo {
704705
readonly command: 'createBranch';
705706
}
706707

tests/dataSource.test.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,10 +4724,11 @@ describe('DataSource', () => {
47244724
mockGitSuccessOnce();
47254725

47264726
// Run
4727-
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', false);
4727+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', false, false);
47284728

47294729
// Assert
4730-
expect(result).toBe(null);
4730+
expect(result).toStrictEqual([null]);
4731+
expect(spyOnSpawn).toBeCalledTimes(1);
47314732
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['branch', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
47324733
});
47334734

@@ -4736,22 +4737,79 @@ describe('DataSource', () => {
47364737
mockGitSuccessOnce();
47374738

47384739
// Run
4739-
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', true);
4740+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', true, false);
47404741

47414742
// Assert
4742-
expect(result).toBe(null);
4743+
expect(result).toStrictEqual([null]);
4744+
expect(spyOnSpawn).toBeCalledTimes(1);
47434745
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['checkout', '-b', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
47444746
});
47454747

4748+
it('Should force create a branch at a commit', async () => {
4749+
// Setup
4750+
mockGitSuccessOnce();
4751+
4752+
// Run
4753+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', false, true);
4754+
4755+
// Assert
4756+
expect(result).toStrictEqual([null]);
4757+
expect(spyOnSpawn).toBeCalledTimes(1);
4758+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['branch', '-f', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
4759+
});
4760+
4761+
it('Should force create a branch at a commit, and check it out', async () => {
4762+
// Setup
4763+
mockGitSuccessOnce();
4764+
mockGitSuccessOnce();
4765+
4766+
// Run
4767+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', true, true);
4768+
4769+
// Assert
4770+
expect(result).toStrictEqual([null, null]);
4771+
expect(spyOnSpawn).toBeCalledTimes(2);
4772+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['branch', '-f', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
4773+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['checkout', 'develop'], expect.objectContaining({ cwd: '/path/to/repo' }));
4774+
});
4775+
47464776
it('Should return an error message thrown by git', async () => {
47474777
// Setup
47484778
mockGitThrowingErrorOnce();
47494779

47504780
// Run
4751-
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', false);
4781+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', false, false);
47524782

47534783
// Assert
4754-
expect(result).toBe('error message');
4784+
expect(result).toStrictEqual(['error message']);
4785+
});
4786+
4787+
it('Should return an error message thrown by git when creating a branch, and not proceed to check out the force-created branch', async () => {
4788+
// Setup
4789+
mockGitThrowingErrorOnce();
4790+
4791+
// Run
4792+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', true, true);
4793+
4794+
// Assert
4795+
expect(result).toStrictEqual(['error message']);
4796+
expect(spyOnSpawn).toBeCalledTimes(1);
4797+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['branch', '-f', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
4798+
});
4799+
4800+
it('Should return an error message thrown by git when checking out a force-created branch', async () => {
4801+
// Setup
4802+
mockGitSuccessOnce();
4803+
mockGitThrowingErrorOnce();
4804+
4805+
// Run
4806+
const result = await dataSource.createBranch('/path/to/repo', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', true, true);
4807+
4808+
// Assert
4809+
expect(result).toStrictEqual([null, 'error message']);
4810+
expect(spyOnSpawn).toBeCalledTimes(2);
4811+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['branch', '-f', 'develop', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], expect.objectContaining({ cwd: '/path/to/repo' }));
4812+
expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['checkout', 'develop'], expect.objectContaining({ cwd: '/path/to/repo' }));
47554813
});
47564814
});
47574815

web/main.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,14 +1132,7 @@ class GitGraphView {
11321132
}, {
11331133
title: 'Create Branch' + ELLIPSIS,
11341134
visible: visibility.createBranch,
1135-
onClick: () => {
1136-
dialog.showForm('Create branch at commit <b><i>' + abbrevCommit(hash) + '</i></b>:', [
1137-
{ type: DialogInputType.TextRef, name: 'Name', default: '' },
1138-
{ type: DialogInputType.Checkbox, name: 'Check out', value: this.config.dialogDefaults.createBranch.checkout }
1139-
], 'Create Branch', (values) => {
1140-
runAction({ command: 'createBranch', repo: this.currentRepo, branchName: <string>values[0], commitHash: hash, checkout: <boolean>values[1] }, 'Creating Branch');
1141-
}, target);
1142-
}
1135+
onClick: () => this.createBranchAction(hash, '', this.config.dialogDefaults.createBranch.checkout, target)
11431136
}
11441137
], [
11451138
{
@@ -1557,6 +1550,24 @@ class GitGraphView {
15571550
}
15581551
}
15591552

1553+
private createBranchAction(hash: string, initialName: string, initialCheckOut: boolean, target: DialogTarget & CommitTarget) {
1554+
dialog.showForm('Create branch at commit <b><i>' + abbrevCommit(hash) + '</i></b>:', [
1555+
{ type: DialogInputType.TextRef, name: 'Name', default: initialName },
1556+
{ type: DialogInputType.Checkbox, name: 'Check out', value: initialCheckOut }
1557+
], 'Create Branch', (values) => {
1558+
const branchName = <string>values[0], checkOut = <boolean>values[1];
1559+
if (this.gitBranches.includes(branchName)) {
1560+
dialog.showTwoButtons('A branch with name <b><i>' + escapeHtml(branchName) + '</i></b> already exists, do you want to replace it with this new branch?', 'Yes, replace the existing branch', () => {
1561+
runAction({ command: 'createBranch', repo: this.currentRepo, branchName: branchName, commitHash: hash, checkout: checkOut, force: true }, 'Creating Branch');
1562+
}, 'No, choose another branch name', () => {
1563+
this.createBranchAction(hash, branchName, checkOut, target);
1564+
}, target);
1565+
} else {
1566+
runAction({ command: 'createBranch', repo: this.currentRepo, branchName: branchName, commitHash: hash, checkout: checkOut, force: false }, 'Creating Branch');
1567+
}
1568+
}, target);
1569+
}
1570+
15601571
private deleteTagAction(refName: string, deleteOnRemote: string | null) {
15611572
runAction({ command: 'deleteTag', repo: this.currentRepo, tagName: refName, deleteOnRemote: deleteOnRemote }, 'Deleting Tag');
15621573
}
@@ -3048,7 +3059,7 @@ window.addEventListener('load', () => {
30483059
finishOrDisplayError(msg.error, 'Unable to Create Archive', true);
30493060
break;
30503061
case 'createBranch':
3051-
refreshOrDisplayError(msg.error, 'Unable to Create Branch');
3062+
refreshAndDisplayErrors(msg.errors, 'Unable to Create Branch');
30523063
break;
30533064
case 'createPullRequest':
30543065
finishOrDisplayErrors(msg.errors, 'Unable to Create Pull Request', () => {

0 commit comments

Comments
 (0)