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

回答:


156

スクリプトを作成するときは、gitブランチを使用しないでください。Gitは、スクリプトで使用するために明示的に設計された「配管」インターフェースを提供します(通常のGitコマンドの多くの現在および過去の実装(追加、チェックアウト、マージなど)は、この同じインターフェースを使用します)。

必要な配管コマンドはgit for-each-refです。

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

注:参照名検索パスの複数の場所と一致するremotes/原因となる他の参照がない限り、リモート参照にプレフィックスは必要ありませんorigin/mastergit-rev-parseのリビジョンの指定セクションの「シンボリック参照名...」を参照)。(1))。あいまいさを明示的に避けようとしている場合は、完全なref名を使用してください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にパイプすることができます。

シェルコードを生成するという考えが気に入らない場合は、堅牢性を少しあきらめて*これを行うことができます。

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    git log --oneline "$branch" ^origin/master
done

*参照名はシェルの単語分割から安全でなければなりません(git-check-ref-format(1)を参照)。個人的には、以前のバージョン(生成されたシェルコード)を使用します。私はそれで不適切なことが起こり得ないことをより確信しています。

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

4
しかし、のようにgitブランチのフィルタリングオプションの1つを使用したい場合、gitブランチで--mergedロジックを複製する必要がありますか?これを行うには、より良い方法が必要です。
Thayne、

3
Simplierバージョン:git for-each-ref refs/heads | cut -d/ -f3-
WID

4
@wid:または、単に、git for-each-ref refs/heads --format='%(refname)'
ジョンGietzen

5
@Thayne:この質問は古くなっていますが、Gitの人々はようやく問題に対処しました。for-each-ref現在--mergedgit branchおよびのようなすべてのブランチセレクターをサポートし、少なくともリストが存在する場合は、それ自体でgit 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 2013年

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で問題を解決することは、シェルがどのように機能するかを理解するための素晴らしい練習です。追加のテキスト操作を実行せずにbashを使用してこれを行うコツは、gitブランチの出力がシェルによって実行されるコマンドの一部として展開されないようにすることです。これにより、シェル展開のファイル名展開(ステップ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 ケースコンストラクトとパラメーター置換の両方のアスタリスクをバックスラッシュでエスケープする必要があります。スペースもエスケープされます(トークン化を防ぐため)。文字どおり「*」に一致するためです。


4

私の考えで覚える最も簡単なオプション:

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

出力:

bamboo
develop
master

Grepの-oオプション(--only-matching)は、出力を入力の一致する部分のみに制限します。

Gitブランチ名ではスペースも*も有効ではないため、余分な文字なしでブランチのリストが返されます。

編集:「切り離されたヘッド」状態にある場合は、現在のエントリを除外する必要があります。

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


3

私が最終的に何をしたか、あなたの質問に適用されました(ccccの言及に触発されましたtr):

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

(whileループを頻繁に使用します。特定の用途では、確実にポイントされた変数名(たとえば、 "branch"など)を使用する必要がありますが、ほとんどの場合、私は各入力で何かを行うことにのみ関心があります。 「ブランチ」の代わりにここで「ライン」は、再利用性/筋肉メモリ/効率性に同意するものです。)


1

@finnの回答(ありがとうございます!)を拡張して、次のコードでは、間にシェルスクリプトを作成せずにブランチを繰り返し処理できます。ブランチ名に改行がない限り、それは十分に堅牢です:)

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

グーグリアンの答えですが

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

1
スラッシュが含まれているブランチのように、これは名前空間付きのブランチ名では機能しません。つまり、「dependabot / npm_and_yarn / typescript-3.9.5」のように見えるdependabotによって作成されたブランチは、代わりに「typescript-3.9.5」として表示されます。
ecbrodie

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

これは、スクリプト用に設計されたgit plumbingコマンドを使用します。また、シンプルで標準的です。

参照:GitのBash補完

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.