블로그 목록

비교 기준 태그를 옮기자 사라진 수정 기록과 결함 11건

git 태그를 비교 기준으로 쓰는 도구에서 태그를 새 커밋으로 옮기자 그 사이의 원고 수정이 기록에서 사라졌다. 기준 갱신 앞에 확인 단계를 두고, 한 저장소를 나눠 쓰는 프로젝트는 태그 이름에 범위를 넣었다.

공개 조회 0

TL;DR

  • 5화에 단 git 태그를 7화로 옮기자 그 사이 고친 2화가 "바뀐 파일" 목록에서 사라졌다
  • 태그가 비교 기준이라서다. 기준을 옮기면 그 전의 차이는 비교 범위 밖으로 나간다
  • 한 저장소에 작품 둘을 두면 태그 이름이 겹쳐 A 작품의 태그를 B 작품이 자기 것으로 읽었다
  • 비교 기준을 옮기는 명령은 옛 기준과 차이가 있으면 거부하고 사람이 확인했다는 옵션을 따로 받는다

상황

웹소설 원고를 git 으로 관리하는 도구를 만들고 있다. 연재 플랫폼에 회차를 올릴 때마다 그 시점의 커밋에 태그를 단다. 1화부터 5화까지 올렸으면 published/5 다.

태그를 다는 이유는 나중에 이 질문에 답하려는 것이다.

이미 올린 회차 중에 그 뒤로 고친 것이 있나?

독자가 이미 읽은 회차의 설정을 나중에 바꾸면 독자는 그것을 설정 오류로 받아들인다. 그래서 도구는 이 명령으로 찾는다.

git diff --name-only published/5 HEAD

태그 이후 바뀐 파일 중 5화 이하만 골라 "발행 후 수정"으로 알린다. 이 글에서는 이것을 수정 검사라고 부른다.

증상

예를 들어 이런 일주일이다. 직전 버전(v1.25.0)의 동작이고, 실험 저장소에서 같은 순서로 재현했다.

요일작가가 한 일도구가 한 일
월1~5화를 올렸다태그 published/5
화2화에서 주인공 나이를 17세에서 19세로 고쳤다. 플랫폼에는 아직 안 올렸다수정 검사: 2화가 바뀌었다
수6~7화를 올렸다태그 published/7
목수정 검사: 바뀐 회차 없음

목요일의 결과가 틀렸다. 플랫폼의 2화에는 여전히 17세가 적혀 있다. 원고와 독자가 읽은 본문이 다른데 도구는 다르지 않다고 말했다.

git 으로 보면 바로 보인다(한글 경로를 그대로 보려고 core.quotepath=false 를 준다).

git -c core.quotepath=false diff --name-only published/5 HEAD
# 원고/2화.md, 원고/6화.md, 원고/7화.md  (5화 이하는 2화뿐)
git -c core.quotepath=false diff --name-only published/7 HEAD
# (출력 없음)

반전

비교 기준이 되는 태그를 새 커밋으로 옮기는 일은 "지금까지의 차이를 정상으로 받아들인다"는 결정이다. 그러니 옮기기 전에 옛 기준과의 차이를 보여 주고, 사람이 확인한 뒤에만 옮긴다.

수요일의 태그는 화요일의 수정이 들어간 커밋에 달렸다. 그 순간부터 기준은 "19세인 2화"다. 수정 검사는 기준 이후만 보므로 2화의 수정을 다시는 못 본다.

flowchart LR
  A["월: published/5<br/>2화 17세"] --> B["화: 2화를 19세로 수정"]
  B --> C["수: published/7<br/>2화 19세"]
  C --> D["목: 7과 HEAD 비교<br/>차이 없음"]

이 모양은 흔하다. Jest 스냅숏 테스트에서 jest -u 로 스냅숏을 갱신하면 실패하던 차이가 그 순간 새 정답이 된다. detekt 나 Android Lint 의 기준선(baseline) 파일을 다시 만들 때도 그때까지의 경고가 정상으로 들어간다. 차이를 보고 갱신하면 문제가 없다. 차이를 안 본 채 갱신하면 그 차이는 기록에서 빠진다.

