Jak iterować przez wszystkie gałęzie git za pomocą skryptu bash

105

Jak mogę iterować przez wszystkie lokalne gałęzie w moim repozytorium przy użyciu skryptu bash. Muszę iterować i sprawdzić, czy istnieje różnica między oddziałem a niektórymi zdalnymi oddziałami. Dawny

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

Muszę zrobić coś podobnego do podanego powyżej, ale problem, z którym się zmagam, to $ (gałąź git) daje mi foldery wewnątrz folderu repozytorium wraz z gałęziami obecnymi w repozytorium.

Czy to właściwy sposób rozwiązania tego problemu? Czy jest na to inny sposób?

Dziękuję Ci

Arun P Johny
źródło
1
@pihentagy To zostało napisane przed połączonym pytaniem.
kodaman
Np .: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

Odpowiedzi:

157

Nie powinieneś używać git branch podczas pisania skryptów. Git zapewnia interfejs „hydrauliczny” który został specjalnie zaprojektowany do użycia w skryptach (wiele aktualnych i historycznych implementacji normalnych poleceń Git (dodawanie, pobieranie, scalanie itp.) Używa tego samego interfejsu).

Polecenie hydrauliczne, które chcesz, to git for-each-ref :

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

Uwaga: Nie potrzebujesz remotes/przedrostka w zdalnym odnośniku, chyba że masz inne odniesienia, które powodują origin/masterdopasowanie wielu miejsc w ścieżce wyszukiwania nazw referencyjnych (zobacz „Symboliczna nazwa odniesienia…” w sekcji Określanie wersji w git-rev-parse (1) ). Jeśli próbujesz explictly uniknąć niejasności, a następnie przejść z pełną nazwą ref: refs/remotes/origin/master.

Otrzymasz takie wyniki:

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

Możesz przesłać to wyjście do sh .

Jeśli nie podoba ci się pomysł generowania kodu powłoki, możesz porzucić trochę solidności * i zrobić to:

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

* Nazwy ref powinny być chronione przed podziałem na słowa w powłoce (zobacz git-check-ref-format (1) ). Osobiście trzymałbym się poprzedniej wersji (wygenerowany kod powłoki); Jestem bardziej przekonany, że nie może się z tym stać nic niewłaściwego.

Ponieważ określiłeś bash i obsługuje on tablice, możesz zachować bezpieczeństwo i nadal unikać generowania wnętrzności pętli:

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

Możesz zrobić coś podobnego, $@jeśli nie używasz powłoki obsługującej tablice ( set --do inicjalizacji i set -- "$@" %(refname)dodawania elementów).

Chris Johnsen
źródło
39
Poważnie. Nie ma prostszego sposobu na zrobienie tego?
Jim Fell
4
Ale co, jeśli chcę użyć jednej z opcji filtrowania git branch, na przykład --merged, czy musiałbym zduplikować logikę w git branch? Musi być na to lepszy sposób.
Thayne
3
Prostsza wersja:git for-each-ref refs/heads | cut -d/ -f3-
wid.
4
@wid: Lub po prostugit for-each-ref refs/heads --format='%(refname)'
John Gietzen
5
@Thayne: to pytanie jest stare, ale ludzie z Git w końcu rozwiązali ten problem: for-each-refteraz obsługuje wszystkie selektory gałęzi, takie jak --mergedi, git brancha git tagteraz są one faktycznie zaimplementowane git for-each-refsame w sobie, przynajmniej dla przypadków istniejących na liście. (Tworzenie nowych gałęzi i tagów nie jest i nie powinno być częścią for-each-ref.)
torek
49

Dzieje się tak, ponieważ git branchoznacza bieżącą gałąź gwiazdką, np .:

$ git branch
* master
  mybranch
$ 

więc $(git branch)rozwija się do np. * master mybranch, a następnie *rozwija się do listy plików w bieżącym katalogu.

Nie widzę oczywistej opcji, aby nie drukować gwiazdki w pierwszej kolejności; ale możesz to odciąć:

$(git branch | cut -c 3-)
Matthew Slattery
źródło
4
Jeśli otoczysz podwójnymi cudzysłowami, możesz zatrzymać rozwijanie gwiazdki przez bash - chociaż nadal będziesz chciał usunąć ją z wyniku. Bardziej niezawodnym sposobem usunięcia gwiazdki z dowolnego miejsca byłoby $(git branch | sed -e s/\\*//g).
Nick
2
fajnie, bardzo podoba mi się twoje 3-rozwiązanie.
Andrei-Niculae Petre
1
Nieco prostsza wersja seda:$(git branch | sed 's/^..//')
jdg
5
nieco prostsza wersja tr:$(git branch | tr -d " *")
ccpizza 15.04.15
13

mapfileWbudowany bash jest do tego zbudowany

wszystkie gałęzie git: git branch --all --format='%(refname:short)'

wszystkie lokalne oddziały git: git branch --format='%(refname:short)'

wszystkie zdalne gałęzie git: git branch --remotes --format='%(refname:short)'

iteruj przez wszystkie gałęzie git: mapfile -t -C my_callback -c 1 < <( get_branches )

przykład:

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 )

