배경 :
conflict 관련해서 rebase 를 공부 중.
궁금증 :
merge할 때 최종 커밋만 conflict에 영향을 미칠까?
예를 들어, main 브랜치와 coupon 브랜치가 있다고 해 보자.
main은 line 3을 수정해 commit 하고,
coupon은 첫 commit 에서 line 3 4을 수정하고, 둘째 commit 에서 line 3을 수정하여,
-> 결국 original 버전의 line 3과 똑같이 원상복귀 되었다고 해 보자.
(즉, main의 최신 버전과 coupon의 최신 버전은 original 버전 대비 각각 line 3, line 4만 바뀌어 있음.)
위 가정은 틀림. 다시 가정함.
예를 들어, main 브랜치와 coupon 브랜치가 있다고 해 보자.
main은 line 3을 수정해 commit하고,
coupon은 여러 commit 과정을 통해 계속 수정하다가 마지막 commit에서 original 버전과 똑같이 원상복귀시킨다.
이때, 해당 coupon 브랜치에 대해,
1. 3 way merge 하면?
2. rebase 후 fast-forward merge 하면?
-> conflict가 발생할까?
키워드를 잘 검색해봐도 해답이 나오지 않아 직접 시도해본다.
시간 없으면 빨간글씨만 봐도 됨
@@이 아래에 있는 첫 번째 실험은 가정이 잘못됐음. 틀린 내용. 건너뛰고 맨 아래에 있는 실험을 참고해주삼.
실험 :
다음과 같은 app.txt 파일이 있다.
# app.txt
첫 번째 줄
두 번째 줄
세 번째 줄
이얏호우~~
coupon branch를 만든 후,
main 브랜치에서는 line 3만 수정한다.
이제 coupon 브랜치에서, 우선 아래와 같이 line 3, 4를 수정한다.
그리고 line3을 original와 같이 수정한다.
현재 log는 아래와 같다.
CASE 1: 3-way merge
main branch에서 바로 merge를 시도하면 3-way merge가 된다.
이 경우, conflict가 발생했다 !!
conflict는 "같은 곳을 다르게 수정했을 경우" 발생하는데, 여기서
이 각각의 "수정한 곳"을 따지는 기준이,
"original 버전 대비 최종 버전을 비교했을 때 수정된 곳"이 아닌,
"original 버전에서 최종 버전까지 수행한 commit 과정에서 수정된 곳"인 것을 확인할 수 있었다.
만약, coupon branch에서, line 34 수정 후 line 3 원상복귀가 아닌,
그냥 처음부터 line 4만 수정하는 한 번의 commit만 수행했다면 conflict가 나지 않았을 것이다.
위 설명은 틀렸음.
그냥 line2, 3을 각각 다른 branch에서 수정하니까, 깃이 어느 쪽을 선택해야 할지 몰라서 conflict가 발생한 것
CASE 2: rebase and (fast-forward) merge
이제, git reset --merge ORIG_HEAD을 수행해서 merge를 취소해주고, rebase and (fast-forward) merge를 수행해보자.
다시, 현재 아래 상태이다.
아래 코드들을 터미널에 입력해 rebase and merge하자.
git switch coupon
git rebase main
git switch main
git merge coupon
근데, `git rebase main`을 입력한 순간, conflict가 발생했다!!
아까와 같은 conflict 내용이 나온다.
즉, rebase의 경우 그 자체가 branch를 이어주기 때문에, main 브랜치의 커밋 한거 이후에 coupon 브랜치의 커밋이 시작되는 판정이 되기 때문이다. 따라서 아직 (fast-forward) merge를 하진 않았지만 conflict가 발생한다.
임의로 coupon의 수정내용을 받아들이기로 하고, rebase를 완료한 모습. (main의 수정내용을 받아들였어도 결과는 같다.)
이제 나머지 `git switch main`, `git merge coupon`을 차례로 시행해 merge해주고 나면 위와 같다. (사실상 이 두 줄은 fast-forward merge를 시행한 것이다.)
참고로, 반대로 coupon에서 merge main을 하면 `Already up to date.`가 나온다.
cf. Already up to date. → 이미 내가 작업하는 공간에 존재하는 branch2 는 branch1 에 합칠거 이미 다 합쳐서 합칠게 없다는거다.
위 실험은 잘못 설계됐음. 당연히 conflict가 나는 게 맞음.
conflict를 내가 잘못 이해해서, 전혀 다른 실험을 해버림.
실험을 아래와 같이 다시 해보자.
첫 번째 줄
두 번째 줄
세 번째 줄
끼얏호우~~
-> app.txt
위 상태에서 merge 하면, conflict 발생
-> coupon에선 1~4번째 줄을 건드리지 않았음에도, 5번째 줄을 추가한 것 때문에 문제가 됨.
아마, 3~5번째 줄을 통째로 봤을 때, 수정 됐냐 안됐냐를 본 것같음.
이 내용은 실험의 핵심은 아니지만, 일단 알아두자.
3~5번째 줄을 통째로 인식한다는 아이디어.
여기서, 또 간과하면 안되는 게 아직 여기 단계에서 3 way merge에서 conflict가 난 건최종 commit 두 개 때문이지, 그 중간에 있는 coupon 커밋들의 영향을 받은 게 아님.실제로 중간 커밋 없이 따로 실험했을 때도 똑같이 conflict 발생했음.
@@@@@@@@@@@@@@@@@@@@@
24.10.31 03:00 추가)
해당 conflict에 대해 조언을 받았는데, 결론은
"깃에서 내가 기대한 것과 다르게 diff를 딴 거다" 라고 함. 수정한 변경 사항들(두 줄)이 인접해서 하나의 변경 블록으로 인식한 것임. 그래서 실제로 코드를 가져와서 위아래 거리두고 수정하면 내가 생각했던 대로 동작할거라고 함.
-> 진짜 이거 알려준 사람 너무 감사합니다 잠 못잘뻔...
gpt 답변은 위와 같다.
이 때, diff 정의가 뭔지, diff를 딴다는 게 뭔 말인지 확실히 알고 가자.
이렇게 coupon을 수정하고 다시 merge 하면?
이 경우 merge가 잘 됨. 3 way merge.
이렇게 coupon을 수정한 최종본에서, 이제 `git rebase main`을 하면?
-> conflict 발생.
아까 coupon을 한번 더 수정(5번째 줄 없앤 그 커밋 말하는거) 하기 전에 생겼던 conflict랑 내용이 동일함!!
그 이유는, main의 커밋과, coupon의 첫 번째 커밋에 대해 conflict 발생 여부를 먼저 비교했기 때문!!
@@@ 이 내용이 이번 실험의 핵심이라 생각.
여기서, 내가 최종적으로 반영하고 싶은 건, main의 것, 즉 5번째 줄이 없는 버전이지 않겠음??
5번째 줄을 반영하고 싶지 않으니까 coupon에서 5번째 줄을 없앤 commit을 한 번 더 한거잖음??
그러니 '현재 변경 사항 수락'을 눌러 main의 것을 유지해주는 게 합리적임.
여담) git rebase --quit 하면 quit 가능
이렇게 main의 것을 유지시켜주고(즉, conflict 해결해 준거임), 커밋해주면
이렇게 rebashing이 완료됐고,
이렇게 (fast-forward) merge도 완료됐다.
핵심 정리 :
1. 같은 상황에서 (3 way) merge랑 rebase 했을 때, 머지는 그냥 최종 커밋 버전만 가지고 conflict가 있는지 비교함. 그 이전에 branch에서 몇번째 라인을 어떻게 수정하고 온갖 걸 다 해놨어도, 최종 commit에서 main이랑 conflict 일어날 껀덕지만 안 만들어놨으면 conflict가 일어나지 않음.
그러나, rebase의 경우는 최종 커밋 뿐만아니라 그 이전 커밋들도 고려하기에 최종 commit이 conflict일어날 껀덕지가 없다 하더라도, 그 이전 커밋들때문에 conflict가 일어날 수 있음.
-> main에다가 coupon을 rebase 한다 하면, main의 여러 commit들 중 가장 마지막에 있는 commit을 기준으로, coupon의 뿌리 부분(브랜치 갈리는 부분. 거기서부터 붙을 거니까)부터 브랜치 끝까지 역순으로 오면서 diff를 수행함.
즉, main은 가장 최신꺼만, branch은 과거부터 지금까지.
따라서 이 과정에서 conflict가 여러번 발생할 수 있음.
저런 느낌인듯? main - coupon 뿌리, coupon 뿌리 - coupon 다음 커멋...
main의 커밋에다가 계속 각 coupon의 커밋들이 쌓아올라짐.
예를 들어, main의 가장 최신 커밋이 첫 번째 줄과 두 번째 줄을 수정한 커밋이고, coupon은 가장 오래된 커밋부터 순서대로 3, 4, 5, 6, 7번째 줄을 수정한 커밋임.
그러면 coupon을 main에 rebase 한다고 하면, main의 최신 커밋과 coupon의 가장 오래된 커밋을 diff하고 적용해서 쌓아올림. 이제 main의 커밋이 적용된 coupon의 가장 오래된 커밋은 1, 2, 3번째 줄이 수정되어 있을 거고, 여기에 또 4, ... 계속 반복.
여기서, conflict 발생 시 누구 거를 받아들이냐에 따라 최종 commit 형태가 달라질 수 있음.
-> 둘다 main꺼 받아들일 시
-> 둘다 coupon꺼 받아들일 시
2. main에서 특정 line을 수정하고, branch에서 원래 없던 line을 추가한 뒤 둘을 merge 또는 rebase 하려고 하면, [수정한 특정 line ~ 추가한 line] 에 대한 conflict가 일어날 수 있음.
-> 이 2번 내용에 대해선 추가 공부가 필요해보임. 일단은 이렇게 넘어가고, 나중에 실제 프로젝트 경험에서 느껴보자.
=> 2번 이거는 내가 수정한 두 줄이 인접해서 git이 "하나의 큰 변경 블록"으로 인식하고 diff를 내 기대와 다르게 짜서 그런 거임!! 진짜 코드 가져와서는 위아래 간격 길게 하면 이런 충돌 발생 안할거임.
'Tools & Utilities > Git' 카테고리의 다른 글
[Git] 2. 깃으로 버전 관리하기 (0) | 2024.10.28 |
---|---|
[Git] 1. 깃(Git) 기본 (0) | 2024.10.28 |