Cara melakukan iterasi melalui semua cabang git menggunakan skrip bash

105

Bagaimana saya bisa melakukan iterasi melalui semua cabang lokal di repositori saya menggunakan skrip bash. Saya perlu mengulang dan memeriksa apakah ada perbedaan antara cabang dan beberapa cabang jarak jauh. Ex

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

Saya perlu melakukan sesuatu seperti yang diberikan di atas, tetapi masalah yang saya hadapi adalah $ (git branch) memberi saya folder di dalam folder repositori bersama dengan cabang yang ada di repositori.

Apakah ini cara yang benar untuk mengatasi masalah ini? Atau apakah ada cara lain untuk melakukannya?

Terima kasih

Arun P Johny
sumber
Kemungkinan duplikat loop for di semua cabang git dengan nama tertentu
pihentagy
1
@pihentagy Ini ditulis sebelum pertanyaan terkait.
kodaman
Misalnya: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

Jawaban:

156

Anda sebaiknya tidak menggunakan git branch saat menulis skrip. Git menyediakan antarmuka "pipa ledeng" yang secara eksplisit dirancang untuk digunakan dalam skrip (banyak implementasi saat ini dan historis dari perintah Git normal (tambahkan, checkout, gabung, dll.) Menggunakan antarmuka yang sama ini).

Perintah pemipaan yang Anda inginkan adalah git for-each-ref :

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

Catatan: Anda tidak memerlukan remotes/prefiks pada referensi jarak jauh kecuali Anda memiliki referensi lain yang menyebabkan origin/masterkecocokan beberapa tempat di jalur pencarian nama ref (lihat “Nama referensi simbolik.…” Di bagian Revisi Menentukan dari git-rev-parse (1) ). Jika Anda mencoba untuk secara eksplisit menghindari ambiguitas, kemudian pergi dengan nama ref penuh: refs/remotes/origin/master.

Anda akan mendapatkan hasil seperti ini:

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

Anda dapat menyalurkan output ini ke sh .

Jika Anda tidak menyukai ide untuk membuat kode shell, Anda dapat melepaskan sedikit ketahanan * dan melakukan ini:

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

* Nama referensi harus aman dari pemisahan kata shell (lihat git-check-ref-format (1) ). Secara pribadi saya akan tetap menggunakan versi sebelumnya (kode shell yang dihasilkan); Saya lebih yakin bahwa tidak ada yang tidak pantas dapat terjadi dengannya.

Karena Anda menentukan bash dan mendukung array, Anda dapat menjaga keamanan dan tetap menghindari menghasilkan nyali dari loop Anda:

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

Anda bisa melakukan hal serupa $@jika Anda tidak menggunakan shell yang mendukung array ( set --untuk menginisialisasi dan set -- "$@" %(refname)menambahkan elemen).

Chris Johnsen
sumber
39
Sungguh. Tidak ada cara yang lebih sederhana untuk melakukan ini?
Jim Fell
4
Tetapi bagaimana jika saya ingin menggunakan salah satu opsi pemfilteran dari cabang git, seperti --merged, apakah saya harus menduplikasi logika di cabang git? Pasti ada cara yang lebih baik untuk melakukan ini.
Thayne
3
Versi yang lebih sederhana:git for-each-ref refs/heads | cut -d/ -f3-
wid
4
@wid: Atau, sederhananya,git for-each-ref refs/heads --format='%(refname)'
John Gietzen
5
@Thayne: pertanyaan ini sudah lama, tetapi orang-orang Git akhirnya mengatasi masalah tersebut: for-each-refsekarang mendukung semua penyeleksi cabang seperti --mergeddan git branchdan git tagsekarang benar-benar diimplementasikan dengan git for-each-refsendirinya, setidaknya untuk kasus yang ada dalam daftar. (Membuat cabang dan tag baru bukan, dan seharusnya tidak menjadi, bagian dari for-each-ref.)
torek
50

Ini karena git branchmenandai cabang saat ini dengan tanda bintang, misalnya:

$ git branch
* master
  mybranch
$ 

jadi $(git branch)meluas ke misalnya * master mybranch, dan kemudian* meluas ke daftar file di direktori saat ini.

Saya tidak melihat opsi yang jelas untuk tidak mencetak tanda bintang sejak awal; tapi Anda bisa memotongnya:

$(git branch | cut -c 3-)
Matthew Slattery
sumber
4
Jika Anda mengapit tanda kutip ganda, Anda dapat menghentikan bash dari memperluas tanda bintang - meskipun Anda masih ingin menghapusnya dari keluaran. Cara yang lebih kuat untuk menghapus tanda bintang dari titik mana pun adalah $(git branch | sed -e s/\\*//g).
Nick
2
bagus, saya sangat suka 3-solusi Anda .
Andrei-Niculae Petre
1
Versi sed yang sedikit lebih sederhana:$(git branch | sed 's/^..//')
jdg
5
versi tr yang sedikit lebih sederhana:$(git branch | tr -d " *")
ccpizza
13

Bash builtin,, mapfiledibuat untuk ini

semua cabang git: git branch --all --format='%(refname:short)'

semua cabang git lokal: git branch --format='%(refname:short)'

semua cabang git jarak jauh: git branch --remotes --format='%(refname:short)'

iterasi melalui semua cabang git: mapfile -t -C my_callback -c 1 < <( get_branches )

contoh:

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 )

