如何使用Bash脚本遍历所有Git分支

138

如何使用Bash脚本遍历我的代码库中的所有本地分支?我需要迭代并检查分支和一些远程分支之间是否有差异。

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

我需要像上面给出的那样做一些事情,但我面临的问题是$(git branch)会给我返回存储库文件夹内以及存储库中存在的分支。

这是解决此问题的正确方法吗?还是有其他方法可以解决它?

谢谢


可能是循环遍历所有具有特定名称的git分支的重复问题。 - pihentagy
3
@pihentagy 这是在链接问题之前写的。 - thefern
for b in "$(git branch)"; do git branch -D $b; done 将上述代码从英语翻译成中文: - Abhi
不是重复问题。此问题适用于所有分支,而另一个问题适用于具有特定名称的分支。 - cincplug
@Abhi 你抓住我了!哈哈,我甚至没有注意到那里的-D。 - mydoglixu
18个回答

196

编写脚本时不应使用git branch。Git提供了一个“plumbing”接口,专门为脚本设计(许多当前和历史的普通Git命令(如add、checkout、merge等)使用相同的接口)。

你需要的plumbing命令是git for-each-ref

git for-each-ref --shell \
  --format='git log --oneline %(refname) ^origin/master' \
  refs/heads/
注意:除非您拥有其他引用使得origin/master在引用名称搜索路径中多次匹配(请参见“符号引用名称...”在git-rev-parse(1)的指定修订版本章节),否则您不需要使用remotes/前缀来表示远程引用。如果您试图明确避免歧义,请使用完整的引用名称: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

* 引用名称应该避免受到 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) 添加元素),则可以使用类似于 $@ 的方法来实现相似的功能。

64
真的吗?没有更简单的方法吗? - Jim Fell
6
如果我想使用git branch的筛选选项之一,比如--merged,那么我是否需要在git branch中重复这个逻辑呢?肯定有更好的方法来做到这一点。 - Thayne
3
更简单的版本:git for-each-ref refs/heads | cut -d/ -f3- - wid
6
@wid:或者,简单地说,git for-each-ref refs/heads --format='%(refname)' - John Gietzen
7
@Thayne:这个问题很老,但是Git开发人员最终解决了这个问题:for-each-ref现在支持所有分支选择器,例如--merged,并且git branchgit tag现在实际上是通过git for-each-ref本身来实现的,至少对于列出现有情况而言。(创建新分支和标签不应该是 for-each-ref 的一部分。) - torek
显示剩余3条评论

58

这是因为git branch会在当前分支标记一个星号,例如:

$ git branch
* master
  mybranch
$ 

因此,$(git branch)会扩展为例如* master mybranch,然后*扩展为当前目录中的文件列表。

我没有看到明显的选项可以不打印星号; 但是你可以去掉它:

$(git branch | cut -c 3-)

4
如果你在双引号中包含星号,就可以防止bash扩展星号 - 尽管你仍然需要从输出中删除它。更健壮的方法是从任何位置删除星号,可以使用$(git branch | sed -e s/\\*//g) - Nick
4
很好,我真的很喜欢你的 "3-" 解决方案。 - Andrei-Niculae Petre
3
稍微简化的sed版本:$(git branch | sed 's/^..//')该命令的作用是:从git branch命令的输出中提取出分支名称,并去除每个分支名称前面的两个字符。可以将其翻译为:使用sed命令,从git branch的输出中提取分支名称,并去掉它们的前两个字符。 - jdg
6
稍微简化的tr命令版:$(git branch | tr -d " *") - ccpizza
这应该是被标记的答案。目前被标记的答案只是说教,没有回答为什么它不起作用的问题。 - Piyush Soni

19

内置于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}"

来源: 不带星号列出所有本地git分支

以下是要翻译的内容:
如何在git中列出所有没有星号的本地分支?

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

这里使用了git plumbing命令,这些命令是为编写脚本而设计的,并且简单易懂。

参考文献:Git 的 Bash completion


6
我迭代它,例如:

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
接受的答案是正确的,应该采用这种方法,但在bash中解决问题是理解shell工作原理的好练习。使用bash解决此问题而不执行其他文本操作的诀窍是确保git分支的输出永远不会作为要由shell执行的命令的一部分被扩展。这可以防止星号在文件名扩展(步骤8)或shell扩展(参见http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)中被扩展。

使用bash while结构和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的case结构和参数替换中,星号需要用反斜杠转义,以防止shell将其解释为模式匹配字符。空格也需要转义(以防止标记化),因为您要精确匹配“* ”。

4

保持简单

使用 Bash 脚本循环获取分支名称的简单方法。

#!/bin/bash

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

输出:

master
other

4
最后终于找到了一个方法,可以在不使用星号和魔法参数的情况下为 `git branch` 命令生成输出,通过 `git for-each-ref` 实现。
$ git branch --format="%(refname:short)"

为什么这很有价值?git branch命令具有一些额外的过滤器,例如--merged,使用git for-each-ref不容易实现(至少在我看来)。

3

我个人认为最容易记住的选项是:

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

输出结果为:

bamboo
develop
master

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

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

编辑:如果您处于'detached head' state状态,则需要过滤掉当前条目:

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


3

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

。这个命令可以帮助您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接