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은 스크립팅에 사용하도록 명시 적으로 설계된 "plumbing"인터페이스 를 제공 합니다 (일반 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

* 참조 이름은 쉘의 단어 분할로부터 안전해야합니다 ( 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 2015

4
그러나 git 분기의 필터링 옵션 중 하나를 사용하려면 git 분기 --merged에서 논리를 복제해야합니까? 이를위한 더 나은 방법이 있어야합니다.
Thayne 2015-04-20

3
단순한 버전 :git for-each-ref refs/heads | cut -d/ -f3-
wid

4
@wid : 아니면 간단히git for-each-ref refs/heads --format='%(refname)'
John Gietzen 2016 년

5
@Thayne :이 질문은 오래되었지만 Git 사람들은 마침내 문제를 해결했습니다. for-each-ref이제 --mergedand git 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).
Nick

2
좋아, 나는 당신의 3-솔루션을 정말 좋아합니다 .
Andrei-Niculae Petre

1
약간 더 간단한 sed 버전 :$(git branch | sed 's/^..//')
jdg

5
약간 더 간단한 tr 버전 :$(git branch | tr -d " *")
ccpizza 2015

13

이를 위해 bash 내장 기능 mapfile이 빌드되었습니다.

모든 자식 브랜치 : 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 분기 출력을 줄로 자릅니다. '*'는 리터럴 문자로 읽 힙니다. 일치하는 패턴에 특히주의하면서 케이스 문을 사용하여 일치 시키십시오.

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

내가 한 일을 귀하의 질문에 적용했습니다 (& ccpizza 언급에서 영감을 얻었습니다 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

Googlian의 대답하지만, 사용하지 않고 에 대한

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

1
슬래시가있는 브랜치 에서처럼 네임 스페이스 브랜치 이름에는 작동하지 않습니다. 즉, "dependabot / npm_and_yarn / typescript-3.9.5"처럼 보이는 dependentabot에 의해 생성 된 분기가 "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.