합치기 - intellij git merge




작업 흐름 및 리베이스 비교 및 병합 질문 (7)

어쨌든, 최근 브랜치에서 작업 흐름을 따라 가고 있었고 마스터로 다시 병합하려고 시도했을 때 모두 지옥으로갔습니다. 중요하지 않아야 할 일들에 수 많은 갈등이있었습니다. 갈등은 나에게 의미가 없었습니다. 모든 일을 정리하는 데 하루가 걸렸지 만 결국 로컬 마스터가 모든 충돌을 해결했기 때문에 결국 원격 마스터에게 강제로 밀어 넣었습니다.하지만 원격 호스트는 아직 행복하지 않았습니다.

파트너 나 제안 된 워크 플로우 모두에서 이해가되지 않는 갈등을 경험해야합니다. 비록 당신이 가지고 있었다면, 제안 된 워크 플로우를 따르고 있다면 해결 후 '강제적 인'푸시가 필요하지 않아야합니다. 실제로 푸시하는 지점을 병합하지 않았지만 원격 팁의 하위가 아닌 지점을 푸시해야한다는 의미입니다.

무슨 일이 있었는지 조심스럽게 봐야 할 것 같아. 다른 사람이 로컬 브랜치를 만든 후 원격 브랜치를 로컬 브랜치에 다시 병합하려고 시도한 시점에서 원격 마스터 브랜치를 (의도적으로 또는 비공개로) 되 감을 수 있습니까?

다른 버전 관리 시스템과 비교할 때 Git을 사용하면 도구를 사용하는 것이 줄어들고 소스 스트림의 근본적인 문제를 해결할 수 있습니다. 힘내는 마법을 수행하지 않으므로 충돌하는 변화가 충돌을 일으키지 만 커밋 태생을 추적하여 쓰기 작업을 쉽게 수행해야합니다.

나는 Git을 다른 개발자와 함께 프로젝트에서 몇 달간 사용 해왔다. SVN 에 대한 수년 간의 경험이 있으므로 관계에 많은 짐을 가져다 줄 것입니다.

Git이 분기 및 병합에 우수하다는 소식을 들었습니다. 지금까지는 그것을 볼 수 없었습니다. 물론, 분기는 간단하지만, 병합을 시도하면 모든 것이 지옥으로 빠져 든다. 자, 저는 SVN에서 익숙해졌지만 다른 서브 시스템에 대해 하나의 서브 - 버전 시스템을 교환 한 것으로 보입니다.

제 파트너는 제 문제가 제게 욕 많이 합병하려는 욕망에서 비롯된 것이며 많은 경우 병합 대신 리베이스를 사용해야한다고 말합니다. 예를 들어, 그가 내려 놓은 워크 플로우는 다음과 같습니다.

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

기본적으로 기능 분기를 만들고 항상 마스터에서 분기로 리베이스하고 분기에서 마스터로 병합합니다. 중요한 점은 지점은 항상 로컬로 유지된다는 것입니다.

다음은 내가 시작한 워크 플로입니다.

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

필자는 필자가 rebase 대신 merge를 항상 사용하고 필자의 feature 브랜치 (그리고 feature 브랜치 커밋)를 원격 저장소에 push한다.

원격 지사에 대한 나의 논리는 내가 일하는 동안 내 일을 백업하고 싶다는 것이다. 우리 저장소는 자동으로 백업되며 문제가 생겨 복원 될 수 있습니다. 내 노트북은 그렇지 않거나 철저하지 않습니다. 따라서 다른 곳에 미러링되지 않은 코드가 내 랩톱에있는 것을 싫어합니다.

리베이스 대신 병합을 수행하는 이유는 병합이 표준으로 보이고 리베이스가 고급 기능으로 보인다는 것입니다. 내 직감은 내가하려는 것은 고급 설정이 아니기 때문에 리베이스가 필요 없다는 것이다. 필자는 힘내에서 Pragmatic Programming이라는 새로운 책을 읽었고, 병합을 광범위하게 다루고 리베이스를 간신히 언급했다.

