DevOps · Git · Collaboration

합칠 때 - 역사를 남기나 정리하나

지선을 본선으로 되돌리는 방법은 하나가 아니다. 이어 붙이는 머지와 옮겨 놓는 리베이스가 각각 무엇을 남기고 무엇을 지우는지, 충돌은 어디서 생기는지 본다.

·갈래와 합류 2편
목차
  1. 돌아오는 방법이 둘이다
  2. 머지: 이어 붙이고 만난 자리를 남긴다
  3. 리베이스: 지선을 본선 위로 옮겨 놓는다
  4. 충돌은 합치는 방식이 만드는 게 아니다
  5. 마주치는 자리가 다르다
  6. 역사를 다시 쓴다는 것의 값과 대가
  7. 스쿼시: 지선을 한 칸으로 접는다
  8. 그래서 무엇을 위해 역사를 남기나
  9. 실무에서: 자리마다 다르게 쓴다
  10. 정리

지선은 언젠가 본선으로 돌아온다. 돌아오는 방법이 둘인데, 둘은 끝나고 남는 모양이 다르다.

돌아오는 방법이 둘이다

지선에서 일이 끝났다. 그동안 본선도 움직였다. 이제 둘을 하나로 만들어야 한다.

여기서 손이 갈리는 지점은 딱 하나다. 갈라졌다는 사실을 기록에 남길 것인가, 지울 것인가.

남기는 쪽이 머지(merge)고, 지우는 쪽이 리베이스(rebase)다. 결과물의 코드는 대개 같다. 다른 건 그 코드에 이르는 기록이다.

머지: 이어 붙이고 만난 자리를 남긴다

머지는 본선 끝과 지선 끝을 잇는 지점을 새로 하나 만든다. 이 지점이 특이하다. 부모가 둘이다. 하나는 본선의 마지막, 하나는 지선의 마지막이다.

철로로 보면 지선을 본선에 물리적으로 이어 붙인 것이다. 분기점도 합류점도 선로 위에 그대로 남는다. 나중에 이 구간을 보면 “여기서 갈라져 저기서 만났다”가 보인다.

그래서 머지가 남기는 것은 이렇다.

  • 언제 갈라져 언제 합쳐졌는지. 지선이 며칠 살았는지가 기록에 남는다.
  • 어느 커밋이 한 묶음이었는지. 지선 위의 커밋들이 하나의 갈래로 묶여 보인다.
  • 합칠 때 무슨 일이 있었는지. 충돌을 풀었다면 그 결과가 합류 지점에 담긴다.

리베이스: 지선을 본선 위로 옮겨 놓는다

리베이스는 다르게 접근한다. 지선에서 한 일을 지금 본선 끝에서 처음부터 다시 한 것처럼 놓는다. 갈라져 나온 밑동(base)을 옮긴다는 뜻이다.

결과는 한 줄이다. 갈라진 적이 없었던 것처럼 커밋들이 본선 위에 일렬로 이어진다.

여기서 짚어야 할 게 하나 있다. 옮겨진 커밋은 원래 커밋이 아니다. 내용은 같아도 앞에 오는 것이 달라졌으니 다른 커밋이다. 원래 커밋은 아무도 안 가리키는 채로 남았다가 사라진다.

머지 · 이어 붙이고 만난 자리를 남긴다

본선 A B C D 합류 지선 X Y 갈라진 자리와 만난 자리가 그대로 남는다 X·Y는 만들어진 그 커밋 그대로다

리베이스 · 본선 끝으로 옮겨 한 줄로 만든다

본선 A B C D X' Y' 옛 지선 X Y X'·Y'는 내용만 같은 새 커밋이다 원래 X·Y는 아무도 안 가리켜 사라진다

달라지는 건 지선의 커밋이 어디에 놓이느냐 하나다. 본선 옆에 남겨 합류 자리를 만들거나, 본선 끝으로 옮겨 한 줄로 만들거나.