이 태그를 다는 명령은 publish 이고 문서에서는 "발행 표시"라고 불렀다. 이름만 보면 기록을 하나 더하는 일이라 비교 기준을 바꾼다는 것이 잘 안 보였다. 실행 순서를 적은 문서에도 "태그를 달기 전에 수정 검사를 돌린다"는 단계가 없었다. 첫 버전부터 같은 순서였다.

처리

문서에 단계를 넣는 것으로는 부족했다. 문서를 건너뛰면 같은 결과가 난다. 그래서 명령이 직접 거부하게 했다.

$ python wngit.py publish . --upto 7
발행 뒤 고친 회차가 1건 있다 (기준 `published/5`):

  2화  `원고/2화.md`

이대로 새 표시를 남기면 이 수정이 「발행된 것」이 되어 다시는 안 잡힌다.
먼저 작가와 확인하라:
  - 고친 회차를 플랫폼에도 다시 올렸거나, 오탈자만 고쳤다 → --accept-drift 로 표시
  - 플랫폼에는 옛 본문이 있다 → 고친 것을 되돌리거나 플랫폼에도 반영한 뒤 표시
  고친 내용 보기: git diff published/5 HEAD -- <파일>

작가가 확인하면 --accept-drift 옵션으로 태그를 단다. 태그 메시지에 "발행 뒤 고친 회차 2화 확인함"이 남아 기준이 왜 옮겨졌는지 나중에도 보인다.

한 저장소에 작품 둘

저장소 전체가 같이 쓰는 이름에 프로젝트 하나의 상태를 적을 때는 이름에 프로젝트 범위를 넣는다. 범위 없는 옛 이름은 자동으로 이어받지 말고, 이름마다 주인에게 묻는다.

git 태그 이름은 저장소에 하나뿐이다. 작가가 저장소 하나에 작품을 폴더로 나눠 두었다.

소설들/
  작품A/원고/1화.md …
  작품B/원고/1화.md …

작품 A 를 3화까지 올리고 published/3 을 달자 한 화도 안 올린 작품 B 도 "3화까지 올림"이 됐다. B 의 1~3화는 올리기 전 표기 검사(마크다운 기호가 남았는지 보는 것)에서도 빠졌다.

모노레포 도구가 같은 문제를 이미 풀었다. Lerna 의 독립 버전 모드나 Changesets 는 패키지마다 버전을 따로 매기고 패키지이름@1.2.0 꼴로 태그를 단다. 같은 방식으로 하위 폴더 작품의 태그 앞에 폴더 경로를 붙였다(작품A/published/3). 저장소 루트의 작품은 이름이 그대로다.

git 태그 이름에는 공백을 못 쓴다. 공백을 _ 로 바꾸기만 했더니 작품 A 와 작품_A 두 폴더가 같은 이름 작품_A 가 되어 서로의 기록을 읽었다. 글자를 바꾼 이름 뒤에는 원래 폴더 이름의 SHA-1 앞 6자리를 붙여(작품_A-96ef95) 둘을 갈랐다.

남은 것은 예전 버전이 경로 없이 단 published/5 다. 작품이 하나면 그 작품 것이지만 둘이면 누구 것인지 모른다. 그래서 도구는 그 태그를 자기 것으로 읽지 않고, 작가가 답할 때까지 새 태그도 막는다. 작가가 "이 작품 것"이라고 답하면 그 태그가 가리키던 커밋에 새 이름의 태그를 단다. 지금 커밋에 달면 앞 절과 같은 일이 또 생긴다.

처음에는 옛 태그 묶음 전체에 한 번만 답을 받았다. 그런데 작품 둘이 공용 이름을 나눠 썼다면 published/5 는 A 것이고 published/2 는 B 것이다. "전부 다른 작품 것"을 고르면 B 의 2화 수정 기록이 사라지고, "전부 이 작품 것"을 고르면 A 의 기록을 B 가 가져간다. 지금은 목록을 날짜와 함께 보이고 이 작품 것만 골라 원래 설명과 날짜 그대로 옮긴다.

