100명 가까이 참여하는 프로젝트를 하다 보면, 작은 사이드 프로젝트와는 다르게 역할이 참 많다.
역할에 따라 번역을 할 때도 있고, 개발을 할 때도 있는 등 여러가지 일을 해 볼수록 재미가 더해지는데, Git Conflict를 한번 겪어보면 머리가 뜨거워짐과 함께 성장통이 반드시 오게 마련이다. 해결하면 재미가 2배지만, 포기하고 최후의 방법인 다시 fork뜨기, repository 삭제 등을 하다보면 git이라는 이름만 들어도 러다이트 운동을 일으키고 싶을 만큼의 분노가 발생한다.

언제 Conflict가 발생할까?

나는 3주동안 다음과 같은 팀에 참여했다.

1주차 : 번역 및 검수
2주차 : 번역 및 검수, 개발 1팀
3주차 : 번역 및 검수, 개발 2팀, 개발 3팀

번역 및 검수팀과 개발팀은 서로 역할이 다른데, 먼저 PR 프로세스와 Git 전략을 보고 가자.

PR Process

PR Process는 어렵지 않았다. 사실 어렵게 하면 안 되는게, 비개발자들도 많은 참여를 하고 있는 소스라 최대한 단순하게 가는 것이 좋다. 한 파일의 사이클은 아래와 같이 돌린다.

1주 : 개발팀 개발 -> Peer Review 1 / 2 -> Merge
2주 : 번역검수팀 Review 1 / 2 -> 최종 Merge

Git 전략

Git을 모르는 사람들이 많았어서, 깃 전략 또한 Git Flow 또는 Github Flow같은 정형화된 프로세스를 가져가지 않고, trunk-based보다는 "Fork and Pull"(사실 나도 이름모름) 전략을 따른다.

 PC            github          github organization
local ----->   origin  ------ > upstream
main            main            main

Fork and Pull(또는 Fork-based Workflow) 전략은 간단하다. Upstream(원본)에서 Fork를 해서 각 기여자가 본인의 Repository에 Origin을 구성한 후, local에서 작업해서 origin에 push을 하고, origin에서 upstream으로 PR(Pull Request)을 날려서 관리자가 Merge를 하는 방식이다. 이 방법은 단순하면서도 git 초보자가 하기에 정말 좋은 전략이다.

스멀스멀 올라오는 Conflict의 기운

Git 전략은 좋았지만, git branch에 대해서는 따로 정해진 것이 없었다. 깃 초보자가 있는 프로젝트에서 " 'feature branch'를 이용해보세요 ^^ checkout만 해주면 된답니다~" 하면 경험상 무조건 '파일이 날라갔는데요? 제 파일 어디갔죠?'라는 답변이 오기 마련이다. 나 또한 같이 해본 프로젝트가 5인 미만 프로젝트만 있거나, 기능을 혼자 가져가거나 했기 때문에 local에서는 따로 브랜치를 쓸 필요가 없어서 main 이외의 브랜치를 가져간다는 생각조차 하지 못했다.

문제는 여기서 발생했다.

보통 두가지 경우에서 문제가 일어나는데, 다음과 같다.

검수팀

검수팀의 경우, 어떤 부분에서 문제가 발생했는지 쉽게 알려주기 위해 다음과 같은 프로세스를 따른다.

local  -----> origin -----> upstream
main    push   main
작업           작업물

Upstream까지 가지 않고, Origin에 본인의 작업물을 push하고, upstream에는 issue를 발행하며 본인의 작업물을 그대로 올려서 개발자들이 어떤 부분이 바뀌어야 하는지 쉽게 알려준다. 그러면 개발팀은 2주차에 본인의 작업물을 PR해서 반영하게 된다. 그러면 local, origin, upstream은 다음과 같이 구성된다.

사람 local origin upstream
개발 A' A'  
검수 A'' A'' A''