그림에서 아래쪽의 흐릿한 두 칸이 그것이다. 리베이스는 옮기는 게 아니라 베껴 놓고 원본을 버리는 것에 가깝다. 이 성질이 뒤에서 대가로 돌아온다.

충돌은 합치는 방식이 만드는 게 아니다

여기서 흔한 오해를 하나 짚는다. “리베이스하면 충돌이 난다”거나 “머지가 더 안전하다”는 말이다.

충돌은 그 둘 중 무엇을 골랐느냐로 생기지 않는다. 충돌은 같은 자리를 양쪽이 다르게 고쳤을 때 생긴다. 도구가 “어느 쪽이 맞는지” 자동으로 고를 수 없는 상황이다.

상황자동으로 되나
서로 다른 파일을 고쳤다된다
같은 파일의 다른 곳을 고쳤다대개 된다
같은 줄을 양쪽이 다르게 고쳤다안 된다. 사람이 고른다
한쪽이 지운 파일을 다른 쪽이 고쳤다안 된다

그러니 충돌의 원인은 앞 글이 말한 그것, 갈라진 동안 쌓인 차이다. 합치는 방식은 그 차이를 없애지 못한다. 정하는 건 하나뿐이다. 그 충돌을 어디서 몇 번 마주칠 것인가.

마주치는 자리가 다르다

같은 차이를 두고도 두 방식은 사람을 다른 자리에 세운다.

머지는 한 번에 세운다. 지선 전체와 본선 전체를 놓고 딱 한 번 푼다. 커밋이 다섯 개든 쉰 개든 마주치는 건 한 번이다.

리베이스는 커밋마다 세운다. 커밋을 하나씩 옮기며 놓으니, 옮기다 걸리면 거기서 멈춘다. 커밋이 다섯 개면 최대 다섯 번 멈출 수 있다.

거래가 여기서 생긴다.

산다판다
머지한 번만 풀면 된다. 최종 결과만 보고 판단한다덩어리가 크다. 여러 커밋의 사연이 한꺼번에 온다
리베이스한 번이 작다. 그 커밋의 맥락 안에서 판단한다횟수가 많다. 같은 자리를 여러 번 풀 수도 있다

마지막 칸이 리베이스에서 사람들이 지치는 지점이다. 앞 커밋에서 푼 자리를 뒤 커밋이 또 건드리면, 그 자리를 또 푼다. 커밋을 잘게 쪼개둔 지선일수록 이게 늘어난다.

역사를 다시 쓴다는 것의 값과 대가

리베이스는 이미 있던 커밋을 버리고 새 커밋을 만든다. 나 혼자 쓰는 지선이면 아무 문제가 없다. 버려지는 걸 나만 갖고 있었으니까.

남이 그 지선을 이미 받아 갔다면 얘기가 다르다. 그 사람 손에 있는 커밋은 이제 존재하지 않는 커밋이다. 그 상태에서 다시 받으면 같은 변경이 두 벌로 늘어나고, 그걸 다시 합치면서 없던 충돌이 생긴다.

그래서 팀마다 규칙이 갈리는 와중에도 이것 하나는 거의 같다.

주의

남이 이미 보고 있는 갈래의 역사는 다시 쓰지 않는다. 취향이 아니라 안전 문제다. 내 손의 기록을 정리하는 일이 남의 손에 있는 기록을 부순다.

거꾸로 읽으면 이렇게 된다. 아직 나만 보고 있는 지선은 마음껏 정리해도 된다. 실무의 경계선이 대개 여기에 그어진다. 공유 전까지는 자유, 공유 후에는 덧붙이기만.

스쿼시: 지선을 한 칸으로 접는다

셋째 선택도 흔하다. 스쿼시(squash)는 지선 위의 커밋들을 하나로 눌러서 본선에 넣는다. 지선에서 스무 번 커밋했어도 본선에는 한 칸으로 들어간다.

