Programming

Git 브랜치의 가장 가까운 부모를 찾는 방법은 무엇입니까?

procodes 2020. 2. 26. 22:48
반응형

Git 브랜치의 가장 가까운 부모를 찾는 방법은 무엇입니까?


다음과 같은 커밋 트리가있는 다음 로컬 저장소가 있다고 가정 해 봅시다.

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h

master내 인 이 최신 안정 릴리스 코드가되고 , develop이가 '다음'릴리스 코드 , 및 feature입니다 준비중인 새로운 기능develop .

후크를 사용하여 원격 리포지토리에서 수행 할 수있는 것은 feature커밋 fdevelopHEAD 의 직접적인 자손이 아닌 한 푸시를 거부하는 것 입니다. 즉, 기능이 git rebase켜져 있기 때문에 커밋 트리는 다음과 같습니다 d.

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h

따라서 다음이 가능합니다.

  • feature? 의 부모 분기를 식별하십시오 .
  • f후손 인 부모 브랜치에서 커밋을 식별 합니까?

거기에서 상위 브랜치의 HEAD가 무엇인지 확인하고 f이전 모델이 상위 브랜치 HEAD와 일치하는지 확인하여 기능을 리베이스해야하는지 결정합니다.


원격 저장소에 개발 브랜치 의 사본이 있다고 가정하면 (초기 설명은 로컬 저장소에서 설명하지만 원격에도 존재하는 것처럼 들린다) 원하는 생각을 얻을 수 있지만 접근 방식은 당신이 상상 한 것과는 조금 다릅니다.

Git의 역사는 커밋 DAG기반으로합니다 . 분기 (및 일반적으로 "참조")는 지속적으로 커밋되는 커밋 DAG에서 특정 커밋을 가리키는 일시적 레이블입니다. 따라서 분기 간의 관계는 시간이 지남에 따라 달라질 수 있지만 커밋 간의 관계는 변하지 않습니다.

    ---o---1                foo
            \
             2---3---o      bar
                  \
                   4
                    \
                     5---6  baz

그것은 baz(이전 버전)을 기반으로하는 것 같습니다 bar. 그러나 우리가 삭제하면 어떻게 bar될까요?

    ---o---1                foo
            \
             2---3
                  \
                   4
                    \
                     5---6  baz

이제는 baz에 기반한 것 같습니다 foo. 그러나의 조상은 baz바뀌지 않았습니다. 우리는 레이블 (및 결과 매달려있는 커밋)을 제거했습니다. 에 새 라벨을 추가하면 4어떻게 되나요?

    ---o---1                foo
            \
             2---3
                  \
                   4        quux
                    \
                     5---6  baz

이제는 baz에 기반한 것 같습니다 quux. 그럼에도 불구하고 조상은 바뀌지 않았고 라벨 만 바뀌 었습니다.

그러나 우리가“커밋 6의 자손을 커밋 3합니까?” (가정 36여부, 다음 대답은 "예"가 될 것입니다 SHA-1은 이름을 커밋 가득) barquux라벨이 존재하거나하지 않습니다.

따라서 "푸시 된 커밋은 개발 브랜치 의 현재 팁의 후손 입니까?"와 같은 질문을 할 수 있지만 "푸시 된 커밋의 부모 분기는 무엇입니까?"라고 확실하게 요청할 수는 없습니다.

당신이 원하는 것에 가장 가까운 것으로 보이는 가장 신뢰할만한 질문은 다음과 같습니다.

모두를 위해 (현재의 팁을 제외한의 조상 커밋 밀어 개발 의 현재 끝이 있고, 그 조상) 개발을 부모로를 :

  • 그러한 커밋이 하나 이상 존재합니까?
  • 그러한 모든 커밋은 단일 부모 커밋입니까?

다음과 같이 구현할 수 있습니다.

pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_children_of_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
    ,)     echo "must descend from tip of '$basename'"
           exit 1 ;;
    ,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
           exit 1 ;;
    ,*)    exit 0 ;;
esac

이것은 당신이 제한하고 싶은 것의 일부를 다룰 것이지만, 전부는 아닙니다.

