Ob man nun Gitflow, GitHub Flow oder ein selbstausgedachte Abwandlung dieser Modelle verwendet: Einige dieser Modelle benötigen einen Weg, um einen Branch in einen anderen Branch zu mergen. Dieser Handgriff ist in Git mehrteilig – und teilweise etwas nervig.

Git sieht dafür den Weg vor, in den Ziel-Branch zu wechseln (z.B. main), und vom Quell-Branch (z.B. feature/XYZ) mittels git merge zu ziehen. Das ist in der Regel eine sehr gute Methode, um Fehlbedienungen zu vermeiden.

Bei vielen Git-Workflows wird nun aber davon ausgegangen, dass im Ziel-Branch nicht direkt gearbeitet werden soll. Es ist also sehr wichtig, nach Abschluss des Merges wieder in den Quell-Branch zu wechseln, um dort versehentliche Commits zu vermeiden. Die verschiedenen Branch-Wechsel bedürfen also einiger Konzentration.

Zudem ist die Schreibarbeit für einen solchen Merge auf der Kommandozeile etwas umfangreicher, will man nichts übersehen und vor allen Dingen alle Zwischenschritte auch auf dem Repo-Server bzw. origin bekannt machen:

$ git push
$ git checkout main
$ git pull
$ git merge feature/XYZ
$ git push
$ git checkout feature/XYZ

Tatsächlich gibt es für Gitflow die Gitflow-CLI-Tools, die genau dieses Problem lösen:

$ git flow feature finish feature/XYZ

Dummerweise haben diese Tools den Nachteil, dass das Ziel der Operation fest verdrahtet ist – und außerdem die Installation der Tools voraussetzt. In der Regel scheitert es bei mir daran, dass in dem Projekt gar nicht Gitflow verwendet wird. 😉

Der Wunsch ist also: Ein leichtgewichtiges, flexibles Tool, dass sicher einen Quell-Branch in einen Ziel-Branch merged.

Bash Aliases

Nicht zuletzt durch die beeindruckende Sammlung von Bash Aliases meines Kollegen Marty kam ich auf die Idee, für meine Wünsche ebenfalls einen Bash-Alias zu schreiben. Analog zu Git Aliases (zu denen ich meine persönliche Sammlung von Git Aliases angelegt habe) kann man mittels Bash-Aliases neue Befehle für die Bash erzeugen.

Im einfachsten Fall sind Bash-Aliases Aufrufe, die komplizierte Einzeiler ersetzen. Meine Idee war etwas komplexer, so dass ich stattdessen eine Bash Function verwendet habe, um meine Idee umzusetzen. Ich hatte die folgende Vorstellung:

  1. Ich starte in dem Quell-Branch, den ich mergen möchte,
  2. und gebe den Ziel-Branch ein.
  3. Es gibt eine kurze Rückfrage, ob ich mich nicht geirrt habe – und eine Überprüfung ob die Operation wirklich Sinn macht.
  4. Danach wird der Ziel-Branch ausgecheckt, aktualisiert, und der Quell-Branch (kommentarlos) via Merge in den Ziel-Branch gezogen.
  5. Schlussendlich wird zurück auf den Quell-Branch gewechselt.

Sollten Merge Conflicts auftreten, würde das Programm abbrechen und Gelegenheit zur Korrektur des Konflikts geben.

Das finale Script (die aktuellste Version findet sich auch in meinen öffentlichen .bash_aliases) sieht nun wie folgt aus:

# Put me in `.bash_aliases`, works like an alias.
git-merge-to() {
  TARGET_BRANCH=develop
  if [[ "${1}" ]]; then
    TARGET_BRANCH=${1}
  fi

  FEATURE_BRANCH=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')

  if [[ ! "${FEATURE_BRANCH}" ]]; then
    echo -e "\e[91mERROR\e[0m: No git branch found"
    return 4
  fi
  if [[ "${FEATURE_BRANCH}" =~ ^(main|master|develop|preview)$ ]]; then
    echo -e "\e[91mERROR\e[0m: Branch ${FEATURE_BRANCH} is not a mergable branch"
    git branch
    return 2
  fi
  if [[ "${FEATURE_BRANCH}" == "${TARGET_BRANCH}" ]]; then
    echo -e "\e[91mERROR\e[0m: Branches are identical"
    return 3
  fi

  echo -en "Merge branch \e[94m${FEATURE_BRANCH}\e[0m into \e[94m${TARGET_BRANCH}\e[0m? [yn] "
  read CONFIRM
  if [[ ! "${CONFIRM}" =~ ^(y|Y|yes|Yes)$ ]]; then
    echo "Cancelled"
    return 1
  fi

  git push
  git checkout ${TARGET_BRANCH}
  git pull
  git merge ${FEATURE_BRANCH} --no-edit
  git push
  git checkout ${FEATURE_BRANCH}
}

Sobald dieses Script in der .bash_aliases eingetragen ist, kann man es auf dem lokalen Rechner von jedem Ort aus starten:

$ git-merge-to develop
Merge branch feature/web-components into develop? [yn] y
  Everything up-to-date
  Switched to branch 'develop'
  Your branch is up-to-date with 'origin/develop'.
  Already up-to-date.
  Merge made by the 'recursive' strategy.
  Writing objects: 100% (6/6), 578 bytes | 578.00 KiB/s, done.
  Switched to branch 'feature/web-components'
  Your branch is up-to-date with 'origin/feature/web-components'.
$

Netterweise funktioniert auch die Auto-Vervollständigung des Befehles mittels der Tabulatoren-Taste, so dass ein git- und TAB den Befehl anzeigt.