Bash betiğini kullanarak tüm git dalları arasında nasıl yineleme yapılır


105

Bash komut dosyasını kullanarak depomdaki tüm yerel şubeleri nasıl yineleyebilirim. Yinelemem ve kontrol etmem gerekiyor, şube ile bazı uzak şubeler arasında herhangi bir fark var mı? Eski

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

Yukarıda verilen gibi bir şey yapmam gerekiyor, ancak karşılaştığım sorun $ (git branch) bana depoda bulunan dallarla birlikte depo klasörünün içindeki klasörleri veriyor.

Bu sorunu çözmenin doğru yolu bu mu? Yoksa bunu yapmanın başka bir yolu var mı?

teşekkür ederim



1
@pihentagy Bu, bağlantılı sorudan önce yazılmıştır.
kodaman

Örneğin: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

Yanıtlar:


157

Betik yazarken git dalını kullanmamalısınız . Git bir "sıhhi tesisat" arayüzü sağlar komut dosyası oluşturmada kullanılmak üzere açıkça tasarlanmış (normal Git komutlarının (ekleme, teslim alma, birleştirme, vb.) Mevcut ve geçmiş uygulamalarının çoğu aynı arabirimi kullanır).

İstediğiniz sıhhi tesisat komutu her ref için git :

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

Not: ref adı arama yolunda birden çok yerle eşleşmesine remotes/neden origin/masterolan başka referanslarınız yoksa uzak referans üzerinde öneke ihtiyacınız yoktur (git-rev-ayrıştırmanın Revizyonları Belirtme bölümündeki "Bir sembolik referans adı. ..." konusuna bakın) (1) ). Eğer explictly karışıklıkları önlemek çalışıyorsanız, o zaman tam ref adıyla gidin: refs/remotes/origin/master.

Şu şekilde çıktı alacaksınız:

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

Bu çıktıyı sh'ye aktarabilirsiniz .

Kabuk kodunu oluşturma fikrinden hoşlanmıyorsanız, biraz sağlamlıktan * vazgeçebilir ve bunu yapabilirsiniz:

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

* Referans isimleri, kabuğun kelime bölünmesine karşı güvenli olmalıdır (bkz. Git-check-ref-format (1) ). Şahsen ben eski sürüme bağlı kalırım (oluşturulan kabuk kodu); Bununla uygunsuz hiçbir şeyin olmayacağından daha eminim.

Bash'i belirttiğinizden ve dizileri desteklediğinden, güvenliği koruyabilir ve yine de döngünüzün özünü oluşturmaktan kaçınabilirsiniz:

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

$@Dizileri destekleyen bir kabuk kullanmıyorsanız ( öğeleri set --başlatmak ve set -- "$@" %(refname)eklemek için) buna benzer bir şey yapabilirsiniz .


40
Ciddi anlamda. Bunu yapmanın daha basit bir yolu yok mu?
Jim Fell

4
Ama ya git dalının filtreleme seçeneklerinden birini kullanmak istersem --merged, mantığı git dalında çoğaltmam gerekir mi? Bunu yapmanın daha iyi bir yolu olmalı.
Thayne

3
Simplier sürümü:git for-each-ref refs/heads | cut -d/ -f3-
WID

4
@wid: Ya da basitçegit for-each-ref refs/heads --format='%(refname)'
John Gietzen

5
@Thayne: Bu soru eski, ancak Git milleti nihayet sorunu ele aldı: for-each-refşimdi --mergedve gibi tüm şube seçicilerini destekliyor git branchve git tagşimdi git for-each-ref, en azından listedeki vakalar için aslında kendi açısından uygulanıyor . (Yeni dallar ve etiketler oluşturmak, bunun parçası değildir ve olmamalıdır for-each-ref.)
torek

49

Bunun nedeni git branch, geçerli dalı yıldız işaretiyle işaretlemesidir, örneğin:

$ git branch
* master
  mybranch
$ 

bu yüzden $(git branch)örneğin olarak genişler * master mybranchve ardından *geçerli dizindeki dosyaların listesine genişler.

İlk başta yıldız işaretini yazdırmamak için bariz bir seçenek görmüyorum; ama kesebilirsin:

$(git branch | cut -c 3-)

4
Çift tırnak içine alırsanız, bash'ın yıldız işaretini genişletmesini durdurabilirsiniz - ancak yine de çıktıdan kaldırmak isteyeceksiniz. Herhangi bir noktadan yıldız işaretini kaldırmanın daha sağlam bir yolu bu olacaktır $(git branch | sed -e s/\\*//g).
Nick

2
güzel, 3-çözümünü gerçekten beğendim .
Andrei-Niculae Petre

1
Biraz daha basit sed versiyonu:$(git branch | sed 's/^..//')
jdg

5
biraz daha basit tr versiyonu:$(git branch | tr -d " *")
ccpizza

13

