Comment parcourir toutes les branches git en utilisant le script bash

105

Comment puis-je parcourir toutes les branches locales de mon référentiel en utilisant le script bash. Je dois itérer et vérifier s'il y a une différence entre la branche et certaines branches distantes. Ex

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

Je dois faire quelque chose comme indiqué ci-dessus, mais le problème auquel je suis confronté est que $ (git branch) me donne les dossiers à l'intérieur du dossier du référentiel avec les branches présentes dans le référentiel.

Est-ce la bonne façon de résoudre ce problème? Ou y a-t-il une autre façon de le faire?

Je vous remercie

Arun P Johny
la source
Duplication possible de la boucle for sur toutes les branches git avec un certain nom
pihentagy
1
@pihentagy Ceci a été écrit avant la question liée.
kodaman
Par exemple: -for b in "$(git branch)"; do git branch -D $b; done
Abhi le

Réponses:

156

Vous ne devez pas utiliser git branch lors de l'écriture de scripts. Git fournit une interface de «plomberie» qui est explicitement conçue pour être utilisée dans les scripts (de nombreuses implémentations actuelles et historiques des commandes Git normales (ajout, extraction, fusion, etc.) utilisent cette même interface).

La commande de plomberie que vous voulez est git for-each-ref :

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

Remarque: Vous n'avez pas besoin du remotes/préfixe sur la référence distante, sauf si vous avez d'autres références qui font origin/mastercorrespondre plusieurs endroits dans le chemin de recherche du nom de référence (voir «Un nom de référence symbolique.…» Dans la section Spécification des révisions de git-rev-parse (1) ). Si vous essayez d'éviter l' ambiguïté explictly, puis aller avec le nom complet ref: refs/remotes/origin/master.

Vous obtiendrez une sortie comme celle-ci:

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

Vous pouvez diriger cette sortie dans sh .

Si vous n'aimez pas l'idée de générer le code shell, vous pouvez renoncer un peu à la robustesse * et faire ceci:

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

* Les noms de référence doivent être protégés du fractionnement des mots du shell (voir git-check-ref-format (1) ). Personnellement, je m'en tiendrai à l'ancienne version (code shell généré); Je suis plus convaincu que rien d'inapproprié ne peut en résulter.

Puisque vous avez spécifié bash et qu'il prend en charge les tableaux, vous pouvez maintenir la sécurité tout en évitant de générer les tripes de votre boucle:

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

Vous pouvez faire quelque chose de similaire avec $@si vous n'utilisez pas de shell prenant en charge les tableaux ( set --pour initialiser et set -- "$@" %(refname)ajouter des éléments).

Chris Johnsen
la source
39
Sérieusement. Il n'y a pas de moyen plus simple de faire cela?
Jim Fell
4
Mais que se passe-t-il si je veux utiliser l'une des options de filtrage de git branch, par exemple --merged, devrais-je dupliquer la logique dans git branch? Il doit y avoir une meilleure façon de faire cela.
Thayne
3
Version plus simple:git for-each-ref refs/heads | cut -d/ -f3-
wid
4
@wid: Ou, simplement,git for-each-ref refs/heads --format='%(refname)'
John Gietzen
5
@Thayne: cette question est ancienne, mais les gens de Git ont enfin résolu le problème: for-each-refprend désormais en charge tous les sélecteurs de branche comme --mergedet git branchet git tagsont maintenant réellement implémentés en git for-each-refsoi, au moins pour les cas de liste existants. (La création de nouvelles branches et balises ne fait pas et ne devrait pas faire partie de for-each-ref.)
torek
50

C'est parce que git branchmarque la branche actuelle avec un astérisque, par exemple:

$ git branch
* master
  mybranch
$ 

donc se $(git branch)développe par exemple * master mybranch, puis le se *développe à la liste des fichiers dans le répertoire courant.

Je ne vois pas d'option évidente pour ne pas imprimer l'astérisque en premier lieu; mais vous pouvez le couper:

$(git branch | cut -c 3-)
Matthew Slattery
la source
4
Si vous entourez de guillemets doubles, vous pouvez empêcher bash d'étendre l'astérisque - bien que vous souhaitiez toujours le supprimer de la sortie. Un moyen plus robuste de supprimer un astérisque de n'importe quel point serait $(git branch | sed -e s/\\*//g).
Nick
2
sympa, j'aime vraiment ta 3-solution.
Andrei-Niculae Petre
1
Version sed un peu plus simple:$(git branch | sed 's/^..//')
jdg
5
version tr un peu plus simple:$(git branch | tr -d " *")
ccpizza
13

