web-dev-qa-db-ja.com

bashスクリプトを使用してすべてのgitブランチを反復処理する方法

Bashスクリプトを使用して、リポジトリ内のすべてのローカルブランチを反復処理するにはどうすればよいですか。ブランチといくつかのリモートブランチに違いがあるかどうかを繰り返し確認する必要があります。例

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

上記のようなことをする必要がありますが、私が直面している問題は$(git branch)であり、リポジトリに存在するブランチとともにリポジトリフォルダ内のフォルダを提供します。

これはこの問題を解決する正しい方法ですか?それとも別の方法がありますか?

ありがとうございました

91
Arun P Johny

スクリプトを記述するときは、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にパイプすることができます。

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

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

*参照名は、シェルのWord分割から安全でなければなりません( 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 -- "$@" %(refname)を使用して要素を追加する)を使用していない場合は、_set --_で同様のことができます。

144
Chris Johnsen

これは、_git branch_が現在のブランチをアスタリスクでマークするためです。例:

_$ git branch
* master
  mybranch
$ 
_

$(git branch)は、たとえば_* master mybranch_、次に_*_が現在のディレクトリ内のファイルのリストに展開されます。

そもそもアスタリスクを印刷しないという明白なオプションはありません。しかし、あなたはそれを切り落とすことができます:

_$(git branch | cut -c 3-)
_
46

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ブランチをアスタリスクなしでリスト

8
Andrew Miller

私はそれを例として繰り返します:

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;
5
Djory Krache

受け入れられた答えは正解であり、実際に使用されるアプローチである必要がありますが、bashで問題を解決することは、シェルがどのように機能するかを理解する上で素晴らしい練習です。追加のテキスト操作を実行せずにbashを使用してこれを行うコツは、シェルによって実行されるコマンドの一部としてgitブランチの出力が展開されないようにすることです。これにより、シェル展開のファイル名展開(ステップ8)でアスタリスクが展開されなくなります( http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html を参照してください) )

Readコマンドでbashwhileコンストラクトを使用して、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

Bashcaseコンストラクトとパラメーター置換の両方のアスタリスクは、シェルがパターン一致文字として解釈するのを防ぐために、バックスラッシュでエスケープする必要があります。文字列で「*」と一致するため、スペースもエスケープされます(トークン化を防ぐため)。

4
BitByteDog

あなたのローカルブランチが数字、a-z、および/またはA-Z文字のみで名前が付けられている場合、$(git branch|grep -o "[0-9A-Za-z]\+")をお勧めします

4
Xin Guo

私の意見では覚えやすい最も簡単なオプション:

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

出力:

bamboo
develop
master

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

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

編集: 'detached head' state の場合、現在のエントリを除外する必要があります。

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

3
staafl

私がやったことをあなたの質問に適用しました(&ccpizzaからtrに言及したことに触発されました):

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

(私はwhileループを頻繁に使用します。特定のことについては、尖った変数名["branch"など]を使用したいことは間違いありませんが、ほとんどの場合は入力の各lineで何かを実行します。ここで 'branch'の代わりに 'line'を使用することは、再利用性/筋肉の記憶/効率性にうなずきます。)

3
Jan Kyu Peblik

@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
Chris Cogdon

Googlianの答えですが、forを使用せずに

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

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

参照: GitのBashの完了

0
aude

この状態の場合:

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
0
Wijith Bandara

シンプルにする

Bashスクリプトを使用してループ内でブランチ名を取得する簡単な方法。

#!/bin/bash

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

出力:

master
other
0
Googlian