123

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

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

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

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

谢谢

4

15 回答 15

180

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

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

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

注意:您不需要remotes/远程引用上的前缀,除非您有其他引用导致匹配引用名称搜索路径中的多个位置(请参阅git-rev-parse 的指定修订部分中的origin/master“符号引用名称。...” (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

* Ref 名称应该不受 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)添加元素) ,您可以做类似的事情。

于 2010-10-02T21:24:21.333 回答
53

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

$ git branch
* master
  mybranch
$ 

so$(git branch)展开为 eg * master mybranch,然后*展开为当前目录中的文件列表。

首先,我看不到不打印星号的明显选择。但你可以把它砍掉:

$(git branch | cut -c 3-)
于 2010-10-02T15:49:47.523 回答
16

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

于 2018-11-12T22:30:00.733 回答
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;
于 2016-03-21T11:40:51.277 回答
5

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

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

输出:

bamboo
develop
master

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

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

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

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

于 2017-08-01T14:00:11.627 回答
4

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

于 2012-07-20T03:43:00.063 回答
4

把事情简单化

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

#!/bin/bash

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

输出:

master
other
于 2019-04-10T10:29:24.107 回答
4

公认的答案是正确的,并且确实应该是使用的方法,但是在 bash 中解决问题是理解 shell 如何工作的一个很好的练习。使用 bash 执行此操作而不执行额外的文本操作的技巧是确保 git branch 的输出永远不会作为要由 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 将它们解释为模式匹配字符。空格也会被转义(以防止标记化),因为您实际上是匹配 '* '。

于 2016-01-30T08:32:11.933 回答
3

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

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

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

于 2018-01-30T18:36:39.220 回答
3

Googlian 的答案,但没有使用for

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
于 2019-07-18T10:13:21.887 回答
3
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

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

参考:Git 的 Bash 补全

于 2019-09-01T17:19:37.313 回答
2

如果您处于这种状态:

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
于 2019-03-07T03:53:46.270 回答
1

延续@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 )
于 2017-10-23T22:31:09.100 回答
1

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

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"<- 这是你施展魔法的地方
于 2021-05-31T16:38:17.690 回答
0
#/bin/bash
for branch in $(git branch -r); 
do
    echo $branch
done
于 2021-10-13T18:56:36.403 回答