Reinventing issue tracking: Local-first and Git-native
이슈 트래킹의 재발명 — 로컬 퍼스트와 Git 네이티브로
코드 포지(forge) Manganin을 만드는 저자가 Git 안에 이슈 데이터를 저장하면서도 로컬 퍼스트로 쓸 수 있는 이슈 트래킹을 설계한 과정을 다룹니다. 리포지토리 안에 .issues/ 디렉터리를 두는 방법과 refs/issues/ 특수 ref를 쓰는 방법의 실패를 거쳐, 최종적으로 이슈를 별도의 숨겨진 자매 리포지토리에 일반 파일로 저장하는 단순한 구조에 도달합니다.
- 주제
본문
협업의 핵심 요소는 공유된 이슈 트래킹 환경이라고 저자는 말합니다. 자신이 개발하는 코드 포지 Manganin에 이슈 트래킹을 설계하면서 두 가지 요구사항을 세웠습니다. 첫째, 이슈의 기반 데이터는 Git에 저장하되 Git의 강점을 살리는 방식이어야 합니다. Postgres 같은 데이터베이스를 붙이는 것이 표준이지만 벤더 락인, 의존성, 배포 복잡성을 가져옵니다. 둘째, 별도 도구 설치 없이 커맨드라인에서, 인터넷이 없는 동안에도 이슈를 다룰 수 있어야 합니다. 이것이 저자가 말하는 '로컬 퍼스트'로, 수십 년간 다듬어진 오픈소스 도구가 주말에 급조한 독점 기술 스택의 일렉트론 앱보다 낫다는 입장입니다.
코드 옆에 이슈를 두는 방법의 실패
가장 순진한 해법은 프로젝트 루트에 .issues/ 디렉터리를 두고 파일 하나가 이슈 하나가 되게 하는 것입니다. 이슈가 코드와 묶이고 브랜치마다 자체 이슈 상태를 가지며, 수정 커밋에서 이슈를 함께 닫을 수 있다는 점이 매력적이었습니다. 그러나 이 장점을 끝까지 밀고 나가면 그것이 곧 치명적 결함이 됩니다. main 브랜치에서 새 이슈가 열리면 어떤 활성 브랜치에 적용되어야 하는지, 새 이슈를 따라잡기 위해 계속 rebase해야 하는지 하는 문제가 생깁니다. 이슈 변경은 코드 커밋보다 훨씬 빈번하기 때문에 열고, 수정하고, 닫고, 다시 열 때마다 pull해야 하고, 프로젝트가 속도를 얻는 순간 금방 지치는 구조가 됩니다.
특수 ref에 저장하는 방법의 실패
Git은 브랜치를 refs/heads에, 태그를 refs/tags에 저장합니다. 저자는 Recurse Center에서 임의 이름의 ref로 데이터를 가리키면 GitHub 같은 호스트가 그 내용을 알지 못하면서도 충실히 전파한다는 기법을 배웠습니다. 이슈에 자동 증가 정수를 부여해 refs/issues/12 같은 ref에 넣으면 소스 트리를 어지럽히지 않으면서 리포지토리와 함께 복제됩니다. 문제는 로컬 편집이 극도로 불편해진다는 점입니다. 이 방식으로 실제 개발을 하려면 복잡성을 관리할 별도 도구를 받아야 하고, 본질적으로 작은 텍스트 파일 하나를 여러 겹의 복잡성으로 감싸게 됩니다. 실제 편집 절차도 무겁습니다. git hash-object -w로 blob을 저장해 OID를 얻고, git mktree로 tree를 만들고, git commit-tree로 commit을 만든 뒤, git update-ref로 ref를 가리켜야 합니다. 마지막 단계가 실행되지 않으면 새 객체들은 결국 GC되므로 원자성은 확보되지만, 하나의 이슈를 가리키는 ID가 사실상 4개나 생기고 충돌과 경쟁 조건 처리, 이슈 ID가 정확히 무엇을 의미하는지 같은 질문이 따라붙습니다.
단순하게 — 별도 리포지토리와 파일
저자는 몇 주간 물러나 SQL 백엔드를 전제로 한 기존 소프트웨어의 선입견을 버리고 Git과 함께 일하는 법을 다시 생각했습니다. Git을 관계형 데이터베이스처럼 쓰려 하면 과도하게 복잡해집니다. 소스 코드와 다른 것을 추적한다면 분리해 저장하고, 도구로 둘 사이를 잇는 것이 낫습니다. Manganin은 이제 이슈 데이터를 별도 리포지토리에 저장합니다. 새 리포지토리가 만들어질 때 이슈 추적용 숨겨진 자매 리포지토리가 함께 생성되고, 프런트엔드 목록에는 보이지 않으며 내부 데이터는 다른 포지처럼 이슈 제목과 본문 목록으로 파싱해 보여줍니다. 특수 경로에서 clone할 수 있어 코드만 받으려는 사람이 이슈 데이터까지 내려받을 필요는 없습니다. 이슈는 파일로 투명하게 저장되고 파일명이 곧 제목, 내용이 곧 본문입니다. 파일시스템이 고유한 파일명을 보장하므로 자동 증가 ID도 필요 없고, 파일명이 부족하면 Git OID를 쓸 수 있습니다. 저자는 Neovim에서 FZF로 이슈 탐색과 분류를 하며, 좋은 도구란 사용자의 방해가 되지 않아 최선의 작업을 하게 하는 도구라고 결론 내립니다.
Lobsters 반응
- @pgeorgi — git-bug(https://github.com/git-bug/git-bug)나 git-issue(https://github.com/dspinellis/git-issue) 같은 이 분야의 선행 작업에 대한 언급이 없네요. :(
- @kolja — 링크 감사합니다! Git-bug는 UI도 꽤 좋아 보이는데, 개발자의 SSH 설정과 일치하지 않으면 push/pull이 안 된다는 게 아쉽습니다. 수년째 알려진 결함이라고 하더군요. go-git이 소프트웨어에 걸림돌이 된 게 이번이 처음은 아닌 것 같은데, 지난번에 저를 못 쓰게 만든 프로젝트가 뭐였는지는 기억이 안 납니다.
- @stephenr — https://gitlab.com/bugseverywhere/bugseverywhere 에 대한 언급도 없습니다.
- @evmar — 요구사항을 보면 왜 별도 리포지토리가 아니라 특수 브랜치에 이슈를 두지 않았나요? 분명 고려했다가 버렸을 텐데 언뜻 보기엔 이유가 안 보입니다.
- @loweg(저자) — 저도 한참 고민했고 트레이드오프가 몇 가지 있습니다. 브랜치 방식은 clone하는 모든 사람이 추가 절차 없이 이슈 이력 전체를 받게 되는데, 그게 장점인지 단점인지 모르겠습니다. clone 시간이 걱정되지만 텍스트는 압축이 잘 되죠. 별도 리포지토리는 더 유연합니다. 모든 프로젝트의 이슈를 모아두는 디렉터리에 clone하거나 git submodule로 넣는 등 선택할 수 있습니다. 이슈를 리포지토리가 아닌 브랜치에 두는 실험용 로컬 브랜치도 있는데, 이 devlog를 공식적으로 Too Long(tm) 동안 묵혀두고 있었습니다.
원문: blog.manganin.dev / 번역·요약: Trawling