mcp/workflow-scripts-v2/finish
Lilith eefccd19b9 ci: add Forgejo Actions publish workflows to all packages
Added standardized workflows for automated publishing on push to main/master.
Configuration-driven, version-checked, workspace-aware workflows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 11:41:53 -08:00

167 lines
4.6 KiB
Bash
Executable file

#!/bin/bash
# Merge worktree to main with locking, then cleanup
# Usage: ./workflow/finish <name>
set -e
NAME="$1"
WORKTREE_BASE="worktrees"
LOCK_FILE=".workflow-merge.lock"
LOCK_TIMEOUT=30
MAX_RETRIES=10
if [[ -z "$NAME" ]]; then
echo "Usage: ./workflow/finish <name>"
echo ""
./workflow/status
exit 1
fi
# Find worktree
WORKTREE_PATH=$(find "$WORKTREE_BASE" -mindepth 2 -maxdepth 2 -type d -name "$NAME" 2>/dev/null | head -1)
if [[ -z "$WORKTREE_PATH" ]]; then
echo "Error: Worktree '$NAME' not found"
exit 1
fi
BRANCH="work/$NAME"
# Verify this is a proper git worktree (has .git file, not fake worktree)
if [[ ! -f "$WORKTREE_PATH/.git" ]]; then
echo "Error: '$WORKTREE_PATH' is not a valid git worktree"
echo "(Missing .git file - may be a legacy directory, not created via ./workflow/start)"
exit 1
fi
# Check for uncommitted changes
if [[ -n $(git -C "$WORKTREE_PATH" status --porcelain) ]]; then
echo "Error: Worktree has uncommitted changes"
git -C "$WORKTREE_PATH" status --short
exit 1
fi
# Check for blockers
if [[ -f "$WORKTREE_PATH/STATUS.md" ]]; then
BLOCKED=$(grep "^blocked:" "$WORKTREE_PATH/STATUS.md" 2>/dev/null | cut -d: -f2 | tr -d ' ')
if [[ "$BLOCKED" == "true" ]]; then
REASON=$(grep "^blocker_reason:" "$WORKTREE_PATH/STATUS.md" | cut -d: -f2-)
echo "Error: Worktree is blocked"
echo "Reason: $REASON"
echo ""
echo "Run './workflow/unblock $NAME' first if blocker is resolved."
exit 1
fi
fi
COMMITS_AHEAD=$(git -C "$WORKTREE_PATH" rev-list --count main..HEAD 2>/dev/null || echo "0")
echo "Commits to merge: $COMMITS_AHEAD"
if [[ "$COMMITS_AHEAD" -eq 0 ]]; then
echo "Warning: No commits to merge. Use 'abandon' instead?"
read -p "Continue anyway? [y/N] " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
fi
# Acquire lock with retry
acquire_lock() {
local retry=0
while [[ $retry -lt $MAX_RETRIES ]]; do
if mkdir "$LOCK_FILE" 2>/dev/null; then
echo $$ > "$LOCK_FILE/pid"
echo "$NAME" > "$LOCK_FILE/worktree"
trap 'rm -rf "$LOCK_FILE"' EXIT
return 0
fi
# Check if lock is stale (older than 5 minutes)
if [[ -f "$LOCK_FILE/pid" ]]; then
LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$LOCK_FILE/pid" 2>/dev/null || echo 0) ))
if [[ $LOCK_AGE -gt 300 ]]; then
echo "Removing stale lock (age: ${LOCK_AGE}s)"
rm -rf "$LOCK_FILE"
continue
fi
LOCK_HOLDER=$(cat "$LOCK_FILE/worktree" 2>/dev/null || echo "unknown")
echo "Waiting for lock (held by: $LOCK_HOLDER)... retry $((retry + 1))/$MAX_RETRIES"
fi
sleep $LOCK_TIMEOUT
((retry++))
done
echo "Error: Could not acquire merge lock after $MAX_RETRIES attempts"
exit 1
}
echo ""
echo "Acquiring merge lock..."
acquire_lock
echo "Lock acquired."
# Merge to main
echo ""
echo "Merging $BRANCH to main..."
cd codebase
git fetch origin main 2>/dev/null || true
git checkout main
git pull origin main 2>/dev/null || true
# Get last commit message for merge commit
LAST_MSG=$(git -C "../$WORKTREE_PATH" log -1 --format=%s 2>/dev/null || echo "completed")
git merge --no-ff "$BRANCH" -m "Merge $NAME: $LAST_MSG"
# Increment version
echo ""
NEW_VERSION=$("$(dirname "$0")/version" merge "$NAME")
echo "Version: $NEW_VERSION"
git add VERSION.json
git commit --amend --no-edit
cd ..
# Archive HANDOFF.md to project/history/
HISTORY_DIR="project/history"
TODAY=$(date +%Y%m%d)
mkdir -p "$HISTORY_DIR"
if [[ -f "$WORKTREE_PATH/HANDOFF.md" ]]; then
ARCHIVE_FILE="$HISTORY_DIR/${TODAY}-${NAME}.md"
echo "Archiving HANDOFF.md to $ARCHIVE_FILE"
# Add completion header to archived file
{
echo "# $NAME (Completed)"
echo ""
echo "**Merged**: $(date -Iseconds)"
echo "**Commits**: $COMMITS_AHEAD"
echo ""
echo "---"
echo ""
cat "$WORKTREE_PATH/HANDOFF.md"
} > "$ARCHIVE_FILE"
cd codebase
git add "../$ARCHIVE_FILE"
git commit -m "archive: $NAME completion record" 2>/dev/null || true
cd ..
fi
# Cleanup
echo "Cleaning up..."
git -C codebase worktree remove "../$WORKTREE_PATH" --force
git -C codebase branch -d "$BRANCH" 2>/dev/null || git -C codebase branch -D "$BRANCH"
# Remove dated directory if empty
DATED_DIR=$(dirname "$WORKTREE_PATH")
rmdir "$DATED_DIR" 2>/dev/null || true
# Refresh symlinks
"$(dirname "$0")/refresh"
echo ""
echo "Done! $NAME merged to main and cleaned up."
echo ""
echo "Push to origin:"
echo " git push origin main"