Practical Pushing of Rebased Chains

The previous post, Practical Rebasing of Chained Branches, covered how to use a custom git command to rebase several git branches stacked on each other. Once wishing to share a set of branches; pushing a chain to a server risks becoming another tedious task. Unless using something like this custom git command, which I call git-force-push-chain

#!/bin/sh -eu
# git force-push-chain <branch-point> [remote]
# Force pushes all branches between HEAD and <branch-point> to [remote].
# If no remote is provided, 'origin' is assumed. Ignores branches without a
# tracking branch, as well as those tracking some other remote than the
# provided one.
# Only histories on a straight line are handled. (No trees are traversed.)

[ "${_from:-}" ] ||
    { echo 'First argument must be <branch-point>.' >&2; exit 1; }
git remote | grep -q "^${_remote}\$" ||
    { echo "fatal: '${_remote}' does not appear to be a remote" >&2; exit 1; }

_commits="$( git log "${_from}..${_to}" --no-merges --pretty='format: %h' )"

for _commit in ${_commits}; do
  _branches="$( git branch --points-at="${_commit}" \
      --format='%(refname:short)' )"
  for _branch in ${_branches}; do
    if _tracking="$( git rev-parse --symbolic-full-name \
        --abbrev-ref "${_branch}@{push}" 2>/dev/null )"
      if [ "${_tracking%%/*}" = "${_remote}" ]; then
        git push --force "${_remote}" "${_branch}"

unset _branch _branches _commit _commits _from _remote _to _tracking

# vim: sw=2 et

Hopefully the usage comment at the start should be explanatory enough for anyone in a situation to make use of the script. The only thing to possible mention is to highlight that this will force-push your local branches and overwrite any with the same name already existing on the server, without asking. That is what the script is designed to do, updating many branches under parallel development.

Putting the following into .zshrc should make tab completion work:

zstyle ':completion:*:*:git:*' user-commands \
    force-push-chain:'fiercely update remote from <branch-point> to HEAD'

_git-force-push-chain() {
  # Exit early if pwd is not a git repository.
  git rev-parse 2>/dev/null || return 0

  case "${CURRENT}" in
      _alternative \
        "branches:branches:($( git branch --list --format="%(refname:short)" ))"
      _alternative \
        "remotes:remotes:($( git remote ))"

compdef _git-force-push-chain git-force-push-chain

With great power comes great responsability. It is easy to mess things up when being able to rewrite git history and posting it with low effort. Think through your policies and use of branches. Always make sure everyone on your team are in agreement on rules and naming for rewritable branches, and never ever rebase main, okay?!

2022-09-04 09:18:18 +0000
Thoughts and feedback may be directed at me using the channel listed on my contact page.

Previous post Next post