วิธีการวนซ้ำผ่านสาขา git ทั้งหมดโดยใช้ bash script


106

ฉันจะวนซ้ำผ่านสาขาท้องถิ่นทั้งหมดในที่เก็บของฉันโดยใช้สคริปต์ทุบตีได้อย่างไร ฉันต้องทำซ้ำและตรวจสอบว่ามีความแตกต่างระหว่างสาขาและสาขาที่อยู่ห่างไกลหรือไม่ เช่น

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

ฉันต้องทำบางอย่างตามที่ระบุไว้ข้างต้น แต่ปัญหาที่ฉันเผชิญคือ $ (git branch) ทำให้ฉันมีโฟลเดอร์ภายในโฟลเดอร์ที่เก็บพร้อมกับสาขาที่มีอยู่ในที่เก็บ

นี่เป็นวิธีที่ถูกต้องในการแก้ปัญหานี้หรือไม่ หรือมีวิธีอื่นทำได้หรือไม่

ขอบคุณ



1
@pihentagy สิ่งนี้เขียนขึ้นก่อนคำถามที่เชื่อมโยง
kodaman

เช่น: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

คำตอบ:


157

คุณไม่ควรใช้git branchเมื่อเขียนสคริปต์ Git มีอินเทอร์เฟซ "ท่อประปา"ที่ได้รับการออกแบบมาอย่างชัดเจนเพื่อใช้ในการเขียนสคริปต์ (การใช้งานคำสั่ง Git ปกติในปัจจุบันและในอดีตจำนวนมาก (เพิ่มชำระเงินรวม ฯลฯ ) ใช้อินเทอร์เฟซเดียวกันนี้)

คำสั่งประปาที่คุณต้องการคือคอมไพล์สำหรับแต่ละการอ้างอิง :

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

หมายเหตุ: คุณไม่จำเป็นต้องมีremotes/คำนำหน้าในการอ้างอิงระยะไกลเว้นแต่คุณจะมีการอ้างอิงอื่น ๆ ที่ทำให้เกิดorigin/masterการจับคู่หลายตำแหน่งในเส้นทางการค้นหาชื่ออ้างอิง (โปรดดูที่“ ชื่ออ้างอิงสัญลักษณ์…” ในส่วนการระบุการแก้ไขของ git-rev-parse (1) ). ถ้าคุณกำลังพยายามที่จะหลีกเลี่ยงความคลุมเครือ explictly refs/remotes/origin/masterแล้วไปที่มีชื่อเต็มโทษ:

คุณจะได้ผลลัพธ์ดังนี้:

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

คุณสามารถท่อส่งออกนี้ในการดวลจุดโทษ

หากคุณไม่ชอบแนวคิดในการสร้างเชลล์โค้ดคุณสามารถยกเลิกความทนทานได้เล็กน้อย*และทำสิ่งนี้:

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

* ชื่ออ้างอิงควรปลอดภัยจากการแยกคำของเชลล์ (ดูgit-check-ref-format (1) ) โดยส่วนตัวแล้วฉันจะยึดติดกับเวอร์ชันเดิม (สร้างรหัสเชลล์); ฉันมั่นใจมากขึ้นว่าไม่มีอะไรที่ไม่เหมาะสมเกิดขึ้นกับมัน

เนื่องจากคุณระบุbashและรองรับอาร์เรย์คุณสามารถรักษาความปลอดภัยและยังคงหลีกเลี่ยงการสร้างความกล้าของลูปของคุณ:

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

คุณสามารถทำสิ่งที่คล้ายกันได้$@หากคุณไม่ได้ใช้เชลล์ที่รองรับอาร์เรย์ ( set --เพื่อเริ่มต้นและset -- "$@" %(refname)เพิ่มองค์ประกอบ)


40
อย่างจริงจัง. ไม่มีวิธีที่ง่ายกว่านี้ใช่ไหม
Jim Fell

4
แต่ถ้าฉันต้องการใช้หนึ่งในตัวเลือกการกรองของ git branch เช่น--mergedฉันจะต้องทำซ้ำตรรกะใน git branch หรือไม่ จะต้องมีวิธีที่ดีกว่านี้
Thayne

3
เวอร์ชัน Simplier:git for-each-ref refs/heads | cut -d/ -f3-
วิด

4
@wid: หรือเพียงแค่git for-each-ref refs/heads --format='%(refname)'
John Gietzen

5
@Thayne: คำถามนี้เป็นรุ่นเก่า แต่คน Git มี addressed ปัญหาที่สุด: for-each-refตอนนี้สนับสนุนทุกสาขาเช่นเตอร์--mergedและgit branchและgit tagในขณะนี้มีการดำเนินการจริงในแง่ของgit for-each-refตัวเองอย่างน้อยสำหรับกรณีรายการที่มีอยู่ (การสร้างสาขาและแท็กใหม่ไม่ใช่และไม่ควรเป็นส่วนหนึ่งของfor-each-ref)
torek

50

เนื่องจากgit branchทำเครื่องหมายสาขาปัจจุบันด้วยเครื่องหมายดอกจันเช่น:

$ git branch
* master
  mybranch
$ 

ดังนั้นจึง$(git branch)ขยายเป็นเช่น* master mybranchจากนั้น*ขยายไปยังรายการไฟล์ในไดเร็กทอรีปัจจุบัน

ฉันไม่เห็นตัวเลือกที่ชัดเจนสำหรับการไม่พิมพ์เครื่องหมายดอกจันตั้งแต่แรก แต่คุณสามารถตัดมันออก:

$(git branch | cut -c 3-)

4
หากคุณล้อมรอบด้วยเครื่องหมายอัญประกาศคู่คุณสามารถหยุด bash จากการขยายเครื่องหมายดอกจันได้แม้ว่าคุณจะยังคงต้องการลบออกจากผลลัพธ์ $(git branch | sed -e s/\\*//g)เป็นวิธีที่มีประสิทธิภาพมากขึ้นในการลบเครื่องหมายดอกจันจากจุดใดจะ
นิค

2
ดีฉันชอบ3-วิธีแก้ปัญหาของคุณมาก
Andrei-Niculae Petre

1
รุ่น sed ที่เรียบง่ายกว่าเล็กน้อย:$(git branch | sed 's/^..//')
jdg

5
รุ่น tr ที่เรียบง่ายกว่าเล็กน้อย:$(git branch | tr -d " *")
ccpizza

13

bash builtin mapfileถูกสร้างขึ้นเพื่อสิ่งนี้

git ทุกสาขา: git branch --all --format='%(refname:short)'

git ในพื้นที่ทั้งหมด: git branch --format='%(refname:short)'

สาขา git ระยะไกลทั้งหมด: git branch --remotes --format='%(refname:short)'

วนซ้ำผ่าน git ทุกสาขา: mapfile -t -C my_callback -c 1 < <( get_branches )

ตัวอย่าง:

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:

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

แหล่งที่มา: แสดงรายการสาขา git ในเครื่องทั้งหมดโดยไม่มีเครื่องหมายดอกจัน


5

ฉันทำซ้ำตามตัวอย่าง:

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]\+")ว่าสาขาในพื้นที่ของคุณตั้งชื่อด้วยตัวเลข az และ / หรือตัวอักษร AZ เท่านั้น


4