어쨌든, 최근 브랜치에서 작업 흐름을 따라 가고 있었고 마스터로 다시 병합하려고 시도했을 때 모두 지옥으로갔습니다. 중요하지 않아야 할 일들에 수 많은 갈등이있었습니다. 갈등은 나에게 의미가 없었습니다. 모든 일을 정리하는 데 하루가 걸렸지 만 결국 로컬 마스터가 모든 충돌을 해결했기 때문에 결국 원격 마스터에게 강제로 밀어 넣었습니다.하지만 원격 호스트는 아직 행복하지 않았습니다.

이와 같은 "올바른"워크 플로우는 무엇입니까? 힘내는 분기를 만들고 슈퍼 - 쉽게 합병하기로되어 있고, 나는 그것을 보지 않고있다.

2011-04-15 업데이트

이것은 매우 인기있는 질문 인 것 같습니다. 그래서 나는 처음으로 물어 본 이래로 2 년 동안의 경험으로 업데이트하겠다고 생각했습니다.

적어도 우리의 경우 원래의 워크 플로가 정확하다는 것이 밝혀졌습니다. 즉, 이것이 우리가하는 일이며 작동합니다 :

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

사실, 우리의 워크 플로우는 약간 다르다. 원시 병합 대신 스쿼시 병합을 하는 경향이 있기 때문이다. ( 참고 : 이것은 논쟁의 여지가 있습니다, 아래를보십시오. ) 이것은 우리의 전체 기능 브랜치를 마스터에서 단일 커밋으로 바꿀 수있게합니다. 그런 다음 지사 지점을 삭제합니다. 이렇게하면 우리 지부에서 약간 지저분한 것이라 할지라도 커밋을 논리적으로 마스터에 구성 할 수 있습니다. 우리가하는 일입니다.

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Squash Merge Controversy - 여러 주석가가 지적했듯이 스쿼시 병합은 지사의 모든 기록을 버립니다. 이름에서 알 수 있듯이 모든 커밋을 단일 작업으로 축소합니다. 작은 기능의 경우 단일 패키지로 압축 할 때 의미가 있습니다. 큰 기능의 경우, 특히 개별 커밋이 이미 원자적일 경우 좋은 아이디어는 아닙니다. 그것은 정말로 개인적 선호에 달려 있습니다.

Github and Bitbucket (기타?) 요청 풀기 - 병합 / 리베이스가 끌어 오기 요청과 어떤 관련이 있는지 궁금한 경우 마스터로 다시 병합 할 준비가 될 때까지 위의 모든 단계를 따르는 것이 좋습니다. git과 수동으로 병합하는 대신 PR 만 받아들입니다. 이것은 스쿼시 병합을 수행하지 않을 것입니다 (적어도 기본적으로는 아님). 그러나 스쿼시가 아닌 비 - 빨리 감기는 풀 요청 커뮤니티에서 승인 된 병합 규칙입니다 (알고있는 한). 특히 다음과 같이 작동합니다.

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

나는 힘내를 사랑하기 위해 왔고 결코 SVN으로 돌아 가기를 원하지 않는다. 고군분투하는 중이라면 그걸로 붙잡아 야합니다. 그러면 터널 끝에 빛이 보일 것입니다.


TL, DR

git rebase 워크 플로우는 분쟁 해결에 나쁜 사람들이나 SVN 워크 플로우에 익숙한 사람들로부터 보호 해주지는 못합니다. 예를 들면 Git Disaster : A Gory Story 에서 제안했습니다. 그것은 단지 분쟁 해결을 그들에게 더 지루하게 만들고, 나쁜 분쟁 해결로부터 복구하는 것을 더 어렵게 만듭니다. 대신 diff3을 사용하면 처음에는 그렇게 어렵지 않습니다.

충돌을 해결하기 위해 워크 플로를 Rebase하지 않는 것이 좋습니다!

