Skip to content

Commit 96a1556

Browse files
committed
Update createBranch method to return IGitRef object, enable temp worktree service, improve branch checkout handling, and refactor configuration management
1 parent 7ca1a3b commit 96a1556

12 files changed

Lines changed: 431 additions & 6409 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ coverage/
1212
post-log.json
1313
*.tsbuildinfo
1414
TODO*
15+
16+
*.log

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,12 @@
173173
"git-smart-checkout.showStatusBar": {
174174
"type": "boolean",
175175
"default": true,
176-
"description": "Show or hide the extension's status bar item"
176+
"description": "Show the extension's status bar item"
177177
},
178178
"git-smart-checkout.useInPlaceCherryPick": {
179179
"type": "boolean",
180180
"default": true,
181-
"description": "(READONLY!) Use in-place cherry-pick instead of temporary worktree for PR cloning",
182-
"scope": "machine"
181+
"description": "Use in-place cherry-pick instead of temporary worktree for PR cloning (this method doesn't allow to solve conflicts, so use when completely sure that there will be no conflicts during cherry pick"
183182
}
184183
}
185184
}

src/commands/checkoutToCommand/index.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getRefLabel,
1010
ICON_BRANCH,
1111
ICON_PLUS,
12+
ICON_REMOTE_BRANCH,
1213
} from '../utils/refFormatting';
1314
import { getMergedBranchLists } from '../utils/getMergedBranchLists';
1415
import {
@@ -176,18 +177,33 @@ export class CheckoutToCommand extends BaseCommand {
176177
};
177178
}
178179