Le bash builtin`` mapfileest construit pour cela

toutes les branches git: git branch --all --format='%(refname:short)'

toutes les branches git locales: git branch --format='%(refname:short)'

toutes les branches git distantes: git branch --remotes --format='%(refname:short)'

itérer à travers toutes les branches git: mapfile -t -C my_callback -c 1 < <( get_branches )

exemple:

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 )

pour la situation spécifique du 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}"

source: liste toutes les branches git locales sans astérisque

Andrew Miller
la source
5

J'itère comme ça par exemple:

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
la source
4

Je suggérerais $(git branch|grep -o "[0-9A-Za-z]\+")que vos succursales locales soient nommées uniquement par des chiffres, des az et / ou des lettres AZ

Xin Guo
la source
4

La réponse acceptée est correcte et devrait vraiment être l'approche utilisée, mais résoudre le problème dans bash est un excellent exercice pour comprendre le fonctionnement des coquilles. L'astuce pour faire cela en utilisant bash sans effectuer de manipulation de texte supplémentaire, est de s'assurer que la sortie de la branche git ne soit jamais développée dans le cadre d'une commande à exécuter par le shell. Cela empêche l'astérisque de s'étendre dans l'expansion du nom de fichier (étape 8) de l'expansion du shell (voir http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Utilisez la construction bash while avec une commande read pour découper la sortie de la branche git en lignes. Le '*' sera lu comme un caractère littéral. Utilisez une instruction case pour la faire correspondre, en accordant une attention particulière aux modèles correspondants.

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

Les astérisques dans la construction de cas bash et dans la substitution de paramètre doivent être échappés avec des barres obliques inverses pour empêcher le shell de les interpréter comme des caractères de correspondance de modèle. Les espaces sont également échappés (pour empêcher la tokenisation) car vous correspondez littéralement à «*».

BitByteDog
la source
4

Option la plus facile à retenir à mon avis:

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

Production:

bamboo
develop
master

L'option -o de Grep (--only-matching) limite la sortie aux seules parties correspondantes de l'entrée.

Étant donné que ni l'espace ni * ne sont valides dans les noms de branches Git, cela renvoie la liste des branches sans les caractères supplémentaires.

Edit: Si vous êtes dans l'état `` tête détachée '' , vous devrez filtrer l'entrée actuelle:

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

staafl
la source
3

Ce que j'ai fini par faire, appliqué à votre question (et inspiré par ccpizza mentionnant tr):

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

(J'utilise beaucoup les boucles while. Alors que pour des choses particulières, vous voudriez certainement utiliser un nom de variable pointé ["branche", par exemple], la plupart du temps, je ne me préoccupe que de faire quelque chose avec chaque ligne d'entrée. 'line' ici au lieu de 'branch' est un clin d'œil à la réutilisabilité / à la mémoire musculaire / à l'efficacité.)

Jan Kyu Peblik
la source
1

Dans le prolongement de la réponse de @ finn (merci!), Ce qui suit vous permettra de parcourir les branches sans créer de script shell intervenant. C'est assez robuste, tant qu'il n'y a pas de nouvelle ligne dans le nom de la branche :)

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

La boucle while s'exécute dans un sous-shell, ce qui est généralement bien, sauf si vous définissez des variables de shell auxquelles vous souhaitez accéder dans le shell actuel. Dans ce cas, vous utilisez la substitution de processus pour inverser le tuyau:

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

Si vous êtes dans cet état:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

Et vous exécutez ce code:

git branch -a | grep remotes/origin/*

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

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

done        

Vous obtiendrez ce résultat:

branch1

branch2

branch3

master
Wijith Bandara
la source
Cela me semble une solution moins complexe +1
Abhi
Cela a fonctionné pour moi aussi:for b in "$(git branch)"; do git branch -D $b; done
Abhi
1

Rester simple

Le moyen simple d'obtenir le nom de la branche en boucle à l'aide du script bash.

#!/bin/bash

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

Production:

master
other
Googlian
la source
1

La réponse de Googlian, mais sans utiliser pour

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
Maître Yoda
la source
1
Cela ne fonctionne pas pour les noms de branche avec un espace de nom, comme dans les branches qui ont une barre oblique. Cela signifie que les branches créées par dependabot, qui ressemblent à quelque chose comme "dependabot / npm_and_yarn / typescript-3.9.5", apparaîtront à la place comme "typescript-3.9.5".
ecbrodie
1
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

Cela utilise la plomberie git commandes de , qui sont conçues pour les scripts. C'est aussi simple et standard.

Référence: Achèvement de Git's Bash

aude
la source