edsger 0.21.4 → 0.21.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/phases/code-implementation/branch-pr-creator.js +22 -2
- package/dist/phases/code-implementation/index.js +42 -35
- package/dist/phases/pull-request/creator.js +52 -3
- package/dist/utils/git-branch-manager.d.ts +25 -1
- package/dist/utils/git-branch-manager.js +109 -27
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ export function devBranchToFeatBranch(devBranchName) {
|
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Push a branch to remote
|
|
45
|
+
* Falls back to force-with-lease if normal push fails (e.g., after rebase)
|
|
45
46
|
*/
|
|
46
47
|
async function pushBranch(branchName, verbose) {
|
|
47
48
|
try {
|
|
@@ -66,8 +67,27 @@ async function pushBranch(branchName, verbose) {
|
|
|
66
67
|
return { success: true };
|
|
67
68
|
}
|
|
68
69
|
catch (retryError) {
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
// If push still fails (likely non-fast-forward after rebase),
|
|
71
|
+
// use force-with-lease for safer force push
|
|
72
|
+
if (verbose) {
|
|
73
|
+
logInfo(`⚠️ Push rejected, attempting force push with lease...`);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
execSync(`git push --force-with-lease origin ${branchName}`, {
|
|
77
|
+
encoding: 'utf-8',
|
|
78
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
79
|
+
});
|
|
80
|
+
if (verbose) {
|
|
81
|
+
logInfo(`✅ Successfully force pushed ${branchName}`);
|
|
82
|
+
}
|
|
83
|
+
return { success: true };
|
|
84
|
+
}
|
|
85
|
+
catch (forceError) {
|
|
86
|
+
const errorMessage = forceError instanceof Error
|
|
87
|
+
? forceError.message
|
|
88
|
+
: String(forceError);
|
|
89
|
+
return { success: false, error: errorMessage };
|
|
90
|
+
}
|
|
71
91
|
}
|
|
72
92
|
}
|
|
73
93
|
}
|
|
@@ -6,7 +6,7 @@ import { fetchCodeImplementationContext, formatContextForPrompt, } from './conte
|
|
|
6
6
|
import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
|
|
7
7
|
import { buildImplementationResult, buildVerificationFailureResult, buildNoResultsError, } from './outcome.js';
|
|
8
8
|
import { performVerificationCycle } from '../code-implementation-verification/index.js';
|
|
9
|
-
import {
|
|
9
|
+
import { prepareCustomBranchGitEnvironmentAsync, syncFeatBranchWithMain, } from '../../utils/git-branch-manager.js';
|
|
10
10
|
import { getCurrentBranch, updateBranch, getBranches, createBranches, getBaseBranchInfo, } from '../../services/branches.js';
|
|
11
11
|
import { createBranchPullRequest, } from './branch-pr-creator.js';
|
|
12
12
|
import { getGitHubConfig } from '../../api/github.js';
|
|
@@ -114,7 +114,7 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
|
|
|
114
114
|
// Determine the actual base branch for branch chaining
|
|
115
115
|
// If the current feature branch depends on another branch, use that as the base
|
|
116
116
|
let actualBaseBranch = baseBranch;
|
|
117
|
-
let
|
|
117
|
+
let originalBaseBranch;
|
|
118
118
|
if (currentBranch && currentBranch.base_branch_id) {
|
|
119
119
|
try {
|
|
120
120
|
const allBranches = await getBranches({
|
|
@@ -123,20 +123,20 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
|
|
|
123
123
|
});
|
|
124
124
|
const baseBranchInfo = await getBaseBranchInfo(currentBranch, allBranches, baseBranch);
|
|
125
125
|
actualBaseBranch = baseBranchInfo.baseBranch;
|
|
126
|
-
|
|
126
|
+
originalBaseBranch = baseBranchInfo.originalBaseBranch;
|
|
127
127
|
if (verbose) {
|
|
128
128
|
logInfo(`🔗 Branch chaining detected:`);
|
|
129
129
|
logInfo(` Current branch: ${currentBranch.name}`);
|
|
130
130
|
logInfo(` Base branch: ${actualBaseBranch}`);
|
|
131
|
+
if (originalBaseBranch) {
|
|
132
|
+
logInfo(` Original base branch: ${originalBaseBranch} (for --onto rebase)`);
|
|
133
|
+
}
|
|
131
134
|
if (baseBranchInfo.baseBranchMerged) {
|
|
132
|
-
logInfo(` Parent branch merged: yes
|
|
135
|
+
logInfo(` Parent branch merged: yes`);
|
|
133
136
|
}
|
|
134
137
|
else {
|
|
135
138
|
logInfo(` Parent branch merged: no (using parent branch)`);
|
|
136
139
|
}
|
|
137
|
-
if (needsRebase) {
|
|
138
|
-
logInfo(` Rebase needed: yes`);
|
|
139
|
-
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
catch (error) {
|
|
@@ -178,29 +178,15 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
encoding: 'utf-8',
|
|
191
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
192
|
-
});
|
|
193
|
-
if (verbose) {
|
|
194
|
-
logInfo(`✅ Successfully force pushed ${devBranchName}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (pushError) {
|
|
198
|
-
if (verbose) {
|
|
199
|
-
logInfo(`⚠️ Could not force push ${devBranchName}: ${pushError instanceof Error ? pushError.message : String(pushError)}`);
|
|
200
|
-
}
|
|
201
|
-
// Don't fail the whole process if push fails
|
|
202
|
-
}
|
|
203
|
-
}
|
|
181
|
+
// Use async version to support --onto rebase for branch chaining
|
|
182
|
+
// This correctly handles cases where base branch was squash-merged
|
|
183
|
+
const { cleanup: cleanupGit } = await prepareCustomBranchGitEnvironmentAsync({
|
|
184
|
+
featureBranch: devBranchName,
|
|
185
|
+
baseBranch: actualBaseBranch,
|
|
186
|
+
originalBaseBranch, // For --onto rebase when base branch was squash-merged
|
|
187
|
+
verbose,
|
|
188
|
+
forcePushAfterRebase: featSyncedToMain, // Trigger GitHub to recalculate PR diff
|
|
189
|
+
});
|
|
204
190
|
try {
|
|
205
191
|
// Fetch all required context information via MCP endpoints
|
|
206
192
|
if (verbose) {
|
|
@@ -856,6 +842,7 @@ const parseImplementationResponse = (response, featureId) => {
|
|
|
856
842
|
};
|
|
857
843
|
/**
|
|
858
844
|
* Push branch to remote repository
|
|
845
|
+
* Falls back to force-with-lease if normal push fails (e.g., after rebase)
|
|
859
846
|
*/
|
|
860
847
|
async function pushToRemote(branchName, verbose) {
|
|
861
848
|
try {
|
|
@@ -883,11 +870,31 @@ async function pushToRemote(branchName, verbose) {
|
|
|
883
870
|
return { success: true };
|
|
884
871
|
}
|
|
885
872
|
catch (retryError) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
873
|
+
// If push still fails (likely non-fast-forward after rebase),
|
|
874
|
+
// use force-with-lease for safer force push
|
|
875
|
+
// force-with-lease ensures we don't overwrite others' work by checking remote state
|
|
876
|
+
if (verbose) {
|
|
877
|
+
logInfo(`⚠️ Push rejected, attempting force push with lease...`);
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
execSync(`git push --force-with-lease origin ${branchName}`, {
|
|
881
|
+
encoding: 'utf-8',
|
|
882
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
883
|
+
});
|
|
884
|
+
if (verbose) {
|
|
885
|
+
logInfo(`✅ Successfully force pushed ${branchName}`);
|
|
886
|
+
}
|
|
887
|
+
return { success: true };
|
|
888
|
+
}
|
|
889
|
+
catch (forceError) {
|
|
890
|
+
const errorMessage = forceError instanceof Error
|
|
891
|
+
? forceError.message
|
|
892
|
+
: String(forceError);
|
|
893
|
+
return {
|
|
894
|
+
success: false,
|
|
895
|
+
error: errorMessage,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
891
898
|
}
|
|
892
899
|
}
|
|
893
900
|
}
|
|
@@ -62,12 +62,44 @@ const discardUncommittedChanges = (verbose) => {
|
|
|
62
62
|
};
|
|
63
63
|
/**
|
|
64
64
|
* Switch to an existing branch
|
|
65
|
+
* By default, checks remote if branch doesn't exist locally (handles multi-clone scenarios)
|
|
66
|
+
* @param branch - The branch to switch to
|
|
67
|
+
* @param verbose - Whether to log verbose output
|
|
65
68
|
*/
|
|
66
69
|
const switchToBranch = (branch, verbose) => {
|
|
67
70
|
try {
|
|
68
|
-
// First check if branch exists
|
|
71
|
+
// First check if branch exists locally
|
|
69
72
|
if (!branchExists(branch)) {
|
|
70
|
-
|
|
73
|
+
// Branch doesn't exist locally, try to fetch and create tracking branch from remote
|
|
74
|
+
if (verbose) {
|
|
75
|
+
console.log(`🔍 Branch '${branch}' not found locally, checking remote...`);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Fetch to get latest remote refs
|
|
79
|
+
execSync('git fetch origin', { encoding: 'utf-8', stdio: 'pipe' });
|
|
80
|
+
// Check if remote branch exists
|
|
81
|
+
execSync(`git rev-parse --verify origin/${branch}`, {
|
|
82
|
+
encoding: 'utf-8',
|
|
83
|
+
stdio: 'pipe',
|
|
84
|
+
});
|
|
85
|
+
// Remote branch exists, create local tracking branch
|
|
86
|
+
if (verbose) {
|
|
87
|
+
console.log(`📥 Found branch '${branch}' on remote, creating local tracking branch...`);
|
|
88
|
+
}
|
|
89
|
+
// Discard any uncommitted changes before creating branch
|
|
90
|
+
discardUncommittedChanges(verbose);
|
|
91
|
+
execSync(`git checkout -b ${branch} --track origin/${branch}`, {
|
|
92
|
+
encoding: 'utf-8',
|
|
93
|
+
});
|
|
94
|
+
if (verbose) {
|
|
95
|
+
console.log(`✅ Created and switched to tracking branch: ${branch}`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
// Remote branch doesn't exist either
|
|
101
|
+
throw new Error(`Branch '${branch}' does not exist locally or on remote. The branch should have been created during code implementation phase.`);
|
|
102
|
+
}
|
|
71
103
|
}
|
|
72
104
|
if (verbose) {
|
|
73
105
|
console.log(`🔀 Switching to branch: ${branch}`);
|
|
@@ -85,6 +117,7 @@ const switchToBranch = (branch, verbose) => {
|
|
|
85
117
|
};
|
|
86
118
|
/**
|
|
87
119
|
* Push current branch to remote
|
|
120
|
+
* Falls back to force-with-lease if normal push fails (e.g., after rebase)
|
|
88
121
|
*/
|
|
89
122
|
const pushBranch = (branch, verbose) => {
|
|
90
123
|
try {
|
|
@@ -97,7 +130,22 @@ const pushBranch = (branch, verbose) => {
|
|
|
97
130
|
}
|
|
98
131
|
}
|
|
99
132
|
catch (error) {
|
|
100
|
-
|
|
133
|
+
// If push fails (likely non-fast-forward after rebase),
|
|
134
|
+
// use force-with-lease for safer force push
|
|
135
|
+
if (verbose) {
|
|
136
|
+
console.log(`⚠️ Push rejected, attempting force push with lease...`);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
execSync(`git push --force-with-lease origin ${branch}`, {
|
|
140
|
+
encoding: 'utf-8',
|
|
141
|
+
});
|
|
142
|
+
if (verbose) {
|
|
143
|
+
console.log(`✅ Successfully force pushed ${branch}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (forceError) {
|
|
147
|
+
throw new Error(`Failed to push branch: ${forceError}`);
|
|
148
|
+
}
|
|
101
149
|
}
|
|
102
150
|
};
|
|
103
151
|
/**
|
|
@@ -208,6 +256,7 @@ export async function createPullRequest(config, feature) {
|
|
|
208
256
|
console.log(`🔍 Current branch: ${currentBranch}`);
|
|
209
257
|
}
|
|
210
258
|
// If we're on the base branch, switch to dev branch (should already exist)
|
|
259
|
+
// Default behavior now checks remote if not found locally (multi-clone scenario)
|
|
211
260
|
if (currentBranch === baseBranch) {
|
|
212
261
|
const devBranch = `dev/${feature.id}`;
|
|
213
262
|
if (verbose) {
|
|
@@ -68,10 +68,34 @@ export declare function branchExists(branch: string): boolean;
|
|
|
68
68
|
* Check if a remote branch exists
|
|
69
69
|
*/
|
|
70
70
|
export declare function remoteBranchExists(branch: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Options for switchToBranch
|
|
73
|
+
*/
|
|
74
|
+
export interface SwitchToBranchOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Skip checking remote for branch existence.
|
|
77
|
+
* By default, switchToBranch will check remote if branch doesn't exist locally,
|
|
78
|
+
* and create a tracking branch if found on remote.
|
|
79
|
+
* Set this to true to skip the remote check (useful for well-known local branches like 'main').
|
|
80
|
+
*/
|
|
81
|
+
skipRemoteCheck?: boolean;
|
|
82
|
+
}
|
|
71
83
|
/**
|
|
72
84
|
* Switch to a specific Git branch, creating it if it doesn't exist
|
|
85
|
+
*
|
|
86
|
+
* By default, if the branch doesn't exist locally, it will:
|
|
87
|
+
* 1. Fetch from origin to check if branch exists on remote
|
|
88
|
+
* 2. If found on remote, create a local tracking branch from it
|
|
89
|
+
* 3. If not found on remote, create a new branch from current HEAD
|
|
90
|
+
*
|
|
91
|
+
* This handles multi-clone scenarios where a branch may have been created
|
|
92
|
+
* and pushed from another local clone.
|
|
93
|
+
*
|
|
94
|
+
* @param branch - The branch name to switch to
|
|
95
|
+
* @param verbose - Whether to log verbose output
|
|
96
|
+
* @param options - Additional options for branch switching
|
|
73
97
|
*/
|
|
74
|
-
export declare function switchToBranch(branch: string, verbose?: boolean): void;
|
|
98
|
+
export declare function switchToBranch(branch: string, verbose?: boolean, options?: SwitchToBranchOptions): void;
|
|
75
99
|
/**
|
|
76
100
|
* Pull latest changes from remote for a specific branch
|
|
77
101
|
*/
|
|
@@ -240,20 +240,32 @@ export function remoteBranchExists(branch) {
|
|
|
240
240
|
}
|
|
241
241
|
/**
|
|
242
242
|
* Switch to a specific Git branch, creating it if it doesn't exist
|
|
243
|
+
*
|
|
244
|
+
* By default, if the branch doesn't exist locally, it will:
|
|
245
|
+
* 1. Fetch from origin to check if branch exists on remote
|
|
246
|
+
* 2. If found on remote, create a local tracking branch from it
|
|
247
|
+
* 3. If not found on remote, create a new branch from current HEAD
|
|
248
|
+
*
|
|
249
|
+
* This handles multi-clone scenarios where a branch may have been created
|
|
250
|
+
* and pushed from another local clone.
|
|
251
|
+
*
|
|
252
|
+
* @param branch - The branch name to switch to
|
|
253
|
+
* @param verbose - Whether to log verbose output
|
|
254
|
+
* @param options - Additional options for branch switching
|
|
243
255
|
*/
|
|
244
|
-
export function switchToBranch(branch, verbose) {
|
|
256
|
+
export function switchToBranch(branch, verbose, options) {
|
|
245
257
|
try {
|
|
246
258
|
if (verbose) {
|
|
247
259
|
logInfo(`🔄 Switching to branch ${branch}...`);
|
|
248
260
|
}
|
|
249
261
|
// Check if branch exists locally
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
// Branch exists, just switch to it
|
|
262
|
+
const localExists = branchExists(branch);
|
|
263
|
+
if (localExists) {
|
|
264
|
+
// Branch exists locally, just switch to it
|
|
253
265
|
execSync(`git checkout ${branch}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
254
266
|
}
|
|
255
|
-
else {
|
|
256
|
-
//
|
|
267
|
+
else if (options?.skipRemoteCheck) {
|
|
268
|
+
// Skip remote check, just create from current HEAD
|
|
257
269
|
if (verbose) {
|
|
258
270
|
logInfo(` Branch ${branch} doesn't exist, creating it...`);
|
|
259
271
|
}
|
|
@@ -262,6 +274,37 @@ export function switchToBranch(branch, verbose) {
|
|
|
262
274
|
stdio: 'pipe',
|
|
263
275
|
});
|
|
264
276
|
}
|
|
277
|
+
else {
|
|
278
|
+
// Default: Check if branch exists on remote before creating locally
|
|
279
|
+
// This handles multi-clone scenarios where branch was pushed from another clone
|
|
280
|
+
try {
|
|
281
|
+
// Fetch to get latest remote refs
|
|
282
|
+
execSync('git fetch origin', { encoding: 'utf-8', stdio: 'pipe' });
|
|
283
|
+
// Check if remote branch exists
|
|
284
|
+
execSync(`git rev-parse --verify origin/${branch}`, {
|
|
285
|
+
encoding: 'utf-8',
|
|
286
|
+
stdio: 'pipe',
|
|
287
|
+
});
|
|
288
|
+
// Remote branch exists, create local tracking branch from it
|
|
289
|
+
if (verbose) {
|
|
290
|
+
logInfo(` Branch ${branch} found on remote, creating local tracking branch...`);
|
|
291
|
+
}
|
|
292
|
+
execSync(`git checkout -b ${branch} --track origin/${branch}`, {
|
|
293
|
+
encoding: 'utf-8',
|
|
294
|
+
stdio: 'pipe',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
// Remote branch doesn't exist, create new branch from current HEAD
|
|
299
|
+
if (verbose) {
|
|
300
|
+
logInfo(` Branch ${branch} doesn't exist locally or remotely, creating new branch...`);
|
|
301
|
+
}
|
|
302
|
+
execSync(`git checkout -b ${branch}`, {
|
|
303
|
+
encoding: 'utf-8',
|
|
304
|
+
stdio: 'pipe',
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
265
308
|
if (verbose) {
|
|
266
309
|
logInfo(`✅ Switched to ${branch} branch`);
|
|
267
310
|
}
|
|
@@ -338,11 +381,12 @@ export function switchToFeatureBranchAndRebase(featureBranch, baseBranch = 'main
|
|
|
338
381
|
}
|
|
339
382
|
}
|
|
340
383
|
// Switch to feature branch (will create if doesn't exist)
|
|
384
|
+
// Default behavior now checks remote first (handles multi-clone scenarios)
|
|
341
385
|
if (getCurrentBranch() !== featureBranch) {
|
|
342
386
|
switchToBranch(featureBranch, verbose);
|
|
343
387
|
}
|
|
344
|
-
// Sync with remote feature branch if it exists
|
|
345
|
-
// This handles the case where
|
|
388
|
+
// Sync with remote feature branch if it exists and local branch needs updating
|
|
389
|
+
// This handles the case where local branch exists but is behind remote
|
|
346
390
|
try {
|
|
347
391
|
// Check for uncommitted changes and reset if found before any git operations
|
|
348
392
|
if (hasUncommittedChanges()) {
|
|
@@ -351,7 +395,7 @@ export function switchToFeatureBranchAndRebase(featureBranch, baseBranch = 'main
|
|
|
351
395
|
}
|
|
352
396
|
resetUncommittedChanges(verbose);
|
|
353
397
|
}
|
|
354
|
-
// Fetch to get latest remote state
|
|
398
|
+
// Fetch to get latest remote state (may have been fetched in switchToBranch, but ensures fresh data)
|
|
355
399
|
execSync('git fetch origin', { encoding: 'utf-8', stdio: 'pipe' });
|
|
356
400
|
// Check if remote feature branch exists
|
|
357
401
|
try {
|
|
@@ -359,18 +403,35 @@ export function switchToFeatureBranchAndRebase(featureBranch, baseBranch = 'main
|
|
|
359
403
|
encoding: 'utf-8',
|
|
360
404
|
stdio: 'pipe',
|
|
361
405
|
});
|
|
362
|
-
// Remote branch exists,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
// Reset local branch to match remote branch
|
|
367
|
-
// This ensures we have all commits from the remote (e.g., from another machine)
|
|
368
|
-
execSync(`git reset --hard origin/${featureBranch}`, {
|
|
406
|
+
// Remote branch exists, check if we need to sync
|
|
407
|
+
// Get local and remote commit SHAs
|
|
408
|
+
const localSha = execSync(`git rev-parse ${featureBranch}`, {
|
|
369
409
|
encoding: 'utf-8',
|
|
370
410
|
stdio: 'pipe',
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
|
|
411
|
+
}).trim();
|
|
412
|
+
const remoteSha = execSync(`git rev-parse origin/${featureBranch}`, {
|
|
413
|
+
encoding: 'utf-8',
|
|
414
|
+
stdio: 'pipe',
|
|
415
|
+
}).trim();
|
|
416
|
+
if (localSha !== remoteSha) {
|
|
417
|
+
// Local and remote differ, reset to remote
|
|
418
|
+
if (verbose) {
|
|
419
|
+
logInfo(`📥 Syncing with remote feature branch origin/${featureBranch}...`);
|
|
420
|
+
logInfo(` Local: ${localSha.substring(0, 7)}`);
|
|
421
|
+
logInfo(` Remote: ${remoteSha.substring(0, 7)}`);
|
|
422
|
+
}
|
|
423
|
+
// Reset local branch to match remote branch
|
|
424
|
+
// This ensures we have all commits from the remote (e.g., from another machine)
|
|
425
|
+
execSync(`git reset --hard origin/${featureBranch}`, {
|
|
426
|
+
encoding: 'utf-8',
|
|
427
|
+
stdio: 'pipe',
|
|
428
|
+
});
|
|
429
|
+
if (verbose) {
|
|
430
|
+
logInfo(`✅ Synced local branch with origin/${featureBranch}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (verbose) {
|
|
434
|
+
logInfo(`✅ Local branch is up to date with origin/${featureBranch}`);
|
|
374
435
|
}
|
|
375
436
|
}
|
|
376
437
|
catch (e) {
|
|
@@ -660,10 +721,12 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
|
|
|
660
721
|
}
|
|
661
722
|
}
|
|
662
723
|
// Switch to feature branch (will create if doesn't exist)
|
|
724
|
+
// Default behavior now checks remote first (handles multi-clone scenarios)
|
|
663
725
|
if (getCurrentBranch() !== featureBranch) {
|
|
664
726
|
switchToBranch(featureBranch, verbose);
|
|
665
727
|
}
|
|
666
|
-
// Sync with remote feature branch if it exists
|
|
728
|
+
// Sync with remote feature branch if it exists and local branch needs updating
|
|
729
|
+
// This handles the case where local branch exists but is behind remote
|
|
667
730
|
try {
|
|
668
731
|
if (hasUncommittedChanges()) {
|
|
669
732
|
if (verbose) {
|
|
@@ -671,21 +734,40 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
|
|
|
671
734
|
}
|
|
672
735
|
resetUncommittedChanges(verbose);
|
|
673
736
|
}
|
|
737
|
+
// Fetch to get latest remote state (may have been fetched in switchToBranch, but ensures fresh data)
|
|
674
738
|
execSync('git fetch origin', { encoding: 'utf-8', stdio: 'pipe' });
|
|
675
739
|
try {
|
|
676
740
|
execSync(`git rev-parse --verify origin/${featureBranch}`, {
|
|
677
741
|
encoding: 'utf-8',
|
|
678
742
|
stdio: 'pipe',
|
|
679
743
|
});
|
|
680
|
-
if
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
execSync(`git reset --hard origin/${featureBranch}`, {
|
|
744
|
+
// Remote branch exists, check if we need to sync
|
|
745
|
+
// Get local and remote commit SHAs
|
|
746
|
+
const localSha = execSync(`git rev-parse ${featureBranch}`, {
|
|
684
747
|
encoding: 'utf-8',
|
|
685
748
|
stdio: 'pipe',
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
|
|
749
|
+
}).trim();
|
|
750
|
+
const remoteSha = execSync(`git rev-parse origin/${featureBranch}`, {
|
|
751
|
+
encoding: 'utf-8',
|
|
752
|
+
stdio: 'pipe',
|
|
753
|
+
}).trim();
|
|
754
|
+
if (localSha !== remoteSha) {
|
|
755
|
+
// Local and remote differ, reset to remote
|
|
756
|
+
if (verbose) {
|
|
757
|
+
logInfo(`📥 Syncing with remote feature branch origin/${featureBranch}...`);
|
|
758
|
+
logInfo(` Local: ${localSha.substring(0, 7)}`);
|
|
759
|
+
logInfo(` Remote: ${remoteSha.substring(0, 7)}`);
|
|
760
|
+
}
|
|
761
|
+
execSync(`git reset --hard origin/${featureBranch}`, {
|
|
762
|
+
encoding: 'utf-8',
|
|
763
|
+
stdio: 'pipe',
|
|
764
|
+
});
|
|
765
|
+
if (verbose) {
|
|
766
|
+
logInfo(`✅ Synced local branch with origin/${featureBranch}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
else if (verbose) {
|
|
770
|
+
logInfo(`✅ Local branch is up to date with origin/${featureBranch}`);
|
|
689
771
|
}
|
|
690
772
|
}
|
|
691
773
|
catch (e) {
|