정말, Git에서 병합하는 것이 SVN보다 쉬운 구체적인 예?
Stack Overflow question Git에서 병합하는 것이 SVN보다 더 나은 방법 및 / 또는 이유는 무엇입니까? 몇 가지 훌륭한 답변 이있는 훌륭한 질문입니다 . 그러나 그들 중 어느 것도 Git에서의 병합이 SVN 보다 더 잘 작동하는 간단한 예를 보여주지 않습니다 .
이 질문이 중복으로 종료 될 가능성이 있습니다.
- 구체적인 병합 시나리오
- SVN에서 얼마나 어려운가요?
- Git에서 동일한 병합이 얼마나 쉬운가요?
몇 가지 사항 :
- DVCS가 무엇인지에 대한 철학이나 깊은 설명은 없습니다. 이것들은 정말로 훌륭하지만, 나는 그들의 세부 사항이 이것에 대한 대답을 난독하게 만들고 싶지 않습니다 (IMHO) 중요
- 지금은 "역사적인 SVN"에 대해 신경 쓰지 않습니다. 최신 Git (1.7.5)과 최신 SVN (1.6.15)을 비교하세요.
- 이름을 변경하지 마십시오-Git은 이름 변경 및 이동을 감지하지만 SVN은 감지하지 않습니다. 이것은 훌륭하지만 더 깊은 것을 찾고 이름을 바꾸거나 이동하지 않는 예제를 찾고 있습니다.
- 리베이스 또는 기타 '고급'Git 작업이 없습니다. 병합을 보여주세요.
A로부터 실제적인 관점에서 전통적 때문에 나는 "빅뱅 병합"문제 부르는 "하드"이었다 병합. 개발자가 한동안 일부 코드를 작업 중이고 아직 작업을 수행하지 않았다고 가정합니다 (개발자가 Subversion에서 작업하는 데 익숙하고 trunk
완료되지 않은 코드를 수행하지 않을 수 있음). 개발자가 마침내 커밋하면 많은 변경 사항이 모두 하나의 커밋으로 롤업됩니다. 자신의 작업을이 "빅뱅"커밋과 병합하려는 다른 개발자의 경우 VCS 도구는 첫 번째 개발자가 자신이 커밋 한 지점에 도달 한 방법에 대한 충분한 정보를 가지고 있지 않을 것이므로 "여기에 거대한 충돌이 있습니다. 이 전체 기능에서 수정하십시오. "
반면에 저렴한 로컬 브랜치가있는 Git 및 기타 DVCS로 작업 하는 일반적인 스타일 은 정기적으로 커밋하는 것입니다. 이해하기 쉬운 작업을 한 번 완료하면 커밋합니다. 완벽 할 필요는 없지만 일관된 작업 단위 여야합니다. 병합하기 위해 돌아 왔을 때 원래 상태에서 현재 상태로 어떻게 이동했는지 를 보여주는 더 작은 커밋의 기록이 있습니다. DVCS가 다른 사람의 일에 이것을 병합 갈 때, 그것은 변경이 때 만들어진 것에 대해 더 많은 정보를 가지고, 당신은 점점 결국 작은 과 적은 충돌을.
요점은 당신이 무언가를 마친 후에 만 단일 빅뱅 커밋을 만들어서 Git과 함께 어려운 문제를 병합 할 수 있다는 것입니다. Git은 (가능한 한 고통스럽지 않게 만들어) 더 작은 커밋을 만들도록 권장하므로 향후 병합이 더 쉬워집니다.
나는 Git이 Subversion보다 낫지 않았다는 작은 실험에 대해서만 말할 수 있습니다 (동일한 문제).
이 경우에 대해 궁금합니다. 동일한 커밋을 기반으로 "mytest1"및 "mytest2"두 가지 분기로 시작합니다. blub () 함수가 포함 된 C 파일이 있습니다. mytest1 분기에서 "blub ()"을 파일의 다른 위치로 이동하고 커밋합니다. mytest2 분기에서 blub ()을 수정하고 커밋합니다. mytest2 브랜치에서 "git merge mytest1"을 사용하려고합니다.
병합 충돌을 일으키는 것 같습니다. 나는 Git이 "blub ()"이 mytest1에서 이동되었음을 인식하고 mytest2의 수정을 mytest1의 이동과 함께 자동 병합 할 수 있기를 바랍니다. 그러나 적어도 내가 시도했을 때 이것이 자동으로 작동하지 않았습니다 ...
그래서 나는 Git이 병합 된 것과 아직 병합되지 않은 것을 추적하는 데 훨씬 더 낫다는 것을 충분히 이해하고 있지만, Git이 SVN보다 나은 "순수한"병합 케이스가 있는지도 궁금합니다.
이제이 질문이 오랫동안 나를 괴롭 혔기 때문에 SVN에서의 병합은 실패하는 반면 Git이 더 나은 구체적인 예제를 만들려고 노력했습니다 .
https://stackoverflow.com/a/2486662/1917520 에서 하나를 찾았 지만 여기 에는 이름 변경이 포함되어 있으며 여기에서 질문은 이름이 변경되지 않은 사례에 대한 것입니다.
그래서 기본적으로 이것을 시도하는 SVN 예제가 있습니다 :
bob +-----r3----r5---r6---+
/ / \
anna / +-r2----r4----+--+ \
/ / \ \
trunk r1-+-------------------r7-- Conflict
여기서 아이디어는 다음과 같습니다.
- Anna와 Bob은 모두 자체 브랜치가있는 개발자입니다 (r2, r3에서 생성됨).
- Anna는 약간의 수정을합니다 (r4).
- Bob은 약간의 수정을합니다 (r5).
- Bob은 Anna의 수정 사항을 자신의 지점으로 병합합니다. 이것은 Bob이 수정 한 다음 커밋 (r6)하는 충돌을 제공합니다.
- Annas 수정 사항은 트렁크 (r7)에 다시 병합됩니다.
- Bob은 자신의 수정 사항을 트렁크에 다시 병합하려고 시도하고 이로 인해 다시 충돌이 발생합니다.
다음은 이 충돌을 일으키는 Bash 스크립트입니다 (SVN 1.6.17 및 SVN 1.7.9 사용).
#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A\nA\nB\nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob -m "Created branch bob"
svn up
echo -e "A\nMA\nA\nB\nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A\nMB\nA\nB\nMB\nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A\nMAB\nA\nB\nMB\nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk
마지막 "--dry-run"은 충돌이 있음을 알려줍니다. 대신 Anna의 재 통합을 Bob의 브랜치에 병합하려고하면 충돌이 발생합니다. 그래서 마지막 svn merge
을
svn merge ^/trunk branches/bob
이것은 또한 충돌을 보여줍니다.
다음은 Git 1.7.9.5와 동일합니다.
#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A\nA\nB\nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A\nMA\nA\nB\nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A\nMB\nA\nB\nMB\nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A\nMAB\nA\nB\nMB\nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob
f.txt의 내용은 이렇게 변경됩니다.
초기 버전
A
A
B
B
안나의 수정
A
MA
A
B
B
밥의 수정
A
MB
A
B
MB
B
Anna의 지점이 Bob의 지점에 병합 된 후
A
MAB
A
B
MB
B
많은 사람들이 이미 지적했듯이 : 문제는 Subversion이 Bob이 이미 갈등을 해결했다는 사실을 기억할 수 없다는 것입니다. 따라서 이제 Bob의 분기를 트렁크에 병합하려고 할 때 충돌을 다시 해결해야합니다.
Git은 완전히 다르게 작동합니다. 다음은 git이 수행하는 작업을 그래픽으로 나타낸 것입니다.
bob +--s1----s3------s4---+
/ / \
anna / +-s1----s2----+--+ \
/ / \ \
master s1-+-------------------s2----s4
s1 / s2 / s3 / s4는 git이 촬영 한 작업 디렉토리의 스냅 샷입니다.
메모:
- anna와 bob이 개발 브랜치를 만들 때 git 아래에 커밋 이 생성 되지 않습니다 . git은 두 브랜치가 처음에 마스터 브랜치와 동일한 커밋 객체를 참조한다는 것을 기억할 것입니다. (이 커밋은 차례로 s1 스냅 샷을 참조합니다).
- anna가 수정 사항을 구현할 때 새 스냅 샷 "s2"+ 커밋 객체가 생성됩니다. 커밋 객체에는 다음이 포함됩니다.
- 스냅 샷에 대한 참조 (여기서는 s2)
- 커밋 메시지
- 조상 (기타 커밋 객체)에 대한 정보
- bob이 수정을 구현하면 다른 스냅 샷 s3 + 커밋 객체가 생성됩니다.
- bob이 annas 수정 사항을 개발 브랜치에 병합하면 또 다른 스냅 샷 s4 (그의 변경 사항과 anna의 변경 사항 병합 포함) + 또 다른 커밋 객체가 생성됩니다.
- anna가 변경 사항을 마스터 브랜치에 다시 병합 할 때 마스터가 그 동안 변경되지 않았기 때문에 표시된 예에서 "빨리 감기"병합이됩니다. 여기서 "빨리 감기"는 마스터가 아무 것도 병합하지 않고 단순히 anna의 s2 스냅 샷을 가리킬 것임을 의미합니다. 이러한 "빨리 감기"를 사용하면 다른 커밋 개체도 없을 것입니다. "master"분기는 이제 "anna"분기의 마지막 커밋을 직접 참조합니다.
- bob이 이제 자신의 변경 사항을 트렁크에 병합하면 다음이 발생합니다.
- git은 s2 스냅 샷을 생성 한 anna의 커밋이 s4 스냅 샷을 생성 한 bobs 커밋의 (직접) 조상임을 알게됩니다.
- 이 때문에 git은 마스터 브랜치를 "bob"브랜치의 마지막 커밋으로 다시 "빨리 감기"합니다.
- 다시 이것은 새로운 커밋 객체를 생성하지 않을 것입니다. "마스터"브랜치는 단순히 "bob"브랜치의 마지막 커밋을 가리 킵니다.
다음은이 모든 것을 보여주는 "git ref-log"의 출력입니다.
88807ab HEAD@{0}: merge bob: Fast-forward
346ce9f HEAD@{1}: merge anna: Fast-forward
15e91e2 HEAD@{2}: checkout: moving from bob to master
88807ab HEAD@{3}: commit (merge): anna merged into bob with conflict
83db5d7 HEAD@{4}: commit: bob added text
15e91e2 HEAD@{5}: checkout: moving from anna to bob
346ce9f HEAD@{6}: commit: anna added text
15e91e2 HEAD@{7}: checkout: moving from master to anna
15e91e2 HEAD@{8}: commit (initial): Initial file
이것에서 볼 수 있듯이 :
- anna의 개발 브랜치 (HEAD @ {7})로 이동할 때 다른 커밋으로 변경 하지 않고 커밋을 유지합니다. git은 우리가 이제 다른 지점에 있다는 것을 기억합니다.
- HEAD @ {5}에서는 bob의 초기 브랜치로 이동합니다. 이것은 bob이 아직 아무것도 변경하지 않았기 때문에 작업 복사본을 마스터 브랜치와 동일한 상태로 이동합니다.
- HEAD @ {2}에서 마스터 브랜치로 돌아가서 모든 것이 시작된 동일한 커밋 객체로 이동합니다.
- Head @ {1}, HEAD @ {0}는 새 커밋 개체를 생성하지 않는 "빨리 감기"병합을 표시합니다.
"git cat-file HEAD @ {8} -p"를 사용하면 초기 커밋 객체의 전체 세부 정보를 검사 할 수 있습니다. 위의 예에서는 다음을 얻었습니다.
tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
Initial file
"트리"줄은이 커밋이 참조하는 스냅 샷 s1 (== b634f7c9c819bb524524bcada067a22d1c33737f)을 식별합니다.
"git cat-file HEAD @ {3} -p"를 수행하면 다음과 같은 결과가 나타납니다.
tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
anna merged into bob with conflict
위는 anna의 개발 브랜치를 병합 할 때 생성 된 커밋 객체 인 bob을 보여줍니다. 다시 "트리"줄은 생성 된 스냅 샷을 나타냅니다 (여기서는 s3). 또한 "부모"줄을 확인하십시오. "parent 346ce9f"로 시작하는 두 번째는 나중에 git에게 bob의 개발 브랜치를 마스터 브랜치로 다시 병합하려고 할 때 bob의 마지막 커밋에 anna의 마지막 커밋이 조상이라는 것을 알려줍니다. 이것이 git이 bob의 개발 브랜치를 마스터 브랜치로 병합하는 것이 "빨리 감기"라는 것을 아는 이유입니다.
구체적인 예는 없지만 어떤 종류의 반복 병합 도 어렵습니다. 특히 소위 십자 병합 이라고 합니다.
a
/ \
b1 c1
|\ /|
| X |
|/ \|
b2 c2
b2와 c2 병합
The wiki page on Subversion Wiki describing differences between mergeinfo based assymetric Subversion merge (with 'sync' and 'reintegrate' directions) and merge tracking based symmetric merge in DVCS has a section "Symmetric Merge with Criss-Cross Merge"
The most concrete example I can think of is the simplest merge that does not result in merge conflicts. However (TL;DR) with that example Git is still inherently a simpler procedure than with Subversion. Lets review why:
Subversion
Consider the following scenario in subversion; the trunk and the feature branch:
1 2 3
…--o--o--o trunk
\4 5
o--o branches/feature_1
To merge you can use the following command in subversion:
# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1
# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"
In subversion merging the changes requires another commit. This is to publish the changes that the merge did with applying the changes on the feature branch virtual directory back into the trunk. That way everyone working with the trunk can now use it and the revision graph looks like sort of like this:
1 2 3 6
…--o--o--o------o /trunk
\4 5/
o--o /branches/feature_1
Lets see how this is done in git.
Git
In Git this merge commit is really not necessary as branches are glorified bookmarks on the revision graph. So with the same kind of revision graph structure it sort of looks like this:
v-- master, HEAD
1 2 3
…--o--o--o
\4 5
o--o
^-- feature_branch
With the head currently on the master branch we can perform a simple merge with the feature branch:
# Attempt a merge
git merge feature_branch
# build, test, and then... I am done with the merge
... and it will fast-forward the branch over to the commit where feature branch is pointing at. This is made possible because Git knows that the goal of the merge is a direct descendant and the current branch only needs to take in all the changes that happened. The revision graph will end up looking like this:
1 2 3 4 5
…--o--o--o--o--o
^-- feature_branch, master, HEAD
The changes does not need a new commit as all git has done is to move the branch references further up to the front. All that is left is to publish this to the public repository if you have any:
# build, test, and then... just publish it
git push
Conclusion
Given this simple scenario you can assert two things in the difference between Subversion and Git:
- SVN requires at least a couple of commands to finalize the merge and forces you to publish your merge as a commit.
- Git only requires one command and does not force you to publish your merge.
Given this to be the most simplest merge scenario it is difficult to argue that subversion is easier than git.
In more difficult merge scenarios, git also provides you the ability to rebase the branch that produces a simpler revision graph at the cost of history rewriting. Once you get the hang of it though, and avoid publishing history rewrites of things already published, it isn't really that bad of a thing to do.
Interactive rebases is outside the scope of the question but to be honest; it enables you the ability to rearrange, squish and remove commits. I wouldn't willingly want to switch back to Subversion as history rewriting is not possible by design.
Keeping the answer short - In DVCS , since you have a local source control, if something get screwed up in the merge process (which will probably happen in large merges), you can always rollback to a previous local version which has the changes you've made before merging, and then try again.
So basically you can do merge without the fear that your local changes might get damaged during the process.
Looks like it's a myth that merging in Git is easier than in SVN...
For example, Git cannot merge into a working tree that has changes, unlike SVN.
Consider the following simple scenario: you have some changes in your working tree and want to integrate remote changes without committing your own.
SVN: update
, [resolve conflicts].
Git: stash
, fetch
, rebase
, stash pop
, [resolve conflicts], [stash drop
if there were conflicts].
Or do you know an easier way in Git?
Btw, this use case seems to be so important that IntelliJ even implemented the missing "Update Project" functionality for Git (analogon to SVN update) which can automate the manual steps described above:
If you exclude "Merge-Refactored Hell" you will not get fair samples, because they just doesn't exist
'developer tip' 카테고리의 다른 글
.NET 4.0의 주요 변경 사항 (0) | 2020.12.13 |
---|---|
전처리 단계없이 Qt는 얼마나 유용합니까? (0) | 2020.12.13 |
정의되지 않은 전달의 목적은 무엇입니까? (0) | 2020.12.12 |
자바 스크립트 : 배경 이미지 URL 가져 오기 (0) | 2020.12.12 |
루트에 상대적인 지시어 templateUrl 지정 (0) | 2020.12.12 |