Skip to content

Commit 6d4c319

Browse files
feat: configurable merge method for /land-and-deploy
Three-tier merge method resolution: 1. User config override (gstack-config set merge_method merge) 2. Auto-detect from GitHub repo settings via API 3. Default to squash (unchanged behavior) /land-and-deploy now queries the repo's allowed merge methods via gh api repos/{owner}/{repo}. If only one method is enabled, it uses that. If multiple are allowed, it picks squash as the default. Users can override with: gstack-config set merge_method merge /setup-deploy now persists the merge method to both CLAUDE.md and gstack-config so /land-and-deploy picks it up automatically. This fixes the disconnect where the UI said '(from repo settings)' but nothing actually queried repo settings. The --auto flag still takes first priority (respects repo settings + merge queues), but the direct fallback is now configurable instead of hardcoded.
1 parent 7ea6ead commit 6d4c319

5 files changed

Lines changed: 182 additions & 10 deletions

File tree

bin/gstack-config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
3838
# skill_prefix: false # true = namespace skills as /gstack-qa, /gstack-ship
3939
# # false = short names /qa, /ship
4040
#
41+
# ─── Git ─────────────────────────────────────────────────────────────
42+
# merge_method: squash # squash | merge | rebase
43+
# # Overrides repo settings for /land-and-deploy.
44+
# # If unset, auto-detects from GitHub repo settings.
45+
# # If detection fails, defaults to squash.
46+
#
4147
# ─── Advanced ────────────────────────────────────────────────────────
4248
# codex_reviews: enabled # disabled = skip Codex adversarial reviews in /ship
4349
# gstack_contributor: false # true = file field reports when gstack misbehaves

land-and-deploy/SKILL.md

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ readiness first.
530530
- Production health issues detected by canary (offer revert)
531531

532532
**Never stop for:**
533-
- Choosing merge method (auto-detect from repo settings)
533+
- Choosing merge method (config override > repo settings > squash default)
534534
- Timeout warnings (warn and continue gracefully)
535535

