🤖 🐍
🛠 👨‍💻

🤖

Navigate back to the homepage

우아한 Git 커밋 히스토리 유지하기

Seonghyeon Kim
February 26th, 2020 · 2 min read

왜 Git 커밋 스토리는 (대부분의 경우) 수정되어야만 하는가

회사에서 일을 하다 보면 필연적으로 체감상 백만년 전쯤 작업된 다른 사람(보통 과거의 자신도 다른 사람과 마찬가지라는건 잘 알려진 사실입니다.) 이 작업한 부분을 건드릴 일이 생깁니다.

아니 이게 도대체 왜 이렇게 생겨먹은 코드/함수/클래스 인거지?

이제 그렇다면 저는 보통 IDE에서 제공하는 git blame 툴을 통해 이 부분이 어떠한 이유로 추가/수정되었는지를 찾아내려고 합니다. 이럴 경우 바로바로 관련이 있어보이는 커밋을 찾아낼 수 있다면 빠른 결정이 내려질 수 있는 반면. 반면 관련 없는 커밋에 같이 엮여있거나, 린트 반영 등 chore-한 커밋이라면 또다시 탐색을 해서 찾아내야 하거나 심지어는 코딩-고고학을 통해 직접 의미를 추론해내야만 하는 불상사가 벌어지기도 합니다.

이런걸 생각하면 미래를 위해서는 지금 당장은 조금 귀찮고, 조금 두렵더라도(Git 조작이 두려운건 필멸자들에겐 당연한 일입니다.) 커밋 스토리를 알맞게 고쳐 백만년이 지난 후에도 해당 코드 조각이 어떠한 사유로 이렇게 생겨먹은지를 바로 알아낼 수 있도록 해두는 것이 정신건강에 이롭다는 것을 알 수 있습니다.

가장 간단한 예시로는 저는 git hook 같은 도구로 퀄리티 필터를 사용하다 보면 퀄리티를 준수하지 못해 푸쉬가 안되는 경우가 있어서 포맷팅 등울 위해 간단히 코드를 고치는 경우가 있습니다.

이때 단순하게 생각을 하자면 새로운 커밋을 생성할 수도 있지만 blame-편리성을 생각하면 저는 가급적 기존 커밋에 내용을 합쳐서 불필요한 내용들이 기록에 섞여들어가는 것을 막는 편입니다.

지금부터 소개할 자문자답들은 이 상황를 포함해 주로 제가 일을 하면서 맞닥트리는 상황들에 대한 제 자신의 표준적인 gotcha들입니다.

상황 #1

나는 간단한 기능을 빠르게 만들고 커밋 feat: A를 만들었다. 그런데 푸쉬하려고 보니 최대 라인 제한을 넘은 코드가 있어서 다시 고쳐서 푸쉬하려고 한다.

그런데 fix: lint violation 같은 이름의 커밋을 만드는 것은 불필요한것 같다.

해결법

1git commit --amend

가장 간단한 방법은 이전 커밋에 지금 스테이징된 내용을 합치는 것입니다. --amend 옵션은 이전 커밋에 지금 스테이징 되어 있는 작업을 합쳐줍니다.

상황 #2

나는 브랜치를 따서 규모가 있는 기능을 만들고, PR을 올려 리뷰를 받았다. 리뷰를 반영하고 원 브랜치에 머지하려고 하니 수정 커밋으로 인해 기능 규모에 비해 지나치게 많은 커밋이 포함되어 있다.

나는 커밋들을 합쳐서 머지하고 싶다.

이 경우에는 두가지 해결방법중에서 선택하는 편입니다.

해결법 A

1git rebase -i HEAD~N
2git push --force
11: feat: A
22: feat: B
33: fix: A-1
44: fix: A-1-1
55: fix: B-1
66: fix: B-1-1
77: fix: B-1-2

A와 B에 대한 리뷰를 받고 커밋하고 머지하려고 보니 수정 커밋들끼리는 합쳐질것 같다고 생각되면 단순 rebase 선에서 정리가 가능합니다.

1. --1-2-(3)-(5)->
2--1-2-3-4-5-6-7-> =rebase=> (4) (6)
3 (7)

간단하게 interactive rebase 를 사용해서 fix: A-1-1fix: A 로 squash 합니다. 그리고 fix: B-1-* 들도 fix: B-1 로 squash 하면 불필요하게 흩어진 fix 커밋들을 합쳐서 정리할 수 있습니다.

그 후 보통 이럴 경우는 리모트에 올라가 있는 브랜치를 건드리는 경우이기 떄문에 포스 푸쉬를 사용해 리모트 브랜치를 덮어씁니다.

해결법 B

