简体   繁体   English

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

[英]How to iterate through all git branches using bash script

How can I iterate through all the local branches in my repository using bash script.如何使用 bash 脚本遍历存储库中的所有本地分支。 I need to iterate and check is there any difference between the branch and some remote branches.我需要迭代并检查分支和一些远程分支之间是否有任何区别。 Ex前任

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谢谢

You should not use git branch when writing scripts.编写脚本时不应使用git branch Git provides a “plumbing” interface that 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 提供了一个明确设计用于脚本编写的“管道”接口(普通 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) ).注意:您不需要remotes/前缀遥控器上的裁判,除非你有其他裁判这项事业origin/master ,以配合在裁判的名字搜索路径的多个地方(见“A符号裁判的名字......。” 的指定修订部分git-rev-parse(1) )。 If you are trying to explictly avoid ambiguity, then go with the full ref name: refs/remotes/origin/master .如果您试图明确避免歧义,请使用完整的参考名称: 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) ). * Ref 名称应该不受 shell 的分词影响(请参阅git-check-ref-format(1) )。 Personally I would stick with the former version (generated shell code);我个人会坚持使用以前的版本(生成的 shell 代码); I am more confident that nothing inappropriate can happen with it.我更有信心,它不会发生任何不恰当的事情。

Since you specified bash and 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)添加元素)。

This is because git branch marks the current branch with an asterisk, eg:这是因为git branch用星号标记当前分支,例如:

$ git branch
* master
  mybranch
$ 

so $(git branch) expands to eg * master mybranch , and then the * expands to the list of files in the current directory.所以$(git branch)扩展到例如* 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-)

The bash builtin, mapfile , is built for this bash 内置函数mapfile就是为此而构建的

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=${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 )

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}"

source: List all local git branches without an asterisk来源: 列出所有本地 git 分支,不带星号

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;

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

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.接受的答案是正确的,确实应该是所使用的方法,但是在 bash 中解决问题是理解 shell 如何工作的一个很好的练习。 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.在不执行额外文本操作的情况下使用 bash 执行此操作的技巧是确保 git branch 的输出永远不会被扩展为要由 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 )这可以防止星号在 shell 扩展的文件名扩展(第 8 步)中扩展(参见http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Use the bash while construct with a read command to chop the git branch output into lines.使用带有读取命令的 bash while构造将 git 分支输出分成几行。 The '*' will be read in as a literal character. '*' 将作为文字字符读入。 Use a case statement to match it, paying special attention to the matching patterns.使用 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 case construct and in the parameter substitution need to be escaped with backslashes to prevent the shell interpreting them as pattern matching characters. bash case构造和参数替换中的星号都需要用反斜杠转义,以防止 shell 将它们解释为模式匹配字符。 The spaces are also escaped (to prevent tokenization) because you are literally matching '* '.空格也会被转义(以防止标记化),因为您实际上是在匹配 '* '。

Easiest option to remember in my opinion:我认为最容易记住的选项:

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

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

(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 line of input. Using 'line' here instead of 'branch' is a nod to reusability/muscle memory/efficiency.) (我经常使用 while 循环。虽然对于特定的事情,你肯定想使用一个带尖的变量名 [“branch”,例如],但大多数时候我只关心对每一输入做一些事情。使用这里的“line”而不是“branch”是对可重用性/肌肉记忆/效率的认可。)

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

Keep it simple保持简单

The simple way of getting branch name in loop using bash script.使用 bash 脚本在循环中获取分支名称的简单方法。

#!/bin/bash

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

Output:输出:

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

This uses git plumbing commands, which are designed for scripting.这使用了专为编写脚本而设计的git 管道命令。 It's also simple and standard.它也很简单和标准。

Reference: Git's Bash completion参考: Git 的 Bash 完成

Extending on from @finn's answer (thank you!), the following will let you iterate over the branches without creating an intervening shell script.延续@finn 的回答(谢谢!),以下内容将让您在不创建干预 shell 脚本的情况下遍历分支。 It's robust enough, as long as there's no newlines in the branch name :)只要分支名称中没有换行符,它就足够强大:)

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. while 循环在子shell 中运行,这通常很好,除非您设置要在当前shell 中访问的shell 变量。 In that case you use process substitution to reverse the pipe:在这种情况下,您可以使用进程替换来反转管道:

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

The correct way to simply iterate over local branch names is to use for-each-ref over refs/heads/ .简单地迭代本地分支名称的正确方法是使用for-each-ref over refs/heads/ For example:例如:

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

This works with standard/default values for IFS because spaces are illegal in branch names in git.这适用于IFS的标准/默认值,因为 git 中的分支名称中的空格是非法的。

List heads (branches) in the local repository列出本地存储库中的头(分支)

git show-ref --heads

This will list the heads something like这将列出头部类似

682e47c01dc8d0f4e4102f183190a48aaf34a3f0 refs/heads/main
....

so if you're only interested in the name, you can use something like sed to obtain the output you want所以如果你只对名字感兴趣,你可以使用像sed这样的东西来获得你想要的输出

git show-ref --heads | sed 's/.*refs\/heads\///'

Iterate through the branches遍历分支

With this output you can easily iterate through it, say using a bash loop, xargs, whatever floats your boat有了这个输出,你可以轻松地遍历它,比如使用 bash 循环、xargs、任何漂浮在你船上的东西

for SHA in $(git show-ref --heads | awk '{ print $1 }'); do
 echo "magic! $SHA"
done
  • git show-ref --heads get the branches as per above git show-ref --heads按照上面的git show-ref --heads获取分支
  • awk '{ print $1 }' obtain the SHA awk '{ print $1 }'获取 SHA
  • echo "magic! $SHA" <- this is where you would do your magic echo "magic! $SHA" <- 这是你施展魔法的地方
#/bin/bash
for branch in $(git branch -r); 
do
    echo $branch
done

Of course in theory one should use a special interface that Git indeed has for use when scripting.当然,理论上应该使用 Git 在编写脚本时确实具有的特殊接口。 But often you want something simpler — handy for a oneliner.通常你想要一些更简单的东西——对于单行来说很方便。 Something that doesn't urge you remember stuff like git for-each-ref --format … refs … amen.有些东西不会促使你记住像 git for-each-ref --format ... refs ... amen 这样的东西。 And it's UNIX anyways finally.最后还是 UNIX。 Then it goes like that:然后它是这样的:

  1. There's an utility widely known for its obscure but a terse way to print the last column.有一个实用程序以其晦涩但简洁的方式打印最后一列而广为人知。
  2. git branch puts asterisk before the branch name. git branch将星号放在分支名称之前 Meaning we're seemingly always interested in the last column exactly.这意味着我们似乎总是对最后一列感兴趣。

Resulting:结果:

git branch | awk '{print $NF}'

This works because awk has an interval variable NF which is the number of the last field.这是因为awk有一个区间变量NF ,它是最后一个字段的编号。 Prefixing this number with $ will yield the contents of that field witch is exactly what is need here.$前缀这个数字将产生该字段的内容,这正是这里需要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM