Durchlaufen aller Git-Zweige mit dem Bash-Skript


104

Wie kann ich mit Bash-Skript alle lokalen Zweige in meinem Repository durchlaufen? Ich muss iterieren und prüfen, ob es einen Unterschied zwischen dem Zweig und einigen entfernten Zweigen gibt. Ex

for branch in $(git branch); 
do
    git log --oneline $branch ^remotes/origin/master;
done

Ich muss etwas wie oben angegeben tun, aber das Problem, mit dem ich konfrontiert bin, ist $ (Git-Zweig), der mir die Ordner im Repository-Ordner zusammen mit den im Repository vorhandenen Zweigen gibt.

Ist dies der richtige Weg, um dieses Problem zu lösen? Oder gibt es einen anderen Weg, es zu tun?

Danke dir



1
@pihentagy Dies wurde vor der verknüpften Frage geschrieben.
Kodaman

ZB: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

Antworten:


156

Sie sollten beim Schreiben von Skripten keinen Git-Zweig verwenden . Git bietet eine "Sanitär" -Schnittstelle , die explizit für die Verwendung in Skripten entwickelt wurde (viele aktuelle und historische Implementierungen normaler Git-Befehle (Hinzufügen, Auschecken, Zusammenführen usw.) verwenden dieselbe Schnittstelle).

Der gewünschte Installationsbefehl lautet git for-each-ref :

git for-each-ref --shell \
  --format='git log --oneline %(refname) ^origin/master' \
  refs/heads/

Hinweis: Sie benötigen das remotes/Präfix auf der Remote-Referenz nur, wenn Sie andere Referenzen haben, die dazu führen origin/master, dass mehrere Stellen im Suchpfad für Referenznamen übereinstimmen (siehe „Ein symbolischer Referenzname .…“ Im AbschnittRevisionen angeben“ von git-rev-parse (1) ). Wenn Sie versuchen, Mehrdeutigkeiten explizit zu vermeiden, verwenden Sie den vollständigen Referenznamen : refs/remotes/origin/master.

Sie erhalten folgende Ausgabe:

git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master

Sie können diese Ausgabe in sh leiten .

Wenn Ihnen die Idee, den Shell-Code zu generieren, nicht gefällt, können Sie auf Robustheit * verzichten und dies tun:

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    git log --oneline "$branch" ^origin/master
done

* Ref-Namen sollten vor der Wortteilung der Shell geschützt sein (siehe git-check-ref-Format (1) ). Persönlich würde ich mich an die frühere Version halten (generierter Shell-Code); Ich bin zuversichtlicher, dass damit nichts Unangemessenes passieren kann.

Da Sie bash angegeben haben und es Arrays unterstützt, können Sie die Sicherheit aufrechterhalten und dennoch vermeiden, die Eingeweide Ihrer Schleife zu generieren:

branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
    # …
done

Ähnliches können Sie tun, $@wenn Sie keine Shell verwenden, die Arrays unterstützt ( set --zum Initialisieren und set -- "$@" %(refname)Hinzufügen von Elementen).


39
Ernsthaft. Es gibt keinen einfacheren Weg, dies zu tun?
Jim Fell

4
Aber was ist, wenn ich eine der Filteroptionen von git branch verwenden --mergedmöchte , wie müsste ich die Logik in git branch duplizieren? Es muss einen besseren Weg geben, dies zu tun.
Thayne

3
Einfachere Version:git for-each-ref refs/heads | cut -d/ -f3-
wid

4
@wid: Oder einfachgit for-each-ref refs/heads --format='%(refname)'
John Gietzen

5
@Thayne: Diese Frage ist alt, aber die Git-Leute haben sich endlich mit dem Problem befasst: for-each-refUnterstützt jetzt alle Branchenselektoren wie --mergedund git branchund git tagwird nun tatsächlich in sich git for-each-refselbst implementiert , zumindest für die in der Liste vorhandenen Fälle. (Das Erstellen neuer Zweige und Tags ist nicht Teil von for-each-ref.)
torek

50

Dies liegt daran git branch, dass der aktuelle Zweig mit einem Sternchen markiert ist, z.

$ git branch
* master
  mybranch
$ 

wird also $(git branch)auf z. B. erweitert * master mybranch, und dann wird die *Liste der Dateien im aktuellen Verzeichnis erweitert.

