edsger 0.21.6 → 0.21.8

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.
@@ -114,6 +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 rebaseTargetBranch;
117
118
  let originalBaseBranch;
118
119
  if (currentBranch && currentBranch.base_branch_id) {
119
120
  try {
@@ -123,11 +124,15 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
123
124
  });
124
125
  const baseBranchInfo = await getBaseBranchInfo(currentBranch, allBranches, baseBranch);
125
126
  actualBaseBranch = baseBranchInfo.baseBranch;
127
+ rebaseTargetBranch = baseBranchInfo.rebaseTargetBranch;
126
128
  originalBaseBranch = baseBranchInfo.originalBaseBranch;
127
129
  if (verbose) {
128
130
  logInfo(`šŸ”— Branch chaining detected:`);
129
131
  logInfo(` Current branch: ${currentBranch.name}`);
130
- logInfo(` Base branch: ${actualBaseBranch}`);
132
+ logInfo(` Base branch (for new branches): ${actualBaseBranch}`);
133
+ if (rebaseTargetBranch) {
134
+ logInfo(` Rebase target: ${rebaseTargetBranch}`);
135
+ }
131
136
  if (originalBaseBranch) {
132
137
  logInfo(` Original base branch: ${originalBaseBranch} (for --onto rebase)`);
133
138
  }
@@ -183,7 +188,8 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
183
188
  const { cleanup: cleanupGit } = await prepareCustomBranchGitEnvironmentAsync({
184
189
  featureBranch: devBranchName,
185
190
  baseBranch: actualBaseBranch,
186
- originalBaseBranch, // For --onto rebase when base branch was squash-merged
191
+ rebaseTargetBranch, // For --onto rebase target (e.g., main) when base branch is merged
192
+ originalBaseBranch, // For --onto rebase starting point when base branch was squash-merged
187
193
  verbose,
188
194
  forcePushAfterRebase: featSyncedToMain, // Trigger GitHub to recalculate PR diff
189
195
  });
@@ -76,6 +76,7 @@ export const refineCodeFromPRFeedback = async (options, config) => {
76
76
  let currentBranch = null;
77
77
  let allBranches = [];
78
78
  let baseBranchForRebase = 'main'; // Default base branch for rebase
79
+ let rebaseTargetBranchForRebase; // Target for --onto rebase (e.g., main)
79
80
  let originalBaseBranchForRebase; // For --onto when base was squash-merged
80
81
  try {
81
82
  // Get all branches to determine base branch info
@@ -92,9 +93,12 @@ export const refineCodeFromPRFeedback = async (options, config) => {
92
93
  const baseBranchInfo = await getBaseBranchInfo(currentBranch, allBranches, 'main');
93
94
  // Always use the baseBranch from getBaseBranchInfo - it handles all cases:
94
95
  // - No base branch: returns 'main'
95
- // - Base branch merged: returns feat branch (dev/xxx -> feat/xxx)
96
+ // - Base branch merged: returns feat branch (new branches created from feat/xxx)
97
+ // - Base branch completed: returns main
96
98
  // - Base branch not merged: returns dev branch
97
99
  baseBranchForRebase = baseBranchInfo.baseBranch;
100
+ // When base branch is merged, rebaseTargetBranch is 'main' (target for --onto)
101
+ rebaseTargetBranchForRebase = baseBranchInfo.rebaseTargetBranch;
98
102
  // When base branch is merged (squash merge), we need originalBaseBranch for --onto
99
103
  originalBaseBranchForRebase = baseBranchInfo.originalBaseBranch;
100
104
  if (verbose) {
@@ -162,6 +166,7 @@ export const refineCodeFromPRFeedback = async (options, config) => {
162
166
  ? await prepareCustomBranchGitEnvironmentAsync({
163
167
  featureBranch: branchName,
164
168
  baseBranch: baseBranchForRebase,
169
+ rebaseTargetBranch: rebaseTargetBranchForRebase,
165
170
  originalBaseBranch: originalBaseBranchForRebase,
166
171
  verbose,
167
172
  resolveConflicts: true,
@@ -128,6 +128,7 @@ export const reviewPullRequest = async (options, config) => {
128
128
  let currentBranch = null;
129
129
  let allBranches = [];
130
130
  let baseBranchForRebase = 'main'; // Default base branch for rebase
131
+ let rebaseTargetBranchForRebase; // Target for --onto rebase (e.g., main)
131
132
  let originalBaseBranchForRebase; // For --onto when base was squash-merged
132
133
  try {
133
134
  // Get all branches to determine base branch info
@@ -145,9 +146,12 @@ export const reviewPullRequest = async (options, config) => {
145
146
  const baseBranchInfo = await getBaseBranchInfo(currentBranch, allBranches, 'main');
146
147
  // Always use the baseBranch from getBaseBranchInfo - it handles all cases:
147
148
  // - No base branch: returns 'main'
148
- // - Base branch merged: returns feat branch (dev/xxx -> feat/xxx)
149
+ // - Base branch merged: returns feat branch (new branches created from feat/xxx)
150
+ // - Base branch completed: returns main
149
151
  // - Base branch not merged: returns dev branch
150
152
  baseBranchForRebase = baseBranchInfo.baseBranch;
153
+ // When base branch is merged, rebaseTargetBranch is 'main' (target for --onto)
154
+ rebaseTargetBranchForRebase = baseBranchInfo.rebaseTargetBranch;
151
155
  // When base branch is merged (squash merge), we need originalBaseBranch for --onto
152
156
  originalBaseBranchForRebase = baseBranchInfo.originalBaseBranch;
153
157
  if (verbose) {
@@ -215,6 +219,7 @@ export const reviewPullRequest = async (options, config) => {
215
219
  ? await prepareCustomBranchGitEnvironmentAsync({
216
220
  featureBranch: branchName,
217
221
  baseBranch: baseBranchForRebase,
222
+ rebaseTargetBranch: rebaseTargetBranchForRebase,
218
223
  originalBaseBranch: originalBaseBranchForRebase,
219
224
  verbose,
220
225
  resolveConflicts: true,
@@ -61,22 +61,20 @@ export declare function getNextPendingBranch(options: PipelinePhaseOptions): Pro
61
61
  export declare function allBranchesCompleted(options: PipelinePhaseOptions): Promise<boolean>;
62
62
  /**
63
63
  * Get the base branch information for a branch.
64
- * Returns:
65
- * - If base_branch_id is null: use main (first branch in chain)
66
- * - If base_branch_id is set and that branch is completed: use main
67
- * (completed means feat merged to main)
68
- * - If base_branch_id is set and that branch is merged: use base branch's feat branch
69
- * (merged means dev merged to feat, not to main)
70
- * - If base_branch_id is set and that branch is not merged: use base branch's dev branch
71
64
  *
72
- * When base branch is merged/completed (squash merge), we need to use `git rebase --onto` to
73
- * correctly rebase only the new commits. This function returns both the new base
74
- * and the original base for this purpose:
75
- * - completed: baseBranch=main, originalBaseBranch=feat/xxx
76
- * - merged: baseBranch=feat/xxx, originalBaseBranch=dev/xxx
65
+ * Rebase strategies based on base branch status:
66
+ * - no base_branch_id: git rebase main (first branch in chain)
67
+ * - completed (feat merged to main): git rebase main
68
+ * - merged (dev merged to feat): create from feat/xxx, then git rebase --onto main feat/xxx
69
+ * - in_progress/pending: git rebase dev/xxx (base on parent dev branch)
70
+ *
71
+ * For 'merged' status:
72
+ * - New branches are created from feat/xxx (which contains squashed commits)
73
+ * - We use --onto to rebase only new commits (after feat branch) onto main
77
74
  */
78
75
  export declare function getBaseBranchInfo(branch: Branch, allBranches: Branch[], mainBranch?: string): Promise<{
79
76
  baseBranch: string;
77
+ rebaseTargetBranch?: string;
80
78
  originalBaseBranch?: string;
81
79
  needsRebase: boolean;
82
80
  baseBranchMerged: boolean;
@@ -160,19 +160,16 @@ export async function allBranchesCompleted(options) {
160
160
  }
161
161
  /**
162
162
  * Get the base branch information for a branch.
163
- * Returns:
164
- * - If base_branch_id is null: use main (first branch in chain)
165
- * - If base_branch_id is set and that branch is completed: use main
166
- * (completed means feat merged to main)
167
- * - If base_branch_id is set and that branch is merged: use base branch's feat branch
168
- * (merged means dev merged to feat, not to main)
169
- * - If base_branch_id is set and that branch is not merged: use base branch's dev branch
170
163
  *
171
- * When base branch is merged/completed (squash merge), we need to use `git rebase --onto` to
172
- * correctly rebase only the new commits. This function returns both the new base
173
- * and the original base for this purpose:
174
- * - completed: baseBranch=main, originalBaseBranch=feat/xxx
175
- * - merged: baseBranch=feat/xxx, originalBaseBranch=dev/xxx
164
+ * Rebase strategies based on base branch status:
165
+ * - no base_branch_id: git rebase main (first branch in chain)
166
+ * - completed (feat merged to main): git rebase main
167
+ * - merged (dev merged to feat): create from feat/xxx, then git rebase --onto main feat/xxx
168
+ * - in_progress/pending: git rebase dev/xxx (base on parent dev branch)
169
+ *
170
+ * For 'merged' status:
171
+ * - New branches are created from feat/xxx (which contains squashed commits)
172
+ * - We use --onto to rebase only new commits (after feat branch) onto main
176
173
  */
177
174
  export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main') {
178
175
  // No base branch - start from main (first branch in chain)
@@ -195,27 +192,21 @@ export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main'
195
192
  }
196
193
  // Check if base branch is completed (feat merged to main)
197
194
  if (baseBranch.status === 'completed') {
198
- // Base branch's feat is merged to main - rebase directly from main
199
- // Use feat branch as originalBaseBranch for --onto
200
- if (!baseBranch.branch_name) {
201
- return {
202
- baseBranch: mainBranch,
203
- needsRebase: false,
204
- baseBranchMerged: true,
205
- };
206
- }
207
- const featBranchName = baseBranch.branch_name.replace(/^dev\//, 'feat/');
195
+ // Base branch's feat is already squash-merged to main
196
+ // Just rebase directly to main - no need for --onto
197
+ // Main already contains all the changes, so a normal rebase will work correctly
208
198
  return {
209
199
  baseBranch: mainBranch,
210
- originalBaseBranch: featBranchName, // Use feat branch for --onto
211
- needsRebase: true,
200
+ // Don't set originalBaseBranch - we want a normal rebase, not --onto
201
+ needsRebase: false,
212
202
  baseBranchMerged: true,
213
203
  };
214
204
  }
215
- // Check if base branch is merged (dev merged to feat)
205
+ // Check if base branch is merged (dev merged to feat, but feat not yet merged to main)
216
206
  if (baseBranch.status === 'merged') {
217
- // Base branch's dev is merged to feat - rebase from base branch's feat branch
218
- // Convert dev/xxx/1-name to feat/xxx/1-name
207
+ // Base branch's dev is squash-merged to feat
208
+ // New branches should be created from feat/xxx (which has the squashed commits)
209
+ // Use --onto to rebase only new commits (after feat branch) onto main
219
210
  if (!baseBranch.branch_name) {
220
211
  return {
221
212
  baseBranch: mainBranch,
@@ -225,8 +216,9 @@ export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main'
225
216
  }
226
217
  const featBranchName = baseBranch.branch_name.replace(/^dev\//, 'feat/');
227
218
  return {
228
- baseBranch: featBranchName,
229
- originalBaseBranch: baseBranch.branch_name, // Return original dev branch for --onto
219
+ baseBranch: featBranchName, // Create new branches from feat/xxx
220
+ rebaseTargetBranch: mainBranch, // Rebase onto main
221
+ originalBaseBranch: featBranchName, // Use feat branch for --onto starting point
230
222
  needsRebase: true,
231
223
  baseBranchMerged: true,
232
224
  };
@@ -174,10 +174,21 @@ export declare function syncFeatBranchWithMain(featBranch: string, githubToken:
174
174
  */
175
175
  export interface RebaseWithConflictResolutionOptions {
176
176
  featureBranch: string;
177
+ /**
178
+ * Base branch to create new branches from.
179
+ * For merged parent branches, this is feat/xxx.
180
+ * For completed parent branches, this is main.
181
+ */
177
182
  baseBranch?: string;
183
+ /**
184
+ * Target branch for --onto rebase.
185
+ * When set, uses `git rebase --onto rebaseTargetBranch originalBaseBranch`.
186
+ * If not set, baseBranch is used as the rebase target.
187
+ */
188
+ rebaseTargetBranch?: string;
178
189
  /**
179
190
  * Original base branch before squash merge.
180
- * When set, uses `git rebase --onto baseBranch originalBaseBranch` to only
191
+ * When set, uses `git rebase --onto <target> originalBaseBranch` to only
181
192
  * rebase commits that are new relative to originalBaseBranch.
182
193
  * This is required when the base branch was squash-merged, otherwise
183
194
  * git will try to reapply all the original commits causing many conflicts.
@@ -689,7 +689,10 @@ export async function syncFeatBranchWithMain(featBranch, githubToken, owner, rep
689
689
  * @returns Object containing the previous branch name and conflict resolution result
690
690
  */
691
691
  export async function switchToFeatureBranchAndRebaseAsync(options) {
692
- const { featureBranch, baseBranch = 'main', originalBaseBranch, verbose, resolveConflicts = false, conflictResolverConfig, forcePushAfterRebase = false, } = options;
692
+ const { featureBranch, baseBranch = 'main', rebaseTargetBranch, originalBaseBranch, verbose, resolveConflicts = false, conflictResolverConfig, forcePushAfterRebase = false, } = options;
693
+ // Determine the actual target branch for rebase
694
+ // If rebaseTargetBranch is set (e.g., main), use it; otherwise use baseBranch
695
+ const actualRebaseTarget = rebaseTargetBranch || baseBranch;
693
696
  const previousBranch = getCurrentBranch();
694
697
  if (verbose) {
695
698
  logInfo(`\nšŸ”„ Preparing feature branch: ${featureBranch}`);
@@ -781,13 +784,13 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
781
784
  logInfo(`āš ļø Could not sync with remote feature branch, continuing with local branch`);
782
785
  }
783
786
  }
784
- // Rebase feature branch with latest base branch
787
+ // Rebase feature branch with latest target branch
785
788
  if (verbose) {
786
789
  if (originalBaseBranch) {
787
- logInfo(`šŸ“„ Rebasing ${featureBranch} onto origin/${baseBranch} (from ${originalBaseBranch})...`);
790
+ logInfo(`šŸ“„ Rebasing ${featureBranch} onto origin/${actualRebaseTarget} (from ${originalBaseBranch})...`);
788
791
  }
789
792
  else {
790
- logInfo(`šŸ“„ Rebasing ${featureBranch} with origin/${baseBranch}...`);
793
+ logInfo(`šŸ“„ Rebasing ${featureBranch} with origin/${actualRebaseTarget}...`);
791
794
  }
792
795
  }
793
796
  try {
@@ -797,8 +800,8 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
797
800
  }
798
801
  resetUncommittedChanges(verbose);
799
802
  }
800
- // Fetch the base branch first
801
- execSync(`git fetch origin ${baseBranch}`, {
803
+ // Fetch the target branch first
804
+ execSync(`git fetch origin ${actualRebaseTarget}`, {
802
805
  encoding: 'utf-8',
803
806
  stdio: 'pipe',
804
807
  });
@@ -807,23 +810,28 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
807
810
  // This prevents re-applying commits that were already included in the squash
808
811
  // Command: git rebase --onto <new-base> <old-base>
809
812
  // This rebases commits from <old-base>..HEAD onto <new-base>
813
+ // Also fetch the originalBaseBranch for the --onto reference
814
+ execSync(`git fetch origin ${originalBaseBranch}`, {
815
+ encoding: 'utf-8',
816
+ stdio: 'pipe',
817
+ });
810
818
  if (verbose) {
811
819
  logInfo(` Using --onto to rebase only new commits (after ${originalBaseBranch})`);
812
820
  }
813
- execSync(`git rebase --onto origin/${baseBranch} origin/${originalBaseBranch}`, {
821
+ execSync(`git rebase --onto origin/${actualRebaseTarget} origin/${originalBaseBranch}`, {
814
822
  encoding: 'utf-8',
815
823
  stdio: verbose ? 'inherit' : 'pipe',
816
824
  });
817
825
  }
818
826
  else {
819
827
  // Normal rebase
820
- execSync(`git rebase origin/${baseBranch}`, {
828
+ execSync(`git rebase origin/${actualRebaseTarget}`, {
821
829
  encoding: 'utf-8',
822
830
  stdio: verbose ? 'inherit' : 'pipe',
823
831
  });
824
832
  }
825
833
  if (verbose) {
826
- logInfo(`āœ… Successfully rebased ${featureBranch} with origin/${baseBranch}\n`);
834
+ logInfo(`āœ… Successfully rebased ${featureBranch} with origin/${actualRebaseTarget}\n`);
827
835
  }
828
836
  // Force push after rebase to trigger GitHub to recalculate PR diff
829
837
  if (forcePushAfterRebase) {
@@ -866,7 +874,7 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
866
874
  });
867
875
  if (result.success) {
868
876
  if (verbose) {
869
- logInfo(`āœ… Successfully resolved conflicts and rebased ${featureBranch} with origin/${baseBranch}\n`);
877
+ logInfo(`āœ… Successfully resolved conflicts and rebased ${featureBranch} with origin/${actualRebaseTarget}\n`);
870
878
  }
871
879
  // Force push after rebase to trigger GitHub to recalculate PR diff
872
880
  if (forcePushAfterRebase) {
@@ -898,7 +906,7 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
898
906
  else {
899
907
  // Conflict resolution failed, abort rebase
900
908
  abortRebase(verbose);
901
- throw new Error(`Failed to automatically resolve conflicts during rebase ${featureBranch} with ${baseBranch}.\n` +
909
+ throw new Error(`Failed to automatically resolve conflicts during rebase ${featureBranch} with ${actualRebaseTarget}.\n` +
902
910
  `${result.error || 'Unknown error'}\n` +
903
911
  `Please resolve conflicts manually and try again.`);
904
912
  }
@@ -909,7 +917,7 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
909
917
  if (verbose) {
910
918
  logError(`āŒ Rebase failed and was aborted. Repository is in clean state.`);
911
919
  }
912
- throw new Error(`Failed to rebase ${featureBranch} with ${baseBranch}: ${error instanceof Error ? error.message : String(error)}\n` +
920
+ throw new Error(`Failed to rebase ${featureBranch} with ${actualRebaseTarget}: ${error instanceof Error ? error.message : String(error)}\n` +
913
921
  `This usually means there are conflicts between your feature branch and base branch.\n` +
914
922
  `Please resolve conflicts manually and try again.`);
915
923
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.21.6",
3
+ "version": "0.21.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"