Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .gemini/skills/sync-fork/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: sync-fork
description: >
Sync a fork's main branch with upstream and rebase local feature branches.
Designed for the triangle workflow where development happens in fork feature
branches and PRs go to the upstream repo.
---

# Sync Fork

Synchronize a fork with its upstream repository and rebase local feature
branches. This skill assumes the standard triangle workflow:

```
upstream repo (e.g. PAIR-code/deliberate-lab)
▲ PRs
your fork (e.g. <you>/deliberate-lab)
▲ push
local clone
```

## When to use

Invoke this skill when the user asks to:

- "Sync my fork"
- "Update my branches"
- "Pull from upstream"
- "Rebase my branches on main"
- "Catch up with upstream"

## Prerequisites

The local repo must have two remotes configured:

| Remote | Points to |
|--------|-----------|
| `origin` | The user's fork (e.g. `<you>/deliberate-lab`) |
| `upstream` | The canonical repo (e.g. `PAIR-code/deliberate-lab`) |

Verify with `git remote -v`. If `upstream` is missing, add it:

```sh
git remote add upstream git@github.com:PAIR-code/deliberate-lab.git
```

## Procedure

### Step 1 — Run the sync script

Run the helper script from the repository root:

```sh
bash .gemini/skills/sync-fork/scripts/sync-fork.sh
```

This script will:

1. Verify the `upstream` remote exists
2. `git fetch --all --prune`
3. Check out `main`, fast-forward to `upstream/main`
4. Push `main` to `origin`
5. Print a list of local feature branches that are behind `main`

If the script exits with an error, report it to the user and stop.

### Step 2 — Rebase feature branches

After the script completes, it will list branches that are behind `main`.
For each branch:

1. **Ask the user** which branches they want to rebase (they may not want
all of them). If the user already specified which branches to rebase,
skip asking.

2. **Check for uncommitted changes** — if the branch has uncommitted work,
warn the user and skip it unless they confirm.

3. **Rebase onto main**:
```sh
git checkout <branch>
git rebase main
```

4. **If the rebase is clean** — force-push to origin:
```sh
git push origin <branch> --force-with-lease
```

5. **If there are conflicts** — abort by default:
- Run `git rebase --abort`
- Tell the user which branch has conflicts and what files are affected
- **Exception**: if the conflicts are trivially obvious (e.g., import
ordering, adjacent non-overlapping additions), you may attempt to
resolve them:
- Read the conflicting files
- Resolve only conflicts where both sides' intent is unambiguous
- `git add` the resolved files and `git rebase --continue`
- After all conflicts are resolved, force-push with lease
- If any conflict is ambiguous, abort the entire rebase — do not
partially resolve

### Step 3 — Return to the original branch

After processing all branches, check out whichever branch the user was on
when the skill was invoked.

## Safety rules

- **Never rebase `main`** — `main` is always fast-forward only.
- **Always use `--force-with-lease`** — never bare `--force`.
- **Never rebase without user consent** — always confirm which branches to
rebase (unless they already specified).
- **Abort on ambiguity** — if a conflict resolution is uncertain, abort the
rebase and ask the user rather than guessing.
- **Stash gracefully** — if the user has uncommitted changes on the current
branch, either stash them or warn and skip.
119 changes: 119 additions & 0 deletions .gemini/skills/sync-fork/scripts/sync-fork.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
#
# sync-fork.sh — Sync fork's main branch with upstream.
#
# This script handles the mechanical (deterministic) parts of the
# triangle workflow sync:
#
# 1. Verify the upstream remote exists
# 2. Fetch all remotes
# 3. Fast-forward main to upstream/main
# 4. Push main to origin
# 5. List feature branches that are behind main
#
# Feature branch rebasing is intentionally left out — that's the AI
# agent's job since it may require conflict resolution.
#
# Usage:
# bash .gemini/skills/sync-fork/scripts/sync-fork.sh
#
# Exit codes:
# 0 — success
# 1 — error (missing remote, not a git repo, FF failed, etc.)

set -euo pipefail

# --- Helpers ---

info() { printf '✓ %s\n' "$*"; }
warn() { printf '⚠ %s\n' "$*" >&2; }
die() { printf '✗ %s\n' "$*" >&2; exit 1; }

# --- Preflight checks ---

git rev-parse --is-inside-work-tree &>/dev/null \
|| die "Not inside a git repository."

git remote get-url upstream &>/dev/null \
|| die "No 'upstream' remote found. Add one with:
git remote add upstream <upstream-url>"

git remote get-url origin &>/dev/null \
|| die "No 'origin' remote found."

# --- Step 1: Fetch all remotes ---

info "Fetching all remotes..."
git fetch --all --prune
info "Fetch complete."

# --- Step 2: Fast-forward main ---

ORIGINAL_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD)
STASHED=false

# If we're not on main, just switch. If we are, no-op.
if [ "$ORIGINAL_BRANCH" != "main" ]; then
# Check for uncommitted changes
if ! git diff --quiet || ! git diff --cached --quiet; then
warn "Stashing uncommitted changes on '$ORIGINAL_BRANCH'..."
git stash push -m "sync-fork: auto-stash before switching to main"
STASHED=true
fi
git checkout main
fi

# Fast-forward main to upstream/main
if git merge --ff-only upstream/main; then
info "main is now at $(git rev-parse --short HEAD)."
else
# If FF fails, return to original branch and bail
if [ "$ORIGINAL_BRANCH" != "main" ]; then
git checkout "$ORIGINAL_BRANCH"
if [ "$STASHED" = true ]; then
git stash pop
fi
fi
die "Cannot fast-forward main to upstream/main.
main may have diverged from upstream. Manual intervention required."
fi

# --- Step 3: Push main to origin ---

info "Pushing main to origin..."
git push origin main
info "origin/main is now in sync with upstream/main."

# --- Step 4: Return to original branch ---

if [ "$ORIGINAL_BRANCH" != "main" ]; then
git checkout "$ORIGINAL_BRANCH"
if [ "$STASHED" = true ]; then
info "Restoring stashed changes on '$ORIGINAL_BRANCH'..."
git stash pop
fi
fi

# --- Step 5: Report feature branches behind main ---

echo ""
echo "=== Feature branches behind main ==="
echo ""

BEHIND_COUNT=0
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/ | grep -v '^main$'); do
# How many commits is this branch behind main?
BEHIND=$(git rev-list --count "$branch..main" 2>/dev/null || echo 0)
if [ "$BEHIND" -gt 0 ]; then
AHEAD=$(git rev-list --count "main..$branch" 2>/dev/null || echo 0)
printf " %-50s %s behind, %s ahead of main\n" "$branch" "$BEHIND" "$AHEAD"
BEHIND_COUNT=$((BEHIND_COUNT + 1))
fi
done

if [ "$BEHIND_COUNT" -eq 0 ]; then
info "All local branches are up to date with main."
else
echo ""
echo "$BEHIND_COUNT branch(es) behind main. Rebase recommended."
fi
Loading
Loading