web-dev-qa-db-fra.com

Comment parcourir toutes les branches git en utilisant le script bash

Comment puis-je parcourir toutes les branches locales de mon référentiel en utilisant le script bash. J'ai besoin d'itérer et de 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 ci-dessus, mais le problème auquel je suis confronté est $ (branche git) me donne les dossiers à l'intérieur du dossier du référentiel ainsi que les branches présentes dans le référentiel.

Est-ce la bonne façon de résoudre ce problème? Ou existe-t-il une autre façon de procéder?

Merci

91
Arun P Johny

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 une utilisation 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 souhaitez 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 préfixe remotes/ Sur la référence distante, sauf si vous avez d'autres références qui font que Origin/master Correspond à plusieurs endroits dans le chemin de recherche de 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 explicitement toute ambiguïté, choisissez le nom de référence complet: 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 pourriez renoncer à un peu de robustesse* et faites 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 confiant que rien d'inapproprié ne peut s'y produire.

Puisque vous avez spécifié bash et qu'il prend en charge les tableaux, vous pouvez maintenir la sécurité et toujours éviter de générer les entrailles 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 un shell qui prend en charge les tableaux (set -- Pour initialiser et set -- "$@" %(refname) pour ajouter des éléments).

144
Chris Johnsen

En effet, git branch Marque la branche actuelle avec un astérisque, par exemple:

$ git branch
* master
  mybranch
$ 

donc $(git branch) se développe par exemple * master mybranch, Puis le * Se développe dans la liste des fichiers du répertoire en cours.

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-)
46
Matthew Slattery

La commande bash, mapfile, est conçue 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)'

parcourir 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

8
Andrew Miller

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;
5
Djory Krache

La réponse acceptée est correcte et devrait vraiment être l'approche utilisée, mais résoudre le problème en bash est un excellent exercice pour comprendre le fonctionnement des shells. 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 n'est 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'extension de nom de fichier (étape 8) de l'extension 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 déclaration de cas pour la faire correspondre, en accordant une attention particulière aux modèles de correspondance.

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 bash case et dans la substitution de paramètres 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 éviter la tokenisation) car vous faites littéralement correspondre '*'.

4
BitByteDog

Je suggérerais $(git branch|grep -o "[0-9A-Za-z]\+") si vos branches locales sont nommées uniquement par des chiffres, des lettres a-z et/ou A-Z

4
Xin Guo

Option la plus simple à retenir à mon avis:

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

Sortie:

bamboo
develop
master

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

Puisque ni l'espace ni * ne sont valides dans les noms de branche Git, cela retourne la liste des branches sans les caractères supplémentaires.

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

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

3
staafl

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 voudrez certainement utiliser un nom de variable pointée ["branche", par exemple], la plupart du temps je ne suis concerné que par faire quelque chose avec chaque ligne d'entrée. L'utilisation de 'ligne' ici au lieu de 'branche' est un clin d'œil à la réutilisabilité/mémoire musculaire/efficacité.)

3
Jan Kyu Peblik

S'étendant sur la réponse de @ finn (merci!), Ce qui suit vous permettra de parcourir les branches sans créer de script Shell intermédiaire. 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 Shell auxquelles vous souhaitez accéder dans le Shell actuel. Dans ce cas, vous utilisez la substitution de processus pour inverser le canal:

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

Réponse de Googlian, mais sans utiliser pour

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
0
Master Yoda
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

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

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

0
aude

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
0
Wijith Bandara

Restez simple

Le moyen simple d'obtenir le nom de la branche en boucle en utilisant le script bash.

#!/bin/bash

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

Sortie:

master
other
0
Googlian