如何使用 bash 脚本迭代所有 git 分支

发布于 2024-09-26 07:25:35 字数 305 浏览 6 评论 0原文

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

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

我需要做类似上面给出的事情,但我面临的问题是 $(gitbranch) 为我提供了存储库文件夹内的文件夹以及存储库中存在的分支。

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

谢谢

How can I iterate through all the local branches in my repository using bash script.
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.

Is this the correct way to solve this issue? Or is there another way to do it?

Thank you

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

呆° 2024-10-03 07:25:36

扩展 @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 )

Extending on from @finn's answer (thank you!), the following will let you iterate over the branches without creating an intervening shell script. 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. 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 )
勿挽旧人 2024-10-03 07:25:36

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

git show-ref --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" <- 这就是你施展魔法的地方

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

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

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
  • awk '{ print $1 }' obtain the SHA
  • echo "magic! $SHA" <- this is where you would do your magic
雪若未夕 2024-10-03 07:25:36

当然,理论上,人们应该使用 Git 在编写脚本时确实具有的特殊接口。
但是通常你想要一些更简单的东西——对于单行来说很方便。有些东西不会促使你记住像 git for-each-ref --format … refs … 阿门这样的东西。无论如何,最终还是 UNIX。然后事情是这样的:

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

结果:

git branch | awk '{print $NF}'

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

Of course in theory one should use a special interface that Git indeed has for use when scripting.
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. And it's UNIX anyways finally. 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. 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. Prefixing this number with $ will yield the contents of that field witch is exactly what is need here.

千纸鹤带着心事 2024-10-03 07:25:36
#/bin/bash
for branch in $(git branch -r); 
do
    echo $branch
done
#/bin/bash
for branch in $(git branch -r); 
do
    echo $branch
done
爱格式化 2024-10-03 07:25:35

编写脚本时不应使用git分支。 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

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

You should not use git branch when writing scripts. 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).

The plumbing command you want is 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)). If you are trying to explictly avoid ambiguity, then go with the full ref name: 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.

If you do not like the idea of generating the shell code, you could give up a bit of robustness* and do this:

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)). Personally I would stick with the former version (generated shell code); 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:

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).

地狱即天堂 2024-10-03 07:25:35

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

$ git branch
* master
  mybranch
$ 

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

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

$(git branch | cut -c 3-)

This is because git branch marks the current branch with an asterisk, e.g.:

$ git branch
* master
  mybranch
$ 

so $(git branch) expands to e.g. * master mybranch, and then the * expands to the list of files in the current directory.

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-)
温柔少女心 2024-10-03 07:25:35

bash 内置函数 mapfile 是为此

所有 git 分支构建的: gitbranch --all --format='%(refname:short)'

所有本地 git 分支: gitbranch --format='%(refname:short)'

所有远程 git 分支: gitbranch --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 分支

The bash builtin, mapfile, is built for this

all git branches: git branch --all --format='%(refname:short)'

all local git branches: git branch --format='%(refname:short)'

all remote git branches: git branch --remotes --format='%(refname:short)'

iterate through all git branches: 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:

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

暖心男生 2024-10-03 07:25:35
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads); do
    ...
done

这使用 git pipeline 命令,这些命令是为脚本编写而设计的。它也很简单和标准。

参考:Git 的 Bash 补全

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

This uses git plumbing commands, which are designed for scripting. It's also simple and standard.

Reference: Git's Bash completion

蹲墙角沉默 2024-10-03 07:25:35

我迭代例如:

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;

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;
熊抱啵儿 2024-10-03 07:25:35

最后确定了如何在没有星号和 git for-each-ref 参数中没有魔法的情况下为 git 分支生成输出:

$ git branch --format="%(refname:short)"

为什么这很有价值? git Branch 命令有一些额外的过滤器,例如 --merged,使用 git for-each-ref 实现起来并不容易(至少是这样)据我所知)。

Finally nailed how to make an output for git branch without an asterisk and without magic in git for-each-ref arguments:

$ git branch --format="%(refname:short)"

Why this is valuable? git branch command has some additional filters like --merged, which is not easy to implement using git for-each-ref (at least as far as I see).

沉默的熊 2024-10-03 07:25:35

接受的答案是正确的,确实应该是所使用的方法,但是在 bash 中解决问题是理解 shell 如何工作的一个很好的练习。使用 bash 执行此操作而不执行额外的文本操作的技巧是确保 git 分支的输出永远不会作为 shell 执行的命令的一部分进行扩展。这可以防止星号在 shell 扩展的文件名扩展(步骤 8)中扩展(请参阅 http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html)

使用带有 read 命令的 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 将它们解释为模式匹配字符。空格也会被转义(以防止标记化),因为您实际上是在匹配“*”。

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. 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. 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)

Use the bash while construct with a read command to chop the git branch output into lines. The '*' will be read in as a literal character. Use a case statement to match it, paying special attention to the matching patterns.

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. The spaces are also escaped (to prevent tokenization) because you are literally matching '* '.

紫轩蝶泪 2024-10-03 07:25:35

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

gitbranch| grep "[^* ]+" -Eo

输出:

bamboo
develop
master

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

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

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

gitbranch --list | grep -v“头分离”| grep "[^* ]+" -oE

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.

Since neither space nor * are valid in Git branch names, this returns the list of branches without the extra characters.

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

空心↖ 2024-10-03 07:25:35

保持简单

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

#!/bin/bash

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

输出:

master
other

Keep it simple

The simple way of getting branch name in loop using bash script.

#!/bin/bash

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

Output:

master
other
刘备忘录 2024-10-03 07:25:35

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

I would suggest
$(git branch|grep -o "[0-9A-Za-z]\+") if your local branches are named by digits, a-z, and/or A-Z letters only

一袭白衣梦中忆 2024-10-03 07:25:35

我最终做了什么,应用于你的问题(&amp;受到ccpizza提到tr的启发):

git分支| tr -d ' *' |而 IFS='' 读取 -r 行;执行 git log --oneline "$line" ^remotes/origin/master;完成

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

What I ended up doing, applied to your question (& inspired by ccpizza mentioning 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.)

眉目亦如画i 2024-10-03 07:25:35

谷歌的答案,但没有使用 for

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

Googlian's answer, but without using for

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
懵少女 2024-10-03 07:25:35

如果您处于这种状态:

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

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
沧笙踏歌 2024-10-03 07:25:35

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

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

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

The correct way to simply iterate over local branch names is to use 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文