179-
async getTargetBranch(git: GitExecutor, selection: string, branchList: IGitRef[]) {
180+
async getTargetBranch(
181+
git: GitExecutor,
182+
selection: string,
183+
branchList: IGitRef[]
184+
): Promise<IGitRef> {
185+
const iconsToRemove = [ICON_BRANCH, ICON_REMOTE_BRANCH];
186+
180187
switch (true) {
181188
case selection === LABEL_CREATE_NEW_BRANCH:
182189
return await this.createNewBranch(git);
183190
case selection === LABEL_CREATE_NEW_BRANCH_FROM:
184191
return await this.createNewBranchFrom(git, branchList);
185192
default:
186-
return selection.replace(`${ICON_BRANCH} `, '');
193+
const branchName = iconsToRemove.reduce(
194+
(prev, icon) => prev.replace(`${icon} `, ''),
195+
selection
196+
);
197+
const branch = branchList.find((ref) => ref.fullName === branchName);
198+
if (!branch) {
199+
throw new Error(`Cannot find appropriate object for a ref ${branchName}`);
200+
}
201+
202+
return branch;
187203
}
188204
}
189205

190-
async createNewBranch(git: GitExecutor) {
206+
async createNewBranch(git: GitExecutor): Promise<IGitRef> {
191207
const newBranchName = await vscode.window.showInputBox({
192208
placeHolder: 'Branch name',
193209
prompt: 'Please provide a new branch name',
@@ -198,8 +214,8 @@ export class CheckoutToCommand extends BaseCommand {
198214
}
199215

200216
try {
201-
await git.createBranch(newBranchName);
202-
return newBranchName;
217+
const newBranch = await git.createBranch(newBranchName);
218+
return newBranch;
203219
} catch (e) {
204220
await vscode.window.showErrorMessage('Failed to create the new branch.', 'OK');
205221
throw new Error('Failed to create the new branch.');
@@ -227,9 +243,8 @@ export class CheckoutToCommand extends BaseCommand {
227243
}
228244

229245
try {
230-
await git.createBranch(newBranchName, baseBranchName);
231-
232-
return newBranchName;
246+
const newBranch = await git.createBranch(newBranchName, baseBranchName);
247+
return newBranch;
233248
} catch (e) {
234249
throw new Error('Failed to create the new branch.');
235250
}
@@ -286,30 +301,36 @@ export class CheckoutToCommand extends BaseCommand {
286301
async checkoutAndStashChanges(
287302
git: GitExecutor,
288303
currentBranch: string,
289-
newBranch: string,
304+
newBranch: IGitRef,
290305
autoStashMode: TAutoStashMode = AUTO_STASH_CURRENT_BRANCH
291306
) {
307+
const newBranchName = newBranch.name;
292308
const isWorkdirHasChanges = await git.isWorkdirHasChanges();
293309
switch (autoStashMode) {
294310
case AUTO_STASH_CURRENT_BRANCH:
295-
await this.doAutoStashCurrentBranch(git, currentBranch, newBranch, isWorkdirHasChanges);
311+
await this.doAutoStashCurrentBranch(git, currentBranch, newBranchName, isWorkdirHasChanges);
296312
break;
297313
case AUTO_STASH_AND_POP_IN_NEW_BRANCH:
298-
await this.doAutoStashAndPopInNewBranch(git, currentBranch, newBranch, isWorkdirHasChanges);
314+
await this.doAutoStashAndPopInNewBranch(
315+
git,
316+
currentBranch,
317+
newBranchName,
318+
isWorkdirHasChanges
319+
);
299320
break;
300321
case AUTO_STASH_AND_APPLY_IN_NEW_BRANCH:
301322
await this.doAutoStashAndPopInNewBranch(
302323
git,
303324
currentBranch,
304-
newBranch,
325+
newBranchName,
305326
isWorkdirHasChanges,
306327
true
307328
);
308329
break;
309330
case AUTO_STASH_IGNORE:
310331
default:
311332
try {
312-
await git.checkout(newBranch);
333+
await git.checkout(newBranchName);
313334
} catch (e) {
314335
throw new Error('Failed to checkout the selected branch.');
315336
}

src/common/git/gitExecutor.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,26 @@ export class GitExecutor {
5151
}
5252

5353
async #checkLocalBranchExists(branchName: string): Promise<boolean> {
54+
const command = `git show-ref --verify --quiet refs/heads/${branchName}`;
55+
5456
try {
55-
await this.#execGitCommand(`git show-ref --verify --quiet refs/heads/${branchName}`);
57+
await this.#execGitCommand(command);
58+
5659
return true;
5760
} catch {
5861
return false;
5962
}
6063
}
6164

62-
async #checkRemoteBranchExists(branchName: string): Promise<boolean> {
65+
async #checkRemoteBranchExists(
66+
branchName: string,
67+
includeRemoteName = false,
68+
remoteName = 'origin'
69+
): Promise<boolean> {
70+
const command = `git show-ref --verify --quiet refs/remotes${includeRemoteName ? `/${remoteName}` : ''}/${branchName}`;
71+
6372
try {
64-
await this.#execGitCommand(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`);
73+
await this.#execGitCommand(command);
6574
return true;
6675
} catch {
6776
return false;
@@ -105,14 +114,14 @@ export class GitExecutor {
105114
return branchName;
106115
}
107116

108-
async checkout(branchName: string) {
117+
async checkout(branchName: string, remoteName = 'origin') {
109118
// Check if it's a remote branch that doesn't have a local counterpart
110119
const localBranchExists = await this.#checkLocalBranchExists(branchName);
111120
const remoteBranchExists = await this.#checkRemoteBranchExists(branchName);
112121

113122
if (!localBranchExists && remoteBranchExists) {
114123
// Create a local tracked branch for the remote branch
115-
const command = `git checkout -b ${branchName} origin/${branchName}`;
124+
const command = `git checkout -b ${branchName} ${remoteName}/${branchName}`;
116125
const { stdout } = await this.#execGitCommand(command);
117126
return stdout;
118127
} else {
@@ -134,12 +143,29 @@ export class GitExecutor {
134143
return stdout;
135144
}
136145

137-
async createBranch(branchName: string, sourceBranch: string | undefined = undefined) {
146+
async createBranch(
147+
branchName: string,
148+
sourceBranch: string | undefined = undefined
149+
): Promise<IGitRef> {
138150
const command = `git checkout -b ${branchName} ${sourceBranch ? sourceBranch : ''}`;
139151

140-
const { stdout } = await this.#execGitCommand(command);
152+
await this.#execGitCommand(command);
141153

142-
return stdout;
154+
// Get detailed information about the newly created branch
155+
const SEPARATOR = '|';
156+
const branchInfoCommand = `git for-each-ref --format="%(refname)${SEPARATOR}%(objectname:short)${SEPARATOR}%(committerdate:unix)${SEPARATOR}%(subject)${SEPARATOR}%(authorname)" refs/heads/${branchName}`;
157+
const { stdout: branchInfo } = await this.#execGitCommand(branchInfoCommand);
158+
159+
const [, hash, committerDate, comment, authorName] = branchInfo.trim().split(SEPARATOR);
160+
161+
return {
162+
name: branchName,
163+
fullName: branchName,
164+
hash,
165+
comment,
166+
authorName,
167+
committerDate,
168+
};
143169
}
144170

145171
async createStash(stashName: string, include: 'all' | 'untracked' | 'none' = 'untracked') {
@@ -431,21 +457,11 @@ export class GitExecutor {
431457
}
432458

433459
async branchExist(branchName: string) {
434-
const command = `git show-ref --verify --quiet refs/heads/${branchName}`;
435-
const commandRemote = `git show-ref --verify --quiet refs/remotes/origin/${branchName}`;
436-
437-
try {
438-
await this.#execGitCommand(command);
460+
if (await this.#checkLocalBranchExists(branchName)) {
439461
return true;
440-
} catch (error) {
441-
//verify remote branch
442-
try {
443-
await this.#execGitCommand(commandRemote);
444-
return true;
445-
} catch {
446-
return false;
447-
}
448462
}
463+
464+
return await this.#checkRemoteBranchExists(branchName);
449465
}
450466

451467
async pushBranchToGitHub(branchName: string): Promise<void> {

src/configuration/configurationManager.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ export class ConfigurationManager {
1414
showStatusBar: vscodeConfig.get('showStatusBar', true),
1515
defaultTargetBranch: vscodeConfig.get('defaultTargetBranch', 'main'),
1616
prBranchPrefix: vscodeConfig.get('prBranchPrefix', ''),
17-
// todo: update when tem dir cherry pick service is ready
18-
useInPlaceCherryPick: true,
19-
// useInPlaceCherryPick: vscodeConfig.get('useInPlaceCherryPick', true),
17+
useInPlaceCherryPick: vscodeConfig.get('useInPlaceCherryPick', true),
2018
logging: {
2119
enabled: vscodeConfig.get('logging.enabled', true),
2220
},
@@ -32,9 +30,7 @@ export class ConfigurationManager {
3230
showStatusBar: vscodeConfig.get('showStatusBar', true),
3331
defaultTargetBranch: vscodeConfig.get('defaultTargetBranch', 'main'),
3432
prBranchPrefix: vscodeConfig.get('prBranchPrefix', ''),
35-
// todo: update when tem dir cherry pick service is ready
36-
useInPlaceCherryPick: true,
37-
// useInPlaceCherryPick: vscodeConfig.get('useInPlaceCherryPick', true),
33+
useInPlaceCherryPick: vscodeConfig.get('useInPlaceCherryPick', true),
3834
logging: {
3935
enabled: vscodeConfig.get('logging.enabled', true),
4036
},

src/services/prCloneInPlaceService.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ export class PrCloneInPlaceService extends PrCloneServiceBase {
264264
}
265265
}
266266

267+
dispose() {
268+
this.cleanUpActionEnd = [];
269+
this.cleanUpActionBegin = [];
270+
}
271+
267272
private async createGitHubPR(
268273
originalPr: GitHubPR,
269274
featureBranch: string,
@@ -274,8 +279,8 @@ export class PrCloneInPlaceService extends PrCloneServiceBase {
274279
const prBody = description;
275280

276281
// Extract labels and assignees from original PR
277-
const labels = originalPr.labels?.map(label => label.name) || [];
278-
const assignees = originalPr.assignees?.map(assignee => assignee.login) || [];
282+
const labels = originalPr.labels?.map((label) => label.name) || [];
283+
const assignees = originalPr.assignees?.map((assignee) => assignee.login) || [];
279284

280285
// Create PR using the GitHub API
281286
const newPr = await this.ghClient.createPullRequest(

src/services/prCloneService.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ConfigurationManager } from '../configuration/configurationManager';
88
// import { PrCloneTempWorktreeService } from './prCloneTempWorktreeService';
99
import { PrCloneInPlaceService } from './prCloneInPlaceService';
1010
import { setContextIsCloning } from '../utils/setContext';
11+
import { PrCloneTempWorktreeService } from './prCloneTempWorktreeService';
1112

1213
export interface PrCloneData {
1314
prData: GitHubPR;
@@ -24,8 +25,7 @@ export interface ICleanUpActions {
2425
}
2526

2627
export class PrCloneService {
27-
// todo: uncomment when PrCloneTempWorktreeService is ready
28-
// private _tempWorktreeService?: PrCloneTempWorktreeService;
28+
private _tempWorktreeService?: PrCloneTempWorktreeService;
2929
private _inPlaceService?: PrCloneInPlaceService;
3030
private _git?: GitExecutor;
3131
private _ghClient?: GitHubClient;
@@ -44,13 +44,13 @@ export class PrCloneService {
4444
return this._isInited;
4545
}
4646

47-
// get TempWorktreeService(): PrCloneTempWorktreeService {
48-
// if (!this.isInited || !this._tempWorktreeService) {
49-
// throw new Error(`Getter "TempWorktreeService" is not initialized`);
50-
// }
47+
get TempWorktreeService(): PrCloneTempWorktreeService {
48+
if (!this.isInited || !this._tempWorktreeService) {
49+
throw new Error(`Getter "TempWorktreeService" is not initialized`);
50+
}
5151

52-
// return this._tempWorktreeService;
53-
// }
52+
return this._tempWorktreeService;
53+
}
5454

5555
get InPlaceService(): PrCloneInPlaceService {
5656
if (!this.isInited || !this._inPlaceService) {
@@ -84,11 +84,11 @@ export class PrCloneService {
8484
this._git = git;
8585
this._ghClient = ghClient;
8686

87-
// this._tempWorktreeService = new PrCloneTempWorktreeService(
88-
// this.git,
89-
// this.ghClient,
90-
// this.loggingService
91-
// );
87+
this._tempWorktreeService = new PrCloneTempWorktreeService(
88+
this.git,
89+
this.ghClient,
90+
this.loggingService
91+
);
9292

9393
this._inPlaceService = new PrCloneInPlaceService(
9494
this.git,
@@ -104,11 +104,9 @@ export class PrCloneService {
104104
setContextIsCloning(true);
105105

106106
if (config.useInPlaceCherryPick) {
107-
// todo: remove inPlaceService and clean up class
108-
// await this.inPlaceService.clonePR(data);
109107
await this.InPlaceService.clonePR(data);
110108
} else {
111-
// await this.TempWorktreeService.clonePR(data);
109+
await this.TempWorktreeService.clonePR(data);
112110
}
113111
}
114112

@@ -118,8 +116,7 @@ export class PrCloneService {
118116
if (config.useInPlaceCherryPick) {
119117
await this.InPlaceService.cherryPickNext(isContinue);
120118
} else {
121-
// todo: add cherryPickNext to temp workdir flow
122-
// await this.TempWorktreeService.clonePR(data);
119+
await this.TempWorktreeService.cherryPickNext();
123120
}
124121
}
125122

@@ -133,13 +130,15 @@ export class PrCloneService {
133130

134131
addCleanUpActions(cleanUpActions: ICleanUpActions) {
135132
this.InPlaceService.addCleanUpActions(cleanUpActions);
133+
this.TempWorktreeService.addCleanUpActions(cleanUpActions);
136134
}
137135

138136
dispose(): void {
139137
if (!this.init) {
140138
return;
141139
}
142140

143-
// this.TempWorktreeService.dispose();
141+
this.TempWorktreeService.dispose();
142+
this.TempWorktreeService.dispose();
144143
}
145144
}

0 commit comments

Comments
 (0)