저장소와 히스토리

대체와 접목

git replace 는 git에게 이렇게 말해요. 객체 A를 읽으려던 자리에서는 대신 B를 읽어라. 다시 쓰이는 것은 없어요. sha도 바뀌지 않아요. 모든 커밋은 원래 있던 자리에 그대로 있고, git이 지나가는 길에 다른 곳을 볼 뿐이에요.

클론을 작게 만들고 싶어지기 전까지는 신기한 재주로만 들려요. 그 순간부터는 히스토리 재작성의 정직한 대안이 돼요. 커밋을 부모 없는 상태로 접목(graft)하면 그 이전의 모든 것이 로그에서, 그래프에서, 그리고 거기서 만든 모든 클론에서 빠져요 — 여전히 저장되어 있고, 여전히 페치할 수 있고, ref 하나만 지우면 돌아오는 채로요.

⌘K → 대체와 접목.

기존 대체 목록과 그 아래의 접목 입력 폼

접목하기

이렇게 주면 이렇게 돼요
커밋 하나, 부모 없음 그 커밋이 히스토리의 시작이 돼요
커밋 하나, 부모 하나 이상 실제로 놓인 자리 대신 거기에 붙어요

두 번째 형태가 재미있는 쪽이에요. 전체 히스토리는 아카이브 저장소에 두고, 작업용 저장소는 잘라내고, 아카이브의 끝을 가리키는 접목으로 둘을 다시 이어 붙이는 거죠 — GitHub가 나중에 더 깊게 만들 수 있는 얕은 클론을 제공할 때 쓰는 것과 같은 수법이에요.

부모 없이 접목할 때는 먼저 물어봐요. "히스토리가 사라졌다"와 "히스토리가 감춰졌다"는 로그에서 똑같아 보이지만 전혀 같은 일이 아니니까요. 객체는 gc 가 정리하기 전까지 살아남아요. 유지 보수를 보세요.

함께 살아가기

대체는 ref예요. refs/replace/ 아래에 있죠. 알아 둘 만한 결과가 세 가지 있어요:

  • 푸시하기 전까지는 로컬에만 있어요. git push origin "refs/replace/*" 로 공유할 수 있고, 그것 없이 클론한 사람은 손대지 않은 히스토리를 봐요.
  • 되돌리기가 통해요 — ref를 없애면 실제 조상 관계가 곧바로 돌아오고, Gitcito는 접목도 다른 작업과 마찬가지로 되돌릴 수 있는 동작으로 기록해요.
  • core.useReplaceRefs=false 는 git이 그 전부를 한꺼번에 무시하게 만들어요. 여기 있는 토글은 정확히 그것을 쓰고, 꺼져 있으면 대화 상자가 그렇다고 알려줘요. 자기 대체를 조용히 무시하는 저장소는 헷갈리는 곳이니까요.

명령줄에서는 git --no-replace-objects log 가 설정을 하나도 바꾸지 않고 진짜 히스토리를 보여줘요.

재작성 대신 이것을 꺼낼 때

목표 도구
클론이 너무 큰데 히스토리 자체는 멀쩡함 접목 — 다시 쓰는 것 없이, 되돌릴 수 있게
비밀이나 거대한 블롭이 사라져야 함 히스토리에서 파일 제거 — 진짜 재작성
그냥 한 번 덜 내려받고 싶음 git clone --depth — 얕게, 관리할 ref 없이

접목은 아무것도 지우지 않아요. 옛 커밋을 걷어내고 싶은 이유가 거기에 애초에 커밋되지 말았어야 할 것이 들어 있어서라면, 이 페이지는 잘못 찾아온 거예요. 객체는 여전히 거기 있고, 여전히 sha로 페치할 수 있으며, 이미 존재하는 모든 클론 안에도 그대로 있어요.

알아둘 만한 한계

  • 보이는 것이 저장된 것과 어긋나기 시작해요. 그게 기능이자 위험이에요. 대체가 걸린 클론을 디버깅하는 사람은 그것이 있다는 사실을 알아야 해요.
  • 대체는 기본적으로 따라다니지 않아서, 동료의 git log 와 당신의 git log 가 정당하게 서로 다를 수 있어요.
  • 대체는 커밋을 도구에게서 감출 뿐, git에게서 감추지 못해요. git cat-file 과 객체 탐색기는 여전히 sha로 원본을 열어요.
  • Gitcito는 git replace --edit 를 제공하지 않아요 (객체의 내용을 손으로 다시 쓰는 것 말이에요). 그건 원시 객체를 두고 텍스트 편집기가 할 일이고, UI를 씌우면 발등을 찍는 도구가 돼요.

함께 보기: 객체 탐색기 · 히스토리에서 파일 제거 · 저장소 유지 보수

GitHub에서 이 번역 개선하기