참고로 다음은 확장 된 예제 기록입니다.

    A                                   master
     \
      \                    o-----J
       \                  /       \
        \                | o---K---L
         \               |/
          C--------------D              develop
           \             |\
            F---G---H    | F'--G'--H'
                    |    |\
                    |    | o---o---o---N
                     \   \      \       \
                      \   \      o---o---P
                       \   \   
                        R---S

위의 코드는 거부하는 데 사용할 수 있습니다 HS수용하면서 H', J, K, 또는 N, 그러나 또한 받아 들일 LP(그들은 병합을 포함, 그러나 끝 병합하지 않는 개발을 ).

또한 거부 L하고 P, 당신은 질문을 변경 요청할 수 있습니다

푸시 된 커밋의 조상 (현재 개발 팁과 조상 제외 ) :

  • 두 부모와의 약속이 있습니까?
  • 그렇지 않다면, 그러한 커밋 중 하나 이상이 현재 유일한 부모 개발 하고 있습니까?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_commits_beyond_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
    *\ *)          echo "must not push merge commits (rebase instead)"
                   exit 1 ;;
    *"$baserev"*)  exit 0 ;;
    *)             echo "must descend from tip of '$basename'"
                   exit 1 ;;
esac

찬사

질문을 표현하는 또 다른 방법은 "현재 분기 이외의 분기에 상주하는 가장 가까운 커밋은 무엇이며 어떤 분기입니까?"입니다.

해결책

약간의 커맨드 라인 마술로 찾을 수 있습니다.

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/.*\[\(.*\)\].*/\1/' \
| sed 's/[\^~].*//'

작동 방식은 다음과 같습니다.

  1. 원격 브랜치를 포함하여 모든 커밋의 텍스트 기록을 표시합니다.
  2. 현재 커밋의 조상은 별표로 표시됩니다. 다른 모든 것을 걸러 내십시오.
  3. 현재 브랜치의 모든 커밋을 무시하십시오.
  4. 첫 번째 결과는 가장 가까운 조상 지점입니다. 다른 결과는 무시하십시오.
  5. 지점 이름이 괄호 안에 표시됩니다. 대괄호와 대괄호 밖의 모든 것을 무시하십시오.
  6. 때로는 분기 이름에 ~ # 또는 ^ #이 포함되어 참조 된 커밋과 분기 팁 사이의 커밋 수를 나타냅니다. 우리는 상관하지 않습니다. 그들을 무시하라.

그리고 결과

위의 코드를 실행

 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic

당신을 줄 것이다 develop당신이 H에서 실행한다면 master당신은 I.에서 실행하는 경우

코드는 요점으로 제공됩니다


시도해 볼 수도 있습니다 :

git log --graph --decorate

자식 부모

당신은 명령을 실행할 수 있습니다

git parent

@Joe Chrysler 의 답변을 자식 별칭 으로 추가하면 분기의 부모를 찾을 수 있습니다 . 사용법을 단순화합니다.

"~/.gitconfig"텍스트 편집기를 사용하여 위치한 gitconfig 파일을 엽니 다 . (리눅스의 경우). Windows의 경우 ".gitconfig"경로는 일반적으로 다음 위치에 있습니다.c:\users\your-user\.gitconfig

vim  ~/.gitconfig

파일에 다음 별명 명령을 추가하십시오.

[alias]
            parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\\[\\(.*\\)\\].*/\\1/' | sed 's/[\\^~].*//' #"

편집기를 저장하고 종료하십시오.

명령을 실행 git parent

그게 다야!


전반적인 문제에 대한 해결책이 있지만 ( feature의 끝에서 내려진 경우 결정 develop), 설명 한 방법으로는 작동하지 않습니다.

당신은 사용할 수 git branch --contains의 끝에서 내려 모든 지점을 나열 develop한 후, 사용 grep확인하기 위해 feature그들 중 하나입니다.

git branch --contains develop | grep "^ *feature$"

그 중 하나 인 경우 " feature"표준 출력으로 인쇄 하고 리턴 코드는 0입니다. 그렇지 않으면 아무것도 인쇄하지 않고 리턴 코드는 1입니다.


