如何使用 bash 脚本遍历所有 git 分支
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3846380/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to iterate through all git branches using bash script
提问by Arun P Johny
How can I iterate through all the local branches in my repository using bash script. I need to iterate and check is there any difference between the branch and some remote branches. Ex
如何使用 bash 脚本遍历存储库中的所有本地分支。我需要迭代并检查分支和一些远程分支之间是否有任何区别。前任
for branch in $(git branch);
do
git log --oneline $branch ^remotes/origin/master;
done
I need to do something like given above, but the issue I'm facing is $(git branch) gives me the folders inside the repository folder along with the branches present in the repository.
我需要执行上面给出的操作,但我面临的问题是 $(git branch) 为我提供了存储库文件夹中的文件夹以及存储库中存在的分支。
Is this the correct way to solve this issue? Or is there another way to do it?
这是解决这个问题的正确方法吗?或者有其他方法可以做到吗?
Thank you
谢谢
回答by Chris Johnsen
You should not use git branchwhen writing scripts. Git provides a “plumbing” interfacethat is explicitly designed for use in scripting (many current and historical implementations of normal Git commands (add, checkout, merge, etc.) use this same interface).
编写脚本时不应使用git branch。Git 提供了一个明确设计用于脚本编写的“管道”接口(普通 Git 命令(添加、签出、合并等)的许多当前和历史实现都使用相同的接口)。
The plumbing command you want is git for-each-ref:
你想要的管道命令是git for-each-ref:
git for-each-ref --shell \
--format='git log --oneline %(refname) ^origin/master' \
refs/heads/
Note: You do not need the remotes/
prefix on the remote ref unless you have other refs that cause origin/master
to match multiple places in the ref name search path (see “A symbolic ref name. …” in the Specifying Revisions section of git-rev-parse(1)). If you are trying to explictly avoid ambiguity, then go with the full ref name: refs/remotes/origin/master
.
注意:remotes/
除非您有其他引用导致origin/master
匹配引用名称搜索路径中的多个位置,否则您不需要远程引用的前缀(请参阅git-rev-parse 的指定修订部分中的“符号引用名称。...” (1)). 如果您试图明确避免歧义,请使用完整的参考名称:refs/remotes/origin/master
。
You will get output like this:
你会得到这样的输出:
git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master
You can pipe this output into sh.
您可以将此输出通过管道传输到sh。
If you do not like the idea of generating the shell code, you could give up a bit of robustness*and do this:
如果您不喜欢生成 shell 代码的想法,您可以放弃一些健壮性*并执行以下操作:
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
git log --oneline "$branch" ^origin/master
done
* Ref names should be safe from the shell's word splitting (see git-check-ref-format(1)). Personally I would stick with the former version (generated shell code); I am more confident that nothing inappropriate can happen with it.
* Ref 名称应该不受 shell 的分词影响(请参阅git-check-ref-format(1))。我个人会坚持使用以前的版本(生成的 shell 代码);我更有信心,它不会发生任何不恰当的事情。
Since you specified bashand it supports arrays, you could maintain safety and still avoid generating the guts of your loop:
由于您指定了bash并且它支持数组,因此您可以保持安全并仍然避免生成循环的内脏:
branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
# …
done
You could do something similar with $@
if you are not using a shell that supports arrays (set --
to initialize and set -- "$@" %(refname)
to add elements).
$@
如果您不使用支持数组(set --
初始化和set -- "$@" %(refname)
添加元素)的外壳,您可以执行类似的操作。
回答by Matthew Slattery
This is because git branch
marks the current branch with an asterisk, e.g.:
这是因为git branch
用星号标记当前分支,例如:
$ git branch
* master
mybranch
$
so $(git branch)
expands to e.g. * master mybranch
, and then the *
expands to the list of files in the current directory.
so$(git branch)
扩展为 eg * master mybranch
,然后*
扩展为当前目录中的文件列表。
I don't see an obvious option for not printing the asterisk in the first place; but you could chop it off:
我没有看到首先不打印星号的明显选项;但你可以把它砍掉:
$(git branch | cut -c 3-)
回答by Andrew Miller
The bash builtin, mapfile
, is built for this
内置 bashmapfile
就是为此而构建的
all git branches: git branch --all --format='%(refname:short)'
所有 git 分支: git branch --all --format='%(refname:short)'
all local git branches: git branch --format='%(refname:short)'
所有本地 git 分支: git branch --format='%(refname:short)'
all remote git branches: git branch --remotes --format='%(refname:short)'
所有远程 git 分支: git branch --remotes --format='%(refname:short)'
iterate through all git branches: mapfile -t -C my_callback -c 1 < <( get_branches )
遍历所有 git 分支: mapfile -t -C my_callback -c 1 < <( get_branches )
example:
例子:
my_callback () {
INDEX=
BRANCH=
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 )
for the OP's specific situation:
对于 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}"
回答by Djory Krache
I iterate as it for example :
我迭代它例如:
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;
回答by Xin Guo
I would suggest
$(git branch|grep -o "[0-9A-Za-z]\+")
if your local branches are named by digits, a-z, and/or A-Z letters only
我建议
$(git branch|grep -o "[0-9A-Za-z]\+")
您当地的分支机构是否仅以数字、az 和/或 AZ 字母命名
回答by BitByteDog
The accepted answer is correct and really should be the approach used, but solving the problem in bash is a great exercise in understanding how shells work. The trick to doing this using bash without performing additional text manipulation, is to ensure the output of git branch never gets expanded as part of a command to be executed by the shell. This prevents the asterisk from ever being expanding in the file name expansion (step 8) of shell expansion (see http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)
接受的答案是正确的,确实应该是所使用的方法,但是在 bash 中解决问题是理解 shell 如何工作的一个很好的练习。在不执行额外文本操作的情况下使用 bash 执行此操作的技巧是确保 git branch 的输出永远不会被扩展为要由 shell 执行的命令的一部分。这可以防止星号在 shell 扩展的文件名扩展(第 8 步)中扩展(参见http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)
Use the bash whileconstruct with a read command to chop the git branch output into lines. The '*' will be read in as a literal character. Use a case statement to match it, paying special attention to the matching patterns.
使用带有读取命令的 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
The asterisks in both the bash caseconstruct and in the parameter substitution need to be escaped with backslashes to prevent the shell interpreting them as pattern matching characters. The spaces are also escaped (to prevent tokenization) because you are literally matching '* '.
bash case构造和参数替换中的星号都需要用反斜杠转义,以防止 shell 将它们解释为模式匹配字符。空格也会被转义(以防止标记化),因为您实际上是在匹配 '* '。
回答by staafl
Easiest option to remember in my opinion:
我认为最容易记住的选项:
git branch | grep "[^* ]+" -Eo
git branch | grep "[^* ]+" -Eo
Output:
输出:
bamboo
develop
master
Grep's -o option (--only-matching) restricts the output to only the matching parts of the input.
Grep 的 -o 选项(--only-matching)将输出限制为仅输入的匹配部分。
Since neither space nor * are valid in Git branch names, this returns the list of branches without the extra characters.
由于空格和 * 在 Git 分支名称中均无效,因此返回不带额外字符的分支列表。
Edit: If you're in 'detached head' state, you'll need to filter out the current entry:
编辑:如果您处于“分离头”状态,则需要过滤掉当前条目:
git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE
git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE
回答by Jan Kyu Peblik
What I ended up doing, applied to your question (& inspired by ccpizza mentioning tr
):
我最终做了什么,适用于您的问题(并受到 ccpizza 提及的启发tr
):
git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done
git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done
(I use while loops a lot. While for particular things you'd definitely want to use a pointed variable name ["branch", for example], most of the time I am only concerned with doing something with each lineof input. Using 'line' here instead of 'branch' is a nod to reusability/muscle memory/efficiency.)
(我经常使用 while 循环。虽然对于特定的事情,你肯定想使用一个带尖的变量名 [“branch”,例如],但大多数时候我只关心对每一行输入做一些事情。使用这里的“line”而不是“branch”是对可重用性/肌肉记忆/效率的认可。)
回答by Chris Cogdon
Extending on from @finn's answer (thank you!), the following will let you iterate over the branches without creating an intervening shell script. It's robust enough, as long as there's no newlines in the branch name :)
延续@finn 的回答(谢谢!),以下内容将让您在不创建干预 shell 脚本的情况下遍历分支。只要分支名称中没有换行符,它就足够强大:)
git for-each-ref --format='%(refname)' refs/heads | while read x ; do echo === $x === ; done
The while loop runs in a subshell, which is usually fine unless you're setting shell variables that you want to access in the current shell. In that case you use process substitution to reverse the pipe:
while 循环在子shell 中运行,这通常很好,除非您设置要在当前shell 中访问的shell 变量。在这种情况下,您可以使用进程替换来反转管道:
while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )
回答by Wijith Bandara
If you're at this state:
如果您处于这种状态:
git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/branch1
remotes/origin/branch2
remotes/origin/branch3
remotes/origin/master
And you run this code:
你运行这个代码:
git branch -a | grep remotes/origin/*
for BRANCH in `git branch -a | grep remotes/origin/*` ;
do
A="$(cut -d'/' -f3 <<<"$BRANCH")"
echo $A
done
You'll get this result:
你会得到这样的结果:
branch1
branch2
branch3
master