Bash yerleşik, mapfilebunun için inşa edilmiştir

tüm git dalları: git branch --all --format='%(refname:short)'

tüm yerel git dalları: git branch --format='%(refname:short)'

tüm uzak git dalları: git branch --remotes --format='%(refname:short)'

tüm git dallarında yineleyin: mapfile -t -C my_callback -c 1 < <( get_branches )

misal:

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 )

OP'nin özel durumu için:

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

kaynak: Yıldız işareti olmadan tüm yerel git dallarını listeleyin


5

Örneğin, yineliyorum:

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

$(git branch|grep -o "[0-9A-Za-z]\+")Yerel şubelerinizin yalnızca rakamlarla, az ve / veya AZ harfleriyle adlandırılıp adlandırılmadığını öneririm


4

Kabul edilen cevap doğrudur ve gerçekten kullanılan yaklaşım olmalıdır, ancak problemi bash'ta çözmek, kabukların nasıl çalıştığını anlamak için harika bir egzersizdir. Bunu ek metin işleme yapmadan bash kullanarak yapmanın püf noktası, git dalının çıktısının kabuk tarafından yürütülecek bir komutun parçası olarak asla genişletilmemesini sağlamaktır. Bu, yıldız işaretinin kabuk genişletmenin dosya adı genişletmesinde (adım 8) genişlemesini engeller (bkz. Http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Git dal çıktısını satırlara ayırmak için bir okuma komutuyla inşa ederken bash kullanın . '*' Harfi harfine bir karakter olarak okunacaktır. Eşleşen modellere özellikle dikkat ederek, onu eşleştirmek için bir durum ifadesi kullanın.

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

Hem bash durum yapısındaki hem de parametre ikamesindeki yıldız işaretlerinin , kabuğun bunları desen eşleşen karakterler olarak yorumlamasını önlemek için ters eğik çizgilerle önlenmesi gerekir. Boşluklar da (belirteç oluşturmayı önlemek için) kaçtı çünkü kelimenin tam anlamıyla '*' ile eşleşiyorsunuz.


4

Bence hatırlanması gereken en kolay seçenek:

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

Çıktı:

bamboo
develop
master

Grep'in -o seçeneği (--only-eşleşme), çıktıyı yalnızca girdinin eşleşen parçalarıyla sınırlar.

Git dal adlarında ne boşluk ne de * geçerli olmadığından, bu ekstra karakterler olmadan dalların listesini döndürür.

Düzenleme: 'Ayrılmış kafa' durumundaysanız , mevcut girişi filtrelemeniz gerekir:

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


3

Yaptığım şey, sorunuza uygulandı (ve ccpizza'dan bahseden esinlenerek tr):

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

(I döngüleri çok kullanıyorum. Belirli şeyler için kesinlikle sivri uçlu bir değişken adı [örneğin "dal" kullanmak isteseniz de, çoğu zaman sadece her girdi satırıyla bir şeyler yapmakla ilgileniyorum . Buradaki 'dal' yerine 'çizgi', yeniden kullanılabilirlik / kas hafızası / verimliliği için bir selamdır.)


1

@ Finn'in cevabından itibaren (teşekkür ederim!), Aşağıdakiler, araya giren bir kabuk betiği oluşturmadan dallar üzerinde yineleme yapmanıza izin verecektir. Şube adında satırsonu olmadığı sürece yeterince sağlamdır :)

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

While döngüsü bir alt kabukta çalışır ve bu, mevcut kabukta erişmek istediğiniz kabuk değişkenlerini ayarlamadığınız sürece genellikle iyidir. Bu durumda boruyu tersine çevirmek için işlem ikamesi kullanırsınız:

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

1

Bu durumdaysanız:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

Ve bu kodu çalıştırırsınız:

git branch -a | grep remotes/origin/*

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

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

done        

Bu sonucu alacaksınız:

branch1

branch2

branch3

master

Bu bana daha az karmaşık bir çözüm gibi görünüyor +1
Abhi

Bu benim için de çalıştı:for b in "$(git branch)"; do git branch -D $b; done
Abhi

1

Basit tutun

Bash betiğini kullanarak döngüde şube adını almanın basit yolu.

#!/bin/bash

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

Çıktı:

master
other

1

Googlian cevabı, ancak kullanmadan için

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/

1
Bu, bölü çizgisi olan dallarda olduğu gibi, ad alanlı dal adlarında çalışmaz. Bu, "bağımlıabot / npm_and_yarn / typescript-3.9.5" gibi bir şeye benzeyen bağımlıabot tarafından oluşturulan dalların, bunun yerine "typabot-3.9.5" olarak görüneceği anlamına gelir.
ecbrodie

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

Bu git sıhhi tesisat kullanıyor komut dosyası oluşturmak için tasarlanmış komutlarını . Aynı zamanda basit ve standarttır.

Referans: Git'in Bash'i tamamlama

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.