이것은 나를 위해 잘 작동합니다.

git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

@droidbot 및 @Jistanidiot의 답변


위의 답변 중 어느 것도 우리 저장소에서 작동하지 않았으므로 최신 병합을 사용하여 내 방식을 공유하고 싶습니다 git log.

#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10

라는 스크립트에 넣으십시오.이 스크립트 git-last-merges는 분기 이름을 현재 분기 대신 git log인수 및 다른 인수로 허용합니다.

출력에서 자체 분기 규칙과 각 분기의 병합 수를 기반으로 상위 분기를 수동으로 감지 할 수 있습니다.

편집 :git rebase 자식 분기에서 자주 사용 하고 병합이 자주 전달되므로 병합 커밋이 너무 많지 않으면이 답변이 제대로 작동하지 않으므로 미리 커밋을 계산하는 스크립트를 작성했습니다 (정상 및 병합) 현재 브랜치와 비교하여 모든 브랜치에서, 및 커밋 뒤 (부모 브랜치에서 뒤 병합은 안됨) 이 스크립트를 실행하고 당신에게 효과가 있는지 알려주세요.

#!/bin/bash
HEAD="`git rev-parse --abbrev-ref HEAD`"
echo "Comparing to $HEAD"
printf "%12s  %12s   %10s     %s\n" "Behind" "BehindMerge" "Ahead" "Branch"
git branch | grep -v '^*' | sed 's/^\* //g' | while read branch ; do
    ahead_merge_count=`git log --oneline --merges $branch ^$HEAD | wc -l`
    if [[ $ahead_merge_count != 0 ]] ; then
        continue
    fi
    ahead_count=`git log --oneline --no-merges $branch ^$HEAD | wc -l`
    behind_count=`git log --oneline --no-merges ^$branch $HEAD | wc -l`
    behind_merge_count=`git log --oneline --merges ^$branch $HEAD | wc -l`
    behind="-$behind_count"
    behind_merge="-M$behind_merge_count"
    ahead="+$ahead_count"
    printf "%12s  %12s   %10s     %s\n" "$behind" "$behind_merge" "$ahead" "$branch"
done | sort -n

"Git : 커밋이 시작된 분기 찾기"에 설명 된대로 git branch --contains <commit>시작 이더라도 커밋이 이루어진 분기를 쉽게 찾아 낼 수 없습니다 (분기의 이름을 바꾸거나, 이동하거나, 삭제할 수 있습니다 ...) .

  • 분기 및 목록 분기를 git branch --contains <commit>나열하지 않을 때까지 커밋에서 커밋으로 돌아갈 수 있습니다 .featuredevelop
  • 그 커밋 SHA1과 비교 /refs/heads/develop

두 커밋 id가 일치하면 가야합니다 ( feature분기가 HEAD에서 원점을 가짐을 의미합니다 develop).


JoeChrysler의 명령 줄 마술을 단순화 할 수 있습니다. 작성된 논리는 다음과 같습니다.

git show-branch -a           |
  ack '\*'                   | # we want only lines that contain an asterisk
  ack -v "$current_branch"   | # but also don't contain the current branch
  head -n1                   | # and only the first such line
  sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
  sed 's/[\^~].*//'            # and with any relative refs (^, ~n) removed

상대적으로 간단한 awk명령 으로 5 개의 개별 명령 필터와 동일한 작업을 수행 할 수 있습니다 .

git show-branch -a | awk -F'[]^~[]' '/\*/ && !/'"$current_branch"'/ {print $2;exit}'  

다음과 같이 분류됩니다.

-F'[]^~[]' 