검증

나머지 결함 아홉도 이 버전에서 고쳤다. 원고에 없는 9화까지 태그를 달 수 있던 것, 프롤로그.md 를 표기 검사가 건너뛰던 것 등이다.

직전 버전의 회귀 테스트는 태그를 하나만 만들었다. "가장 최근 태그 고르기"와 "태그 번호순 정렬"은 태그가 둘 이상일 때만 실행된다. 새 테스트는 published/9 다음에 published/10 을 단다. 문자열로 정렬하면 9 가 10 뒤에 선다.

테스트가 버그를 잡는지는 변이 테스트로 봤다. 코드에 버그를 한 곳씩 일부러 넣은 사본을 만들고 테스트가 실패하는지 본다. 처음 43개는 33개만 잡혔고, 빠진 자리에 테스트를 더해 41개가 됐다. 그런데 작성자와 따로 돌린 리뷰어가 새 코드를 겨눠 51개를 만들자 35개만 잡혔다. 내가 고른 버그 자리가 내가 테스트를 짠 자리와 겹쳐 있었다. 폴더 이름을 바꾸는 규칙처럼 테스트가 한 번도 안 지나가는 코드는 내 표본에 없었다.

리뷰어가 찾은 자리에 테스트를 더하고 버그 목록을 74개로 늘렸다. 71개에서 테스트가 실패했다. 이 수치도 내가 고른 표본이라 리뷰어 표본과 견줄 수 없다.

개선

직전 버전이번 버전
고친 회차가 있는 채로 다음 태그태그가 달리고 수정 기록이 사라짐거부, 확인 후 기록과 함께 태그
한 저장소의 작품 둘태그를 둘이 같이 씀작품마다 따로
원고에 없는 9화까지 태그달림거부
프롤로그의 마크다운 기호위험 0건검사함

남은 것

  • 변이 테스트에서 살아남은 셋: 직전 버전 코드 한 줄, 실행되지 않는 숫자 판단, 설정 모듈을 못 읽을 때만 쓰는 대체 경로. 그 상태를 만드는 테스트 데이터가 없다
  • 폴더 이름 끝의 .lock 같은 이름 규칙 일부는 테스트가 안 지난다. 실험으로만 확인했다
  • 프롤로그를 이미 올렸는지는 "마지막 태그 때와 내용이 같은가"로 추정한다. 안 올리고 태그만 달았다면 틀린다. 그런 실제 사례는 아직 없다

다음부터

비교 기준을 새 커밋으로 옮기는 명령을 만들 때는 옛 기준 이후의 차이가 있으면 거부하고 사람이 확인했다는 옵션을 따로 받는다.

한 저장소를 여러 프로젝트가 나눠 쓸 수 있으면 태그 같은 공용 이름에 프로젝트 범위를 넣고 범위 없는 옛 이름은 이름마다 주인에게 물은 뒤에만 옮긴다.

댓글

아직 보이는 댓글이 없습니다.

댓글 쓰기

댓글은 관리자가 승인한 뒤에 보입니다. 이메일은 공개하지 않습니다.

개인정보 수집·이용 동의 (필수)
  • 수집 항목: 이름, 이메일, 댓글 내용
  • 목적: 댓글 게시와 관리, 댓글에 관한 연락
  • 보유 기간: 이름과 내용은 댓글을 게시하는 동안, 이메일은 1년. 게시하지 않은 댓글은 90일 뒤에 지웁니다.
  • 스팸을 막으려고 접속 IP 주소를 90일 동안 저장합니다. 이 저장은 동의가 아니라 운영자의 정당한 이익에 근거합니다.
  • 동의를 거부할 권리가 있습니다. 거부하면 댓글을 쓸 수 없습니다. 자세한 내용은 개인정보처리방침에 있습니다.