untuk situasi spesifik 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}"

sumber: Cantumkan semua cabang git lokal tanpa tanda bintang

Andrew Miller
sumber
5

Saya mengulang seperti itu misalnya:

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
sumber
4

Saya akan menyarankan $(git branch|grep -o "[0-9A-Za-z]\+")jika cabang lokal Anda diberi nama hanya dengan angka, az, dan / atau huruf AZ

Xin Guo
sumber
4

Jawaban yang diterima benar dan seharusnya menjadi pendekatan yang digunakan, tetapi menyelesaikan masalah di bash adalah latihan yang bagus untuk memahami cara kerja cangkang. Trik untuk melakukan ini menggunakan bash tanpa melakukan manipulasi teks tambahan, adalah memastikan keluaran dari git branch tidak pernah diperluas sebagai bagian dari perintah yang akan dijalankan oleh shell. Hal ini mencegah asterisk berkembang dalam perluasan nama file (langkah 8) dari ekspansi shell (lihat http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Gunakan bash saat membuat dengan perintah baca untuk memotong keluaran git branch menjadi beberapa baris. '*' Akan dibaca sebagai karakter literal. Gunakan pernyataan kasus untuk mencocokkannya, berikan perhatian khusus pada pola pencocokan.

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

Tanda bintang dalam konstruksi bash case dan dalam substitusi parameter harus di-escape dengan garis miring terbalik untuk mencegah shell menafsirkannya sebagai karakter yang cocok dengan pola. Spasi juga dikosongkan (untuk mencegah tokenisasi) karena Anda benar-benar mencocokkan '*'.

BitByteDog
sumber
4

Pilihan termudah untuk diingat menurut saya:

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

Keluaran:

bamboo
develop
master

Opsi -o dari Grep (--only-matching) membatasi keluaran hanya ke bagian masukan yang cocok.

Karena tidak ada spasi maupun * yang valid dalam nama cabang Git, ini mengembalikan daftar cabang tanpa karakter tambahan.

Edit: Jika Anda dalam keadaan 'kepala terpisah' , Anda harus memfilter entri saat ini:

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

staafl
sumber
3

Apa yang akhirnya saya lakukan, diterapkan pada pertanyaan Anda (& terinspirasi oleh penyebutan ccpizza tr):

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

(Saya sering menggunakan while loop. Sementara untuk hal-hal tertentu Anda pasti ingin menggunakan nama variabel runcing ["cabang", misalnya], sebagian besar waktu saya hanya mementingkan melakukan sesuatu dengan setiap baris masukan. Menggunakan 'garis' di sini bukan 'cabang' adalah anggukan untuk digunakan kembali / memori otot / efisiensi.)

Jan Kyu Peblik
sumber
1

Memperluas dari jawaban @ finn (terima kasih!), Berikut ini akan memungkinkan Anda beralih ke cabang tanpa membuat skrip shell yang mengganggu. Ini cukup kuat, selama tidak ada baris baru di nama cabang :)

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

Loop sementara berjalan dalam subkulit, yang biasanya baik-baik saja kecuali Anda menyetel variabel shell yang ingin Anda akses di shell saat ini. Dalam hal ini Anda menggunakan substitusi proses untuk membalikkan pipa:

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

Jika Anda berada di negara bagian ini:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

Dan Anda menjalankan kode ini:

git branch -a | grep remotes/origin/*

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

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

done        

Anda akan mendapatkan hasil ini:

branch1

branch2

branch3

master
Wijith Bandara
sumber
Ini solusi yang terlihat kurang kompleks bagi saya +1
Abhi
Ini bekerja untuk saya juga:for b in "$(git branch)"; do git branch -D $b; done
Abhi
1

Tetap sederhana

Cara sederhana untuk mendapatkan nama cabang dalam loop menggunakan skrip bash.

#!/bin/bash

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

Keluaran:

master
other
Googlian
sumber
1

Jawaban Googlian, tapi tanpa menggunakan for

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
Tuan Yoda
sumber
1
Ini tidak berfungsi untuk nama cabang dengan spasi nama, seperti pada cabang yang memiliki garis miring di dalamnya. Ini berarti bahwa cabang yang dibuat oleh dependabot, yang terlihat seperti "dependabot / npm_and_yarn / typescript-3.9.5", akan muncul sebagai "typescript-3.9.5".
ecbrodie
1
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

Ini menggunakan pipa git perintah , yang dirancang untuk pembuatan skrip. Ini juga sederhana dan standar.

Referensi: Penyelesaian Git's Bash

aude
sumber