Pregunta Cómo iterar a través de todas las ramas de git usando script bash


¿Cómo puedo iterar a través de todas las sucursales locales en mi repositorio usando script bash? Necesito iterar y verificar si hay alguna diferencia entre la rama y algunas ramas remotas. Ex

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

Necesito hacer algo como lo anterior, pero el problema que estoy enfrentando es $ (git branch) me da las carpetas dentro de la carpeta del repositorio junto con las ramas presentes en el repositorio.

¿Es esta la forma correcta de resolver este problema? ¿O hay otra forma de hacerlo?

Gracias


75
2017-10-02 15:33


origen


Respuestas:


No deberías usar git branch al escribir guiones. Git proporciona una Interfaz de "fontanería" que está diseñado explícitamente para su uso en scripts (muchas implementaciones actuales e históricas de los comandos normales de Git (agregar, extraer, fusionar, etc.) utilizan esta misma interfaz).

El comando de plomería que quieres es git for-each-ref:

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

Nota: No necesita el remotes/ prefijo en la referencia remota a menos que tenga otras referencias que causan origin/master para hacer coincidir múltiples lugares en la ruta de búsqueda del nombre de la referencia (ver "Un nombre de referencia simbólico ..." en la sección Especificación de revisiones de git-rev-parse (1)) Si intenta evitar explícitamente la ambigüedad, entonces vaya con el nombre completo de la referencia: refs/remotes/origin/master.

Obtendrás resultados como este:

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

Puede canalizar esta salida a sh.

Si no te gusta la idea de generar el código de shell, podrías renunciar a un poco de solidez* y haz esto:

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

* Los nombres de referencia deberían estar a salvo de la división de palabras del shell (ver git-check-ref-format (1)) Personalmente, me quedaría con la versión anterior (código de shell generado); Estoy más seguro de que nada inapropiado puede suceder con eso.

Como ha especificado intento y admite matrices, puede mantener la seguridad y aún así evitar generar las agallas de su ciclo:

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

Podrías hacer algo similar con $@ si no está utilizando un shell que admita matrices (set -- para inicializar y set -- "$@" %(refname) para agregar elementos).


123
2017-10-02 21:24



Esto es porque git branch marca la rama actual con un asterisco, por ejemplo:

$ git branch
* master
  mybranch
$ 

asi que $(git branch) se expande a, p. * master mybranch, y luego el * se expande a la lista de archivos en el directorio actual.

No veo una opción obvia para no imprimir el asterisco en primer lugar; pero podrías cortarlo:

$(git branch | cut -c 3-)

39
2017-10-02 15:49



Yo sugeriría $(git branch|grep -o "[0-9A-Za-z]\+") si sus ramas locales son nombradas por dígitos, sólo letras a-z o A-Z


4
2017-07-20 03:43



La respuesta aceptada es correcta y realmente debería ser el enfoque utilizado, pero resolver el problema en bash es un gran ejercicio para comprender cómo funcionan las conchas. El truco para hacer esto usando bash sin realizar una manipulación adicional de texto, es asegurar que la salida de la rama git nunca se expanda como parte de un comando para ser ejecutado por el shell. Esto evita que el asterisco se expanda en la expansión del nombre de archivo (paso 8) de la expansión del shell (ver http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)

Usa el bash mientras construir con un comando de lectura para cortar la salida de la rama git en líneas. El '*' se leerá como un carácter literal. Use una declaración de caso para unirla, prestando especial atención a los patrones coincidentes.

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

Los asteriscos en ambos bash caso constructo y en la sustitución de parámetros debe escaparse con barras invertidas para evitar que el intérprete las interprete como caracteres de coincidencia de patrones. Los espacios también se escapan (para evitar la tokenización) porque está literalmente haciendo coincidir '*'.


4
2018-01-30 08:32



Lo repito como por ejemplo:

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
2018-03-21 11:40



La opción más fácil de recordar en mi opinión:

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

Salida:

bamboo
develop
master

La opción -o de Grep (--only-matching) restringe la salida a solo las partes coincidentes de la entrada.

Como ni el espacio ni * son válidos en los nombres de las ramas de Git, esto devuelve la lista de ramas sin los caracteres adicionales.

Editar: si estás en estado 'cabeza separada', deberás filtrar la entrada actual:

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


1
2017-08-01 14:00



Ampliando desde la respuesta de @ finn (¡gracias!), Lo siguiente le permitirá iterar sobre las ramas sin crear un guión de shell intermedio. Es lo suficientemente robusto, siempre y cuando no haya nuevas líneas en el nombre de la sucursal :)

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

El ciclo while se ejecuta en una subshell, que generalmente está bien, a menos que esté configurando las variables de shell a las que desea acceder en el shell actual. En ese caso, usa la sustitución del proceso para invertir la tubería:

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

1
2017-10-23 22:31



Lo que terminé haciendo, aplicado a tu pregunta (e inspirado por ccpizza mencionando tr)

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

(Yo uso mientras bucles mucho. Mientras que para cosas particulares definitivamente querrías usar un nombre de variable puntiagudo ["rama", por ejemplo], la mayoría de las veces solo me preocupa hacer algo con cada uno línea de entrada. El uso de 'línea' aquí en lugar de 'rama' es un guiño a la reutilización / memoria muscular / eficiencia.)


1
2018-01-30 18:36