작업을 하거나 협업을 할 때 브랜치를 만들고 해당 브랜치를 Merge 한 경험은 다들 있을 것이다.
회사에서 Feather 브랜치를 만들고 delvop 같은 브랜치에 merge 하여 자신이 한 작업물을 올리는 등의 각자 회사에서 따르고 있는 git flow가 있을 것이다. 이 부분에서 각자 팀에서 암묵적으로나 명시적으로 merge 규칙 등이 있을 텐데,
한동안 생각 없이 규칙대로만 작업하다 보니 해당 개념을 좀 잡아보려, 이 Merge에 대해서 좀 더 알아보려고 한다.
우선 대표적인 3가지 Merge 방법에 대해 알아보고 좀 더 특이 케이스에 대해 보도록 하자.
- Merge (3-way-merge)
- Sqush and Merge
- Rebase and Merge
우선 위 3가지 Merge 방법은 GitHub GUI에서 지원해 주는 Merge 방법이다.
Merge (3-way-merge)
가장 기본적은 Merge 방법이다. 따로 설정하지 않은 경우 대부분 해당 방법으로 Merge가 될 것이다.
- 현재 브랜치의 commit [HEAD]
- 병합할 브랜치의 commit [MERGE_HEAD]
- 공통 조상이 되는 commit [merge base]
이렇게 3가지의 commit을 비교하여 충돌 여부, 병합 방식을 판단하기 때문에 3-way-merge라고 불린다.
GitHub의 Create a merge commit 방식도 해당 방식을 따르고 새로운 merge commit을 생성하고 병합할 브랜치의 commit 히스토리를 그대로 보존하는 특징이 있기 때문에 모든 변경사항을 상세히 파악할 수 있다.
Sqush and Merge
해당 머지 방식은 병합할 브랜치의 모든 변경 사항을 하나의 commit으로 합쳐서 merge 된다.
앞서 말한 Merge 방식과 전혀 다르게 병합할 브랜치의 작업분을 Merge 했다는 점에 초점을 두는 방식으로,
병합 브랜치의 각 시점의 변경사항 정보가 사리 진다.
Rebase and Merge
해당 방법은 현재 브랜치에 병합할 브랜치의 변경사항의 commit을 추가하여 재배열하는 방식을 말한다.
프로젝트에서 해당 Merge들의 실제 동작 결과를 확인해 보자.
우선 여러 브랜치를 Merge해 올 main 브랜치의 작업 상태이다.
그래고 아래는 해당 main 브랜치에 합병해 올 각 브랜치 변경상태이다.
각 브랜치들은 전부 동일하게 main의 fix: build commit 시점(marge test commit 전)에서 분기하여 각자의 test commit 변경사항을 가지고 있다.
rebase and merge 방식으로 rebase 브랜치를 merge 하면 다음과 같다.
위에서 설명했다시피, rebase에 있던 commit들이 main의 변경사항에 포함된 것을 확인할 수 있다.
rebase를 통해 main 브랜치가 rebase(rebase_merge_test) 브랜치의 변경사항 commit들을 자신의 변경사항인 것처럼 포함시킨 것을 확인할 수 있다.
다음으로, squash and merge 방식으로 squash 브랜치를 merge 하면 다음과 같다.
rebase와 다르게 merge 후 main에 하나의 commit(squash merge test #3)이 생긴 것을 확인할 수 있는데,
squash 브랜치에 있는 변경사항(2개의 commit)이 하나의 commit(squash merge test #3)으로 합쳐져 병합된 것을 확인할 수 있다.
2개의 commit
- merge test: squash_merge-test 2 commit
- merge test: squash_merge-test 1 commit
참고로 GitHub에서 squash and merge를 진행할 때, 병합 commit을 naming 할 수 있다.
다음으로는 merge 브랜치를 main에 가장 기본적인 Merge를 진행하면 다음과 같다.
기존 Merge 방식들과 다르게 Merge 브랜치의 변경사항 commit 히스토리가 실제 main 브랜치에 남아 있는 것을 확인할 수 있다.
이렇게 GitHub에서 제공해 주는 3가지 Merge 방식을 실제로 테스트해 봤는데, 사실 또 다른 Merge 방식이 존재한다.
Fast-Forward Merge이라고, 이 Merge 방식은 main 브랜치에서 적은 변경사항만이 존재할 경우 쉽게 commit tree를 관리할 수 있게 해 주는데, 이 방식은 GitHub GUI에서는 지원해주지 않는다.
Fast-Forward Merge
현재 브랜치에 변경사항이 없고 병합할 브랜치에만 변경사항이 존재할 경우 해당 변경사항을 그대로 가져오는 방법이다.
서로 다른 브랜치를 병합하는 것이 아닌 현재 브랜치의 HEAD를 이동시키는 방법이다.
실제 프로젝트에서 확인해 보면 다음과 같다.
main 브랜치의 최신 상태의 시점에서 commit 2개(3, 4 commit)를 추가한 브랜치가 있다.
main 브랜치에서 해당 브랜치를 Fast-Forward Merge 하면 다음과 같다.
빠르게 병합한 브랜치의 변경사항이 main에 머지된 것을 확인할 수 있다.
언뜻 보면 rebase and merge와 비슷해 보이나, fast-forward merge는 rebase and merge와 다르게 새로운 commit을 생성하여 재배치하는 것이 아닌 병합하는 해당 브랜치의 commit을 그대로 가져오는 방식이기 때문에 기존 commit ID가 동일하다는 차이점이 존재한다.
추가적으로
여러 사람과 협업할 때 공통적으로 많은 사람들이 병합을 진행하는 브랜치가 있을 것이다. (develop, release 브랜치 등등)
그 작업 중 Rebase and Merge를 사용을 자제하라는 권고를 받은 경우가 있을 수도 있다. (우선 우린 그랬다)
왜 그럴까, 해당 부분에서 어떤 위험이 발생할 수 있는지 한번 만들어보도록 하자.
우선 상상해 보도록 하자.
나는 main에서 브랜치를 따, 작업을 해서 청록색 변경사항을 main 브랜치에 머지할 것이다.
내가 작업한 브랜치는 해당 변경사항을 가지고 있다
- rebase_merge_test 4 commit
- rebase_merge_test 3 commit
main 브랜치의 최신 commit은 다음과 같다
- merge_commit_test 4 commit
나는 해당 브랜치 작업을 rebase and merge 하기 위해 main을 rebase 하였다.
이제 main에서 해당 브랜치를 merge 할 거다.
아뿔싸 그런데!
사실 main 브랜치의 최신 commit은 merge_commit_test 4 commit이 아니라 추가로 commit 된 main 3 commit 이였던 것이다.
내 로컬에서 해당 최신 값의 반영이 늦어 이 부분을 알지 못하고 나는 옛 버전의 main 브랜치로 rebase를 진행한 것이다.
내가 아무것도 모른 상태에서 main의 변경사항 하나(main 3 commit)를 지워버린 것이다.
보통 해당 부분에서 로컬 브랜치와 원격 브랜치의 commit tree가 맞지 않게 때문에 해당 내용의 팝업으로 일반적인 push가 불가능하다.
하지만 보통 rebase를 진행한 경우 commit들이 재정렬되기 때문에 기존 배열과 달라진 commit tree를 push 하기 위해 force push를 진행하는 경우가 많다.
그렇다면 이런 대참사가 일어나는 것이다.
당황하지 말라
본인이 참사를 일으킨 당사자라면, 아직 진정하고 되돌릴 방법이 있다.
우선, 해당 참사를 일으킨 당사자로서 물의를 일으킨 부분에 대해(나 자신과 모두에게) 석고대죄하며 울면서 참회하길 바란다.
로컬에는 해당 작업을 진행한 명령어 기록이 존재한다.
해당 git 명령어는 reflog로, 이때까지 본인이 돌아가고자 하는 hash 값을 찾아 작업을 되돌리도록 하자.
reflog나 checkout, reset 명령어 등으로 해당 소동은 침착하게 복구될 수 있다.
기존 브랜치 상태를 복원하든, 작업 로컬 브랜치를 복원하여 되돌리든 어떻게든 방법이 있다.
해당 방법으로 해결이 불가능하다면?
해당 작업의 로컬 브랜치가 존재하지 않고 복구도 되지 않는다면?
사실 focus push를 실행 시점 명령어를 reflog로 찾을 수 없다면? (사실 범인은 내가 아니었다?)
일단 슬퍼하자. 너무 슬픈 일이다. 처참한 재앙의 현장이 맞다.
진짜 내가 focus push 정도야 흥 하는 생각이 얼마나 오만하고 미친 생각이었는지 다시 한번 배울 수 있었다는 점에서 성장 포인트를 얻을 수 있는 경험이다.
만약 내가 사고를 일으킨 당사자 아니라면? 그래서 나는 되도록 로컬 브랜치를 삭제하지 않고 운영배포 전까지 보관한다.
운전도 방어 운전이 중요하듯이 개발도 방어 개발(?)이 중요하다는 점을 또 한번 배울 수 있었다. (알고 싶지 않았다)
여기서 사라진 commit을 뒤지는 방법이 있다.
git fsck --lost-found
gitHub는 기본 90일 정도(설정값)의dangling commit을 GC 하지 않는다.
git fsck --lost-found를 실행할 경우 dangling commit 내역을 확인할 수 있다.
여기서 git show 명령어를 통해 하나하나 내가 잃어버린 commit인지 확인하여 원하는 commit hash를 찾으면 된다.
제발 존재하길 빌어보자
만약에 원하는 commit을 찾은 경우 해당 commit을 복원하여 브랜치를 생성하는 방법은 다음과 같다.
git checkout -b [브랜치 명] [commit hash]
이렇게라도 복원할 수 있다면 무척 다행이다.
추가적으로 하면 좋은 일
- 각 Merge 설명 보충
참고 자료
https://stackoverflow.com/questions/60597400/how-to-do-a-fast-forward-merge-on-github
How to do a fast-forward merge on GitHub?
So one of my colleagues attempted to merge a branch using GitHub's "merge via fast-forward" option in the web-interface, to keep the history clean from bogus merge commits (the master bra...
stackoverflow.com
https://tlatmsrud.tistory.com/155
[Git] Git Merge 란? / 쉽게 이해하기 / Fast Forward / 3-way-merge
개요 평소 Git을 사용했고, 이슈별로 브런치를 생성하며 개발했지만 혼자이다보니 Main 브랜치로 Merge 해도 코드 충돌이 발생할 확률은 극히 적었다. 협업을 시작하니 특정 브랜치로 Merge 해야하는
tlatmsrud.tistory.com
https://shanepark.tistory.com/317#google_vignette
Git) 실수로 삭제한 Branch 복구하기
Intro Pull Request를 기다리다가, merge가 되었다고 착각하고 커밋 했던 브랜치를 삭제해 버렸습니다.. 로컬과 remote 모두에서 삭제 했기 때문에 원래대로라면 데이터를 날려먹은게 맞지만, 다행히도
shanepark.tistory.com
https://disco-biscuit.tistory.com/99
[git] git push --force 강제 push로 유실된 커밋 되돌리기
git reflog로 X되기 이전의 커밋 hash 찾기 git checkout {commitHash} git branch {your-branch} 인데 나는 마스터므로, git branch master 안되면 git branch master -f git checkout master master 브랜치로 이동 git push origin master 프
disco-biscuit.tistory.com
'Android > 학습' 카테고리의 다른 글
[Git] GitFlow(깃플로우), 기존 Git 명령어 관점에서 (3) | 2025.08.23 |
---|---|
[Android] LiveData란 (vs StateFlow) (6) | 2025.07.19 |
[Kotlin] Coroutine Flow란, 그리고 Flow, StateFlow, SharedFlow.. (9) | 2025.07.06 |
[CS] 동기 vs 비동기, 블로킹 vs 논블로킹, 그리고.. (1) | 2025.06.14 |
[Android] RecyclerView의 동작 과정 (5) | 2025.05.16 |