dla szczególnej sytuacji PO:

#!/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}"

źródło: wyświetla wszystkie lokalne gałęzie git bez gwiazdki

Andrew Miller
źródło
5

Iteruję tak jak na przykład:

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;
Djory Krache
źródło
4

Zasugerowałbym $(git branch|grep -o "[0-9A-Za-z]\+"), aby nazwy lokalnych oddziałów miały tylko cyfry, az i / lub litery AZ

Xin Guo
źródło
4

Przyjęta odpowiedź jest poprawna i naprawdę powinna być zastosowanym podejściem, ale rozwiązanie problemu w bash to świetne ćwiczenie w zrozumieniu, jak działają powłoki. Sztuczka polegająca na zrobieniu tego za pomocą basha bez wykonywania dodatkowych operacji na tekście polega na zapewnieniu, że wyjście git branch nigdy nie zostanie rozwinięte jako część polecenia wykonywanego przez powłokę. Zapobiega to rozszerzaniu się gwiazdki w rozszerzaniu nazwy pliku (krok 8) rozwijania powłoki (patrz http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Użyj bash while construct z poleceniem read, aby podzielić wyjście git branch na linie. Znak „*” będzie czytany jako znak dosłowny. Użyj instrukcji case, aby ją dopasować, zwracając szczególną uwagę na pasujące wzorce.

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

Gwiazdki zarówno w konstrukcji bash case, jak i przy podstawianiu parametrów muszą być poprzedzone odwrotnymi ukośnikami, aby zapobiec ich interpretacji przez powłokę jako znaków pasujących do wzorca. Spacje są również chronione (aby zapobiec tokenizacji), ponieważ dosłownie dopasowujesz „*”.

BitByteDog
źródło
4

Moim zdaniem najłatwiejsza do zapamiętania opcja:

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

Wynik:

bamboo
develop
master

Opcja -o Grepa (--only-matching) ogranicza dane wyjściowe tylko do pasujących części danych wejściowych.

Ponieważ ani spacja, ani * nie są prawidłowe w nazwach gałęzi Git, zwraca to listę gałęzi bez dodatkowych znaków.

Edycja: jeśli jesteś w stanie odłączonej głowy , musisz odfiltrować bieżący wpis:

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

staafl
źródło
3

To, co ostatecznie zrobiłem, w odniesieniu do twojego pytania (i zainspirowane wspomnieniem ccpizza tr):

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

(Często używam pętli while. Podczas gdy dla pewnych rzeczy na pewno chciałbyś użyć wskazanej nazwy zmiennej [na przykład "gałąź"], przez większość czasu zajmuję się tylko zrobieniem czegoś z każdym wierszem danych wejściowych. Używanie `` linia '' zamiast `` gałęzi '' jest ukłonem w stronę wielokrotnego użytku / pamięci mięśni / wydajności).

Jan Kyu Peblik
źródło
1

Rozpoczynając od odpowiedzi @ finn (dziękuję!), Poniższe instrukcje pozwolą ci iterować po gałęziach bez tworzenia interweniującego skryptu powłoki. Jest wystarczająco solidny, o ile w nazwie gałęzi nie ma znaków nowych linii :)

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

Pętla while działa w podpowłoce, co zwykle jest w porządku, chyba że ustawiasz zmienne powłoki, do których chcesz uzyskać dostęp w bieżącej powłoce. W takim przypadku użyj podstawiania procesu, aby odwrócić potok:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )
Chris Cogdon
źródło
1

Jeśli jesteś w tym stanie:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

I uruchamiasz ten kod:

git branch -a | grep remotes/origin/*

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

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

done        

Otrzymasz ten wynik:

branch1

branch2

branch3

master
Wijith Bandara
źródło
To wydaje mi się mniej skomplikowane rozwiązanie +1
Abhi,
U mnie też to zadziałało:for b in "$(git branch)"; do git branch -D $b; done
Abhi
1

Nie komplikuj

Prosty sposób na uzyskanie nazwy gałęzi w pętli za pomocą skryptu bash.

#!/bin/bash

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

Wynik:

master
other
Googlian
źródło
1

Odpowiedź Googliana, ale bez użycia dla

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
Mistrz Yoda
źródło
1
Nie działa to w przypadku nazw gałęzi z przestrzenią nazw, tak jak w przypadku gałęzi, które mają ukośnik. Oznacza to, że gałęzie utworzone przez dependabot, które wyglądają jak „dependabot / npm_and_yarn / typescript-3.9.5”, pojawią się zamiast tego jako „typescript-3.9.5”.
ecbrodie
1
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

Używa poleceń git plumbing , które są przeznaczone do obsługi skryptów. Jest to również proste i standardowe.

Odniesienie: Ukończenie Bash w Git

aude
źródło