prosource

Git으로 분기점을 찾으십니까?

probook 2023. 4. 23. 10:36
반응형

Git으로 분기점을 찾으십니까?

브랜치 마스터와 A를 가진 저장소가 있으며, 둘 사이에 많은 머지 액티비티가 있습니다.마스터를 기반으로 지점 A가 생성되었을 때 저장소에서 커밋을 찾으려면 어떻게 해야 합니까?

저장소는 기본적으로 다음과 같습니다.

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

A를 A는 리비전 A가 .git merge-base (--all)findsfinds.manageda를 합니다.

저도 같은 걸 찾다가 이 질문을 발견했어요물어봐줘서 고마워!

하지만, 여기 보이는 답변은 당신이 (또는 내가 찾고 있던) 당신이 원하는 답변이 아닌 것 같다는 것을 알게 되었습니다.Gcommit(커밋) 커밋)A지르르다

테스트하기 위해 다음 트리(연대순으로 할당된 문자)를 만들었습니다.

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

이것은 당신의 것과 조금 다른 것 같습니다.왜냐하면 저는 (이 그래프를 참조하지 않고) B가 A(D나 E가 아니라)가 아닌 것을 확인하고 싶었기 때문입니다.다음은 SHA 접두사와 커밋 메시지에 첨부된 문자입니다(내 레포는 여기에서 복제할 수 있습니다).

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

그래서 목표는 B를 찾는 입니다.조금 손질해 본 결과, 다음의 3가지 방법을 알게 되었습니다.


1. 시각적으로 gitk:

다음과 같은 트리가 표시됩니다(마스터에서 보기).

마스터에서 gitk 화면 캡처

또는 (토픽에서 참조):

토픽에서 gitk 화면 캡처

모두 B내 그래프에서.클릭하면 그래프 바로 아래의 텍스트 입력 필드에 전체 SHA가 표시됩니다.


2. 시각적으로, 단자에서:

git log --graph --oneline --all

(「/」:「」)--decorate지점 이름, 태그 등의 표시를 추가할 수도 있습니다.위의 명령줄에 이 명령어를 추가하지 않는 것은 다음 출력에 해당 명령어의 사용이 반영되지 않기 때문입니다.)

