Git은 괜찮지 않다

12 hours ago 3
  • Git은 소스의 분산 저장소로는 성공했지만, 분산 워크플로 처리는 나중에 붙은 해법에 가까워 한계가 드러남
  • Git의 커밋과 브랜치는 후속 커밋, amend 이력, rebase 이력, 버려진 상태를 스스로 표현하지 못함
  • Stacked PR에서는 후속 PR을 찾고 스택을 유지한 채 rebase해야 하지만, Git은 이 관계를 안정적으로 파악하기 어려움
  • Git은 staging, unstaged, 파일 시스템, HEAD 같은 변경 가능한 상태를 커밋·브랜치 바깥에 두어 학습과 사용을 복잡하게 만듦
  • 병합 전 PR들을 함께 써야 하는 비동기 개발 흐름에서 Git의 뒤를 향한 불변 이력 모델은 반복적인 문제를 낳음

Git의 두 역할

  • Git은 소스의 분산 저장소이면서 동시에 분산 워크플로 도구로 쓰임
  • 소스 저장소로서는 매우 성공했지만, 분산 워크플로를 다루는 방식은 대부분 나중에 덧댄 해법에 가까움
  • 비동기 개발은 East River Source Control의 표현처럼 기본 조건에 가까우며, 시간대가 다른 협업뿐 아니라 자기 자신과 시간차를 두고 일할 때도 발생함
  • jj는 Git의 한계를 더 선명하게 드러내는 도구이며, Git이 충분하다고 느끼는 사람은 jj를 진지하게 시도하지 않을 가능성이 큼

Git의 기본 모델이 놓치는 관계

  • Git 사고의 중심에는 커밋과 브랜치가 있음
    • 커밋은 소스 코드와 이력을 담는 불변 객체임
    • 브랜치는 로그가 붙은 변경 가능한 포인터임
  • 전형적인 Git 다이어그램은 커밋을 C1, C2, C3처럼 그려 순서와 관계가 명확해 보이게 하지만, 실제 저장소의 커밋 이름은 해시나 메시지에 가까워 그런 순서 관계가 시스템 안에 존재하지 않음
  • rebase 후의 C2와 C2’ 같은 표기는 사람이 이해하기 쉬운 설명일 뿐, Git은 두 커밋이 서로 대응된다는 사실을 알지 못함
  • 특정 커밋의 후속 커밋을 찾으려면 모든 브랜치를 훑어 해당 커밋으로 이어지는 경로의 커밋을 찾아야 하므로 간단하지 않음

Git에는 “C”가 없음

  • Git 커밋은 다음 정보를 스스로 알 수 없음
    • 후속 커밋

      • amend 이후 새 커밋에서 예전 커밋으로 이어지는 수정 이력
    • rebase 이력

      • 해당 커밋이 버려졌는지 여부
      • 브랜치에도 한계가 있음
      • 브랜치는 이력 개념은 있지만, 코드 변경과 1:1로 대응된다고 신뢰하기 어려움
      • 브랜치끼리는 관계를 갖지 않으며, 예컨대 trunk에서 wp/bugfix를 안정적으로 찾을 수 없음
      • trunk에서 wp/bugfix로 향하는 전방 참조가 없기 때문에 도달 가능한 관계도 아님
      • Git 다이어그램은 사람이 보기에는 순서와 대응 관계가 있어 보이지만, 실제 도구가 제공하는 능력을 과장해 보일 수 있음

Stacked PR이 어려운 이유

  • 시간대가 다른 사람과 협업하면서 리뷰 전에는 병합하지 않으려면, CPU처럼 작업을 파이프라이닝해야 함
  • 하나의 PR을 만들고 리뷰가 끝날 때까지 기다리는 대신, 첫 PR 위에 두 번째 PR을 만들고 다시 그 위에 다음 PR을 쌓아 여러 순차 PR을 동시에 리뷰에 올리는 방식이 Stacked PR
  • Git은 Stacked PR 구조를 안정적으로 다루기 어렵게 만듦
    • Fix key entry race 위에 Refactor key entry code 같은 후속 PR을 만들고, 이후 trunk를 fetch해 갱신하면 스택을 유지한 채 rebase해야 함
    • Git은 후속 커밋을 알지 못하므로 Fix key entry race에서 Refactor key entry code를 쉽게 볼 수 없음
    • 커밋이 버려진 것일 수도 있어, 후속 커밋을 볼 수 있더라도 최신 상태인지 알기 어려움
    • 브랜치는 PR 자체처럼 쓰이지만 이 흐름에서는 실수로 덮어쓰기 쉬움
  • Graphite 같은 스태킹 도구는 Git 위에서 이 작업을 할 수 있지만, Git의 커밋이나 브랜치 자체를 보강하지는 못함
    • 별도의 브랜치 메타데이터 저장소를 만들고 Git과 동기화해야 함
    • 사용자가 Git 자체를 직접 조작하면 그 저장소가 Git 상태와 어긋날 수 있음