Ich sehe keine offensichtliche Option, das Sternchen überhaupt nicht zu drucken. aber du könntest es abhacken:

$(git branch | cut -c 3-)

4
Wenn Sie in doppelte Anführungszeichen setzen, können Sie verhindern, dass bash das Sternchen erweitert - obwohl Sie es trotzdem aus der Ausgabe entfernen möchten. Eine robustere Methode zum Entfernen eines Sternchens an einem beliebigen Punkt wäre $(git branch | sed -e s/\\*//g).
Nick

2
Schön, ich mag deine 3-Lösung wirklich .
Andrei-Niculae Petre

1
Etwas einfachere Version:$(git branch | sed 's/^..//')
JDG

5
etwas einfachere tr-Version:$(git branch | tr -d " *")
ccpizza

13

Die eingebaute Bash mapfileist dafür gebaut

Alle Git-Zweige: git branch --all --format='%(refname:short)'

Alle lokalen Git-Filialen: git branch --format='%(refname:short)'

Alle Remote-Git-Zweige: git branch --remotes --format='%(refname:short)'

iteriere durch alle Git-Zweige: mapfile -t -C my_callback -c 1 < <( get_branches )

Beispiel:

my_callback () {
  INDEX=${1}
  BRANCH=${2}
  echo "${INDEX} ${BRANCH}"
}
get_branches () {
  git branch --all --format='%(refname:short)'
}
# mapfile -t -C my_callback -c 1 BRANCHES < <( get_branches ) # if you want the branches that were sent to mapfile in a new array as well
# echo "${BRANCHES[@]}"
mapfile -t -C my_callback -c 1 < <( get_branches )

für die spezifische Situation des OP:

#!/usr/bin/env bash


_map () {
  ARRAY=${1?}
  CALLBACK=${2?}
  mapfile -t -C "${CALLBACK}" -c 1 <<< "${ARRAY[@]}"
}


get_history_differences () {
  REF1=${1?}
  REF2=${2?}
  shift
  shift
  git log --oneline "${REF1}" ^"${REF2}" "${@}"
}


has_different_history () {
  REF1=${1?}
  REF2=${2?}
  HIST_DIFF=$( get_history_differences "${REF1}" "${REF2}" )
  return $( test -n "${HIST_DIFF}" )
}


print_different_branches () {
  read -r -a ARGS <<< "${@}"
  LOCAL=${ARGS[-1]?}
  for REMOTE in "${SOME_REMOTE_BRANCHES[@]}"; do
    if has_different_history "${LOCAL}" "${REMOTE}"; then
      # { echo; echo; get_history_differences "${LOCAL}" "${REMOTE}" --color=always; } # show differences
      echo local branch "${LOCAL}" is different than remote branch "${REMOTE}";
    fi
  done
}


get_local_branches () {
  git branch --format='%(refname:short)'
}


get_different_branches () {
  _map "$( get_local_branches )" print_different_branches
}


# read -r -a SOME_REMOTE_BRANCHES <<< "${@}" # use this instead for command line input
declare -a SOME_REMOTE_BRANCHES
SOME_REMOTE_BRANCHES=( origin/master remotes/origin/another-branch another-remote/another-interesting-branch )
DIFFERENT_BRANCHES=$( get_different_branches )

echo "${DIFFERENT_BRANCHES}"

Quelle: Listet alle lokalen Git-Zweige ohne Sternchen auf


5

Ich iteriere wie es zum Beispiel:

for BRANCH in `git branch --list|sed 's/\*//g'`;
  do 
    git checkout $BRANCH
    git fetch
    git branch --set-upstream-to=origin/$BRANCH $BRANCH
  done
git checkout master;

4

Ich würde vorschlagen, $(git branch|grep -o "[0-9A-Za-z]\+")wenn Ihre lokalen Niederlassungen nur durch Ziffern, Az und / oder AZ-Buchstaben benannt sind


4

Die akzeptierte Antwort ist richtig und sollte eigentlich der Ansatz sein, aber das Lösen des Problems in Bash ist eine großartige Übung, um zu verstehen, wie Muscheln funktionieren. Der Trick, dies mit bash zu tun, ohne zusätzliche Textmanipulationen durchzuführen, besteht darin, sicherzustellen, dass die Ausgabe des Git-Zweigs niemals als Teil eines von der Shell auszuführenden Befehls erweitert wird. Dies verhindert, dass das Sternchen in der Dateinamenerweiterung (Schritt 8) der Shell-Erweiterung jemals erweitert wird (siehe http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html ).

Verwenden Sie das bash while- Konstrukt mit einem Lesebefehl, um die Ausgabe des Git-Zweigs in Zeilen zu zerlegen. Das '*' wird als wörtliches Zeichen eingelesen. Verwenden Sie eine case-Anweisung, um sie abzugleichen, und achten Sie dabei besonders auf die übereinstimmenden Muster.

git branch | while read line ; do                                                                                                        
    case $line in
        \*\ *) branch=${line#\*\ } ;;  # match the current branch
        *) branch=$line ;;             # match all the other branches
    esac
    git log --oneline $branch ^remotes/origin/master
