【问题标题】:How to iterate through all git branches using bash script如何使用 bash 脚本遍历所有 git 分支
【发布时间】:2011-04-20 06:29:06
【问题描述】:

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

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

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

这是解决此问题的正确方法吗?或者有其他方法吗?

谢谢

【问题讨论】:

  • @pihentagy 这是在链接问题之前写的。
  • 例如:-for b in "$(git branch)"; do git branch -D $b; done
  • 不是重复的。这个问题适用于所有分支,而另一个适用于具有特定名称的分支

标签: git bash git-bash


【解决方案1】:

你不应该在编写脚本时使用 git branch。 Git 提供了一个“plumbing” interface,它明确设计用于脚本(普通 Git 命令的许多当前和历史实现(添加、签出、合并等)使用相同的接口)。

你想要的管道命令是 git for-each-ref:

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

注意:您不需要远程引用上的remotes/ 前缀,除非您有其他引用导致origin/master 匹配引用名称搜索路径中的多个位置(请参阅the Specifying Revisions section of 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

* 引用名称应该不受 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) 用于添加元素),您可以使用 $@ 执行类似的操作。

【讨论】:

  • 说真的。没有更简单的方法吗?
  • 但是如果我想使用 git 分支的过滤选项之一,比如--merged,我是否必须复制 git 分支中的逻辑?必须有更好的方法来做到这一点。
  • 更简单的版本:git for-each-ref refs/heads | cut -d/ -f3-
  • @wid:或者,简单地说,git for-each-ref refs/heads --format='%(refname)'
  • @Thayne:这个问题很老了,但 Git 的人终于解决了这个问题:for-each-ref 现在支持所有分支选择器,如 --mergedgit branchgit tag 现在实际上是根据git for-each-ref 本身实现,至少对于列表现有的情况。 (创建新的分支和标签不是,也不应该是for-each-ref 的一部分。)
【解决方案2】:

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

$ git branch
* master
  mybranch
$ 

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

我没有看到一个明显的选择,即不打印星号;但你可以把它砍掉:

$(git branch | cut -c 3-)

【讨论】:

  • 如果你用双引号括起来,你可以阻止 bash 扩展星号——尽管你仍然想从输出中删除它。从任何点删除星号的更可靠的方法是$(git branch | sed -e s/\\*//g)
  • 很好,我真的很喜欢你的3- 解决方案。
  • 稍微简单的 sed 版本:$(git branch | sed 's/^..//')
  • 稍微简单的tr版本:$(git branch | tr -d " *")
【解决方案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}"

来源:List all local git branches without an asterisk

【讨论】:

    【解决方案4】:

    我以它为例进行迭代:

    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;
    

    【讨论】:

      【解决方案5】:

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

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

      输出:

      bamboo
      develop
      master
      

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

      由于在 Git 分支名称中空格和 * 均无效,因此这将返回不带额外字符的分支列表。

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          公认的答案是正确的,并且确实应该是使用的方法,但是在 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 将它们解释为模式匹配字符。空格也被转义(以防止标记化),因为您在字面上匹配 '* '。

          【讨论】:

            【解决方案8】:

            我最终做了什么,应用于您的问题(并受到 ccpizza 提及 tr 的启发):

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

            (我经常使用 while 循环。虽然对于特定的事情你肯定想使用一个指向变量名 [“branch”,例如],但大多数时候我只关心做一些事情每 输入。在这里使用“行”而不是“分支”是对可重用性/肌肉记忆/效率的认可。)

            【讨论】:

              【解决方案9】:

              保持简单

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

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

              输出:

              master
              other
              

              【讨论】:

                【解决方案10】:

                如果你处于这种状态:

                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
                • 如:删除所有以abc开头的分支:for b in $(git branch | grep abc); do git branch -D $b; done
                【解决方案11】:

                Googlian 的回答,但没有使用 for

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

                【讨论】:

                • 这不适用于命名空间的分支名称,例如带有斜杠的分支。这意味着由dependabot 创建的类似于“dependabot/npm_and_yarn/typescript-3.9.5”的分支将显示为“typescript-3.9.5”。
                【解决方案12】:
                for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
                    ...
                done
                

                这使用了为脚本设计的git plumbing 命令。它也简单而标准。

                参考:Git's Bash completion

                【讨论】:

                  【解决方案13】:

                  延续@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 )
                  

                  【讨论】:

                    【解决方案14】:

                    列出本地存储库中的头(分支)

                    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按上述方法获取分支
                    • awk '{ print $1 }'获取SHA
                    • echo "magic! $SHA"

                    【讨论】:

                      【解决方案15】:
                      #/bin/bash
                      for branch in $(git branch -r); 
                      do
                          echo $branch
                      done
                      

                      【讨论】:

                      • 这包括 -&gt;origin/HEADorigin/main 对我来说......
                      猜你喜欢
                      • 2020-08-04
                      • 2017-02-09
                      • 2013-01-02
                      • 2016-06-29
                      • 2011-05-11
                      • 1970-01-01
                      • 2023-03-15
                      • 2010-11-09
                      • 2014-12-30
                      相关资源
                      最近更新 更多