버전 관리에 대한 추가 내용

발행: (2026년 3월 30일 AM 04:26 GMT+9)
12 분 소요

Source: Hacker News

놀랍게도 그리고 기쁘게도 제 버전 관리에 관한 마지막 글이 Hacker News에 소개되어 많은 조회수를 얻었습니다. 참여해 주신 모든 분들께 감사드리며, 새로운 구독자분들을 환영합니다. 저는 이런 일에 엄청난 노력을 기울였고, 그에 대한 결과가 보이는 것이 기쁩니다.

안전한 리베이스 & 안전한 스쿼시

제가 지난 글을 쓸 때 완전히 깨닫지 못했던 점은, 부모 중 하나를 primary 로 선택하여 “safe rebase” 를 지원하는 것 외에도, 더 오래된 조상을 primary 로 선택함으로써 “safe squash” 를 지원할 수 있다는 것입니다.

이 접근법의 장점은 Git 방식보다 엄격히 더 많은 정보를 제공한다는 점입니다. blame/history의 안전 버전이 항상 primary 경로를 따르도록 하면, 두 구현 모두에서 동일한 순서로 같은 명령을 실행했을 때 출력 결과가 거의 동일하게 보이도록 만들 수 있습니다—단, 안전 버전이 더 합리적으로 동작하는 미묘한 경계 상황을 제외하고는.

안전 버전은 전체 히스토리를 여전히 기억합니다. 이는:

  • 실제로 무슨 일이 일어났는지 살펴볼 수 있게 해 주며,
  • 이미 스쿼시되었거나 리베이스된 것을 다시 가져오는 경우와 같은 실수를 크게 줄여 줍니다.

반면, 안전하지 않은 버전은 히스토리를 완전히 버리고, 최종 작업을 수행한 사람이 모든 것을 작성했거나 원본 작성자가 실제와는 크게 달라질 수 있는 내용을 썼다는 허구로 대체합니다.

왜 Git이 아닐까?

Git은 매우 단순하고 신뢰할 수 있으며 다재다능하지만, 기능적이지는 않다. 펜과 종이로 글을 쓰는 것이 인라인 편집을 “지원”하는 방식처럼, Git은 squash와 rebase를 “지원”한다—즉, 버전 관리 시스템의 많은 작업을 인간이 암묵적으로 수행하도록 만든다.

더 정교한 버전 관리 시스템들이 다재다능함과 신뢰성의 암묵적 감소를 보상할 만큼 새로운 기능을 제공하지 못했기 때문에 Git이 여전히 사용되고 있다.

내 목표는 전환할 가치가 있을 만큼 설득력 있는 이야기를 제공하는 기반을 만드는 것이다. 나는 그 사례가 충분히 좋다고 생각한다:

  • 커밋 시점에 diff에 커밋하는 약간의 비용만 들이면, 그냥 작동하는 더 안전한 squash와 rebase를 가질 수 있다.
  • 가끔 발생하는 악몽 같은 상황을 위한 더 나은 로컬 언두도 제공한다.
  • 보너스로, 꽤 좋은 체리‑피킹 기능도 얻을 수 있다.

“커밋 시점에 차이점 커밋하기”

행동 관점에서 보면 이것은 명확한 장점이지만, 구현 위험을 수반합니다. 이러한 구현은 매우 충분히 테스트되고 감사된 핵심 기능을 필요로 하며, 업데이트는 극히 보수적으로만 이루어져야 합니다.

그래서 저는 최종 일관성의 제약 하에서 데모 구현을 가능한 한 단순하게 만들었습니다. 이를 수행한다고 주장하는 다른 시스템들은 제가 이해조차 할 수 없는 기술 문서를 가지고 있었으며, 그들의 동작에 대한 직관을 형성하는 것은 말할 것도 없었습니다. “충돌은 너무 가까운 시점에 발생하는 업데이트”라는 접근 방식을 통해 저는 동작을 직관적으로 추론할 수 있습니다.

좌 / 우 별명

일부 사람들은 **“left”**와 “right” 별명이 혼란스럽다고 느꼈습니다. 이는 전적으로 권고 사항이며, 원하는 어떤 정보로도 교체할 수 있습니다—예를 들어, 해당 브랜치 이름, 로컬인지 원격인지 여부, 혹은 blame 정보 등.

Source: https://arxiv.org/abs/2305.00583

Anchoring Algorithm for CRDTs

CRDT에 대한 앵커링 알고리즘은 발명 여러 차례 반복 로, 지난 몇 년간 독립적인 그룹들에 의해 이루어졌습니다. 저는 이 알고리즘이 최근 몇 년 안에 등장했다는 점이 기쁘게 느껴집니다. 왜냐하면 저는 20년 전쯤에 이 아이디어를 떠올렸어야 한다고 생각했기 때문입니다.

