如何使用 bash 脚本遍历所有 git 分支

如何使用 bash 脚本迭代存储库中的所有本地分支。 我需要迭代并检查分支和一些远程分支之间是否有区别。 前女友

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

我需要执行上面给出的操作,但是我面临的问题是 $(git 分支)给出了存储库文件夹中的文件夹以及存储库中存在的分支。

这是解决这个问题的正确方法吗? 还是有其他的方法?

谢谢你

56269 次浏览

这是因为 git branch用星号标记当前分支,例如:

$ git branch
* master
mybranch
$

因此,$(git branch)展开为例如 * master mybranch,然后 *展开为工作目录中的文件列表。

我不认为一开始就不打印星号是一个明显的选择,但是你可以把它剪掉:

$(git branch | cut -c 3-)

在编写脚本时不应该使用 Git Branch。Git 提供了一个显式设计用于脚本的 “管道”界面(许多当前和历史上的普通 Git 命令实现(add、 checkout、 merge 等)都使用这个接口)。

您需要的管道命令是 Git for-each-ref:

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

注意: 您不需要在远程引用上使用 remotes/前缀,除非您有其他引用,导致 origin/master在引用名称搜索路径中匹配多个位置(参见“符号引用名称”。... 」)。如果您试图显式地避免歧义,那么使用完整的引用名称: 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

您可以将此输出通过管道传输到

如果您不喜欢生成 shell 代码的想法,您可以放弃一些健壮性 *并执行以下操作:

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

* 引用名称应该可以避免 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

如果不使用支持数组的 shell (set --用于初始化,set -- "$@" %(refname)用于添加元素) ,那么可以对 $@进行类似的操作。

我建议 如果本地分支仅由数字、 A-Z 和/或 A-Z 字母命名,则为 $(git branch|grep -o "[0-9A-Za-z]\+")

公认的答案是正确的,确实应该使用这种方法,但是在 bash 中解决问题是理解 shell 如何工作的一个很好的练习。在不执行额外文本操作的情况下使用 bash 完成此操作的诀窍是确保 git 分支的输出永远不会作为要由 shell 执行的命令的一部分展开。这可以防止星号在 shell 扩展的文件名扩展(步骤8)中扩展(参见 http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)

使用 bash 同时结构和 read 命令将 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 案件结构和参数替换中的星号都需要用反斜杠来转义,以防 shell 将它们解释为模式匹配字符。这些空格也进行了转义(以防止标记化) ,因为您正在逐字地匹配“ *”。

例如,我重复它:

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 "[^* ]+" -Eo

产出:

bamboo
develop
master

Grep 的-o 选项(—— only-match)将输出限制为仅输入的匹配部分。

由于空格和 * 在 Git 分支名中都是无效的,因此返回的分支列表中没有额外的字符。

编辑: 如果你在 分头状态,你需要过滤掉当前条目:

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

从@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 )

我最后做了什么,用来回答你的问题(并受到提到 tr的 ccizza 的启发) :

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

(我经常使用 while 循环。虽然对于特定的事情,您肯定希望使用指向变量名[“ Branch”,例如] ,但大多数情况下,我只关心如何处理每个 台词输入。在这里使用“ line”而不是“ Branch”是对可重用性/肌肉记忆/效率的肯定。)

内置的 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 )

特别报告员的具体情况:

#!/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 分支

如果你处于这种状态:

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 的 Bash 完成

#/bin/bash
for branch in $(git branch -r);
do
echo $branch
done

当然,理论上是应该使用 Git 在编写脚本时确实具有的特殊接口。 但是 通常你需要的是 简单点的ー方便的一线笔。一些不会让你想起 git for-each-ref 格式的东西,参考文献,阿门。最终还是 UNIX。然后是这样的:

  1. 有一个实用程序以其晦涩但简洁的方式打印最后一栏而广为人知。
  2. git branch将星号 之前作为分支名称。这意味着我们似乎总是对最后一列感兴趣。

结果:

git branch | awk '{print $NF}'

这是因为 awk有一个间隔变量 NF,它是最后一个字段的编号。在这个数字前面加上 $将产生这个字段的内容,这正是这里所需要的。

简单地迭代本地分支名称的正确方法是在 refs/heads/上使用 for-each-ref:

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

这适用于 IFS的标准/默认值,因为 git 中的分支名称中的空格是非法的。