이 해결법은 전체적으로 커밋 로그가 답이 없어 보여서 역사 조작을 매우 많이 하고 싶을때 사용합니다.

1feat: A
2fix: A-1
3feat: B
4fix: A-2
5fix: B-1
6fix: B-2
7fix: B-3
8Merge remote-tracking branch ...
9fix: Reflect review on B-2

이런 커밋 히스토리가 있다고 가정해봅시다. fix 커밋이 굉장히 많고 여러가지 feat에 대해서 fix가 섞여있습니다. 게다가 fix 일부는 다시보니 새로운 기능 C 로 묶는게 났다고까지 생각됩니다. 이럴 경우에는 이 꼬인 히스토리를 여러번에 걸쳐서 rebase 하는건 귀찮아서 저는 보통 꼬인 커밋들에게 기록말살형을 내린 후 새롭게 역사를 쓰는 편을 선호합니다.

1git reset --soft HEAD~N
2
3# rewrite commits
4
5git push --force

원하는 지점(보통은 브랜치의 처음) 까지 커밋을 리셋하지만 --soft 옵션을 사용해서 작업 내용을 보존합니다. 그 후 좋게 보인다고 생각하는 만큼 헝크를 묶어서 다시 커밋합니다.

그 후 보통 이럴 경우는 리모트에 올라가 있는 브랜치를 건드리는 경우이기 떄문에 포스 푸쉬를 사용해 리모트 브랜치를 덮어씁니다.

상황 #3

신들린 작업을 하던 도중 다른 분이 먼저 푸쉬하신것을 발견했다. 이미 새로운 커밋을 만들었는데 그것때문에 새로운 머지 커밋을 만들면 그래프가 더러워질게 뻔하다.

나는 다른 분이 올리신 커밋과 내 커밋을 스무스하게 트리에 합치고 싶다.

이것도 두가지 방법이 있기 때문에 골라서 사용합니다.

해결법 A

1git pull --rebase

보통의 경우에는 Git이 풀받은 후 자동으로 리베이스를 잘 해주기 때문에 대부분 이 방법으로 해결이 가능합니다.

해결법 B

1git reset --soft HEAD~N
2git pull
3# rewrite commits
1current:
2 --Init-A-----B-C-> (local/dev)
3 \
4 *--ㄱ-ㄴ-> (remote/dev)
5
6git reset --soft HEAD~N
7
8 --Init-> (local/dev)
9 \
10 *--ㄱ-ㄴ-> (remote/dev)
11
12git pull
13
14 --Init-ㄱ-ㄴ-> (dev)
15
16rewrite commits:
17
18 --Init-ㄱ-ㄴ-ReA-ReB-ReC-> (dev)

다른 분이 올리신 커밋을 봤는데 딱뵈도 컨플릭트 각이 섰는데 컨플릭트 수정하는것보다 다시 커밋 쌓는게 편할것같으면 제가 올린 커밋을 다 리셋하고 풀 받은 다음 그 다음에 다시 커밋을 쌓는게 오히려 편하다고 생각헤서 이런 괴상한 방법을 사용하기도 합니다.

상황 #4

또다시 신들린 작업을 하던 도중… 아차! 브랜치를 분리하는 것을 까먹었다!

나는 브랜치를 따로 분리해서 리뷰받고 나중에 Squash merge로 합치고 싶다.

해결법

이번에도 git reset이 답이지만 이전에 리셋을 활용하던 것처럼 커밋을 다시 만들 필요는 없습니다.

1git branch new-branch
2git reset --hard HEAD~N # at current branch

현재 상태에서 새로운 브랜치를 만들어 놓고 잘못 커밋한 브랜치 커밋하기 전으로 원래대로 리셋하면 그런 과오는 저지른 적이 없었던 일이 됩니다.

1-Init-A-B-*> -Init->
2 \ =reset=> \
3 *NEW *--A-B->

More articles from seonghyeon.dev

git hook과 Makefile을 사용해 파이썬 코드 검열하기

git hook, Makefile, 그리고 강철같은 불굴의 뚝심으로 정신을 바짝 차리고 숭고하고 순결한 코드와 비타협적 타입 안정성을 관철하기

February 16th, 2020 · 1 min read

GitHub Action과 Poetry를 사용한 파이썬 패키지 개발

Poetry와 GitHub Action을 사용하여 파이썬 패키지를 관리하는 법에 대하여 알아봅시다.

December 25th, 2019 · 1 min read
© 2019–2020 seonghyeon.dev
Link to $https://twitter.com/NovemberOscar_Link to $https://github.com/NovemberOscarLink to $https://www.linkedin.com/in/novemberoscar/