(가르침, (가르침)을 나타냅니다.git config --global color.ui auto

git log --graph --online --all 출력

또는 스트레이트 텍스트:

* a9546a2 토픽에서 마스터로 병합|\| * 648ca35 마스터를 토픽에 통합| |\| * | 132ee2a 토픽 브랜치의 첫 번째 커밋* | | e7c863d 마스터가 토픽에 병합된 후 마스터에서 커밋| |/|/|* | 37ad159 마스터에 대한 사후 커밋|/* 브런치 전 마스터에서 6afd7f 두 번째 커밋* 마스터에 대한 4112403의 초기 커밋

경우든 6afd7f 커밋으로 됩니다.B 내내 in in in in in in in in in in in inA네 안에.


3. 셸 매직으로:

위와 같은 것을 원하는지, 아니면 하나의 리비전만 가져올 수 있는 단일 명령어인지 질문에는 명시되어 있지 않습니다.후자는 다음과 같습니다.

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

~/.gitconfig에 넣을 수도 있습니다(주의: 대시 후행은 중요합니다. Brian에게 주의를 기울여 주셔서 감사합니다).

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

이 작업은 다음 명령줄을 통해 수행할 수 있습니다(견적과 함께 변환).

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

★★★★★★zsh로 할 수 .bashsh동작하지 않습니다.<()이 vanilla vanilla vanilla에 sh(@conny님, 이 페이지의 다른 답변 코멘트로 알려 주셔서 다시 한 번 감사드립니다!)

주의: 위의 대체 버전:

Liori동일한 브랜치를 비교할 때 위의 내용이 떨어질 수 있음을 지적하고 SED 폼을 믹스에서 삭제하는 대체 diff 폼을 생각해 낸 후 이 "안전"을 만듭니다(마스터와 마스터를 비교해도 결과(즉, 최신 커밋)를 반환합니다).

.git-config 행으로서:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

셸에서:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

테스트 트리(잠시 사용 불가, 죄송합니다.다시 돌아왔습니다)에서는 마스터와 토픽(각각 커밋 G와 B 제공)에서 모두 사용할 수 있게 되었습니다.다시 한 번 고마워요, 리오, 대체 양식 고마워요


그래서 생각해 낸 것입니다.나한테는 효과가 있는 것 같아.또한 편리한 에일리어스도 몇 개 사용할 수 있습니다.

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

해피 겟잉!

다음을 찾고 있습니다.

git merge-base는 3방향 머지에서 사용하는2개의 커밋 간에 최적의 공통 조상을 찾습니다.만약 후자가 전자의 조상이라면, 한 공통의 조상이 다른 공통의 조상보다 낫다.더 나은 공통 조상이 없는 공통 조상은 가장 좋은 공통 조상(예: 병합 기준)입니다.커밋 쌍에는 여러 개의 Marge Base가 있을 수 있습니다.

가 쓴 적이 있어요.git rev-list이런 일 때문에.예를 들어 (3개의 도트에 주의)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

분기점을 뱉어냅니다.이 방법은 완벽하지 않습니다.여러 번 마스터를 지점A에 통합했기 때문에 가능한 지점 몇 (기본적으로 원래 지점과 마스터를 지점A에 병합한 각 지점)가 분할됩니다.그러나 최소한 가능성을 좁혀야 한다.

~/.gitconfig같이요.

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

이렇게 부를 수 있습니다.

$ git diverges branch-a master

간결한 명령어를 좋아하신다면

git rev-list $(first-parent ^first_parent_name master | tail - n1)^^! 

여기 설명이 있습니다.

다음 명령어는 branch_name 작성 후 발생한 마스터 내의 모든 커밋 목록을 보여줍니다.

git rev-list --first-parent ^first_name 마스터

이러한 커밋 중 첫 번째 커밋에만 관심이 있으므로 출력의 마지막 행이 필요합니다.

git rev-list ^first_name --first-parent master | tail -n1

"branch_name"의 조상이 아닌 초기 커밋의 부모는 정의상 "branch_name"에 있으며 "master"에 있는 무언가의 조상이므로 "master"에 있습니다.즉, 두 지점 모두에서 가장 먼저 커밋을 받으실 수 있습니다.

명령어

git rev-list commit^^!

부모 커밋 참조를 표시하는 방법일 뿐입니다.사용할 수 있습니다.

git log - 1 commit^

뭐 그런 거.

추신: 저는 조상의 질서는 무관하다는 주장에 동의하지 않습니다.그것은 당신이 무엇을 원하는 지에 달려 있어요.예를 들어, 이 경우

_C1__C2_____________________\_XXXXX_ 지점 A(X는 마스터와 A 사이의 임의의 크로스오버를 나타냅니다)\___/ 지점 B

C2 를 「브랜치」커밋으로서 출력하는 것은 지극히 타당합니다.이때 개발자가 "마스터"에서 손을 뗐다.그가 분기했을 때, 지점 "B"는 그의 지점에도 합병되지 않았습니다!이것이 이 투고의 솔루션이 제공하는 것입니다.

브랜치 "A"의 발신기지에서 마지막 커밋까지의 모든 경로가 C를 통과하도록 마지막 커밋 C를 원하는 경우 조상의 순서를 무시합니다.이것은 순전히 토폴로지이며, 동시에 두 가지 버전의 코드가 언제 실행되는지 알 수 있습니다.그 때, 머지 베이스 베이스의 어프로치를 채용하면, 이 예에서는 C1이 반환됩니다.

목적:이 답변은 이 스레드에 제시된 다양한 답변을 테스트합니다.

테스트 저장소

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)
$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

올바른 솔루션

유일하게 효과가 있는 솔루션은 린데스가 올바르게 반환하는 것입니다.A:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

Charles Bailey가 지적했듯이 이 솔루션은 매우 취약합니다.

당신이 가 if if if ifbranch_Amaster 해서 합치는 거예요.masterbranch_A개입 커밋 없이 린즈의 솔루션은 가장 최근의 첫 번째 발산만 제공합니다.

즉, 워크플로우의 경우 나중에 확실하게 브랜치를 찾을 수 있을지는 장담할 수 없기 때문에 길게 실행되는 브랜치의 브랜치포인트에 태그를 붙이는 것이 좋다고 생각합니다.

이 모든 this this this this this this this this 。git합니까?hg이름 있는 브런치 콜블로거 jhw는 그의 기사 Why I Like Mercurial More Than Git과 그의 후속 기사 More On Mercurial vs.에서 이러한 혈통 대 가족이라고 부른다. Git(그래프 포함!)나는 사람들이 왜 몇몇 수은 변환자들이 브랜치 이름을 붙이지 않은 것을 놓쳤는지 보기 위해 그것들을 읽을 것을 권하고 싶다.git.

잘못된 해결 방법

mipadi가 제공하는 솔루션은 두 가지 답변을 반환합니다.I ★★★★★★★★★★★★★★★★★」C:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

Greg Hewgill Return이 제공하는 솔루션I

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Karl이 제공한 솔루션은X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

저장소 복제 테스트

테스트 저장소를 만들려면:

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

유일한 추가는 브랜치를 작성한 지점과 찾고자 하는 커밋을 명시하는 태그입니다.

git 버전이 이것에 큰 차이가 있는지는 의문입니다만,

$ git --version
git version 1.7.1

찰스 베일리가 예제 저장소를 스크립팅할 수 있는 더 간단한 방법을 보여줘서 고마워.

일반적으로 이것은 불가능합니다.브랜치 이력에서는, 브랜치 앤 머지가 브랜치 되기 전의 브랜치 앤 머지(branch-and-merge)로, 2개의 브랜치의 중간 브랜치(intermed branch)가 같은 것을 나타내고 있습니다.

git에서 브랜치는 단지 역사의 일부에 대한 최신 이름일 뿐이다.그들은 사실 강한 정체성을 가지고 있지 않다.

보통 두 개의 커밋의 병합 기반(Greg Hewgill의 답변 참조)이 훨씬 더 유용하기 때문에 두 지점이 공유한 최신 커밋을 제공하므로 이것은 큰 문제가 되지 않습니다.

브런치 역사상 어느 시점에서 브런치가 완전히 통합된 상황에서는 커밋된 부모의 순서에 의존하는 솔루션은 분명 효과가 없습니다.

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

이 기술은 부모와의 연동이 반대로 이루어진 경우에도 다운됩니다(예를 들어 임시 분기를 사용하여 마스터로 테스트 Marge를 수행한 후 기능 분기로 빠르게 전송하여 추가 구축).

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"

을 볼 수 방법git log --graph옵션을 사용하는 것입니다.

를 들어, 승인된 답변에서 repo를 취합니다.

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

★★를 추가해 .--first-parent:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

그게 더 쉬워요!

경우 를 지정하는 의 브런치를 지정합니다.--all:

$ git log --decorate --oneline --graph --first-parent master origin/topic

이런 거 어때?

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

분명 뭔가 놓치고 있는 것이 있습니다만, IMO, 위의 모든 문제는 항상 과거로 거슬러 올라가는 분기점을 찾으려고 하기 때문에 발생하고 있습니다.그 때문에, 머지 조합에 의해서 모든 종류의 문제가 발생합니다.

대신, 저는 두 지점이 많은 역사를 공유하고 있다는 사실을 바탕으로, 분기 전의 모든 이력은 100% 동일하기 때문에, 다시 돌아가지 않고 (첫 번째 커밋부터) 두 지점의 첫 번째 차이점을 찾는 것을 제안합니다.분기점은 단순히 발견된 첫 번째 차이의 모선이 됩니다.

실제:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

그리고 내 일상적인 사건들을 해결해주고 있어물론 커버되지 않는 국경도 있겠지만...ciao:-)

많은 연구와 논의를 통해 적어도 현재 버전의 Git에서는 모든 상황에서 효과가 있는 마법의 총알은 없다는 것이 명백해졌다.

는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★tail가지만들어져요.tail참조. 이 참조는 브랜치가 재기초될 때마다 업데이트됩니다.

데벨가지 분기점만 .devel@{tail} 그거에요

https://github.com/felipec/git/commits/fc/tail

Git 2.36은 다음과 같은 간단한 명령을 제안합니다.

(branch_A_tag)
     |
--X--A--B--C--D--F  (master) 
      \   / \   /
       \ /   \ /
        G--H--I--J  (branch A)
vonc@vclp MINGW64 ~/git/tests/branchOrigin (branch_A)
git log -1 --decorate --oneline \
  $(git rev-parse \
     $(git rev-list --exclude-first-parent-only ^main branch_A| tail -1)^ \
   )
 80e8436 (tag: branch_A_tag) A - Work in branch main
  • git rev-list --exclude-first-parent-only ^main branch_A 주다J -- I -- H -- G
  • tail -1
  • git rev-parse G^첫 입니다.A(「」)

테스트 스크립트의 경우:

mkdir branchOrigin
cd branchOrigin
git init
git commit --allow-empty -m "X - Work in branch main"
git commit --allow-empty -m "A - Work in branch main"
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch main"
git switch -c branch_A branch_A_tag
git commit --allow-empty -m "G - Work in branch_A"
git switch main
git merge branch_A       -m "C - Merge branch_A into branch main"
git switch branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge main         -m "I - Merge main into branch_A"
git switch main
git commit --allow-empty -m "D - Work in branch main"
git merge branch_A       -m "F - Merge branch_A into branch main"
git switch branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

그 결과, 다음과 같습니다.

vonc@vclp MINGW64 ~/git/tests/branchOrigin (branch_A)
$ git log --oneline --decorate --graph --branches --all
* a55a87e (HEAD -> branch_A) J - Work in branch_A branch
| *   3769cc8 (main) F - Merge branch_A into branch main
| |\
| |/
|/|
* |   1b29fa5 I - Merge main into branch_A
|\ \
* | | e7accbd H - Work in branch_A
| | * 87a62f4 D - Work in branch main
| |/
| *   7bc79c5 C - Merge branch_A into branch main
| |\
| |/
|/|
* | 0f28c9f G - Work in branch_A
| * e897627 B - Work in branch main
|/
* 80e8436 (tag: branch_A_tag) A - Work in branch main
* 5cad19b X - Work in branch main

즉, 다음과 같습니다.

(branch_A_tag)
     |
--X--A--B--C--D--F  (master) 
      \   / \   /
       \ /   \ /
        G--H--I--J  (branch A)

Git 2.36(Q2 2022)git log(man)에서 "와 친구들은 옵션을 배웠습니다.--exclude-first-parent-only첫 번째 부모 체인을 따라서만 재미없는 비트를 전파합니다.--first-parent옵션은 첫 번째 부모 체인에서만 UNINESTING 비트가 없는 커밋을 나타냅니다.

Jerry jerry-skydioZhang()의 커밋 9d505b7(2022년 1월 11일)을 참조하십시오.
(Junio C Hamano에 의해 병합됨--커밋 708cbef, 2022년 2월 17일)

git-rev-list: --first-parent-only 플래그 추가

서명자: Jerry Zhang

사용자의 로컬 변경을 열거할 수 있도록 역사상 분기가 일부 통합 분기에서 처음 분기된 시기를 알면 유용합니다.
그러나 이러한 로컬 변경에는 임의 병합이 포함될 수 있으므로 분기점을 찾을 때 이 병합 구조를 무시해야 합니다.

'우리에게 가르쳐 ', '우리에게 가르쳐 주세요.'rev-list이 ""를 받아들인다""--exclude-first-parent-only": 제외된 커밋의 트래버설을번째 부모 링크만 따르도록 제한합니다.

-A-----E-F-G--main
  \   / /
   B-C-D--topic

이 예에서 목표는 병합된 토픽 분기를 나타내는 집합 }을(를) 반환하는 것입니다.main★★★★★★ 。
git rev-list topic ^main(man) 를 제외했기 때문에 커밋은 반환되지 않습니다.main합니다.topic뿐만 아니라.
git rev-list --exclude-first-parent-only topic ^main(man) 그러나 돌아올 것이다{B, C, D}원하는 대로

플래그에 를 추가하고 를 명확히 .--first-parent포함된 커밋 세트를 통과하는 데만 적용되는 것을 나타냅니다.

rev-list-options이제 man 페이지에 다음이 포함됩니다.

--first-parent

포함할 커밋을 찾을 경우 Marge 커밋을 확인한 후 첫 번째 부모 커밋만 수행합니다.

이 옵션은 특정 토픽브런치로의 Marge는 갱신된 업스트림에만 적응하는 경향이 있기 때문에 특정 토픽브런치의 진화를 표시할 때 보다 나은 개요를 제공할 수 있습니다.또, 이 옵션을 사용하면, 이러한 Marge에 의해서 이력에 가져온 개개의 커밋을 무시할 수 있습니다.

rev-list-options이제 man 페이지에 다음이 포함됩니다.

--exclude-first-parent-only

제외할 커밋을 찾을 때('{carett} 포함) 병합 커밋을 확인한 후 첫 번째 부모 커밋만 따르십시오.

임의 병합이 유효한 토픽 분기 변경 사항일 수 있으므로 원격 분기에서 분기된 지점에서 토픽 분기 변경 사항 집합을 찾는 데 사용할 수 있습니다.


코멘트에서 anarcat에 의해 언급되었듯이, 브랜치가 에서 파생되지 않은 경우master단, 、 、 「 」 、 「 」 。main , 「」prod 다른할 수 있습니다. 다른 브랜치에서는 다음을 사용할 수 있습니다.

git for-each-ref --merged="$local_ref" --no-contains="$local_ref" \
    --format="%(refname:strip=-1)" --sort='-*authordate' refs/heads

philb코멘트에서 옵션(출력 제외 경계 커밋)도 언급하고 있습니다.경계 커밋은 앞에 붙습니다.-

git rev-list --exclude-first-parent-only --boundary ^main  branch_A | tail -1 

A의 「」를 필요로 하지 .git rev-parse G^

저도 최근에 이 문제를 해결할 필요가 있어서 결국 https://github.com/vaneyckt/git-find-branching-point에 Ruby 스크립트를 작성하게 되었습니다.

나는 이 일로 기쁨을 얻고 있는 것 같다.

git rev-list branch...master

마지막 행은 브랜치에서의 첫 번째 커밋입니다.그러면 그 부모를 얻는 것이 중요합니다.그렇게

git rev-list -1 `git rev-list branch...master | tail -1`^

나에게는 효과가 있는 것 같고 diff 등이 필요 없다(diff 버전이 없기 때문에 도움이 된다).

수정:마스터 브런치에서는 동작하지 않지만 스크립트로 하고 있기 때문에 문제가 되지 않습니다.

여기 이전 답변의 개선된 버전이 있습니다.브런치가 처음 생성된 위치를 검색하려면 Marge의 커밋메시지에 의존합니다.

여기 언급된 모든 저장소에서 작동하며 메일링 목록에 생성된 까다로운 저장소도 해결했습니다.이거 시험도 봤어요.

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

다음 명령어는 커밋A의 SHA1을 표시합니다.

git merge-base --fork-point A

실제로 불가능할 수 있으며(다만, 추가 데이터가 있는 경우는 예외로 합니다), 여기서의 솔루션이 기능하지 않는 경우가 있습니다.

Git은 참조 이력(브런치 포함)을 보존하지 않습니다.각 분기(헤드)의 현재 위치만 저장합니다.즉, 시간이 지남에 따라 git의 브랜치 이력이 손실될 수 있습니다.예를 들어 분기할 때마다 원래 분기였던 분기가 즉시 손실됩니다.브랜치가 하는 일은 다음과 같습니다.

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

첫 번째로 커밋된 것은 브랜치라고 생각할 수 있습니다.이런 경향이 있지만 항상 그렇지는 않다.상기의 조작 후에, 어느쪽의 지점에도 커밋 하는 것을 막을 수 없습니다.또한 git 타임스탬프의 신뢰성은 보증되지 않습니다.두 가지 모두에 전념해야 진정으로 구조적으로 분점이 됩니다.

그림에서 우리는 개념적으로 커밋 번호를 매기는 경향이 있지만, git는 커밋 트리가 분기할 때 시퀀스의 실질적인 안정적인 개념을 가지고 있지 않습니다.이 경우 숫자(순서 표시)는 타임스탬프에 의해 결정된다고 가정할 수 있습니다(모든 타임스탬프를 동일하게 설정하면 git UI가 어떻게 처리되는지 보는 것도 재미있을 수 있습니다).

인간이 개념적으로 기대하는 것은 다음과 같습니다.

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

실제로 얻을 수 있는 것은 다음과 같습니다.

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

B1이 원래 브런치라고 생각할 수 있지만 단순히 데드 브런치일 가능성이 있습니다(누군가는 -b를 체크 아웃했지만 그것을 커밋하지 않았습니다).둘 다에 전념할 때까지는 git 내에서 합법적인 브랜치 구조를 얻을 수 없습니다.

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

C1이 C2 및 C3보다 먼저 온 것을 항상 알고 있습니다만, C3보다 먼저 C2가 온 것인지 C3보다 먼저 온 것인지(예를 들어 워크스테이션의 시각을 임의의 것으로 설정할 수 있기 때문에) 신뢰할 수 없습니다.B1과 B2도 어느 지점이 먼저인지 알 수 없기 때문에 오해의 소지가 있습니다.당신은 많은 경우에 그것을 매우 훌륭하고 정확하게 추측할 수 있다.그것은 약간 경주용 트랙 같다.일반적으로 모든 것이 자동차와 같기 때문에 한 바퀴 뒤에서 오는 차가 한 바퀴 뒤에서 출발했다고 가정할 수 있습니다.또한 매우 신뢰할 수 있는 규약도 있습니다. 예를 들어 마스터는 거의 항상 가장 오래 산 분기를 대표하지만 안타깝게도 이마저도 그렇지 않은 경우가 있습니다.

다음으로 이력 보존 예를 나타냅니다.

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

여기서의 진짜는 또한 오해의 소지가 있다.왜냐하면 우리는 그것을 왼쪽에서 오른쪽으로, 뿌리부터 잎까지 읽기 때문이다.Git은 그렇게 하지 않는다.(A->B)가 하고 있는 것은 (A<-B 또는 B->A)입니다.그것은 그것을 처음부터 끝까지 읽는다.참조는 어디에나 있을 수 있지만 적어도 활성 가지에 대해서는 잎이 되는 경향이 있습니다.참조는 커밋을 가리키고 커밋은 부모에게만 포함되며 자녀에게는 포함되지 않습니다.커밋이 머지 커밋일 경우 여러 부모 커밋이 됩니다.첫 번째 부모는 항상 Marge된 원래 커밋입니다.다른 부모는 항상 원래 커밋에 병합된 커밋입니다.

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

이것은 매우 효율적인 표현이 아니라 각 ref(B1 및 B2)에서 git가 취할 수 있는 모든 경로를 표현한 것입니다.

Git의 내부 스토리지는 다음과 같습니다(부모로서 A가 두 번 나타나는 것은 아닙니다).

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

raw git 커밋을 덤프하면 0개 이상의 부모 필드가 표시됩니다.0이 있으면 부모가 없고 커밋이 루트임을 의미합니다(실제로 여러 루트를 가질 수 있습니다).하나가 있는 경우 병합이 없었음을 의미하며 루트 커밋도 아닙니다.둘 이상의 경우 커밋은 머지 결과이며 첫 번째 이후의 모든 부모가 머지 커밋임을 의미합니다.

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

둘 다 A를 치면 체인은 같아지고 그 전에 체인은 완전히 달라집니다.또 다른 두 개의 커밋이 공통되는 첫 번째 커밋은 공통의 조상이며, 어디서부터 갈라졌는가 하는 것입니다.여기서 commit, branch, ref라는 용어 사이에 혼동이 있을 수 있습니다.실제로 커밋을 병합할 수 있습니다.이것이 바로 머지가 실제로 하는 일입니다.참조는 단순히 커밋을 가리키고 브랜치는 .git/refs/heads 폴더의 참조에 지나지 않습니다.폴더 위치는 참조가 태그와 같은 다른 것이 아니라 분기임을 결정하는 것입니다.

이력을 잃게 되는 것은 상황에 따라 머지가 두 가지 작업 중 하나를 수행한다는 것입니다.

고려사항:

      / - B (B1)
    - A
      \ - C (B2)

이 경우 어느 방향으로든 머지를 하면 현재 체크아웃된 브런치가 가리키는 커밋과 현재 브런치에 머지한 브런치 끝에 커밋으로 첫 번째 부모와의 새로운 커밋이 생성됩니다.양쪽 브랜치가 결합해야 하는 공통 조상 이후 변경 사항이 있기 때문에 새로운 커밋을 작성해야 합니다.

      / - B - D (B1)
    - A      /
      \ --- C (B2)

이 시점에서 D(B1)는 양쪽 브런치(자체 및 B2)로부터의 변경 세트를 모두 가지게 되었습니다.하지만 두 번째 브랜치에는 B1에서 변경된 사항이 없습니다.B1에서 B2로 변경 내용을 병합하여 동기화할 경우 다음과 같이 보일 수 있습니다(단, --no-ff를 사용하여 git merge를 강제로 수행할 수 있습니다).

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

B1에 커밋이 추가되어도 당신은 그것을 얻을 수 있습니다.B1에 없는 B2에 변경이 없는 한 두 브랜치는 병합됩니다.리베이스와 같은 고속 전송(이력 리베이스도 사용 또는 선형화)을 수행합니다.단, 리베이스와는 달리 브랜치 간에 변경 세트를 적용할 필요가 없습니다.

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

지하 1층 작업을 중단하면 장기적으로 역사를 보존하는 데 큰 문제가 없습니다.일반적으로는 B1(마스터일 수 있음)만 진행되므로 B2 이력에서 B2의 위치는 B1로 병합된 지점을 나타냅니다.이것은 GIT가 기대하는 B의 브랜치 A에 대해서, 변경이 축적되면 얼마든지 A를 B에 Marge 할 수 있지만, B를 다시 A에 Marge 할 때는 B 이후로는 할 수 없다고 생각됩니다.작업중이던 브랜치에 빨리 Marge한 후 브랜치로 작업을 계속하면 그때마다 B의 이전 이력을 지웁니다.소스로의 고속 전송 후 브랜치로의 커밋 후 매번 새로운 브랜치를 만듭니다.Fast Forward 커밋이 이력 및 구조에서 볼 수 있는 많은 브랜치 또는 머지일 경우 해당 브랜치 이름이 무엇이었는지 또는 두 개의 다른 브랜치가 실제로 같은 브랜치인지 판별할 수 없습니다.

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1 ~ 3 및 5 ~8은 4 또는 9의 이력을 따라가면 표시되는 구조적인 분기입니다.git에서는 이 이름 없는, 참조되지 않은 구조적 브랜치가 이름의 브랜치와 참조 브랜치 중 어느 것에 속하는지 알 수 있는 방법이 없습니다.이 그림에서 0~4는 B1에 속하고 4~9는 B2에 속한다고 생각할 수 있지만, 4와 9는 어느 나뭇가지에 속하는지 알 수 없기 때문에 단순히 B2에 속하는지, 5는 B1에 속하는지 알 수 있도록 그렸습니다.이 경우 16개의 다른 가능성이 있으며, 각각의 구조적 가지가 명명된 가지에 속할 수 있습니다.이는 이러한 구조적인 브랜치가 삭제된 브랜치로부터 온 것이 없거나 마스터로부터 꺼낼 때 브랜치 자체를 Marge한 결과라고 가정합니다(2개의 저장소에서 같은 브랜치명은 2개의 브랜치입니다.다른 저장소는 모든 브랜치를 분기하는 것과 같습니다).

이 문제를 해결하기 위한 많은 git 전략이 있습니다.git merge를 강제로 빠르게 진행하지 않고 항상 merge 브랜치를 만들 수 있습니다.브랜치 이력을 보존하는 최악의 방법은 선택한 규칙에 따라 태그나 브랜치(태그 권장)를 사용하는 것입니다.합병하려는 지점에 더미 같은 빈 커밋은 추천하지 않습니다.매우 일반적인 규칙은 분기를 완전히 닫을 때까지 통합 분기에 병합하지 않는 것입니다.이것은, 지점을 가지는 것에 관해서 일하고 있는 것과 같이, 사람들이 지키려고 노력해야 하는 습관입니다.그러나 현실에서 이상은 항상 실용적인 의미는 아니다. 옳은 일을 하는 것이 모든 상황에서 실행 가능한 것은 아니다.브런치 상에서 작업하고 있는 것이 고립되어 있는 경우, 그 이외의 경우, 복수의 개발자가 1개의 브런치에서의 변경을 신속히 공유할 필요가 있는 상황이 되는 경우가 있습니다(이상적으로는 1개의 브런치에서도 작업을 하고 싶은 경우가 있습니다만, 모든 상황에 적합한 것은 아닙니다.일반적으로 브런치에서는 2명이 작업하고 있는 경우도 있습니다).피하고 싶은 것).

이 문제에 대한 해답은 아니지만, 저는 제가 오래 산 지점을 가지고 있을 때 사용하는 접근법에 주목할 필요가 있다고 생각했습니다.

에 같은 .-init: 「」:feature-branch ★★★★★★★★★★★★★★★★★」feature-branch-init.

(이렇게 대답하기 어려운 질문이 있다니 좀 이상해!)

를 사용하면 이 문제가 되는 것 .git reflog <branchname>에 브랜치 작성을 포함한 브랜치의 모든 커밋을 나타냅니다.

이는 마스터로 병합되기 전에 2개의 커밋을 가지고 있던 브랜치입니다.

git reflog june-browser-updates
b898b15 (origin/june-browser-updates, june-browser-updates) june-browser-updates@{0}: commit: sorted cve.csv
467ae0e june-browser-updates@{1}: commit: browser updates and cve additions
d6a37fb june-browser-updates@{2}: branch: Created from HEAD

분기점에서 커밋을 찾으려면 이 옵션을 사용합니다.

git log --ancestry-path master..topicbranch

문제는 한쪽에서는 양쪽 브랜치 간에 가장 최근의 단일 커밋을 찾고 다른 한쪽에서는 가장 오래된 공통 상위 항목(아마도 repo의 초기 커밋)을 찾는 것입니다.이것은 분기점이 무엇인지에 대한 제 직관과 일치합니다.

.git shell 명령어는 git shell 명령어이기 때문입니다.git rev-list델의 가장 강력한 툴은 커밋에 도달하는 경로를 제한하지 않습니다.가장 가까운 곳은git rev-list --boundary: (비고:git rev-list --ancestry-path흥미롭긴 한데 어떻게 유용하게 써야 할지 모르겠어요.)

다음은 스크립트입니다.https://gist.github.com/abortz/d464c88923c520b79e3d이것은 비교적 간단하지만, 루프로 인해 요점을 입증하기에 충분히 복잡합니다.

하는 다른 은 단순한에서 효과가 것은 .git rev-list --first-parent두 순서 중 하나와 병합될 수 있기 때문에 이력을 선형화할 때 신뢰할 수 없습니다.

git rev-list --topo-order한편, 는 매우 편리합니다(지형순서에서의 워킹 커밋).다만, 디프먼트를 실시하는 것은 매우 취약합니다.즉, 특정 그래프에 복수의 토폴로지 순서가 있을 수 있기 때문에, 순서의 안정성에 의존하고 있습니다.하지만 strongk7의 솔루션은 대부분의 경우 매우 잘 작동합니다.하지만 레포의 전체 역사를 살펴봐야 하기 때문에 내 것보다 더 느리긴 하지만...2회. :-)

다음은 svn log --stop-on-copy와 동등한 git을 구현하며 브랜치 오리진을 찾는 데도 사용할 수 있습니다.

접근

  1. 모든 브랜치 헤드 취득
  2. 대상 분기의 mergeBase를 서로 수집합니다.
  3. git.log 및 반복
  4. mergeBase 목록에 표시되는 첫 번째 커밋에서 중지합니다.

모든 강이 바다로 흐르듯이, 모든 가지는 마스터하기 위해 흐르기 때문에, 겉보기에 무관해 보이는 가지들 사이의 결합 기지를 발견합니다.분기에서 조상까지 거슬러 올라가면 이론상으로는 이 분기의 원점이 되어야 하기 때문에 첫 번째 잠재적 병합 기지에서 멈출 수 있습니다.

메모들

  • 저는 형제자매와 사촌자매 지부가 서로 합쳐지는 이 접근 방식을 시도해 본 적이 없습니다.
  • 더 나은 해결책이 있을 거란 걸 알아요

상세: https://stackoverflow.com/a/35353202/9950

왜 사용하지 않는가?

git log master..enter_your_branch_here --oneline | tail -1

이것에 , 커밋을 수 (「A」의 함수, 「A」의 함수, 「A」의 함수)...및 , 。tail -1지정된 브랜치(브런치A)의 첫 번째 커밋을 검출하는 출력의 마지막 행을 반환합니다.

그리고 그 커밋의 SHA를 통해

git log enter_the_sha_here^1 --oneline | head -1

지정된 커밋 에 모든 됩니다(「」의 ).^1 및 )의 개요head -1브랜치 A의 첫 번째 커밋(일명 브랜치포인트)에 "1개의 커밋 전" 출력의 첫 번째 행을 반환합니다.


하나의 실행 가능한 명령어로서:

for COMMIT in $(git log --format=format:%H  master..HEAD | tail -1) ; do
    git log $COMMIT^1 --oneline | head -1
done

지점 A 내에서 위 사항을 실행합니다(HEAD의 기능).

브런치 A의 reflog를 조사하여 작성된 커밋 및 브런치가 가리킨 커밋의 전체 이력을 확인할 수 있습니다.는 에 있습니다..git/logs.

다음 명령을 사용하여 마스터에서 도달할 수 없는 branch_a에서 가장 오래된 커밋을 반환할 수 있습니다.

git rev-list branch_a ^master | tail -1

아마도 그 커밋의 부모가 마스터에서 실제로 도달할 수 있는지 온전성 체크를 더하면...

여기에 언급된 모든 코너 케이스에 대처할 수 있는 방법을 찾은 것 같습니다.

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

Charles Bailey는 조상들의 순서에 근거한 솔루션이 한정된 가치만을 갖는다는 것은 매우 옳습니다.결국 이러한 레코드는 「이 커밋은 브랜치 X로부터 온 것입니다」라고 하는 일종의 레코드가 필요하지만, 그러한 레코드는 이미 존재합니다.디폴트로는 「Merge branch_A」라고 하는 커밋 메세지가 사용됩니다.이것은 두 번째 부모(commit^2)로부터의 모든 커밋이 'branch_A'로부터 왔고 첫 번째 부모(commit^1)에 병합되었음을 나타냅니다.이것은 'master'입니다.

이 정보로 'branch_A'의 첫 번째 병합('branch_A'가 실제로 존재했을 때)과 분기점인 병합 베이스를 찾을 수 있습니다.

마크 부스와 찰스 베일리의 저장소로 해 봤는데 해결이 잘 되던데 어떻게 안 되겠어?이 방법이 작동하지 않는 유일한 방법은 분기 정보가 실제로 손실되도록 병합에 대한 기본 커밋 메시지를 수동으로 변경한 경우입니다.

유용성:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

'어 주세요'를하면 돼요.git branch-point branch_A

즐기세요;)

언급URL : https://stackoverflow.com/questions/1527234/finding-a-branch-point-with-git

반응형