Sync the current branch with upstream, rebasing local commits on top.
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
usage: git sync-upstream [--dry-run] [--remote <remote>] [--branch <branch>]
Sync local main with upstream/main, then return to the original branch.
git checkout main
git fetch upstream
git merge --ff-only upstream/main
git checkout <original-branch>
Options:
--remote <remote> upstream remote name (default: upstream)
--branch <branch> main branch name (default: main)
EOF
}
die() {
printf 'git sync-upstream: %s\n' "$*" >&2
exit 1
}
run() {
printf '+'
printf ' %q' "$@"
printf '\n'
if [[ "${dry_run}" != true ]]; then
"$@"
fi
}
dry_run=false
remote=upstream
main_branch=main
while (($#)); do
case "$1" in
--dry-run|-n)
dry_run=true
shift
;;
--remote)
(($# >= 2)) || die "--remote requires a value"
remote=$2
shift 2
;;
--branch)
(($# >= 2)) || die "--branch requires a value"
main_branch=$2
shift 2
;;
--help|-h)
usage
exit 0
;;
--)
shift
break
;;
-*)
die "unknown option: $1"
;;
*)
die "unexpected argument: $1"
;;
esac
done
(($# == 0)) || { usage >&2; exit 2; }
git rev-parse --is-inside-work-tree >/dev/null 2>&1 ||
die "not inside a git worktree"
git remote get-url "$remote" >/dev/null 2>&1 ||
die "remote '$remote' does not exist"
original_branch=$(git branch --show-current)
[[ -n "$original_branch" ]] || die "not on a named branch (detached HEAD?)"
run git checkout "$main_branch"
run git fetch "$remote"
run git merge --ff-only "${remote}/${main_branch}"
if [[ "$original_branch" != "$main_branch" ]]; then
run git checkout "$original_branch"
fi