done

Die Sternchen sowohl im Bash- Case- Konstrukt als auch in der Parametersubstitution müssen mit Backslashes maskiert werden, damit die Shell sie nicht als Mustervergleichszeichen interpretiert. Die Leerzeichen werden auch maskiert (um eine Tokenisierung zu verhindern), da Sie buchstäblich mit '*' übereinstimmen.


3

Die meiner Meinung nach am einfachsten zu merkende Option:

git branch | grep "[^* ]+" -Eo

Ausgabe:

bamboo
develop
master

Die Option -o von Grep (--only-matching) beschränkt die Ausgabe nur auf die übereinstimmenden Teile der Eingabe.

Da in Git-Zweignamen weder Leerzeichen noch * gültig sind, wird die Liste der Zweige ohne die zusätzlichen Zeichen zurückgegeben.

Bearbeiten: Wenn Sie sich im Status "Abgelöster Kopf" befinden , müssen Sie den aktuellen Eintrag herausfiltern:

git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE


3

Was ich letztendlich getan habe, bezog sich auf Ihre Frage (& inspiriert von der Erwähnung von ccpizza tr):

git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done

(Ich benutze While - Schleifen viel. Während für bestimmte Dinge würden Sie auf jeden Fall mit einem spitzen Variablennamen verwenden mögen [ „Zweig“, zum Beispiel], die meisten der Zeit , die ich nur mit etwas zu tun , mit jeder betroffenen bin Linie der Eingabe. Mit "Linie" hier anstelle von "Zweig" ist eine Anspielung auf Wiederverwendbarkeit / Muskelgedächtnis / Effizienz.)


1

Im Anschluss an die Antwort von @ finn (danke!) Können Sie im Folgenden über die Zweige iterieren, ohne ein dazwischenliegendes Shell-Skript zu erstellen. Es ist robust genug, solange der Filialname keine Zeilenumbrüche enthält :)

git for-each-ref --format='%(refname)' refs/heads  | while read x ; do echo === $x === ; done

Die while-Schleife wird in einer Subshell ausgeführt. Dies ist normalerweise in Ordnung, es sei denn, Sie legen Shell-Variablen fest, auf die Sie in der aktuellen Shell zugreifen möchten. In diesem Fall verwenden Sie die Prozessersetzung, um die Pipe umzukehren:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )

1

Wenn Sie in diesem Zustand sind:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

Und Sie führen diesen Code aus:

git branch -a | grep remotes/origin/*

for BRANCH in `git branch -a | grep remotes/origin/*` ;

do
    A="$(cut -d'/' -f3 <<<"$BRANCH")"
    echo $A

done        

Sie erhalten dieses Ergebnis:

branch1

branch2

branch3

master

Dies sieht für mich weniger komplex aus +1
Abhi

Das hat auch bei mir funktioniert:for b in "$(git branch)"; do git branch -D $b; done
Abhi

1

Halte es einfach

Die einfache Möglichkeit, den Zweignamen mithilfe eines Bash-Skripts in eine Schleife zu bringen.

#!/bin/bash

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    echo "${branch/'refs/heads/'/''}" 
done

Ausgabe:

master
other

1

Googlians Antwort, aber ohne Verwendung für

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/

1
Dies funktioniert nicht für Zweignamen mit Namespace, wie in Zweigen, in denen ein Schrägstrich enthalten ist. Dies bedeutet, dass von Dependabot erstellte Zweige, die ungefähr wie "Dependabot / npm_and_yarn / typescript-3.9.5" aussehen, stattdessen als "Typoskript-3.9.5" angezeigt werden.
Ecbrodie

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.