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


105

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

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

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

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

谢谢



1
@pihentagy这是在链接的问题之前写的。
kodaman

例如: -for b in "$(git branch)"; do git branch -D $b; done
ABHI

Answers:


156

编写脚本时,不应使用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与引用名称搜索路径中的多个位置匹配(请参阅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

$@如果不使用支持数组的外壳(set --初始化和set -- "$@" %(refname)添加元素),则可以执行类似的操作。


39
说真的 有没有更简单的方法可以做到这一点?
Jim Fell 2015年

4
但是,如果我想使用git分支的过滤选项之一(如)--merged,我将不得不复制git分支中的逻辑吗?必须有一个更好的方法来做到这一点。
Thayne 2015年

3
Simplier版本:git for-each-ref refs/heads | cut -d/ -f3-
WID

4
@wid:或者,简单地说,就是git for-each-ref refs/heads --format='%(refname)'
约翰·

5
@Thayne:这个问题很老了,但是Git的人们终于解决了这个问题:for-each-ref现在支持所有分支选择器,例如--mergedgit branchgit tag现在实际上就git for-each-ref其本身实现了,至少对于现有列表而言。(创建新分支和标签不是,也不应该是的一部分for-each-ref。)
torek

50

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

$ git branch
* master
  mybranch
$ 

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

我没有看到一个明显的选择,那就是不首先打印星号。但您可以将其砍掉:

$(git branch | cut -c 3-)

4
如果用双引号引起来,则可以阻止bash扩展星号-尽管您仍然希望将其从输出中删除。从任何点删除星号的更可靠的方法是$(git branch | sed -e s/\\*//g)
尼克

2
很好,我真的很喜欢您的3-解决方案。
Andrei-Niculae Petre

1
sed版本稍微简单一些:$(git branch | sed 's/^..//')
jdg

5
稍微简单的tr版本:$(git branch | tr -d " *")
ccpizza

13

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分支


5

我以它为例进行迭代:

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

我建议 $(git branch|grep -o "[0-9A-Za-z]\+")您是否仅以数字,az和/或AZ字母命名您的本地分支机构


4

公认的答案是正确的,并且实际上应该是所使用的方法,但是以bash解决问题是了解shell如何工作的好方法。在不执行其他文本操作的情况下使用bash进行此操作的技巧是确保git分支的输出不会作为要由Shell执行的命令的一部分进行扩展。这样可以防止星号在Shell扩展的文件名扩展(第8步)中扩展(请参阅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 大小写构造和参数替换中的星号都需要使用反斜杠进行转义,以防止shell将其解释为模式匹配字符。空格也将转义(以防止标记化),因为您实际上是在匹配'*'。


4

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

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

输出:

bamboo
develop
master

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

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

编辑:如果您处于“分离头”状态,则需要过滤掉当前条目:

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


3

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

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

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


1

从@finn的答案扩展(谢谢!),以下内容将使您可以遍历分支,而无需创建中间的shell脚本。只要分支名称中没有换行符,它就足够健壮:)

git for-each-ref --format='%(refname)' refs/heads  | while read x ; do echo === $x === ; done

while循环在子外壳程序中运行,除非您要设置要在当前外壳程序中访问的外壳程序变量,否则通常都可以。在这种情况下,您可以使用过程替换来反转管道:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )

1

如果您处于此状态:

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
Abhi

这也对我for b in "$(git branch)"; do git branch -D $b; done
有用

1

把事情简单化

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

#!/bin/bash

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

输出:

master
other

1

Googlian的答案,但没有用于

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

1
这不适用于命名空间的分支名称,因为在其中带有斜杠的分支中。这意味着由dependabot创建的分支看起来像“ dependabot / npm_and_yarn / typescript-3.9.5”,将显示为“ typescript-3.9.5”。
ecbrodie

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

这使用了git plumbing命令,该命令是为脚本编写的。它也是简单和标准的。

参考:Git的Bash完成

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.