-
Validate the repository:
git rev-parse --is-inside-work-tree
git branch --show-current
git remote get-url upstream
git remote get-url origin
Abort on detached HEAD or missing upstream. If origin is missing, continue locally and skip push.
-
Require a clean worktree:
worktree_status=$(git status --porcelain) || exit 1
test -z "$worktree_status"
If dirty, stop and ask the user to clean or commit the changes, or use a clean task worktree.
-
Detect the upstream target branch:
- If
--base=<branch> is provided, fetch and verify upstream/<branch>.
- Otherwise run
git remote set-head upstream -a, then read refs/remotes/upstream/HEAD.
- If detection fails, ask the user for
--base=<branch>.
-
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}"
origin_branch_exists=false
if git remote get-url origin >/dev/null 2>&1; then
if ! origin_branch=$(git ls-remote --heads origin "refs/heads/${current_branch}"); then
echo "failed to inspect origin/${current_branch}" >&2
exit 1
elif [ -n "$origin_branch" ]; then
git fetch origin "+refs/heads/${current_branch}:refs/remotes/origin/${current_branch}" || exit 1
origin_branch_exists=true
else
echo "origin/${current_branch} does not exist; current branch is unpublished"
fi
fi
git merge-base HEAD "upstream/${upstream_branch}"
If the repository is shallow, unshallow origin first when available, then upstream only if still shallow.
Always use the fetched remote-tracking ref as the target; do not decide from a previously cached upstream/<branch> tip.
-
Record the exact refs and report divergence:
current_head=$(git rev-parse HEAD)
upstream_tip=$(git rev-parse "upstream/${upstream_branch}")
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"
Report HEAD and upstream/${upstream_branch} with their full SHAs.
-
Merge:
-
Behind 0 means the fetched upstream_tip is already an ancestor of current_head. Confirm that state with both checks:
git merge-base --is-ancestor "$upstream_tip" "$current_head"
upstream_range=$(git rev-list "$current_head..$upstream_tip") || exit 1
test -z "$upstream_range"
This completes upstream integration as a successful no-op. Report the exact refs, SHAs, ancestry result, and empty range; skip the merge, release, and other change-dependent gates. Do not create an empty commit or pull request, or publish a branch solely to represent the sync. If an independent request explicitly approves pushing existing local commits, use step 9's non-destructive push semantics.
-
If behind is greater than 0 and --ff-allow is set with ahead 0, run:
git merge --ff-only "upstream/${upstream_branch}"
-
Otherwise capture previous_head and upstream_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=U
Read 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 --continue
-
Verify:
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"
if [ "$origin_branch_exists" = true ]; then
git merge-base --is-ancestor "origin/${current_branch}" HEAD
fi
In default mode, verify HEAD has two parents: previous_head as first parent and upstream_tip as second parent. In --ff-allow fast-forward mode, verify HEAD equals upstream_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: merge origin/<branch> into HEAD and retry, or stop.