fix: add configurable merge_strategy preference for slice completion (#167)
Squash merge was hardcoded, causing auto-mode to hard-stop when conflicts
arose from long-lived branches or frequently-changing .gsd/* artifacts.
Add git.merge_strategy preference ("squash" | "merge", default: squash).
"merge" uses --no-ff which preserves branch history and handles conflicts
from divergent branches more gracefully. Users hitting repeated squash
merge failures can set merge_strategy: merge in .gsd/preferences.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
71d3a69646
commit
66196b4a4f
2 changed files with 26 additions and 9 deletions
|
|
@ -27,6 +27,7 @@ export interface GitPreferences {
|
|||
pre_merge_check?: boolean | string;
|
||||
commit_type?: string;
|
||||
main_branch?: string;
|
||||
merge_strategy?: "squash" | "merge";
|
||||
}
|
||||
|
||||
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
||||
|
|
@ -526,25 +527,33 @@ export class GitServiceImpl {
|
|||
// Pull latest main before merging to avoid conflicts from remote changes
|
||||
this.git(["pull", "--rebase", "origin", mainBranch], { allowFailure: true });
|
||||
|
||||
// Squash merge — abort cleanly on conflict so the working tree is never
|
||||
// left in a half-merged state (see: merge-bug-fix).
|
||||
// Merge slice branch — strategy is configurable via git.merge_strategy
|
||||
// preference. Default: "squash" (preserves existing behavior).
|
||||
// "merge" uses --no-ff which is more resilient to conflicts from
|
||||
// long-lived branches or frequently-changing .gsd/* artifacts.
|
||||
const strategy = this.prefs.merge_strategy ?? "squash";
|
||||
const mergeArgs = strategy === "merge"
|
||||
? ["merge", "--no-ff", "-m", message, branch]
|
||||
: ["merge", "--squash", branch];
|
||||
|
||||
try {
|
||||
this.git(["merge", "--squash", branch]);
|
||||
this.git(mergeArgs);
|
||||
} catch (mergeError) {
|
||||
// git merge --squash exits non-zero on conflict. The working tree now
|
||||
// has conflict markers and a dirty index. Reset to restore a clean state.
|
||||
// Merge exits non-zero on conflict. Reset to restore a clean state.
|
||||
this.git(["reset", "--hard", "HEAD"], { allowFailure: true });
|
||||
const msg = mergeError instanceof Error ? mergeError.message : String(mergeError);
|
||||
throw new Error(
|
||||
`Squash-merge of "${branch}" into "${mainBranch}" failed with conflicts. ` +
|
||||
`${strategy === "merge" ? "Merge" : "Squash-merge"} of "${branch}" into "${mainBranch}" failed with conflicts. ` +
|
||||
`Working tree has been reset to a clean state. ` +
|
||||
`Resolve manually: git checkout ${mainBranch} && git merge --squash ${branch}\n` +
|
||||
`Resolve manually: git checkout ${mainBranch} && git merge ${strategy === "merge" ? "--no-ff" : "--squash"} ${branch}\n` +
|
||||
`Original error: ${msg}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Commit with rich message via stdin pipe
|
||||
this.git(["commit", "-F", "-"], { input: message });
|
||||
// Squash merge needs a separate commit; --no-ff merge already committed
|
||||
if (strategy === "squash") {
|
||||
this.git(["commit", "-F", "-"], { input: message });
|
||||
}
|
||||
|
||||
// Delete the merged branch
|
||||
this.git(["branch", "-D", branch]);
|
||||
|
|
|
|||
|
|
@ -702,6 +702,14 @@ function validatePreferences(preferences: GSDPreferences): {
|
|||
errors.push(`git.commit_type must be one of: feat, fix, refactor, docs, test, chore, perf, ci, build, style`);
|
||||
}
|
||||
}
|
||||
if (g.merge_strategy !== undefined) {
|
||||
const validStrategies = new Set(["squash", "merge"]);
|
||||
if (typeof g.merge_strategy === "string" && validStrategies.has(g.merge_strategy)) {
|
||||
git.merge_strategy = g.merge_strategy as "squash" | "merge";
|
||||
} else {
|
||||
errors.push("git.merge_strategy must be one of: squash, merge");
|
||||
}
|
||||
}
|
||||
if (g.main_branch !== undefined) {
|
||||
if (typeof g.main_branch === "string" && g.main_branch.trim() !== "" && VALID_BRANCH_NAME.test(g.main_branch)) {
|
||||
git.main_branch = g.main_branch;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue