[英]How to iterate through all git branches using bash script
如何使用 bash 腳本遍歷存儲庫中的所有本地分支。 我需要迭代並檢查分支和一些遠程分支之間是否有任何區別。 前任
for branch in $(git branch);
do
git log --oneline $branch ^remotes/origin/master;
done
我需要做上面給出的事情,但我面臨的問題是 $(git branch) 給了我存儲庫文件夾中的文件夾以及存儲庫中存在的分支。
這是解決此問題的正確方法嗎? 還是有其他方法可以做到這一點?
謝謝
編寫腳本時不應使用git branch 。 Git 提供了一個明確設計用於腳本編寫的“管道”接口(普通 Git 命令(添加、簽出、合並等)的許多當前和歷史實現都使用相同的接口)。
你想要的管道命令是git for-each-ref :
git for-each-ref --shell \
--format='git log --oneline %(refname) ^origin/master' \
refs/heads/
注意:您不需要remotes/
前綴遙控器上的裁判,除非你有其他裁判這項事業origin/master
,以配合在裁判的名字搜索路徑的多個地方(見“A符號裁判的名字......。” 的指定修訂部分git-rev-parse(1) )。 如果您試圖明確避免歧義,請使用完整的參考名稱: 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
您可以將此輸出通過管道傳輸到sh 。
如果您不喜歡生成 shell 代碼的想法,您可以放棄一些健壯性*並執行以下操作:
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
git log --oneline "$branch" ^origin/master
done
* Ref 名稱應該不受 shell 的分詞影響(請參閱git-check-ref-format(1) )。 我個人會堅持使用以前的版本(生成的 shell 代碼); 我更有信心,它不會發生任何不恰當的事情。
由於您指定了bash並且它支持數組,因此您可以保持安全並仍然避免生成循環的內臟:
branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
# …
done
如果您不使用支持數組的外殼程序,您可以對$@
執行類似的操作( set --
初始化並set -- "$@" %(refname)
添加元素)。
這是因為git branch
用星號標記當前分支,例如:
$ git branch
* master
mybranch
$
所以$(git branch)
擴展到例如* master mybranch
,然后*
擴展到當前目錄中的文件列表。
我沒有看到首先不打印星號的明顯選項; 但你可以把它砍掉:
$(git branch | cut -c 3-)
bash 內置函數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}"
我迭代它例如:
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;
我建議$(git branch|grep -o "[0-9A-Za-z]\\+")
如果您的本地分支僅以數字、az 和/或 AZ 字母命名
接受的答案是正確的,確實應該是所使用的方法,但是在 bash 中解決問題是理解 shell 如何工作的一個很好的練習。 在不執行額外文本操作的情況下使用 bash 執行此操作的技巧是確保 git branch 的輸出永遠不會被擴展為要由 shell 執行的命令的一部分。 這可以防止星號在 shell 擴展的文件名擴展(第 8 步)中擴展(參見http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )
使用帶有讀取命令的 bash while構造將 git 分支輸出分成幾行。 '*' 將作為文字字符讀入。 使用 case 語句進行匹配,特別注意匹配模式。
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構造和參數替換中的星號都需要用反斜杠轉義,以防止 shell 將它們解釋為模式匹配字符。 空格也會被轉義(以防止標記化),因為您實際上是在匹配 '* '。
我認為最容易記住的選項:
git branch | grep "[^* ]+" -Eo
輸出:
bamboo
develop
master
Grep 的 -o 選項(--only-matching)將輸出限制為僅輸入的匹配部分。
由於空格和 * 在 Git 分支名稱中均無效,因此返回不帶額外字符的分支列表。
編輯:如果您處於“分離頭”狀態,則需要過濾掉當前條目:
git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE
我最終做了什么,適用於您的問題(並受到 ccpizza 提及tr
啟發):
git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done
(我經常使用 while 循環。雖然對於特定的事情,你肯定想使用一個帶尖的變量名 [“branch”,例如],但大多數時候我只關心對每一行輸入做一些事情。使用這里的“line”而不是“branch”是對可重用性/肌肉記憶/效率的認可。)
如果您處於這種狀態:
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
保持簡單
使用 bash 腳本在循環中獲取分支名稱的簡單方法。
#!/bin/bash
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
echo "${branch/'refs/heads/'/''}"
done
輸出:
master
other
谷歌的答案,但沒有用於
git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
...
done
這使用了專為編寫腳本而設計的git 管道命令。 它也很簡單和標准。
參考: Git 的 Bash 完成
延續@finn 的回答(謝謝!),以下內容將讓您在不創建干預 shell 腳本的情況下遍歷分支。 只要分支名稱中沒有換行符,它就足夠強大:)
git for-each-ref --format='%(refname)' refs/heads | while read x ; do echo === $x === ; done
while 循環在子shell 中運行,這通常很好,除非您設置要在當前shell 中訪問的shell 變量。 在這種情況下,您可以使用進程替換來反轉管道:
while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )
簡單地迭代本地分支名稱的正確方法是使用for-each-ref
over refs/heads/
。 例如:
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
echo branch="${branch}"
done
這適用於IFS
的標准/默認值,因為 git 中的分支名稱中的空格是非法的。
git show-ref --heads
git show-ref
- 列出本地存儲庫中的引用
--heads
只列出heads
(而不是標簽)這將列出頭部類似
682e47c01dc8d0f4e4102f183190a48aaf34a3f0 refs/heads/main
....
所以如果你只對名字感興趣,你可以使用像sed
這樣的東西來獲得你想要的輸出
git show-ref --heads | sed 's/.*refs\/heads\///'
有了這個輸出,你可以輕松地遍歷它,比如使用 bash 循環、xargs、任何漂浮在你船上的東西
for SHA in $(git show-ref --heads | awk '{ print $1 }'); do
echo "magic! $SHA"
done
git show-ref --heads
按照上面的git show-ref --heads
獲取分支awk '{ print $1 }'
獲取 SHAecho "magic! $SHA"
<- 這是你施展魔法的地方#/bin/bash
for branch in $(git branch -r);
do
echo $branch
done
當然,理論上應該使用 Git 在編寫腳本時確實具有的特殊接口。 但通常你想要一些更簡單的東西——對於單行來說很方便。 有些東西不會促使你記住像 git for-each-ref --format ... refs ... amen 這樣的東西。 最后還是 UNIX。 然后它是這樣的:
git branch
將星號放在分支名稱之前。 這意味着我們似乎總是對最后一列感興趣。結果:
git branch | awk '{print $NF}'
這是因為awk
有一個區間變量NF
,它是最后一個字段的編號。 用$
前綴這個數字將產生該字段的內容,這正是這里需要的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.