diff --git a/CHANGELOG.md b/CHANGELOG.md index 893769a..0cf2d43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Unreleased] + +- fix: handle `gh pr merge` in worktree context - detect worktree and use remote-only merge strategy to avoid `fatal: 'main' is already used by worktree` error (#3) + ## [1.0.0] - 2026-02-21 Initial release. Extracted from [agentsys](https://github.com/agent-sh/agentsys) monorepo. diff --git a/commands/ship-deployment.md b/commands/ship-deployment.md index 2de54c4..51357ca 100644 --- a/commands/ship-deployment.md +++ b/commands/ship-deployment.md @@ -184,6 +184,14 @@ Proceeding to production... ### Merge to Production Branch ```bash +# Worktree check must come first - multi-branch deployment requires branch checkout +if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then + MAIN_REPO=$(dirname "$(git rev-parse --git-common-dir 2>/dev/null)") + echo "[ERROR] Multi-branch deployment is not supported from a worktree" + echo "Run from the main repo: cd $MAIN_REPO" + exit 1 +fi + echo "Merging $MAIN_BRANCH → $PROD_BRANCH..." git checkout $PROD_BRANCH @@ -283,9 +291,18 @@ fi ```bash rollback_production() { + # Worktree check must come first + if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then + MAIN_REPO=$(dirname "$(git rev-parse --git-common-dir 2>/dev/null)") + echo "[ERROR] Rollback is not supported from a worktree" + echo "Run from the main repo: cd $MAIN_REPO" + exit 1 + fi + echo "========================================" echo "ROLLBACK INITIATED" echo "========================================" + echo "WARNING: Force pushing to $PROD_BRANCH to revert" git checkout $PROD_BRANCH diff --git a/commands/ship-error-handling.md b/commands/ship-error-handling.md index dc7cbe4..534cc2d 100644 --- a/commands/ship-error-handling.md +++ b/commands/ship-error-handling.md @@ -227,12 +227,22 @@ git push ### Cancel and Cleanup ```bash -# If you need to abandon the PR -gh pr close $PR_NUMBER --delete-branch +# Detect worktree +IS_WORKTREE="false" +if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then + IS_WORKTREE="true" +fi -# Clean up local -git checkout $MAIN_BRANCH -git branch -D $CURRENT_BRANCH +if [ "$IS_WORKTREE" = "true" ]; then + # In worktree: close PR and delete remote branch separately + gh pr close $PR_NUMBER + git push origin --delete "$CURRENT_BRANCH" 2>&1 || echo "[WARN] Remote branch deletion failed - may need manual cleanup" + echo "[OK] PR closed (worktree mode - local cleanup deferred to worktree removal)" +else + gh pr close $PR_NUMBER --delete-branch + git checkout $MAIN_BRANCH + git branch -D $CURRENT_BRANCH +fi ``` ## Exit Codes diff --git a/commands/ship.md b/commands/ship.md index c03a768..a4ecb50 100644 --- a/commands/ship.md +++ b/commands/ship.md @@ -348,17 +348,37 @@ fi echo "[OK] All comments resolved" -# 3. Merge with strategy (default: squash) +# 3. Detect if running from a worktree +IS_WORKTREE="false" +if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then + IS_WORKTREE="true" + echo "[INFO] Running from worktree - using remote-only merge strategy" +fi + +# 4. Merge with strategy (default: squash) STRATEGY=${STRATEGY:-squash} -gh pr merge $PR_NUMBER --$STRATEGY --delete-branch +if [ -z "$OWNER" ] || [ -z "$REPO" ]; then + echo "[ERROR] Failed to extract repo owner/name" + exit 1 +fi -# Update local -git checkout $MAIN_BRANCH -git pull origin $MAIN_BRANCH +if [ "$IS_WORKTREE" = "true" ]; then + # In worktree: merge without --delete-branch (it tries to checkout main locally) + gh pr merge $PR_NUMBER --$STRATEGY --repo "$OWNER/$REPO" + # Delete remote branch separately + git push origin --delete "$CURRENT_BRANCH" 2>&1 || echo "[WARN] Remote branch deletion failed - may need manual cleanup" + # Get merge SHA from the PR metadata (avoids race condition with fetch) + MERGE_SHA=$(gh pr view $PR_NUMBER --repo "$OWNER/$REPO" --json mergeCommit --jq '.mergeCommit.oid') +else + gh pr merge $PR_NUMBER --$STRATEGY --delete-branch + # Update local + git checkout $MAIN_BRANCH + git pull origin $MAIN_BRANCH + MERGE_SHA=$(git rev-parse HEAD) +fi # Update repo-map if it exists (non-blocking) node -e "const { getPluginRoot } = require('@agentsys/lib/cross-platform'); const pluginRoot = getPluginRoot('ship'); if (!pluginRoot) { console.log('Plugin root not found, skipping repo-map'); process.exit(0); } const repoMap = require(\`\${pluginRoot}/lib/repo-map\`); if (repoMap.exists(process.cwd())) { repoMap.update(process.cwd(), {}).then(() => console.log('[OK] Repo-map updated')).catch((e) => console.log('[WARN] Repo-map update failed: ' + e.message)); } else { console.log('Repo-map not found, skipping'); }" || true -MERGE_SHA=$(git rev-parse HEAD) echo "[OK] Merged PR #$PR_NUMBER at $MERGE_SHA" ``` @@ -438,9 +458,20 @@ if (workflowState) { ### Local Branch Cleanup ```bash -git checkout $MAIN_BRANCH -# Feature branch already deleted by --delete-branch -git branch -D $CURRENT_BRANCH 2>/dev/null || true +# Re-detect worktree in case Phase 6 was skipped or run separately +if [ -z "$IS_WORKTREE" ]; then + IS_WORKTREE="false" + if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then + IS_WORKTREE="true" + fi +fi + +if [ "$IS_WORKTREE" = "true" ]; then + echo "[OK] Local branch cleanup deferred (worktree mode - cleaned when worktree is removed)" +else + git checkout $MAIN_BRANCH + git branch -D $CURRENT_BRANCH 2>/dev/null || true +fi ``` ## Phase 12: Completion Report