一键导入
catgit-merge-linear
Merge task branch to base branch with linear history (works from task worktree)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Merge task branch to base branch with linear history (works from task worktree)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Guide for writing clear, descriptive commit messages
MANDATORY: Use instead of `git rebase` - provides automatic backup and conflict recovery
MANDATORY: Use instead of `git rebase -i` for squashing - unified commit messages
Analyze mistakes with conversation length as potential cause (CAT-specific)
Run scheduled retrospective analysis, derive action items, and track effectiveness
Compress documentation while preserving execution equivalence (validation-driven approach)
| name | cat:git-merge-linear |
| description | Merge task branch to base branch with linear history (works from task worktree) |
| allowed-tools | Bash, Read |
Merge task branch to its base branch while staying in the task worktree. Uses git push . HEAD:<base>
to fast-forward the base branch without checking out.
Check PROJECT.md for configured merge preferences before proceeding.
# Check if Git Workflow section exists in PROJECT.md
WORKFLOW_SECTION=$(grep -A30 "^## Git Workflow" .claude/cat/PROJECT.md 2>/dev/null)
if [[ -n "$WORKFLOW_SECTION" ]]; then
# Check if linear merge is allowed by workflow config
MERGE_METHOD=$(echo "$WORKFLOW_SECTION" | grep "MUST use" | head -1)
if echo "$MERGE_METHOD" | grep -qi "merge commit"; then
echo "⚠️ WARNING: PROJECT.md specifies merge commits, but this skill uses fast-forward."
echo "Consider using standard 'git merge --no-ff' instead."
echo ""
echo "To proceed anyway, continue with this skill."
echo "To honor PROJECT.md preference, abort and use: git merge --no-ff {branch}"
# Don't exit - user may choose to override
fi
if echo "$MERGE_METHOD" | grep -qi "squash"; then
echo "⚠️ WARNING: PROJECT.md specifies squash merge, but this skill uses fast-forward."
echo "Consider using 'git merge --squash' instead."
echo ""
# Don't exit - user may choose to override
fi
fi
# Verify we're in a worktree (not main repo)
WORKTREE_PATH=$(pwd)
MAIN_REPO=$(git worktree list | head -1 | awk '{print $1}')
if [[ "$WORKTREE_PATH" == "$MAIN_REPO" ]]; then
echo "ERROR: Must run from task worktree, not main repo"
echo "Navigate to: /workspace/.worktrees/<task-name>"
exit 1
fi
# Get current branch
TASK_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Detect base branch from worktree metadata (fail-fast if missing)
CAT_BASE_FILE="$(git rev-parse --git-dir)/cat-base"
if [[ ! -f "$CAT_BASE_FILE" ]]; then
echo "ERROR: cat-base file not found: $CAT_BASE_FILE"
echo "This worktree was not created properly. Recreate with /cat:work."
echo "Or set manually: echo '<base-branch>' > \"$CAT_BASE_FILE\""
exit 1
fi
BASE_BRANCH=$(cat "$CAT_BASE_FILE")
echo "Task branch: $TASK_BRANCH"
echo "Base branch: $BASE_BRANCH"
echo "Worktree: $WORKTREE_PATH"
# Check for uncommitted changes
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "ERROR: Uncommitted changes detected"
echo "Commit or stash changes before merging"
exit 1
fi
CRITICAL: Squashing without this check can delete commits added to base after worktree creation.
# Check if base branch has commits not in our history
DIVERGED_COMMITS=$(git rev-list --count "HEAD..${BASE_BRANCH}")
if [[ "$DIVERGED_COMMITS" -gt 0 ]]; then
echo "ERROR: Base branch has diverged!"
echo ""
echo "$BASE_BRANCH has $DIVERGED_COMMITS commit(s) not in your branch."
echo "These commits would be LOST if you squash now."
echo ""
echo "Commits on $BASE_BRANCH not in HEAD:"
git log --oneline "HEAD..${BASE_BRANCH}"
echo ""
echo "Solution: Rebase onto $BASE_BRANCH first:"
echo " git rebase $BASE_BRANCH"
echo " # Then retry merge"
exit 1
fi
echo "Base branch has not diverged - safe to proceed"
CRITICAL: Even after rebase, incorrect conflict resolution can delete base branch files.
# Check if task branch deletes files that exist in base branch
DELETED_FILES=$(git diff --name-status "${BASE_BRANCH}..HEAD" | grep "^D" | cut -f2)
if [[ -n "$DELETED_FILES" ]]; then
echo "WARNING: Task branch deletes files from base branch:"
echo "$DELETED_FILES"
echo ""
# Check if these are intentional deletions or rebase artifacts
# Files in .claude/cat/ or plugin/ directories are likely unintentional
SUSPICIOUS=$(echo "$DELETED_FILES" | grep -E "^(\.claude/cat/|plugin/)" || true)
if [[ -n "$SUSPICIOUS" ]]; then
echo "ERROR: Suspicious deletions detected in infrastructure paths:"
echo "$SUSPICIOUS"
echo ""
echo "These deletions are likely from incorrect rebase conflict resolution."
echo ""
echo "Solution: Re-rebase with correct conflict resolution:"
echo " git reset --hard origin/${TASK_BRANCH} # If remote has clean state"
echo " # Or reset to merge-base and cherry-pick task commits"
echo " git checkout ${BASE_BRANCH}"
echo " git checkout -B ${TASK_BRANCH}"
echo " # Then cherry-pick your actual task commits"
exit 1
fi
echo "Deletions appear intentional (not in infrastructure paths)"
fi
echo "No suspicious file deletions - safe to proceed"
# Count commits ahead of base branch
COMMIT_COUNT=$(git rev-list --count "${BASE_BRANCH}..HEAD")
echo "Commits to merge: $COMMIT_COUNT"
if [[ "$COMMIT_COUNT" -eq 0 ]]; then
echo "ERROR: No commits to merge"
exit 1
fi
if [[ "$COMMIT_COUNT" -gt 1 ]]; then
echo "Squashing $COMMIT_COUNT commits into 1..."
# Get combined commit message from all commits
COMBINED_MSG=$(git log --reverse --format="- %s" "${BASE_BRANCH}..HEAD")
FIRST_MSG=$(git log -1 --format="%s" "${BASE_BRANCH}..HEAD" | head -1)
# Soft reset to base and create single commit
git reset --soft "$BASE_BRANCH"
git commit -m "$FIRST_MSG
Changes:
$COMBINED_MSG
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
echo "Squashed into 1 commit"
fi
# Fast-forward base branch to current HEAD without checking out
# This updates the base branch ref to point to our current commit
git push . "HEAD:${BASE_BRANCH}"
if [[ $? -ne 0 ]]; then
echo "ERROR: Fast-forward failed"
echo ""
echo "This usually means $BASE_BRANCH has moved ahead."
echo "Solution: Rebase onto $BASE_BRANCH first:"
echo " git fetch origin $BASE_BRANCH"
echo " git rebase origin/$BASE_BRANCH"
echo " # Then retry merge"
exit 1
fi
echo "$BASE_BRANCH fast-forwarded to $(git rev-parse --short HEAD)"
# Verify base branch was updated
BASE_SHA=$(git rev-parse "$BASE_BRANCH")
HEAD_SHA=$(git rev-parse HEAD)
if [[ "$BASE_SHA" != "$HEAD_SHA" ]]; then
echo "ERROR: $BASE_BRANCH not at expected commit"
echo "$BASE_BRANCH: $BASE_SHA"
echo "HEAD: $HEAD_SHA"
exit 1
fi
echo "Verified: $BASE_BRANCH is at $(git rev-parse --short "$BASE_BRANCH")"
# Show final state
echo ""
echo "=== Merge Complete ==="
git log --oneline -3 "$BASE_BRANCH"
# Navigate to main repo for cleanup
cd "$MAIN_REPO"
# Remove worktree
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || true
# Delete task branch (now safe since worktree removed)
git branch -D "$TASK_BRANCH" 2>/dev/null || true
# Clean up empty worktrees directory
rmdir /workspace/.worktrees 2>/dev/null || true
echo "Cleanup complete"
For experienced users, combine all steps (run from task worktree):
# Detect branches and paths
TASK_BRANCH=$(git rev-parse --abbrev-ref HEAD)
MAIN_REPO=$(git worktree list | head -1 | awk '{print $1}')
WORKTREE_PATH=$(pwd)
# Detect base branch from worktree metadata (fail-fast if missing)
CAT_BASE_FILE="$(git rev-parse --git-dir)/cat-base"
if [[ ! -f "$CAT_BASE_FILE" ]]; then
echo "ERROR: cat-base file not found. Recreate worktree with /cat:work." >&2
exit 1
fi
BASE_BRANCH=$(cat "$CAT_BASE_FILE")
# Check for divergence FIRST (M199)
DIVERGED=$(git rev-list --count "HEAD..${BASE_BRANCH}")
if [[ "$DIVERGED" -gt 0 ]]; then
echo "ERROR: Base branch has $DIVERGED commit(s) not in HEAD. Rebase first." >&2
exit 1
fi
# Check for suspicious file deletions (M233)
SUSPICIOUS_DELETIONS=$(git diff --name-status "${BASE_BRANCH}..HEAD" | grep "^D" | cut -f2 | grep -E "^(\.claude/cat/|plugin/)" || true)
if [[ -n "$SUSPICIOUS_DELETIONS" ]]; then
echo "ERROR: Task branch deletes infrastructure files from base:" >&2
echo "$SUSPICIOUS_DELETIONS" >&2
echo "Likely incorrect rebase conflict resolution. Re-rebase with correct resolution." >&2
exit 1
fi
# Squash if multiple commits
COMMIT_COUNT=$(git rev-list --count "${BASE_BRANCH}..HEAD")
if [[ "$COMMIT_COUNT" -gt 1 ]]; then
FIRST_MSG=$(git log --format="%s" "${BASE_BRANCH}..HEAD" | tail -1)
git reset --soft "$BASE_BRANCH"
git commit -m "$FIRST_MSG"
fi
# Fast-forward base branch
git push . "HEAD:${BASE_BRANCH}"
# Cleanup
cd "$MAIN_REPO"
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null
git branch -D "$TASK_BRANCH" 2>/dev/null
Cause: Base branch has moved ahead since task branch was created Solution: Rebase task branch onto base first:
git fetch origin "$BASE_BRANCH"
git rebase "origin/$BASE_BRANCH"
# Then retry: git push . "HEAD:${BASE_BRANCH}"
Cause: Branch name has special characters or doesn't exist
Solution: Verify branch name with git branch -a
Cause: Uncommitted changes or process using directory
Solution: Use --force flag or manually clean up
Cause: Base branch detection failed (worktree metadata missing)
Solution: Set explicitly with echo "<base-branch>" > "$(git rev-parse --git-dir)/cat-base"