나는 역사를 정리하는 데 매우 프로 리다이즈이다. 그러나 충돌이 발생하면 즉시 rebase를 중단하고 대신 병합을 수행합니다. 실제로 사람들이 충돌 해결을위한 병합 워크 플로의 더 나은 대안으로 rebase 워크 플로를 권장하고 있습니다 (정확히이 질문에 대한 것입니다).

병합하는 동안 "지옥으로 향했다"면, 리베이스하는 동안 "지옥으로 간다", 잠재적으로 훨씬 더 지옥이 될 것입니다! 이유는 다음과 같습니다.

이유 1 : 각 커밋에 대해 한 번이 아니라 충돌을 한 번 해결하십시오.

병합 대신 리베이스하면 동일한 충돌로 리베이스하기 위해 커밋 한 횟수만큼 충돌 해결을 수행해야합니다!

실제 시나리오

나는 지점에서 복잡한 방법을 리펙토링하기 위해 주인을 쫓아 낸다. 내 리펙토링 작업은 리팩토링하고 코드 리뷰를 얻으려고 총 15 가지 커밋으로 구성됩니다. 리팩터링에는 이전에 마스터에 있던 혼합 탭과 공백을 수정하는 작업이 포함됩니다. 이것은 필수적이지만, 불행하게도 마스터의이 메소드 이후에 변경된 내용과 충돌합니다. 물론이 방법으로 작업하는 동안 누군가가 변경 사항과 병합해야하는 마스터 분기의 동일한 메소드를 간단하고 합법적으로 변경해야합니다.

내 브랜치를 마스터와 병합 할 때가되면 두 가지 옵션이 있습니다.

자식 병합 : 나는 갈등을 가져옵니다. 그들이 내 지부 (의 최종 제품)를 마스터하고 병합하기 위해 만든 변화를 봅니다. 끝난.

git rebase : 첫 번째 커밋과 충돌이납니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 두 번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 제 3의 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 네 번째 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 다섯 번째 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 여섯 번째 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 일곱 번째 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 내 8 번째 커밋과 충돌을합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 9 번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 열 번째 커밋과 충돌합니다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 열한번째 커밋과 갈등을 겪는다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 열 두번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 열세 번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 열네 번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다. 나는 내 열 다섯 번째 커밋과 충돌한다. 나는 분쟁을 해결하고 rebase를 계속합니다.

이것이 당신이 선호하는 작업 흐름이라면 농담을해야합니다. 필요한 것은 마스터에서 한 변경 사항과 충돌하는 공백 수정이며 모든 커밋은 충돌하고 해결되어야합니다. 그리고 이것은 공백 충돌 만있는 간단한 시나리오입니다. 천국에서는 파일 간의 주요 코드 변경과 관련된 실제적인 충돌을 피하고 여러 번이를 해결 해야 합니다.

당신이해야 할 모든 여분의 분쟁 해결로, 그것은 당신이 실수 할 가능성을 증가시킵니다. 하지만 당신이 원상 복구 할 수 있기 때문에 실수는 괜찮습니다. 물론 ...

이유 2 : rebase를 사용하면 되돌리기가 없습니다!

나는 우리 모두가 갈등 해소가 어려울 수 있으며 또한 어떤 사람들은 그 일에 매우 나쁘다는 데 동의 할 수 있다고 생각한다. 그것은 매우 실수 할 수 있습니다. 왜 그렇게 큰 자식이야 쉽게 취소 할 수 있습니다!

브랜치 를 병합 할 때 , git은 충돌 해결이 제대로 이루어지지 않으면 버려지거나 수정할 수있는 병합 커밋을 생성합니다. 잘못된 병합 커밋을 public / authoritative repo에 이미 푸시 했더라도 git revert 를 사용하여 병합에 의해 도입 된 변경 사항을 실행 취소하고 새 병합 커밋에서 병합을 올바르게 재실행 할 수 있습니다.

브랜치 를 rebase 할 때 , 충돌 해결이 잘못되었을 가능성이있는 상황에서, 당신은 망설이다. 모든 커밋에는 잘못된 병합이 포함되어 있으므로 rebase *를 다시 수행 할 수는 없습니다. 기껏해야, 돌아가서 영향을받는 커밋을 수정해야합니다. 재미 없어.

