1인 개발자에게 git은 백업이 아니라 시간선
이 글은 git을 GitHub에 push하는 백업 수준으로만 다루는 1인 개발자를 위한 글입니다. 끝까지 읽으면, 지금 매일 조금씩 잃고 있는 것이 무엇인지와, 하루 5분 루틴으로 그것을 어떻게 되찾는지를 알게 됩니다. 결론부터 말하겠습니다. 1인 개발자에게 git의 가치는 협업도 아니고 백업도 아니며, 어제의 나에게 질문을 물을 수 있는 시간선을 만드는 데 있습니다.
대부분의 사람은 git을 ‘만약 뭐가 깨지면’이라는 목적 하나로 시작합니다. 레포지토리를 만들고 GitHub에 push하면, 이후로는 ‘깨지면 클라우드에서 가져오면 되지’라는 정신적 모델로 살아갑니다. 초반에는 아무 문제가 없어 보입니다. 파일이 구름에 있으니까요.
그런데 그 정신적 모델은 git이 줄 수 있는 것의 극히 일부만 쓰게 합니다. 못 쓰고 있는 것의 대가는 한꺼번에 오지 않습니다. 되돌리느라 잃은 오후, 포기해 버린 실험, 왜 이렇게 썼는지 기억하느라 쓴 시간처럼, 매일 조금씩 빠져나갑니다. 이 글은 그 손실의 구조와, 손실을 없애는 일상의 규율을 논합니다.
글의 핵심 개념을 형상화했습니다.
혼자일수록, 물어야 할 상대는 어제의 자신
팀에서는 git의 가치를 협업으로 설명하는 경우가 많습니다. 1인 개발자에게는 협업보다 ‘나’라는 한계점 자체가 문제입니다. 코드를 고쳐보다가 어젯밤 버전이 좋았다는 걸 깨달을 때, ‘어제 그건 어땠지’라고 물을 팀원이 없습니다. 물어야 할 상대는 어제의 나 자신인데, 기록이 없으면 그에게는 아무것도 물을 수 없습니다. 파일은 답해 주지 않고 답할 수 있는 것은 기록뿐이거든요.
결제 함수를 리팩토링했다고 해 보겠습니다. 커밋하고 테스트를 돌렸더니 깨졌습니다. 내가 원하는 답은 ‘어젯밤 전체 파일을 돌려줘’가 아니라, ‘이 리팩토링만 되돌리고 그 이후의 다른 작업은 유지해 줘’입니다. 특정 시점의 파일 복사본만 돌려주는 도구로는 이 질문에 답할 수 없습니다. 진짜로 필요한 답은 ‘이 함수가 마지막으로 정상일 시점이 언제였지’에 있으니까요.
여기서 차이가 드러납니다. 백업은 ‘어제의 파일’을 줍니다. git은 ‘어제의 상태, 그 이유, 그 이후의 모든 상태’를 줍니다. 백업 파일은 그냥 복사본이지만 커밋에는 메시지가 있고 그 메시지에 왜 바꿨는지와 무엇을 바꿨는지가 적혀 있습니다. 여섯 달 뒤 한 줄을 보며 ‘왜 이렇게 썼지’라고 생각할 때, 기억이 아니라 증인이 되어 주는 것이 바로 그 메시지입니다.
결국 1인 개발자에게 git은 협업 도구가 아니라, 시간을 되돌릴 수 있는 기록장치입니다. 이 글의 나머지는, 그 기록을 실제로 쓰게 만드는 규율에 대한 이야기입니다.
모르게 내는 세 가지 비용
이 대가는 일회성 비용이 아닙니다. 매일 조금씩 내는 비용이고 매번 금액이 작아서 대부분은 모릅니다. 첫 번째는 실험하지 못해서 잃는 비용입니다. 망가뜨리면 돌아올 수 없다고 생각하면, 새로운 방식을 시도하지 않게 되고 아는 것만 반복합니다. 1인 개발자의 생산성 성장은 안 해본 것을 시도하기에서 크게 오는데, 상태를 잃을 두려움이 그 시도를 막습니다. git이 줄 수 있는 가장 큰 이점을, 두려움 때문에 안 쓰는 겁니다.
두 번째는 되돌리지 못해서 잃는 비용입니다. 뭔가 깨졌을 때, 손으로 되돌리거나 백업에서 가져와서 다시 시작합니다. 둘 다 시간이 걸리고 본래 잃을 필요가 없었던 작업까지 함께 잃는 위험이 있습니다. 설정 파일을 고치다가 다른 것을 깨뜨렸다면, 백업과 지금 파일 사이를 오가며 한 줄씩 비교합니다. 5분 걸릴 변경이 3시간이 되는 겁니다. 그 파일이 작게 작게 커밋되어 있었다면, 문제의 범위는 이번 커밋 하나로 좁혀져서 그 커밋만 보면 됩니다.
세 번째는 설명하지 못해서 잃는 비용입니다. 석 달 전에 쓴 코드를 보며 왜 그렇게 썼는지 모를 때, 선택지는 두 개뿐입니다. 추측해서 리팩토링하면 깨질 위험이 있고 그대로 두면 채무가 쌓입니다. 커밋 히스토리가 있으면 ‘확인한다’는 세 번째 선택지가 생기고, 이전에 같은 벽에 부딪쳤는지까지 알려줍니다.
이 비용은 쌓입니다. 한 번의 금액은 작지만 한 해를 모으면 상당한 양이 됩니다. 게다가 조사 자체의 시작점도 달라집니다. 기록이 있으면 문제 조사는 ‘마지막으로 알았던 좋은 상태’에서 시작하지만, 백업이면 ‘지금 있는 파일과 기억’에서 시작하죠. 시작점의 차이가 조사 시간의 차이를 정합니다.
커밋은 저장이 아니라, 이유를 붙인 체크포인트
손실을 없애는 규율은 단순합니다. 저장할 가치가 있는 각 단계를 커밋으로 만드는 것, 즉 체크포인트를 두는 겁니다. 저장은 ‘파일 복사본을 남겨 두는’ 것이지만 체크포인트는 ‘여기까지가 온전하고 여기서 되돌려도 괜찮은 지점’에 이름을 붙이는 것입니다. 기준은 세 가지입니다. 동작하는지, 테스트가 통과하거나 최소한 문법 오류가 없는지. 의미가 온전히 담기는지, 무엇을 했는지 한 문장으로 설명할 수 있는지. 여기서 되돌려도 괜찮은지, 이전으로 돌아가도 잃을 것이 없는지.
여기서 git의 핵심 정신 모델을 짚겠습니다. 변경은 커밋되기 전까지는 사실이 아닙니다. 아직 커밋하지 않은 작업은, 언제든 버릴 수 있는 작업 중인 상태일 뿐입니다. 이걸 알면 실험 방식이 달라집니다. 대담하게 리팩토링을 시도하고 안 되면 시작 전으로 돌아오면 되는 거죠. 백업 도구에서는 저장 자체가 긴장이었지만 git에서는 커밋이 긴장이 아닙니다. 커밋은 되돌릴 수 있는 지점을 남기는 일이에요.
실제 하루를 그려 보겠습니다. 작은 서비스를 돌보는 화요일입니다. 아침에 status와 log를 돌려 서 있는 자리를 확인합니다. 어제 보고받은 버그를 고치고 테스트가 통과하는 지점에 커밋합니다. 새 기능을 시작하고 뼈대가 동작하는 지점에 또 커밋합니다. 오후에는 새로운 방식을 시도했는데 방향이 틀렸습니다. 한 명령으로 직전 커밋으로 돌아와 틀린 방식을 버리는데, 그 방식은 reflog에 남아 있어서 나중에 다시 볼 수 있습니다.
목적이 ‘커밋을 많이 하는 것’은 아닙니다. 오타 한 자 고칠 때마다 커밋하라는 말이 아니라, 의미 없는 상태를 계속 커밋하면 시간선이 시끄러워져서 이야기가 읽히지 않습니다. 기준은 앞의 세 조건입니다. 동작하고 의미가 온전하고 여기서 되돌려도 괜찮으면 커밋할 가치가 있는 겁니다. 하나라도 안 되면, 조금 더 기다리거나 stash를 쓰는 것이 맞아요.
두 정신을 나란히 놓으면, 차이가 한눈에 보입니다.
| 구분 | 백업 도구 정신 | 체크포인트 git 정신 |
|---|---|---|
| 무엇을 되찾나 | 그날의 파일 | 그 시점의 상태, 이유, 이후의 모든 상태 |
| 실험에 대한 태도 | 변경을 망설임 | 커밋 전까지 사실 아님, 언제든 버릴 수 있음 |
| 되돌리는 방식 | 전체 복사본으로 교체 | 특정 변경만, 한 명령으로 |
브랜치는 협업 기능이 아니라, 무료 샌드박스
브랜치를 떠올리면 많은 사람이 ‘팀원이 동시에 작업할 때 쓰는 기능’이라고 생각합니다. 1인 개발자에게 브랜치는 다른 것입니다. 무료로 쓰는 샌드박스라고 할 수 있습니다. 데이터 모델로 보면, 커밋은 사슬 위의 노드이고 브랜치는 그 노드를 가리키는 이름입니다. 브랜치를 만드는 명령은 파일을 복사하지 않고 지금의 커밋을 가리키는 새 이름 하나를 만들 뿐입니다. 그래서 비용은 사실상 0이고 ‘브랜치 만들기가 무섭다’가 ‘브랜치는 싸니까 많이 만든다’로 바뀝니다.
브랜치를 지워도 커밋은 사라지지 않습니다. 다른 경로에서 도달 가능하다면 그대로 남고 도달 불가능하더라도 reflog가 잠시 붙들고 있습니다. ‘브랜치를 잘못 만들었다’는 실수의 비용이 아주 작다는 뜻이에요. 실수해도 되니까, 더 만들게 됩니다.
브랜치를 항상 만들라는 규칙은 없지만 세 가지 상황에서는 자르는 것이 정확한 판단입니다. 결과가 불확실한 큰 변경, 리팩토링이나 의존성 업그레이드 같은 일은 main을 안정 상태로 커밋한 뒤 브랜치에서 합니다. 실패하면 브랜치를 지우면 되고 main은 그대로입니다. 여러 방식을 동시에 시도할 때는 A 방식은 A 브랜치에서, B 방식은 B 브랜치에서 해 보고 비교합니다. 독립적인 주기를 가진 작업도 main에서 하면, 작업 중과 안정 상태가 섞여 main이 ‘언제나 배포 가능한 것’이라는 의미를 잃습니다.
실험 브랜치에는 try/라는 접두사를 붙이는 습관을 권장합니다. try/redis-cache, try/in-memory-cache처럼, 나와 미래의 나에게 ‘확인된 작업이 아니라 실험이다’라고 말하는 이름이에요. 새 캐시 계층을 시도하려고 try/cache-v1을 만들고 오후에 방식 A가 느리다는 걸 확인하면 main으로 돌아가 try/cache-v2를 만듭니다. 수요일에 방식 B가 낫다고 판단하면 v2의 작업만 main으로 가져오고 v1은 지웁니다. 브랜치를 만들고 비교하고, 버리고, 남기는 일주일 동안 main은 한 번도 불안하지 않았습니다.
역사는 미래의 자신을 위한 검색 인덱스
커밋 메시지는 남이 보는 글이 아니라, 미래의 내가 찾는 것을 위해 만드는 검색 인덱스입니다. Conventional Commits라는 규약이 형태를 정해 줍니다. type, scope, subject의 조합으로, type은 어떤 변경인지(새 기능, 버그 수정, 문서, 재구성, 테스트, 도구 작업), scope은 어디에서였는지, subject는 무엇을 했는지 한 문장입니다. 1인 개발자는 ‘규약은 팀용이고 나는 필요 없다’고 자주 생각합니다. 그런데 type이 있으면 버그 수정만 따로 볼 수 있고 ‘이 변경은 언제 들어갔지’를 찾는 일이 읽는 문제가 아니라 검색으로 바뀝니다.
더 중요한 규칙이 하나 있습니다. 하나의 커밋에는 논리적 변경 하나만 들어야 한다는 겁니다. 에러 처리를 바꾸는 작업 중, 변수 이름도 바꾸고 리트라이도 추가하고 싶다면 세 개의 커밋으로 나눕니다. 변수 이름만 바꾼 커밋, 에러 처리를 바꾼 커밋, 리트라이를 추가한 커밋. 나중에 리트라이를 없애려면 세 번째 커밋만 되돌리면 되고, 앞의 둘은 그대로 남아요. 세 가지를 한 커밋에 넣으면, 되돌릴 때 세 가지가 같이 사라집니다.
본문은 이유가 스스로 명백하지 않을 때 씁니다. 원인이 잘 안 보이는 버그를 고쳤다면, 본문에 어떻게 일어났고 무엇으로 찾았는지를 적습니다. 여섯 달 뒤 같은 종류의 버그가 다시 나오면, 그 본문이 조사 시작점이 되거든요. 본문은 보고서가 아니라, 다음 날의 나에게 남기는 조사 노트입니다. 하나의 커밋 변경이 300줄을 넘는다면, 나눌 수 있을지 스스로에게 물어 볼 차례이기도 합니다.
이 규율을 지키면, 커밋 사슬은 프로젝트의 이야기가 됩니다. 다섯 줄만 읽어도, 웹훅을 추가했고 테스트 단계를 넣었고 리트리 로직을 뽑았고 결제에 멱등성을 붙였고 이중 과금 버그를 고쳤다는 순서가 보입니다. 반대로 메시지가 전부 asdf였다면, 파일은 그대로인데 이야기는 사라집니다. 왜 그렇게 되었는지 설명할 증인이 없는 셈이거든요.
5분이면 되는, 두려움을 반사로 바꾸는 루틴
결국 규율은 지식이 아니라 반복입니다. 제안하는 루틴은 세 가지예요. 아침 2분, 퇴근 5분, 주간 10분. 아침 루틴은 status로 깨끗한 상태인가를 보고 log의 최근 다섯 줄로 어디에 서 있는가를 확인하는 것입니다. git에 대한 두려움의 상당 부분은 내가 어디에 있는지 몰라서 생기고, 2분의 확인이 그 두려움을 없애요. 커밋되지 않은 변경이 여러 개 보인다면, 어제의 일을 반쯤 남겨둔 채 끝냈다는 신호입니다. 끝내서 커밋할지, stash에 둘지 오늘 결정하면 하루는 알려진 상태에서 시작합니다.
퇴근 루틴은 하루의 마지막 체크포인트를 남기는 것입니다. 저장할 가치가 있는 것은 커밋하고 remote가 있으면 push합니다. log의 최근 열 줄로 하루를 돌아보면, ‘오늘 내가 뭘 했는지’가 보이지 않으면 커밋이 너무 굵은 것이고 각 커밋이 무엇을 했는지 모르겠으면 메시지가 너무 빈약한 것입니다. 커밋한 뒤 안정적이라면 push하고, 큰 변경 전에 push하는 것도 잊지 마세요. remote가 진정한 백업이기 때문입니다. 로컬 레포는 한 디스크 위의 사본일 뿐이거든요.
주간 루틴은 레포를 자르는 일입니다. 죽은 브랜치를 정리하고 오래된 stash를 꺼내거나 지우고 remote 브랜치를 정리합니다. 자르지 않는 레포는 이름이 많은 곳이 되고 어느 날 내가 어디에 있는지 모르게 되는 거예요. 그리고 ‘사라졌다’는 생각은 거의 항상 틀렸습니다. git은 HEAD의 모든 이동을 reflog에 기록하고 기본값으로 약 90일치를 유지합니다. 브랜치를 지웠든, rebase 중에 커밋을 잃었든, reflog를 열어 잃은 커밋을 찾고, 거기서 새 브랜치를 만들면 끝입니다.
모든 것을 합치면, 이 글의 주장은 단순합니다. 1인 개발자에게 git은 백업이 아니라, 어제의 나에게 질문을 물을 수 있는 시간선입니다. 체크포인트에 커밋하고 실험은 브랜치에서 하고 역사에 이유를 적어 두면, 매일 조금씩 빠져나가던 세 가지 비용이 사라집니다. 규율은 규칙으로 시작해서, 반사로 끝납니다. 더 깊이 보고 싶다면, 34페이지 분량의 전자책 ‘보관소의 규율’을 펼쳐 보면 됩니다.
출처
- Pro Git: Branches in a Nutshell (브랜치는 커밋을 가리키는 lightweight movable pointer. 브랜치를 만드는 것은 새 포인터 하나를 만드는 것뿐)
- Pro Git: Maintenance and Data Recovery (git reflog가 HEAD의 모든 이동을 조용히 기록하며, 잃은 커밋을 찾는 가장 빠른 방법)
- git-config 문서 (gc.reflogExpire 기본값 90일. reflog 엔트리 만료 규칙)
- Conventional Commits 스펙 v1.0.0 (type/scope/subject 구조와 본문·푸터 규칙)