이 때, 검수는 origin을 upstream과 맞추기 위해 sync fork를 하는데, 여기서 origin 저장소는 A'를 받아들여야 하는지, A''를 받아들여야 하는지 헷갈려서 conflict를 내거나 discard A'(A'의 커밋을 버리고 A''로 동기화하기)를 지시한다.
잔디에 관심이 없거나 커밋에 쿨한 상남자 바이킹들은 '어 커밋 버려~' 할 수 있겠지만, 깃헙 잔디 수호자나 내 '귀엽고소중하고깜찍한' 커밋이 버려지는 것을 싫어하는 사람들은 쉽게 discard 할 수 없다. 이때부터 '내 커밋을 보호하면서 upstream을 받아오는 일'에 대해 검색하거나, 내 리포지토리를 세련된 닥스훈트로 만드려 수술을 집도하지만 merge 과정에서 수없이 많은 resolve conflict를 하다 케르베로스가 되고 눈물을 흘리며 저장소를 지우고 새로 fork를 따게 된다.

개발팀

개발팀의 경우, 문제가 조금 더 복잡해진다. 위에서 설명했듯, 한 주마다 신규 개발건을 맡기 때문에 타임라인이 다음과 같아진다.

1주차 : 1주차 내용 개발 -> peer review 1 / 2 -> PR Merge
2주차 : 2주차 내용 개발 -> peer review 1 / 2 -> PR Merge
        + 1주차 내용 검수 -> 번역검수팀 review 1 / 2 -> PR Merge

1주차는 문제가 없지만, 2주차에 돌입하면 2주차의 내용을 개발함과 동시에 1주차의 검수 교정까지 들어가야 한다. 그러면 local, origin, upstream은 다음과 같이 구성된다.

 

주차 local origin upstream
1주차 A A A
2주차 A'
B
A'
B
???

의도(개발자) : "나는 A' 따로, B 따로 PR 날려야지"
현실(github) : "제가 최신꺼 다 같은 PR에 묶었어요 ㅎㅎ 잘했죠?"

이때부터 github 공포증이 돌면서 러다이트운동이 마려워지는 일이 생긴다. 내가 원하는 커밋만 보내고 싶은데, 보낼줄 모르기 때문이다.

어떻게 해결해야 할까?

사실 본인의 의도에 맞춰서 branch를 따로 따면 된다.

주의 : 초보자라면 항상 파일 백업을 따 두고 시작하자.

브랜치 생성

검수팀이라면, proofread branch를 따로 따서

1주차
           local  -----> origin  -----> upstream
main         A             A               A
proofread    A'            A'

개발팀의 검수 이후 최종 PR 결과(A'')을 local에 sync

2주차
           local  -----> origin  -----> upstream
main         A''           A''             A''
proof(1주)   A'            A'              A'
proof(2주)  A''+B'         A''+B'          

이렇게 관리하면 절대 conflict가 나지 않는다.


본인의 repository(origin)에 가서, branch를 보면 'view all branches'가 보인다.


여기 브랜치 관리에서 'New branch'를 누르게 되면


upstream / origin의 어떤 브랜치를 source root로 브랜치를 딸 지 결정할 수 있다.
위의 예시를 기반으로 위를 선택하면 A''가, 아래를 선택하면 A' 소스부터 시작하는 branch가 새로 파진다.
그러면 local에서는 해당 소스를 받아와서 새로 작업하면 된다.

git fetch origin # 내 origin의 정보를 가져오고 싶어.
git checkout -b proofread origin/tutorial # 내 local에 proofread라는 branch를 만드는데, 내 github 저장소의 tutorial 브랜치에 있는 내용을 가져오고 싶어. 그리고 branch를 proofread 브랜치로 바꿔줘.

원래 브랜치에서 개발하고 있는 내용이 있다면, 잠깐 stash라는 무적의 명령어를 사용해서 각 브랜치에 임시보관할 수 있다. 물론 개발하고 있는 내용을 현재 branch에 커밋하는게 가장 안전하지만, 혹시나 secret key같은 것이 같이 커밋되면 안되는 경우에는 stash를 사용한다.

git stash
git fetch origin
git checkout -b proofread origin/tutorial
# 작업 시작
git add .
git commit -m "작업 다함 ㅋㅋ 개꿀"
git push origin/proofread
# 작업 끝
git checkout main
git stash pop

그리고 github에 가 보면, PR을 날릴 수 있다.
나오지 않는 경우는 직접 눌러서 compare across forks 버튼을 눌러 서로 다른 저장소의 서로 다른 브랜치끼리의 PR이 가능하다. 이렇게 하면 원하는대로 특정 커밋만 보낼 수 있다.

이런 식으로 작업하면 된다. 혹시나 수술 집도가 들어갔을 경우는 아래에서 다루겠다.

이미 수술 집도가 들어간 경우

커밋이 진행됐으면 진행됐을수록, 작업을 중첩으로 했으면 했을수록 더더욱 손을 대기가 어렵다.
conflict가 나지 않으면, github(origin)에서 sync fork를 눌렀을 때 다음과 같은 화면이 뜬다.

이럴때는 무소유 정신으로 Discard 9 commit을 해서 아예 upstream과 똑같이 동기화를 해주거나, Update branch로 최신 upstream 위에 내 commit을 얹어주면 될 일이다. 그러나 문제는 다음과 같다.


이 경우에는 충돌이 나서 Sync fork가 되지 않는다. 그러면 로컬에서 충돌 해결을 한 후 동기화해줘야 하는데, 그 과정이 쉽지 않다.

나는 급하다

지금 동기화가 문제가 아니라 작업을 제출해야 하는 상황이면, 파일을 백업해두고, 브랜치를 위의 방법처럼 새로 파서 파일을 넣어서 먼저 제출한다. 추후 cherry-pick 기능을 이용해 main branch에서 해당 커밋을 가져올 수 있다.

난 너보다 더 급해서 이미 main 브랜치에 커밋을 해버렸다

이럴 때에는 체리픽( cherry-pick ) 기능을 이용해 원하는 커밋을 가지고 올 수 있다.
체리픽의 어원은 체리 피킹에서 나왔는데, 체리 피킹(cherry picking)은 '케이크 위에 얹어져 있는 체리만 집어먹는 행위'라는 뜻으로, 어떤 회사의 제품이나 서비스 중 인기 있는 특정 요소만을 골라 경제적으로 취하려는 소비 현상을 비유하는 용어였다. 깃에서는 이 단어를 가지고 내가 원하는 커밋만 가져올 수 있게끔 지원한다.
내가 원하는 이럴 때에는 다음의 방법대로 할 수 있다.

git log
---
commit 2ef0c22671ab9b72534c2f57966a4e97494f188c (HEAD -> main, origin/main, origin/HEAD)
Merge: 6f97cad 4d92521
Author: 
Date:   Mon Jan 13 22:26:25 2025 +0900

    Merge branch 'LangChain-OpenTutorial:main' into main

commit 4d92521a5378a37225b5336f470f5a09a0561fb5
Merge: 5a125c5 4731410
Author: 
Date:   Mon Jan 13 06:27:12 2025 +0900

    [GitBook] Update: Uploaded README.md file (#332)

commit 47314103c61e9e9f1e92a0d0fa2ef1101601c820
Author: 
Date:   Mon Jan 13 06:25:40 2025 +0900

    [GitBook] Update: Uploaded README.md file

    [GitBook] Update: Uploaded README.md file

git log 명령어를 입력하면, 커밋에 대한 해시번호가 나온다. 이 해시번호를 사용해서, 내가 있는 브랜치에서 명령어를 입력하면 된다.

git cherry-pick 2ef0c22671ab9b72534c2f57966a4e97494f188c 4d92521a5378a37225b5336f470f5a09a0561fb5
git add . # 또는 git add 파일명
git cherry-pick --continue
git commit -m "원하는 것만 가지고 오기"
git push origin/proofread

만약, cherry-pick 중 문제가 터졌다면 충돌을 해결하고 다시 add 후 --continue로 마무리하면 된다.
답이 나오지 않는다면

git cherry-pick --abort

로 체리픽을 중단하고 원래 상태로 돌아가면 된다.

Sync 맞추기

이제, 남은 Sync를 맞추는 일만 남았다. 도식화하면 아래의 작업을 할 것이다.

                1. fetch
   ↙─────────────────────────────────┐
 local ------->  origin  ------->  upstream
   2.   push
resolve conflict

local에서 upstream의 작업 과정을 가지고 와서, local에서 충돌을 해결한 뒤에 다시 origin에 변경 사항을 작업해 줄 것이다.

git fetch upstream
git merge upstream/main

# 충돌 해결 : IDE 또는 VIM 등...

git add file # 충돌 해결한 파일 커밋
git commit -m "resolve conflict"

git push origin main

결론

현실이 차가워도 내 깃은 더 차갑다. 위에서 설명한 것은 이상적인 시나리오고, 현재 작업하고 있는 상황에 따라 충돌이 날 확률이 높고, abort나 rebase등을 섞어 사용하면서 해결하다보면 어느새 내 완성품이 아래처럼 보인다.


이걸 설명하고 있는 나도 리포지토리를 수없이 날려봤고, 아직도 깃 명령어도 잘 모른다. branch를 새로 파는것도 2년 전쯤 한번 해보고 말았다. cherry-pick이 있는 것도 알았지만, 왜 쓰는지도 잘 몰랐고 git 전략도 불필요하다고 생각했으나 실제 상황에 직면해보니 너무나 좋은 기능이고 또 잘 알게 되었다. 이런 기회가 아니었다면 아마 더 나중에 까마득할 때 써볼 것 같아 이런 상황이 너무 즐겁고 재미있게 공부하고 있다.

'GIT' 카테고리의 다른 글

TIL - 아무것도 못 본척 해야 돼! "REVERT"  (0) 2023.02.06
TIL - 협업시 Repository는 FE/BE로 나눈다.  (0) 2023.02.06

개괄

MBTI에서 늘 P를 유지하고 있는 영준씨는 또 성급하게 커밋을 올리고 PR을 날려버렸다.

그제서야 생각나는 "아, 이거 안 올렸다" 또는 "헉 이걸 올렸네"

긴급하게 눈에 보이는 REVERT를 눌러버렸다.

 

문제 발생

Revert를 하는것까진 좋았다. 다시 pr을 날리려고 했는데 문제는 "Nothing to compare"가 뜬 것이다.

분명히 코드는 예전 코드인데....

구글에 찾아보니 Revert로 되돌린 파일들은

"스포일러를 당했는데 기억 속에서만 삭제된 상태"이라고 한다.

분명히 스포일러는 당한 상태인데 (github 이력에 Revert : ~로 나와있음)

내가 알고있는건 아무것도 없는.... 그런 상태

 

시도한 것

당연하게도, 상남자답게 branch를 밀어버리고 새로이 다른 branch에서 PR을 날렸다.

그렇지만 지금은 회사가 아니고, 나중에 회사라면 정말 큰 문제가 생길 것이다.

 

깨달은 것

후의 나에게 부탁하는 것과 일맥상통한다.

"제발 성급하게 커밋과 PR을 하지 말 것"

그러나 혹시나 했다면

Revert로 돌릴 때 더 먼 옛날의 코드로 revert를 하거나 reset을 하면 된다.

그러나 최대한 하지 말자.

'GIT' 카테고리의 다른 글

Conflict의 냄새가 날 때  (1) 2025.01.16
TIL - 협업시 Repository는 FE/BE로 나눈다.  (0) 2023.02.06

개괄

처음으로 진행하는 협업, FE와 BE 모두 협업은 처음이었다.

대박 프로젝트의 꿈을 안고 협업을 시작하기로 한 대현씨와 영준씨.

그들은 git repo에 프로젝트명을 넣고 그 안에 모든 FE/BE의 코드를 몽땅 넣어버린다.

 

문제 발생

결과

그 결과 미친 키메라가 탄생했다.

한 프로젝트 안에 fe/be를 모두 넣고 돌릴때 두명 다 터미널에서 npm start를 하고, 스프링 컨테이너를 띄웠다.

프론트와 백 전부다 gitignore에 코드를 넣었다.

리포지토리는 난잡해졌고 이게 뭔지 모르겠는 상태가 되어버렸다.

그러다 프론트는 SOS를 요청하러 갔고 백엔드는 다른 사람들의 프로젝트를 염탐하러 갔다.

그리고 발견한 다른 팀의 리포지토리. 이게 뭐야!

이렇게 깔끔한 repo가 있을 수가 없다. 그때서야 github Organization에 대한 필요성을 알게 되었다.

그 전까지는 Organization이란 그냥 "나도 pr 받게 해줘"의 연장선인 줄만 알았는데..

그제서야 organization을 만들고 그 안에 서로의 repo를 파서 코드를 나눠 담았다.

이렇게 깔끔할 수가 없다.

이게 협업의 기본이구나 라는걸 또 새삼 깨달아버렸다.

 

알게 된 점

한 레포에서 BE와 FE를 모두 나누어 담는것은, 1인 프로젝트 때나 하는 것이고,

다른 사람과의, 특히 다른 단에 있는 개발자와 협업을 할 때는 레포지토리를 꼭 나눠야 하는구나 라는것을 깨달았다.

'GIT' 카테고리의 다른 글

Conflict의 냄새가 날 때  (1) 2025.01.16
TIL - 아무것도 못 본척 해야 돼! "REVERT"  (0) 2023.02.06

+ Recent posts