리베이스 후에는 원래 커밋의 일부였던 부분과 잘못된 충돌 해결의 결과로 도입 된 부분을 결정하는 것은 불가능합니다.

* git의 내부 로그에서 이전의 참조를 파 낼 수 있거나 리베이스하기 전에 마지막 커밋을 가리키는 세 번째 분기를 만드는 경우 리베이스를 실행 취소 할 수 있습니다.

갈등 해소 : diff3 사용

이 충돌을 예로 들어 보겠습니다.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

갈등을 살펴보면, 각 지회가 무엇을 바꿨는지 또는 그 의도가 무엇인지를 말하는 것은 불가능합니다. 이것이 왜 분쟁 해결이 혼란스럽고 어려운지에 대한 가장 큰 이유입니다.

구조에 diff3!

git config --global merge.conflictstyle diff3

diff3을 사용하면 새로운 충돌마다 세 번째 섹션 인 병합 된 공통 조상이 생깁니다.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

먼저 병합 된 공통 조상을 검사하십시오. 그런 다음 각 측면을 비교하여 각 지점의 의도를 확인하십시오. HEAD가 EmailMessage를 TextMessage로 변경 한 것을 볼 수 있습니다. 그 의도는 사용 된 클래스를 TextMessage로 변경하고 동일한 매개 변수를 전달하는 것입니다. 또한 지형지 ​​물의 의도가 : include_timestamp 옵션에 대해 true 대신 false를 전달한다는 것을 볼 수 있습니다. 이러한 변경 사항을 병합하려면 두 가지의 의도를 결합하십시오.

TextMessage.send(:include_timestamp => false)

일반적으로 :

  1. 공통 조상과 각 지사를 비교하고 가장 간단한 변화가있는 지사를 결정합니다.
  2. 간단한 변경을 다른 분기의 코드 버전에 적용하여보다 단순한 변경과 복잡한 변경이 모두 포함되도록합니다.
  3. 변경 사항을 병합 한 코드가 아닌 충돌 코드 섹션을 모두 제거하십시오.

대체 : 지점의 변경 사항을 수동으로 적용하여 해결하십시오.

마지막으로, diff3에서도 이해할 수없는 일부 충돌은 끔찍합니다. 특히 diff가 의미 상 공통이 아닌 행을 공통으로 찾은 경우 (예 : 두 브랜치 모두 한 곳에서 공백 행이!). 예를 들어, 한 브랜치는 클래스 본문의 들여 쓰기를 변경하거나 비슷한 메서드를 재정렬합니다. 이러한 경우 병합의 양쪽에서 변경 사항을 검사하고 수동으로 diff를 다른 파일에 적용하는 것이 더 나은 해결 전략이 될 수 있습니다.

lib/message.rb 충돌하는 origin/feature1 병합하는 시나리오에서 어떻게 충돌을 해결할 수 있는지 살펴 보겠습니다.

  1. 현재 체크 아웃 된 지점 ( HEAD 또는 --ours ) 또는 병합중인 지점 ( origin/feature1 또는 --theirs )이 적용하기에 더 간단한 변경인지 여부를 결정하십시오. diff를 삼중 점으로 사용하면 ( git diff a...b ) b 에서 마지막 분기 이후로 b 에서 발생한 변경, 즉 a와 b의 공통 조상을 b와 비교할 수 있습니다.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. 더 복잡한 버전의 파일을 확인하십시오. 그러면 모든 충돌 표시가 제거되고 선택한면을 사용하게됩니다.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. 복잡한 변경 사항을 체크 아웃 한 다음 간단한 변경 사항을 취하십시오 (1 단계 참조). 이 diff의 각 변경 사항을 충돌하는 파일에 적용하십시오.


"충돌"은 "동일한 내용의 병행 진화"를 의미합니다. 그래서 병합하는 동안 "지옥으로가는 것"이라면 같은 파일 세트에서 방대한 진화를 이룬 것입니다.