536536
## Voice & Tone
@@ -707,7 +707,7 @@ Run whichever commands are relevant based on the detected platform. Build the re
707707
║ 4. {Wait for deploy workflow / Wait 60s / Skip} ║
708708
║ 5. {Run canary verification / Skip (no URL)} ║
709709
║ ║
710-
║ MERGE METHOD: {squash/merge/rebase} (from repo settings) ║
710+
║ MERGE METHOD: {squash/merge/rebase} (source: {config/repo/default}) ║
711711
║ MERGE QUEUE: {detected / not detected} ║
712712
╚══════════════════════════════════════════════════════════╝
713713
```
@@ -1055,6 +1055,69 @@ If the user chooses A or C: Tell the user "Merging now." Continue to Step 4.
10551055
Record the start timestamp for timing data. Also record which merge path is taken
10561056
(auto-merge vs direct) for the deploy report.
10571057

1058+
### 4.0: Determine merge method
1059+
1060+
Resolve the merge method using a three-tier priority:
1061+
1062+
1. **User config override** (explicit preference, highest priority)
1063+
2. **Repo settings** (what GitHub allows for this repo)
1064+
3. **Default** (`squash`)
1065+
1066+
```bash
1067+
# Tier 1: Check user config
1068+
_MERGE_METHOD=$(~/.claude/skills/gstack/bin/gstack-config get merge_method 2>/dev/null || true)
1069+
_MERGE_SOURCE=""
1070+
if [ -n "$_MERGE_METHOD" ]; then
1071+
_MERGE_SOURCE="config"
1072+
fi
1073+
1074+
# Tier 2: Query repo settings if no config override
1075+
if [ -z "$_MERGE_METHOD" ]; then
1076+
_REPO_SETTINGS=$(gh repo view --json squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed --jq '{s: .squashMergeAllowed, m: .mergeCommitAllowed, r: .rebaseMergeAllowed}' 2>/dev/null || true)
1077+
if [ -n "$_REPO_SETTINGS" ]; then
1078+
_SQUASH=$(echo "$_REPO_SETTINGS" | jq -r '.s')
1079+
_MERGE=$(echo "$_REPO_SETTINGS" | jq -r '.m')
1080+
_REBASE=$(echo "$_REPO_SETTINGS" | jq -r '.r')
1081+
# Pick the repo's preferred method: if only one is enabled, use that.
1082+
# If multiple are enabled, prefer squash (GitHub's default UI selection).
1083+
_ENABLED_COUNT=0
1084+
[ "$_SQUASH" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
1085+
[ "$_MERGE" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
1086+
[ "$_REBASE" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
1087+
1088+
if [ "$_ENABLED_COUNT" -eq 1 ]; then
1089+
[ "$_SQUASH" = "true" ] && _MERGE_METHOD="squash"
1090+
[ "$_MERGE" = "true" ] && _MERGE_METHOD="merge"
1091+
[ "$_REBASE" = "true" ] && _MERGE_METHOD="rebase"
1092+
_MERGE_SOURCE="repo (only method allowed)"
1093+
elif [ "$_ENABLED_COUNT" -gt 1 ]; then
1094+
# Multiple methods allowed — use squash as default preference
1095+
if [ "$_SQUASH" = "true" ]; then _MERGE_METHOD="squash"
1096+
elif [ "$_MERGE" = "true" ]; then _MERGE_METHOD="merge"
1097+
else _MERGE_METHOD="rebase"
1098+
fi
1099+
_MERGE_SOURCE="repo (multiple allowed, picked default)"
1100+
fi
1101+
fi
1102+
fi
1103+
1104+
# Tier 3: Default to squash
1105+
if [ -z "$_MERGE_METHOD" ]; then
1106+
_MERGE_METHOD="squash"
1107+
_MERGE_SOURCE="default"
1108+
fi
1109+
1110+
echo "MERGE_METHOD: $_MERGE_METHOD (source: $_MERGE_SOURCE)"
1111+
```
1112+
1113+
Valid values: `squash`, `merge`, `rebase`. If the resolved value is not one of these
1114+
three, warn the user and fall back to `squash`.
1115+
1116+
Print the merge method and source in the dry-run UI. If the source is "repo" and only
1117+
one method is allowed, note: "This repo only allows {method} merges."
1118+
1119+
### 4.1: Try auto-merge first
1120+
10581121
Try auto-merge first (respects repo merge settings and merge queues):
10591122

10601123
```bash
@@ -1064,13 +1127,21 @@ gh pr merge --auto --delete-branch
10641127
If `--auto` succeeds: record `MERGE_PATH=auto`. This means the repo has auto-merge enabled
10651128
and may use merge queues.
10661129

1067-
If `--auto` is not available (repo doesn't have auto-merge enabled), merge directly:
1130+
### 4.2: Direct merge fallback
1131+
1132+
If `--auto` is not available (repo doesn't have auto-merge enabled), merge directly
1133+
using the configured merge method:
10681134

10691135
```bash
1070-
gh pr merge --squash --delete-branch
1136+
gh pr merge --${_MERGE_METHOD} --delete-branch
10711137
```
10721138

1073-
If direct merge succeeds: record `MERGE_PATH=direct`. Tell the user: "PR merged successfully. The branch has been cleaned up."
1139+
For example:
1140+
- `merge_method: squash``gh pr merge --squash --delete-branch` (default, current behavior)
1141+
- `merge_method: merge``gh pr merge --merge --delete-branch` (preserves full commit history)
1142+
- `merge_method: rebase``gh pr merge --rebase --delete-branch` (linear history)
1143+
1144+
If direct merge succeeds: record `MERGE_PATH=direct`. Tell the user: "PR merged successfully (via ${_MERGE_METHOD}). The branch has been cleaned up."
10741145

10751146
If the merge fails with a permission error: **STOP.** "I don't have permission to merge this PR. You'll need a maintainer to merge it, or check your repo's branch protection rules."
10761147

land-and-deploy/SKILL.md.tmpl

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ readiness first.
5656
- Production health issues detected by canary (offer revert)
5757

5858
**Never stop for:**
59-
- Choosing merge method (auto-detect from repo settings)
59+
- Choosing merge method (config override > repo settings > squash default)
6060
- Timeout warnings (warn and continue gracefully)
6161

6262
## Voice & Tone
@@ -200,7 +200,7 @@ Run whichever commands are relevant based on the detected platform. Build the re
200200
║ 4. {Wait for deploy workflow / Wait 60s / Skip} ║
201201
║ 5. {Run canary verification / Skip (no URL)} ║
202202
║ ║
203-
║ MERGE METHOD: {squash/merge/rebase} (from repo settings) ║
203+
║ MERGE METHOD: {squash/merge/rebase} (source: {config/repo/default}) ║
204204
║ MERGE QUEUE: {detected / not detected} ║
205205
╚══════════════════════════════════════════════════════════╝
206206
```
@@ -548,6 +548,69 @@ If the user chooses A or C: Tell the user "Merging now." Continue to Step 4.
548548
Record the start timestamp for timing data. Also record which merge path is taken
549549
(auto-merge vs direct) for the deploy report.
550550

551+
### 4.0: Determine merge method
552+
553+
Resolve the merge method using a three-tier priority:
554+
555+
1. **User config override** (explicit preference, highest priority)
556+
2. **Repo settings** (what GitHub allows for this repo)
557+
3. **Default** (`squash`)
558+
559+
```bash
560+
# Tier 1: Check user config
561+
_MERGE_METHOD=$(~/.claude/skills/gstack/bin/gstack-config get merge_method 2>/dev/null || true)
562+
_MERGE_SOURCE=""
563+
if [ -n "$_MERGE_METHOD" ]; then
564+
_MERGE_SOURCE="config"
565+
fi
566+
567+
# Tier 2: Query repo settings if no config override
568+
if [ -z "$_MERGE_METHOD" ]; then
569+
_REPO_SETTINGS=$(gh repo view --json squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed --jq '{s: .squashMergeAllowed, m: .mergeCommitAllowed, r: .rebaseMergeAllowed}' 2>/dev/null || true)
570+
if [ -n "$_REPO_SETTINGS" ]; then
571+
_SQUASH=$(echo "$_REPO_SETTINGS" | jq -r '.s')
572+
_MERGE=$(echo "$_REPO_SETTINGS" | jq -r '.m')
573+
_REBASE=$(echo "$_REPO_SETTINGS" | jq -r '.r')
574+
# Pick the repo's preferred method: if only one is enabled, use that.
575+
# If multiple are enabled, prefer squash (GitHub's default UI selection).
576+
_ENABLED_COUNT=0
577+
[ "$_SQUASH" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
578+
[ "$_MERGE" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
579+
[ "$_REBASE" = "true" ] && _ENABLED_COUNT=$((_ENABLED_COUNT + 1))
580+
581+
if [ "$_ENABLED_COUNT" -eq 1 ]; then
582+
[ "$_SQUASH" = "true" ] && _MERGE_METHOD="squash"
583+
[ "$_MERGE" = "true" ] && _MERGE_METHOD="merge"
584+
[ "$_REBASE" = "true" ] && _MERGE_METHOD="rebase"
585+
_MERGE_SOURCE="repo (only method allowed)"
586+
elif [ "$_ENABLED_COUNT" -gt 1 ]; then
587+
# Multiple methods allowed — use squash as default preference
588+
if [ "$_SQUASH" = "true" ]; then _MERGE_METHOD="squash"
589+
elif [ "$_MERGE" = "true" ]; then _MERGE_METHOD="merge"
590+
else _MERGE_METHOD="rebase"
591+
fi
592+
_MERGE_SOURCE="repo (multiple allowed, picked default)"
593+
fi
594+
fi
595+
fi
596+
597+
# Tier 3: Default to squash
598+
if [ -z "$_MERGE_METHOD" ]; then
599+
_MERGE_METHOD="squash"
600+
_MERGE_SOURCE="default"
601+
fi
602+
603+
echo "MERGE_METHOD: $_MERGE_METHOD (source: $_MERGE_SOURCE)"
604+
```
605+
606+
Valid values: `squash`, `merge`, `rebase`. If the resolved value is not one of these
607+
three, warn the user and fall back to `squash`.
608+
609+
Print the merge method and source in the dry-run UI. If the source is "repo" and only
610+
one method is allowed, note: "This repo only allows {method} merges."
611+
612+
### 4.1: Try auto-merge first
613+
551614
Try auto-merge first (respects repo merge settings and merge queues):
552615

553616
```bash
@@ -557,13 +620,21 @@ gh pr merge --auto --delete-branch
557620
If `--auto` succeeds: record `MERGE_PATH=auto`. This means the repo has auto-merge enabled
558621
and may use merge queues.
559622

560-
If `--auto` is not available (repo doesn't have auto-merge enabled), merge directly:
623+
### 4.2: Direct merge fallback
624+
625+
If `--auto` is not available (repo doesn't have auto-merge enabled), merge directly
626+
using the configured merge method:
561627

562628
```bash
563-
gh pr merge --squash --delete-branch
629+
gh pr merge --${_MERGE_METHOD} --delete-branch
564630
```
565631

566-
If direct merge succeeds: record `MERGE_PATH=direct`. Tell the user: "PR merged successfully. The branch has been cleaned up."
632+
For example:
633+
- `merge_method: squash``gh pr merge --squash --delete-branch` (default, current behavior)
634+
- `merge_method: merge``gh pr merge --merge --delete-branch` (preserves full commit history)
635+
- `merge_method: rebase``gh pr merge --rebase --delete-branch` (linear history)
636+
637+
If direct merge succeeds: record `MERGE_PATH=direct`. Tell the user: "PR merged successfully (via ${_MERGE_METHOD}). The branch has been cleaned up."
567638

568639
If the merge fails with a permission error: **STOP.** "I don't have permission to merge this PR. You'll need a maintainer to merge it, or check your repo's branch protection rules."
569640

setup-deploy/SKILL.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,10 +591,22 @@ Status cmd: {status command}
591591
Merge method: {merge method}
592592
593593
Saved to CLAUDE.md. /land-and-deploy will use these settings automatically.
594+
```
595+
596+
Also persist the merge method to the global gstack config so `/land-and-deploy`
597+
can read it without parsing CLAUDE.md:
594598

599+
```bash
600+
~/.claude/skills/gstack/bin/gstack-config set merge_method {merge method}
601+
```
602+
603+
Tell the user: "Merge method '{merge method}' saved to both CLAUDE.md and gstack config."
604+
605+
```
595606
Next steps:
596607
- Run /land-and-deploy to merge and deploy your current PR
597608
- Edit the "## Deploy Configuration" section in CLAUDE.md to change settings
609+
- Run `gstack-config set merge_method {squash|merge|rebase}` to change merge method
598610
- Run /setup-deploy again to reconfigure
599611
```
600612

setup-deploy/SKILL.md.tmpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,22 @@ Status cmd: {status command}
205205
Merge method: {merge method}
206206
207207
Saved to CLAUDE.md. /land-and-deploy will use these settings automatically.
208+
```
209+
210+
Also persist the merge method to the global gstack config so `/land-and-deploy`
211+
can read it without parsing CLAUDE.md:
208212

213+
```bash
214+
~/.claude/skills/gstack/bin/gstack-config set merge_method {merge method}
215+
```
216+
217+
Tell the user: "Merge method '{merge method}' saved to both CLAUDE.md and gstack config."
218+
219+
```
209220
Next steps:
210221
- Run /land-and-deploy to merge and deploy your current PR
211222
- Edit the "## Deploy Configuration" section in CLAUDE.md to change settings
223+
- Run `gstack-config set merge_method {squash|merge|rebase}` to change merge method
212224
- Run /setup-deploy again to reconfigure
213225
```
214226

0 commit comments

Comments
 (0)