Mohammad Kanaan | Blog

gsync: A Nicer Way to Sync Branches Between Remotes

A small zsh function for when you constantly push and pull the same branch between multiple remotes.

At work we keep two git remotes and I have to sync branches between them constantly. Writing it out in plain git every time was a nightmare, so I wrote a small wrapper.

For example, I have a branch fix-all-bugs that has changes in remote2 that I want to sync with remote1. Granted I’m on the fix-all-bugs branch locally, I can simply run:

gsync remote2 -f          # pull from remote2 into current branch
gsync remote1 -t          # push current branch to remote1

Maybe you want to push/pull from a different branch on remote1?

gsync remote1 -t backup/fix-all-bugs          # push current branch to remote1 as backup/fix-all-bugs
gsync remote1 -f backup/fix-all-bugs          # pull from remote1 backup/fix-all-bugs into current branch

That’s it. gsync <remote> -t pushes, gsync <remote> -f pulls. Branch name defaults to whatever you’re on. Pass a different one if you need it.

It checks you’re in a repo, not in detached HEAD, and that the remote actually exists before doing anything. Catches typos like gsync orign.

The push uses a refspec (current:remote) because that’s how you push to a differently-named remote branch. It’s not hard to remember, it’s just annoying to type every time. This hides it behind a flag.

Here’s the code, hope it helps :)

No dependencies beyond git. ~50 lines of zsh. Drop it in your .zshrc. (or anything bash compatible)

Github gist

gsync() {
  local target="$1"
  local arg2="$2"
  local arg3="$3"

  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
    echo "Error: Not inside a git repository."
    return 1
  }

  local current_branch
  current_branch="$(git rev-parse --abbrev-ref HEAD)"

  if [[ "$current_branch" == "HEAD" ]]; then
    echo "Error: Detached HEAD state."
    return 1
  fi

  if ! git remote get-url "$target" >/dev/null 2>&1; then
    echo "Error: Unknown remote '$target'."
    return 1
  fi

  local action="push"
  local remote_branch="$current_branch"

  case "$arg2" in
    --from|-f) action="pull"; remote_branch="${arg3:-$current_branch}" ;;
    --to|-t)   action="push"; remote_branch="${arg3:-$current_branch}" ;;
    "")        ;;
    *)         remote_branch="$arg2" ;;
  esac

  [[ -z "$remote_branch" ]] && { echo "Error: Remote branch is empty."; return 1; }

  if [[ "$action" == "pull" ]]; then
    echo "⬇️ Pulling '$remote_branch' into '$current_branch'..."
    git pull "$target" "$remote_branch" || { echo "Error: Pull failed."; return 1; }
  else
    echo "⬆️ Pushing '$current_branch' to '$remote_branch'..."
    git push "$target" "$current_branch:$remote_branch" || { echo "Error: Push failed."; return 1; }
  fi
}