리베이스가 병합보다 나은 이유는 다음과 같습니다.

  • 당신은 마스터 중 하나와 로컬 커밋 내역을 다시 작성하고 (그리고 나서 당신의 작업을 다시 적용하여 충돌을 해결하십시오)
  • 마스터의 모든 커밋 내역과 다시 적용 할 변경 사항 만 포함하므로 최종 병합은 확실히 "빨리 감기"됩니다.

이 경우 정확한 작업 흐름 (일반적인 파일 집합에 대한 발전)이 먼저 리베이스 (rebase ) 된 다음 병합 됨을 확인합니다.

그러나 로컬 브랜치 (백업 이유)를 푸시하는 경우 다른 브랜치에 의해 그 브랜치를 끌어 오지 말아야한다. (커밋 히스토리는 연속적인 rebase에 의해 재 작성 될 것이기 때문에).

해당 주제 (리베이스 및 워크 플로우 병합)에서 barraponto 는 barraponto 두 가지 흥미로운 게시물에 주석을 randyfay.com .

  • Git Rebase Workflow : 먼저 rebase를 가져 오도록 상기시켜줍니다.

이 기술을 사용하면 작업은 항상 현재 HEAD 최신 인 패치와 같이 공용 분기 맨 위에 HEAD 됩니다.

( 바자 와 비슷한 기술 이 있음 )


Git에는 "올바른"워크 플로우가 없다. 보트를 뜨는 것을 사용하십시오. 그러나 분기를 병합 할 때 지속적으로 충돌이 발생하면 동료 개발자와 더 잘 협조해야합니다. 두 분이 같은 파일을 계속 편집하는 것처럼 들리 네요. 또한 공백과 서브 버전 키워드 (예 : "$ Id $"등)에주의하십시오.


내 작업 흐름에서는 가능한 한 리베이스 (rebase)하고 (자주 시도합니다. 불일치가 크게 누적되지 않도록함으로써 분기 간 충돌의 양과 심각도를 줄입니다).

그러나 대부분 rebase 기반 워크 플로에서도 병합을위한 장소가 있습니다.

병합은 실제로 두 개의 부모가있는 노드를 만듭니다. 이제 다음과 같은 상황을 고려해보십시오 : 저는 두 개의 독립적 인 피쳐 브랜치 A와 B를 가지고 있으며, 이제 A와 B에 의존하는 지형지 물 C를 개발하려고합니다. A와 B는 검토 중입니다.

내가하는 일은 다음과 같습니다.

  1. 지점 C를 A 위에 작성 (및 체크 아웃)하십시오.
  2. B와 병합

이제 지점 C에는 A와 B의 변경 사항이 포함되어 있으며 계속 개발할 수 있습니다. A를 변경하면 다음과 같은 방식으로 분기 그래프가 재구성됩니다.

  1. A의 새로운 상단에 가지 T를 만듭니다.
  2. T와 B를 병합하다.
  3. C를 T에 rebase
  4. 분지 T 삭제

이 방법으로 실제로 임의의 그래프 그래프를 유지할 수 있지만 부모가 변경 될 때 리베이스를 수행 할 자동 도구가 없으므로 위에서 설명한 상황보다 더 복잡한 작업을 수행하는 것은 이미 너무 복잡합니다.


내가 관찰 한 것부터 git merge는 병합 후에도 브랜치를 분리하는 경향이있는 반면 rebase는 병합하여 하나의 브랜치로 결합합니다. 후자는 훨씬 더 깨끗하게 나오지만, 전자에서는 병합 후에도 어느 분기에 어떤 커밋이 속하는지를 쉽게 알 수 있습니다.


git push origin을 사용하지 마세요 - 어떤 상황에서도 기분을 상하게하십시오.

이 작업을 수행 할 것인지 묻는 메시지는 표시되지 않으며 로컬 상자에없는 원격 브랜치를 모두 지울 수 있기 때문에 확실해야합니다.

http://twitter.com/dysinger/status/1273652486





git-rebase