이상하게도, 그들은 세대‑카운팅 트릭을 아직 파악하지 못한 듯합니다. 이 트릭은 제가 20년 전쯤에 고안한 것입니다. 두 아이디어를 결합하면 히스토리에서 커밋 ID에 대한 어떠한 참조도 없게 만들 수 있고, 전체 알고리즘을 구조적으로 유지할 수 있습니다.

충돌 섹션 처리

제가 자세히 다루지 않은 구현 세부 사항 중 하나는, 제 데모 구현에서는 양쪽 모두 삽입이 없고 삭제만으로 구성된 conflict 섹션을 플래그하지 않는다는 점입니다.

이와 관련된 의미론을 사람들과 논의해 보았는데, 대부분의 경우 사용자들은 이를 플래그하는 것이 excessively conservative(과도하게 보수적)이라고 여기며, “모두 사라짐”으로 깔끔하게 병합되는 것을 허용하는 듯합니다.

이는 분명 사람들 사이에서 종교 전쟁처럼 격렬하게 논쟁될 수 있는 사안이며, 누군가 실제 데이터를 수집한다면 매우 흥미로울 것입니다. 실제로는 결국 사용자가 원하는 대로 설정할 수 있는 per‑user flag가 될 것이며, 논쟁의 핵심은 기본값에 관한 것입니다.

두 개의 동일한 브랜치 병합

버전 관리에 관한 흥미로운 질문 중 하나는, 겉보기에는 동일해 보이는 두 브랜치를 두 부모 중 어느 쪽에도 해당하지 않는 버전으로 병합할 수 있는지 여부이다.

내가 제안한 접근 방식은 실제로는 얼굴에 폭발을 일으킬 수 있는 많은 경우를 피하도록 해 주지만, 절대 일어나지 않는 것은 아니다. 예를 들어:

Start:   XaXbX
Branch 1: XaXbX → aXbX → XX   (첫 번째 X 삭제)
Branch 2: XaXbX → XaXb → XX   (마지막 X 삭제)

이를 병합하면, 첫 번째 브랜치는 세 개 중 첫 번째 X를 삭제하고, 두 번째 브랜치는 세 개 중 마지막 X를 삭제했기 때문에 단일 X 로 깔끔하게 병합된다.

이는 실제 상황에서는 발생하기 어려운 다소 인위적인 예시이며—X가 빈 줄이라 할지라도—반복되는 줄을 신뢰성 있게 일관되게 유지하려고 노력하는 diff 알고리즘을 사용할 경우 특히 그렇다.

만약 이런 일이 발생한다면, 히스토리는 이 재미있는 동작을 “얻어냈으며”, 충돌 없이 단일 X 로 축소되는 것이 직관에 반할지라도 올바른 처리처럼 보인다.


Artisanal Code vs. AI‑Generated Code

마지막 글 하단에 달린 내 댓글이 수공예(artisanal) 코드에 관한 것이었지만, 글 자체는 그렇지 않다는 논의가 있었습니다.

AI의 위험은 코드가 스파게티 같은 엉망이 되어버려서 사람이 한 번도 읽어보지 않기 때문에 아무도 그 문제를 인식하지 못한다는 점입니다. 수공예 코딩—또는 최소한 인간이 감审하는 것—에는 명확한 이점이 있습니다.

AI‑보조 작문의 위험은 훨씬 적습니다: 인간은 글이 무엇을 말하고 있는지 이해할 수 있으니, AI가 쓰는 문장이 … 좀 더 가볍고 흥미는 덜할지라도 참고 자료로는 아마도 원하는 바일 겁니다. 저는 지난 글 전체를 여러 번 검토하면서 AI에게 잘못된 부분을 알려주고 수정하도록 했습니다. 그 경험은 수동으로 편집하는 것보다 훨씬 좋았습니다. AI는 제가 말한 내용을 더 빠르게, 더 적절한 위치에, 손으로 직접 할 때보다 더 나은 문장으로 삽입해 주었기 때문입니다. 저는 AI처럼 들리지 않게 어조를 바꾸려 하지 않았는데, 그럴 경우 작업량이 늘어나고, 오히려 AI가 그런 글을 쓸 수 있다는 식으로 AI 종말론을 부추기는 것이 재미있다고 생각했기 때문입니다. 이 글은 전적으로 수공예 방식으로 작성되었습니다. “artisanal code” 라는 용어가 하나의 개념이 되길 바랍니다.

No posts

0 조회
Back to Blog

관련 글

더 보기 »

손상된 Git 저장소 복구 방법

손상된 Git 저장소 복구 git status가 fatal: bad object HEAD error: objec... 와 같은 메시지를 반환할 때 느껴지는 특별한 두려움이 있다.

스페인 법률을 Git repo로

Legalize — España: 스페인 법률을 Git 저장소처럼. 각 법은 Markdown 파일이며, 각 개정은 commit이다. 8,600개가 넘는 법률이 공개 데이터 API에 포함되어 있다.