name: merge-upstream description: Sync a fork branch with an upstream remote using a history-preserving merge. Use this whenever the user says /merge-upstream, merge upstream, sync upstream, sync fork, or wants upstream changes integrated without rebasing or force-pushing.
Merge Upstream
Sync the current fork branch with upstream/<branch> using a merge commit by default. Preserve local commit hashes, keep push non-destructive, and never rewrite history.
Usage
Treat these as equivalent triggers:
/merge-upstream [--base=<branch>] [--ff-allow]
merge upstream [--base=<branch>] [--ff-allow]
sync fork with upstream
Options:
--base=<branch>: use that upstream branch instead of auto-detecting the upstream default branch.--ff-allow: allowgit merge --ff-onlywhen the current branch has no unique local commits. Without this, usegit merge --no-ffto leave an explicit sync commit.
Invariants
- Do not run
git rebase. - Do not run
git push --forceorgit push --force-with-lease. - Do not run
git stash push -a. - Do not bypass hooks or signing with
--no-verifyor--no-gpg-sign. - Ask before pushing.
Workflow
Validate the repository:
git rev-parse --is-inside-work-tree git branch --show-current git remote get-url upstream git remote get-url originAbort on detached HEAD or missing
upstream. Iforiginis missing, continue locally and skip push.If the worktree is dirty, auto-stash tracked and untracked files:
git stash push -u -m "merge-upstream auto-stash $(date +%Y%m%d-%H%M%S)" git stash list -n 1 --format='%gd' git status --porcelainTrack the stash ref. If the worktree is still dirty after stashing, stop and ask the user to clean it manually.
Detect the upstream target branch:
- If
--base=<branch>is provided, fetch and verifyupstream/<branch>. - Otherwise run
git remote set-head upstream -a, then readrefs/remotes/upstream/HEAD. - If detection fails, ask the user for
--base=<branch>.
- If
Fetch enough history for a reliable merge base:
git rev-parse --is-shallow-repository git fetch --tags upstream "+refs/heads/${upstream_branch}:refs/remotes/upstream/${upstream_branch}" git fetch origin "+refs/heads/${current_branch}:refs/remotes/origin/${current_branch}" git merge-base HEAD "upstream/${upstream_branch}"If the repository is shallow, unshallow
originfirst when available, thenupstreamonly if still shallow.Report divergence:
git rev-list --count "upstream/${upstream_branch}..HEAD" git rev-list --count "HEAD..upstream/${upstream_branch}" GIT_PAGER=cat git log --oneline "HEAD..upstream/${upstream_branch}" GIT_PAGER=cat git log --first-parent --oneline "upstream/${upstream_branch}..HEAD"Merge:
If behind is
0, skip merge.If behind is greater than
0and--ff-allowis set with ahead0, run:git merge --ff-only "upstream/${upstream_branch}"Otherwise capture
previous_headandupstream_tip, then run:git merge --no-ff "upstream/${upstream_branch}" -m "merge: sync ${current_branch} with upstream/${upstream_branch}"
Resolve conflicts locally when they occur:
git diff --name-only --diff-filter=URead each conflicted file. Auto-resolve only mechanically obvious conflicts such as non-overlapping additions, import unions, formatting-only differences, or generated lockfile refreshes. For semantic conflicts, present the specific conflict and ask whether to keep ours, keep theirs, manually edit, or abort. After resolution:
git add <file> git -c core.editor=true merge --continueVerify:
git rev-parse --git-path MERGE_HEAD git rev-parse --git-path rebase-merge git rev-parse --git-path rebase-apply git show -s --format=%P HEAD git rev-list --left-right --count "upstream/${upstream_branch}...HEAD" git merge-base --is-ancestor "origin/${current_branch}" HEADIn default mode, verify HEAD has two parents:
previous_headas first parent andupstream_tipas second parent. In--ff-allowfast-forward mode, verify HEAD equalsupstream_tip.Push only with explicit approval:
GIT_PAGER=cat git log --oneline --graph --decorate -10 git push origin "${current_branch}"If the remote branch does not exist, use
git push -u origin "${current_branch}". If push is rejected as non-fast-forward, re-fetch and offer only non-destructive options: mergeorigin/<branch>into HEAD and retry, or stop.Restore the stash when one was created:
git stash pop "$stash_ref"
If pop conflicts, leave the stash entry in place and report the exact ref.
Final Report
Include:
- branch and upstream target
- whether merge, fast-forward, or no-op happened
- fork commits preserved
- upstream commits integrated
- push status
- stash status
- any conflicts and how they were resolved