지선을 통째로 접어서 본선에 한 칸을 끼워 넣는 것이라, 지선이라는 갈래 자체가 기록에서 사라진다.

산다: 본선 기록이 “기능 하나 = 한 줄”이 된다. 훑기 쉽고, 되돌릴 때도 한 줄만 되돌리면 된다. 지선 안의 “오타 수정”, “다시 되돌림” 같은 커밋이 본선에 안 남는다.

판다: 그 과정이 통째로 사라진다. 왜 이 순서로 갔는지, 중간에 무엇을 시도했다 접었는지가 없어진다. 그리고 접힌 한 칸이 클수록 나중에 그 커밋 하나를 이해하기 어려워진다.

그래서 무엇을 위해 역사를 남기나

세 방식을 고르는 기준은 취향이 아니라 기록을 나중에 누가 왜 읽는가다. 읽는 목적은 대개 둘이다.

“이 줄은 왜 이렇게 됐나”를 되짚을 때. 버그를 쫓다가 한 줄의 내력을 캐는 경우다. 이때는 커밋이 작고 뜻이 분명할수록 좋다. 스무 개를 하나로 접어놨으면 “이 큰 덩어리 안 어딘가”까지만 알 수 있다.

“이 기능은 언제 어떻게 들어왔나”를 훑을 때. 릴리스 노트를 쓰거나 최근 변경을 훑는 경우다. 이때는 본선이 짧고 한 줄일수록 좋다. 잔가지가 많으면 뭐가 뭔지 안 보인다.

이 둘은 서로 반대 방향으로 잡아당긴다. 머지는 앞쪽을 편들고, 스쿼시는 뒤쪽을 편들고, 리베이스는 그 사이에 있다. 팀마다 답이 다른 게 이상한 일이 아니다. 어느 쪽을 더 자주 읽는지가 팀마다 다르니까.

실무에서: 자리마다 다르게 쓴다

실제로는 셋 중 하나를 고르는 게 아니라 자리마다 다르게 쓴다. 자주 보이는 조합은 이렇다.

조합어떤 팀에서
지선 안에서 본선을 따라잡을 땐 리베이스, 본선에 들어올 땐 머지갈래는 남기되 지선 기록은 깔끔하게
본선에 들어올 때 스쿼시본선을 한 줄로 유지하고 싶은 팀
전부 머지있었던 일을 그대로 남기려는 팀

무엇을 고르든 지켜야 할 것은 둘이다. 팀이 하나로 정할 것. 사람마다 다르게 하면 기록의 절반은 이렇게 생기고 절반은 저렇게 생겨서, 읽는 규칙 자체가 없어진다. 그리고 공유된 갈래의 역사는 건드리지 않을 것.

그리고 무엇을 고르든 줄지 않는 비용이 하나 남는다. 갈라진 동안 쌓인 차이다. 합치는 방식은 그 차이를 어떻게 다룰지를 정할 뿐, 차이 자체를 줄이지 못한다.

차이를 줄이려면 다른 축을 건드려야 한다. 얼마나 오래 갈라져 있느냐다.

정리

갈래를산다판다
머지남긴다있었던 일 그대로. 충돌을 한 번에 푼다기록에 잔가지가 는다
리베이스지운다한 줄로 정리된 기록. 충돌이 작게 나뉜다커밋을 다시 쓴다. 공유된 갈래엔 못 쓴다
스쿼시접는다본선이 짧다. 되돌리기가 한 줄과정이 사라진다

어느 쪽이 옳은 게 아니다. 셋 다 같은 차이를 다루는 서로 다른 방법이고, 고르는 기준은 그 기록을 나중에 무엇에 쓸 것인가다.

다음 글은 축을 하나 위로 옮긴다. 합치는 방법이 아니라 언제 합치나를 정하는 규칙, 브랜치 전략이다.