에서 필드에 선을 분할 ], ^, ~, 및 [문자.

/\*/                      

별표가 포함 된 줄 찾기

&& !/'"$current_branch"'/

...하지만 현재 지점 이름이 아닙니다

{ print $2;               

그러한 행을 찾으면 두 번째 필드 (즉, 첫 번째 필드와 두 번째 필드 구분 문자 사이의 부분)를 인쇄하십시오. 간단한 브랜치 이름의 경우, 대괄호 사이에있는 것입니다. 상대 점프가있는 심판의 경우 수정자가없는 이름 일뿐입니다. 따라서 필드 구분 기호 세트는 두 sed명령 의 의도를 처리 합니다.

  exit }

그런 다음 즉시 종료하십시오. 즉, 첫 번째 일치하는 줄만 처리하므로 출력을 통해 파이프 할 필요가 없습니다 head -n 1.


나는 이것이이 문제를 해결하는 좋은 방법이라고 말하지는 않지만 이것이 효과가있는 것처럼 보입니다.

git branch --contains $(cat .git/ORIG_HEAD) 문제는 파일을 cating하는 것이 git의 내부 작업을 엿보기 때문에 반드시 순방향 호환 (또는 역 호환) 할 필요는 없습니다.


Mark Reed 솔루션의 PowerShell 구현은 다음과 같습니다.

git show-branch -a | where-object { $_.Contains('*') -eq $true} | Where-object {$_.Contains($branchName) -ne $true } | select -first 1 | % {$_ -replace('.*\[(.*)\].*','$1')} | % { $_ -replace('[\^~].*','') }

해결책

이 솔루션 에 기반git show-branch 나는 하나를 결합했습니다, 그래서 내가 (아래 참조) 잘 작동하지 않았다 기반으로git log 이 함께 결국 :

git log --decorate --simplify-by-decoration --oneline \ # selects only commits with a branch or tag
      | grep -v "(HEAD" \                               # removes current head (and branch)
      | head -n1 \                                      # selects only the closest decoration
      | sed 's/.* (\(.*\)) .*/\1/' \                    # filters out everything but decorations
      | sed 's/\(.*\), .*/\1/' \                        # picks only the first decoration
      | sed 's/origin\///'                              # strips "origin/" from the decoration

제한 사항 및주의 사항

  • HEAD를 분리 할 수 ​​있지만 (많은 CI 도구는 지정된 분기에서 올바른 커밋을 빌드하기 위해 그렇게합니다), 원래 분기와 로컬 분기현재 HEAD 와 동등하거나 "위"에 있어야합니다.
  • 방해 되지 않는 태그 가 없어야합니다 . (나는 가정합니다; 자식과 부모 브랜치 사이의 태그를 사용하여 커밋에서 스크립트를 테스트하지 않았습니다)
  • 스크립트는 사실에 의존 "HEAD" 항상 최초의 장식으로 나열 바이 log명령
  • 실행 스크립트 masterdevelop 결과 (주로)에서<SHA> Initial commit

결과

 A---B---D---E---F <-origin/master, master
      \      \
       \      \
        \      G---H---I <- origin/hotfix, hotfix
         \
          \
           J---K---L <-origin/develop, develop
                \
                 \
                  M---N---O <-origin/feature/a, feature/a
                       \   \
                        \   \
                         \   P---Q---R <-origin/feature/b, feature/b
                          \
                           \
                            S---T---U <-origin/feature/c, feature/c

로컬 브랜치 존재에도 불구하고 (예 : SHA가 origin/topic커밋 O을 직접 체크 아웃 한 이후 에만 존재 ) 스크립트는 다음과 같이 인쇄해야합니다.

  • 커밋의 경우 G, H,I (지점 hotfix) →master
  • 커밋의 경우 M, N,O (지점 feature/a) →develop
  • 커밋의 경우 S, T,U (지점 feature/c) →develop
  • 커밋의 경우 P, Q,R (지점 feature/b) →feature/a
  • 커밋의 경우 J, K, L(지점 develop) → <sha> Initial commit*
  • 커밋의 경우 B, D, E, F(지점 master) →<sha> Initial commit

*-또는 '커밋이 마스터의 HEAD보다 위에있는 master경우 develop(~ 마스터는 개발하기가 빠르다)


왜 나를 위해 가지를 보여주지 않았습니까?

다음과 같은 상황에서 기반git show-branch 솔루션 은 신뢰할 수 없음을 입증했습니다.

  • 분리 된 헤드 – 분리 된 헤드 케이스를 포함 grep '\*' \하면`grep '을 교체 할 수 있습니다! – – 그리고 그것은 모든 문제의 시작일뿐입니다
  • 스크립트 masterdevelop실행 하고 결과는 develop각각``
  • 에 지점master 지점 ( hotfix/지점)이와 끝까지 develop자신의 가장 가까운 때문에 부모로서 master분기 부모가 표시되었다 !대신의 *이유.

Ant를 사용한 크로스 플랫폼 구현

    <exec executable="git" outputproperty="currentBranch">
        <arg value="rev-parse" />  
        <arg value="--abbrev-ref" />  
        <arg value="HEAD" />  
    </exec>

    <exec executable="git" outputproperty="showBranchOutput">
        <arg value="show-branch" />  
        <arg value="-a" />  
    </exec>

    <loadresource property="baseBranch">
      <propertyresource name="showBranchOutput"/>
          <filterchain>
            <linecontains>
              <contains value="*"/>
            </linecontains>
            <linecontains negate="true">
              <contains value="${currentBranch}"/>
            </linecontains>
            <headfilter lines="1"/>
            <tokenfilter>
                <replaceregex pattern=".*\[(.*)\].*" replace="\1"/>
                <replaceregex pattern="[\^~].*" replace=""/>
            </tokenfilter>
          </filterchain>
    </loadresource>

    <echo message="${currentBranch} ${baseBranch}" />

@Mark Reed : 커밋 라인에 별표가 포함될뿐만 아니라 별표로 시작해야한다는 것을 추가해야합니다! 그렇지 않으면 별표가 포함 된 커밋 메시지도 일치하는 행에 포함됩니다. 따라서 다음과 같아야합니다.

git show-branch -a | awk -F'[]^~[]' '/^\*/ && !/'"$current_branch"'/ {print $2;exit}'

또는 긴 버전 :

git show-branch -a           |
  awk '^\*'                  | # we want only lines that contain an asterisk
  awk -v "$current_branch"   | # but also don't contain the current branch
  head -n1                   | # and only the first such line
  sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
  sed 's/[\^~].*//'            # and with any relative refs (^, ~n) removed`

vbc=$(git rev-parse --abbrev-ref HEAD)
vbc_col=$(( $(git show-branch | grep '^[^\[]*\*' | head -1 | cut -d* -f1 | wc -c) - 1 )) 
swimming_lane_start_row=$(( $(git show-branch | grep -n "^[\-]*$" | cut -d: -f1) + 1 )) 
git show-branch | tail -n +$swimming_lane_start_row | grep -v "^[^\[]*\[$vbc" | grep "^.\{$vbc_col\}[^ ]" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

Mark Reed의 답변과 같은 목적을 달성하지만 많은 시나리오에서 오작동하지 않는 훨씬 안전한 접근 방식을 사용합니다.

  1. 부모 브랜치의 마지막 커밋은 병합이므로 열이 표시 -되지 않습니다.*
  2. 커밋 메시지에 지점 이름이 포함됨
  3. 커밋 메시지 포함 *

요즘이 일을하고 싶은 사람-Atlassian의 SourceTree 애플리케이션은 브랜치가 서로 관련되는 방식, 즉 시작 지점과 현재 커밋 순서 (예 : HEAD 또는 4 개의 커밋 등)를 시각적으로 잘 보여줍니다. .


소스 트리를 사용하면 커밋 세부 정보> 부모>를 보면 커밋 번호에 밑줄이 나타납니다 (링크)


대안: git rev-list master | grep "$(git rev-list HEAD)" | head -1

내 지점과 master(또는 지정하려는 지점) 모두 마지막 커밋을 가져옵니다.


이 같은 일을했을 때 나에게 효과가 없었습니다. develop > release-v1.0.0 > feature-foo개발하기 위해 다시 돌아갈 것입니다. 리베이스가 관련되어 있음을 주목하십시오.

다음은 올바른 커밋 해시를 제공했습니다.

git log --decorate \
  | grep 'commit' \
  | grep 'origin/' \
  | head -n 2 \
  | tail -n 1 \
  | awk '{ print $2 }' \
  | tr -d "\n"

참고 URL : https://stackoverflow.com/questions/3161204/how-to-find-the-nearest-parent-of-a-git-branch



반응형