변경 가능한 상태가 커밋 바깥에 있음

  • Git의 여러 문제는 변경 가능성(mutability) 을 직접 모델링하지 않는 방식에서 이어짐
  • Git의 편집 워크플로에는 커밋과 브랜치 바깥에 별도 상태가 존재함
    • Staging 또는 index는 작업 복사본에서 만들어지는 소스 스냅샷이며, 새 커밋은 여기서 만들어짐
    • Unstaged는 index와 파일 시스템 사이의 차이를 나타내는 두 번째 diff임
    • 파일 시스템은 checkout된 내용을 담고, 여기에 staged와 unstaged 변경이 더해짐
    • HEAD는 새 커밋이 만들어지는 위치임
  • stash는 staging과 unstaged 변경을 저장하고 복원하는 별도 저장소처럼 동작함
  • checkout을 다른 커밋이나 브랜치로 바꾸면 Git은 파일 시스템을 새 위치에 맞추면서도 staging 또는 unstaged의 diff를 보존하려고 함
  • 이 과정은 명령은 다르지만, 화살표 관계만 보면 staging을 새 기준 위로 옮기는 rebase와 비슷한 형태를 가짐

모든 것을 커밋으로 모델링하기 어려운 이유

  • Staging과 작업 복사본도 명확한 조상을 갖고 소스 코드를 담으므로, 정적인 상태만 보면 커밋처럼 표현할 수 있음
  • 하지만 커밋 ID는 내용의 해시이기 때문에 커밋이 변경 가능하다면 ID가 계속 바뀜
  • Staging과 작업 복사본이 “무엇인지”를 일관되게 가리키려면 커밋이 아니라 브랜치처럼 다뤄야 하지만, 브랜치에는 앞서 다룬 한계가 있음
  • 이 복잡성은 실제 문제로 이어짐
    • Git 학습과 사용이 더 어려워짐. 같은 개념이 양쪽에 따로 존재하기 때문임
    • 저장소 전체 상태가 clone으로 가져오는 상태와 크게 달라 내보내기가 어색해짐
    • 시간이 지나며 변경 세트가 바뀌는 비동기 흐름이 잘 동작하지 않음
    • 변경 가능한 쪽의 시스템은 merge를 표현하지 못해 실제 워크플로를 나타낼 수 없는 경우가 있음

Git이 실제 워크플로를 표현하지 못하는 경우

  • 새 기능 브랜치에서 아직 커밋하지 않은 채 개발하던 중, 기기에서 개발을 방해하는 버그를 발견할 수 있음
  • 해당 버그가 새 기능을 막지는 않지만 개발을 성가시게 만든다면, 작업을 stash하고 새 브랜치로 이동해 재현 테스트와 수정 작업을 만든 뒤 PR을 올릴 수 있음
  • 이후 다시 새 기능 브랜치로 돌아오면 선택지가 제한됨
    • new-feature를 실제 의존성이 없는 bugfix 위로 rebase하고 리뷰를 진행함
    • 개발 중에는 new-feature를 bugfix 위로 rebase해 쓰다가, 브랜치를 제출하기 전에 rebase를 되돌림
  • Git으로는 “편집 작업 공간에는 bugfix의 모든 코드와 이미 커밋한 new feature 코드가 함께 있어야 한다”는 상태를 표현할 수 없음
  • 이런 요구는 병합되지 않은 PR과의 호환성 테스트 같은 더 어려운 문제에서도 같은 구조로 나타남
  • Jujutsu megamerges처럼 적절한 도구를 쓰면 여러 PR을 병렬로 유지하면서도 편집 공간에서는 함께 사용할 수 있음

Git은 더 이상 충분하지 않음

  • 2000년대 초반의 버전 관리 도구들은 사용과 관리가 어렵고 품질이 들쭉날쭉했으며, Subversion도 고통스럽다는 인식이 널리 퍼져 있었음
  • 당시에는 로컬에 전체 저장소 복사본을 갖고 싶다는 요구가 일반적이지 않았고, 로컬 브랜치를 만들고 싶다는 요구도 보편적이지 않았음
  • 파일 잠금에 불편을 느끼는 사람도 많았지만, 어떤 사람들은 파일 잠금이 필요하다고 보았고 Git에서 개별 파일이나 디렉터리를 잠글 수 있는지 묻기도 했음
  • 오픈소스처럼 분산 워크플로를 직접 겪던 사람들에게 DVCS는 오래된 상처를 막아주는 붕대처럼 받아들여졌음
  • 오늘날 의미 있게 분산된 워크플로를 쓰는 사람에게 Git의 뒤를 향한 불변 이력 모델은 반복적인 문제의 원천이 됨
  • Meta 같은 회사는 거의 10년 동안 Git을 크게 앞서는 사내 시스템을 사용해 왔음
  • “이제 Git은 Claude가 대신 만진다”는 흐름이 이런 대안을 무의미하게 만들지는 않음
  • LLM을 쓰면서 단일 머신 안에서도 엔지니어들이 이전보다 더 많은 비동기 개발을 하고 있는 것으로 보임
Read Entire Article