คำตอบที่ยอมรับนั้นถูกต้องและควรเป็นแนวทางที่ใช้จริง ๆ แต่การแก้ปัญหาด้วยการทุบตีเป็นแบบฝึกหัดที่ดีในการทำความเข้าใจว่าเปลือกหอยทำงานอย่างไร เคล็ดลับในการทำสิ่งนี้โดยใช้ bash โดยไม่ต้องดำเนินการจัดการข้อความเพิ่มเติมคือเพื่อให้แน่ใจว่าเอาต์พุตของ git branch จะไม่ถูกขยายเนื่องจากเป็นส่วนหนึ่งของคำสั่งที่เชลล์จะดำเนินการ ซึ่งจะป้องกันไม่ให้เครื่องหมายดอกจันขยายในส่วนขยายชื่อไฟล์ (ขั้นตอนที่ 8) ของการขยายเชลล์ (ดูhttp://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

ใช้ bash ในขณะที่สร้างด้วยคำสั่ง read เพื่อตัดเอาท์พุท git branch เป็นบรรทัด '*' จะถูกอ่านเป็นอักขระตามตัวอักษร ใช้คำชี้แจงกรณีเพื่อจับคู่โดยให้ความสนใจเป็นพิเศษกับรูปแบบที่ตรงกัน

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

เครื่องหมายดอกจันทั้งในโครงสร้าง bash caseและในการแทนที่พารามิเตอร์จะต้องมีการใช้เครื่องหมายแบ็กสแลชเพื่อป้องกันไม่ให้เชลล์ตีความเป็นอักขระที่ตรงกับรูปแบบ ช่องว่างจะถูกหลีกเลี่ยงด้วย (เพื่อป้องกันโทเค็น) เนื่องจากคุณจับคู่ '*' อย่างแท้จริง


4

ตัวเลือกที่ง่ายที่สุดในการจำในความคิดของฉัน:

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

เอาท์พุต:

bamboo
develop
master

อ็อพชัน -o ของ Grep (- เฉพาะการจับคู่) จำกัด เอาต์พุตให้เฉพาะส่วนที่ตรงกันของอินพุตเท่านั้น

เนื่องจากไม่มีช่องว่างหรือ * ไม่ถูกต้องในชื่อสาขา Git สิ่งนี้จะส่งคืนรายการของสาขาที่ไม่มีอักขระพิเศษ

แก้ไข: หากคุณอยู่ในสถานะ "หัวแยก"คุณจะต้องกรองรายการปัจจุบันออก:

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


3

สิ่งที่ฉันทำนำไปใช้กับคำถามของคุณ (& แรงบันดาลใจจากการกล่าวถึง ccpizza tr):

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

(ฉันใช้ในขณะที่วนซ้ำบ่อยมากในขณะที่บางสิ่งบางอย่างคุณต้องการใช้ชื่อตัวแปรปลายแหลม ["branch" เช่น] ส่วนใหญ่แล้วฉันกังวลกับการทำอะไรบางอย่างกับอินพุตแต่ละบรรทัดเท่านั้นโดยใช้ 'line' ที่นี่แทนที่จะเป็น 'branch' เป็นการพยักหน้าถึงการนำกลับมาใช้ใหม่ / ความจำของกล้ามเนื้อ / ประสิทธิภาพ)


1

ต่อจากคำตอบของ @ finn (ขอบคุณ!) ต่อไปนี้จะช่วยให้คุณทำซ้ำบนกิ่งก้านได้โดยไม่ต้องสร้างเชลล์สคริปต์แทรกแซง มีประสิทธิภาพเพียงพอตราบใดที่ไม่มีการขึ้นบรรทัดใหม่ในชื่อสาขา :)

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

while loop ทำงานใน subshell ซึ่งโดยปกติจะใช้ได้ดีเว้นแต่คุณจะตั้งค่าตัวแปรเชลล์ที่คุณต้องการเข้าถึงในเชลล์ปัจจุบัน ในกรณีนั้นคุณใช้การทดแทนกระบวนการเพื่อย้อนกลับท่อ:

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

1

หากคุณอยู่ในสถานะนี้:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

และคุณเรียกใช้รหัสนี้:

git branch -a | grep remotes/origin/*

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

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

done        

คุณจะได้รับผลลัพธ์นี้:

branch1

branch2

branch3

master

วิธีนี้ดูซับซ้อนน้อยลงสำหรับฉัน +1
Abhi

สิ่งนี้ได้ผลสำหรับฉันเช่นกัน:for b in "$(git branch)"; do git branch -D $b; done
Abhi

1

ง่าย ๆ เข้าไว้

วิธีง่ายๆในการรับชื่อสาขาในลูปโดยใช้สคริปต์ทุบตี

#!/bin/bash

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

เอาท์พุต:

master
other

1

คำตอบของ Googlian แต่ไม่ใช้สำหรับ

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

1
สิ่งนี้ใช้ไม่ได้กับชื่อสาขาที่มีการระบุชื่อเช่นเดียวกับสาขาที่มีเครื่องหมายทับอยู่ ซึ่งหมายความว่าสาขาที่สร้างโดย dependabot ซึ่งมีลักษณะคล้ายกับ "dependabot / npm_and_yarn / typescript-3.9.5" จะปรากฏเป็น "typescript-3.9.5" แทน
ecbrodie

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

ใช้คำสั่งgit plumbingซึ่งออกแบบมาสำหรับการเขียนสคริปต์ นอกจากนี้ยังเรียบง่ายและเป็นมาตรฐาน

อ้างอิง: Git's Bash เสร็จสิ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.