모든 글
Silent Failures · 3분 읽기

조용히 실패하는 것들 3 — 해시를 파일에 적는 순간 그 파일은 이력의 일부가 됩니다

커밋 154개의 작성자를 고치려고 이력을 다시 썼어요. 그 뒤 검사 하나가 조용히 bad object로 떨어져 있었습니다. 깨질 거라고 미리 적어두었는데도 깨졌어요.

#git#자동화#기록

지난 글의 결론은 “확인하려면 해시를 보라”였어요. 스토어에 올라간 이미지의 sha1과 내 파일의 해시를 맞춰봐야 확인이라는 이야기였죠.

이번 글은 그 해시를 파일에 적어두면 어떻게 되는지에 대한 이야기입니다. 그리고 이 시리즈에서 제일 뼈아픈 편이기도 해요. 깨질 걸 미리 알고 적어뒀는데도 깨졌거든요.

일이 이렇게 시작됐습니다. 커밋 작성자가 옛 메일로 남아 있었어요. 물타기 계산기 122개, 루트 32개, 합쳐서 154개요. filter-branch로 이력을 다시 썼습니다.

며칠 뒤에 보니 스토어 사진 검사(fresh.sh)가 떨어져 있었어요.

fatal: bad object 8c7235f…

무엇이 깨졌나

이 검사는 “스토어 스크린샷이 최신 화면과 같은가”를 봅니다. 방법은 이래요.

reviewed-at이라는 파일에 사람이 눈으로 봐둔 시점의 커밋 해시를 적어둡니다. 검사는 그 해시를 읽어서 git log -1 <해시>로 그때 이후에 화면 코드가 바뀌었는지 봐요.

그런데 이력을 다시 쓰면서 그 해시가 새 이력에 없는 이름이 됐습니다.

알고 있었는데도 깨졌어요

이 부분이 제일 뼈아픕니다.

백로그에 이렇게 적혀 있었거든요.

이력을 다시 쓰면 reviewed-at도 어긋난다

미리 적어뒀어요. 그날 새벽에 잘못 섞인 커밋을 되감지 않은 이유이기도 했습니다. “되감으면 저게 깨진다”를 알고 있었으니까요.

그런데 정작 계획된 재작성에서는 그 자리를 잊었습니다. 알고 있는 것과 그 순간 손이 그 자리로 가는 것은 다르더라고요. 그리고 도구는 그 자리를 몰랐고요.

왜 조용했을까요

해시는 이력 안에서만 뜻이 있어요. 이력이 다시 쓰이면 옛 해시는 어느 저장소에도 없는 이름이 됩니다.

그때 그것을 읽는 검사는 “틀렸다”로 떨어지지 않아요. “없다”로 떨어집니다.

fatal: bad object …

bad object는 “비교해보니 다르다”가 아니라 “비교할 대상이 없다”예요. 그리고 없는 것은 소리를 내지 않습니다.

검사는 실패했는데, 그 실패가 “스크린샷이 낡았다”는 신호로 읽히지 않았어요.

두 번째 재작성에서 같이 옮겼습니다

filter-branchmap을 써서 두 가지를 함께 새 해시로 옮겼어요.

  1. reviewed-at 파일 안의 해시
  2. 그 해시를 본문에 적은 커밋 메시지 둘

두 번째를 빠뜨릴 뻔했습니다. 커밋 메시지 안에도 해시가 있었거든요.

검사가 읽는 해시와 사람이 읽는 해시를 가릅니다

기록 금고의 일지에도 옛 해시가 잔뜩 적혀 있어요. 그건 안 고쳤습니다.

검사가 읽는 해시사람이 읽는 해시
어디reviewed-at 같은 파일일지·기록
이력을 고치면같이 고칩니다그대로 둡니다
읽는 도구가 있고, 없으면 깨지니까“그때 그렇게 적혔다”는 사실이니까

일지의 옛 해시를 고치면 오히려 거짓이 돼요. 되돌린 결정을 지우지 않는 것과 같은 이유입니다. 기록은 그때의 사실이지 현재 상태가 아니니까요.

다음엔 이렇게 하려고요

이력을 다시 쓰기 전에 해시를 담은 파일을 먼저 찾습니다

git grep -E '[0-9a-f]{7,40}'

찾은 자리를 filter-branchmap이나 git replace로 같이 옮겨요.

검사가 해시를 읽으면 “없는 해시”를 오류로 냅니다

bad object가 통과나 조용한 실패가 되지 않게 합니다.

git cat-file -e "$REVIEWED_AT^{commit}" 2>/dev/null || {
  echo "reviewed-at 의 해시가 이 이력에 없다 — 이력이 다시 쓰였나?" >&2
  exit 1
}

“없다”와 “낡았다”는 사람에게 다른 말이어야 하니까요.

어디 있는지를 백로그가 아니라 순서에 적습니다

백로그에 적어둔 것은 읽을 이유가 있을 때만 읽힙니다. 그런데 이력을 고치는 사람은 그날 백로그를 안 봐요.

그러니 “이력 재작성 절차”라는 문서에 적습니다. 그 문서는 그 일을 하는 사람이 반드시 여는 문서니까요.

이번에 확인한 것

세 편의 이야기가 조금씩 달랐어요.

1 도구가 잘못 판정했고, 2 도구가 통째로 실패했는데 말하지 않았고, 3 검사가 비교할 대상을 잃고도 그냥 떨어졌습니다.

그런데 공통점이 하나 있어요. 셋 다 “돌렸다”까지는 성공했다는 겁니다.

그래서 결론도 하나예요.

자동화의 끝은 “돌렸다”가 아니라 “확인했다”입니다.

그리고 확인의 방법은 매번 달랐어요. 겹침 판정은 사람이 같은 일로 알아보는 열쇠로, 업로드는 해시 대조로, 검사는 “없는 것”을 오류로 만드는 것으로요.

마지막으로 하나만 덧붙일게요. 해시를 파일에 적는 건 아주 흔한 일이에요. 락 파일, 캐시 키, “여기까지 확인함” 표시. 전부 편하고, 전부 같은 성질을 갖습니다.

그 파일은 코드가 아니라 이력의 일부예요. 그러니 이력을 고치는 손이 그 파일도 같이 고쳐야 합니다.

세 편을 마치고 하루 뒤, 같은 병을 <head> 안에서 또 만났어요. 이번엔 결과물이 0개인데 빌드가 성공했습니다.

👉 4 — 정본 주소가 404를 가리켜도 화면은 멀쩡히 뜹니다