본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

커밋을 임시 인덱스에서 빚어

~13 min · version-control, plumbing, concurrency, implementation

Level 0젖은 흙
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

저장소마다 인덱스는 하나야

다들 아는데 아무도 적용 안 하는 사실이 이거야. 스테이징 영역은 세션 상태가 아니라 저장소 상태야. 세션 셋이 체크아웃 하나에서 일하면 그걸 공유해. 그리고 커밋을 자기 경로로 좁히는 예의 바른 수는 보이는 것만큼 안 지켜줘. 어느 쪽 절반이 작동하는지를 정확히 알아야 해. 거꾸로 아는 경우가 흔하거든. 경로를 받은 커밋은 다른 스테이징된 걸 무시해. 그 절반은 문서에 있고 믿을 만해. 못 하는 건 네가 지명한 파일 안쪽을 지키는 거야. 거기선 작업 트리를 커밋하니까 이웃의 반쯤 된 편집까지 같이 들어가. 그리고 평범한 방식으로 커밋을 빚으면 git add 가 공유 인덱스에 돌아서, 네 스테이징이 이웃 걸 덮어써.

측정된 판. 어떤 세션이 커밋을 자기 경로로 좁혔는데 이웃이 파일 아흔둘을 스테이징해둔 상태였어. 이웃의 집합은 문서 그대로 커밋 밖에 남았어. 그런데도 그 세션은 그걸 무시할 수 없었어. 자기가 한 add 하나하나가 그 이웃이 일하고 있는 인덱스에 쓰였으니까.

배관에 손을 뻗는 이유 둘

처방은 공유 인덱스를 아예 안 건드리고 커밋을 빚는 거야. 임시 인덱스 파일을 가리킬 수 있게 해주는 저수준 명령을 써서.

격리. 고른 베이스 트리에서 시작해서, 정확히 네 경로만 추가하고, 트리를 쓰고, 커밋 객체를 만들어. 공유 인덱스는 읽히지도 쓰이지도 않으니까, 이웃의 스테이징된 작업이 너랑 같이 갈 수 없고 네 커밋이 그들 걸 흔들 수도 없어.

훅 없음. 배관은 사전 커밋 훅을 안 태워. 그 훅이 자산 재생성이나 외부 서비스 동기화 같은, 진짜지만 비싼 일을 할 땐 커밋 하나가 다들 필요로 하는 잠금 파일을 쥔 채로 몇 분짜리 작업이 돼. 자동 착지를 거기서 우회시키니까 착지가 몇 분에서 몇 초가 됐고, 사람이 치는 보통 커밋은 훅이랑 훅이 하던 일을 그대로 유지했어.

브랜치를 안전하게 옮기기

커밋 객체를 만든다고 뭐가 움직이는 건 아냐. 브랜치 참조를 여전히 갱신해야 해. 그건 비교 후 교체로 해. 시작할 때 가리키던 자리를 여전히 가리키고 있을 때만 참조를 갱신하는 거야. 그 사이에 누가 옮겼으면 갱신이 깨끗하게 실패하고, 맞는 대응은 강제가 아니라 새 끝 위에 다시 빚는 거야.

참조를 옮기기 전에 검사 하나가 더 속해. 커밋의 파일 목록이 이 작업이 건드려도 되는 경로의 부분집합인지 확인해. 싸고, 경로 패턴이 의도보다 많이 잡힌 경우를 잡고, 상관없는 파일을 조용히 쓸어담은 착지랑 거부한 착지를 가르는 차이야.

도구의 편한 인터페이스가 공유 상태를 읽는다면, 저수준 인터페이스는 미세 최적화가 아니라 격리 그 자체야. 고수준 명령은 체크아웃 하나에 사람 하나를 상정하고 만들어졌고, 그건 합리적인 가정이야. 자동화가 작업 사본을 공유하는 순간 성립을 멈출 뿐이지.

Code

공유 인덱스를 한 번도 안 건드리는 착지 커밋·bash
# Everything below runs against a THROWAWAY index file. The
# repository's own index - which a neighbor session may have 92
# files staged in - is neither read nor written.

export GIT_INDEX_FILE="$(mktemp -u)"

# the CAS's expected old value: where the branch is RIGHT NOW.
# Not the base - see step 5.
LOCAL=$(git rev-parse refs/heads/main)

# 1. start from the base we deliberately chose (next lesson)
git read-tree "$BASE"

# 2. add exactly our paths, from the working tree
git add -- content/target/            # OUR material only; the shared
                                      # wiring is a separate pass (L05)

# 3. write a tree object and a commit object. NO HOOKS FIRE.
TREE=$(git write-tree)
NEW=$(git commit-tree "$TREE" -p "$BASE" -m "$MESSAGE")

# 4. leak check BEFORE moving anything: the commit must touch
#    nothing outside the paths this operation is allowed to.
git diff-tree --no-commit-id --name-only -r "$NEW" \
  | grep -qvE '^content/target/' \
  && { echo "refusing: commit touches paths outside land_paths"; exit 1; }

# 5. move the branch by COMPARE-AND-SWAP. The expected old value is
#    the branch's CURRENT tip, NOT the base we built on. When the
#    base came from the remote the two differ, and a swap against
#    the base would reject every time - in precisely the behind
#    case this is for. Fails cleanly if somebody moved it while we
#    were working; rebuild, never force.
git update-ref refs/heads/main "$NEW" "$LOCAL"

# The ordinary human `git commit` in this same checkout still runs
# its hooks and still does the work they do. Only the automated
# landing - which gained nothing from them - routes around.

External links

Exercise

연습용 복제본에서 파일 하나를 스테이징한 다음, 위 배관 절차로 다른 파일을 담은 커밋을 빚어서 착지시켜봐. 끝나고 네가 스테이징한 파일이 여전히 스테이징된 채로 남아 있고 커밋에 안 들어갔는지 확인해. 그게 격리 성질을 실험 하나로 본 거고, 한 번 해봤다는 게 프로세스 셋이 체크아웃을 공유하는 날 이걸 집어들게 만들어줘.
Hint
임시 인덱스 경로를 셸 세션 전체에 내보내지 말고 그 명령들에만 환경으로 줘. 작업보다 오래 사는 값은 그다음에 치는 평범한 명령을 헷갈리게 만들고 아주 어리둥절한 몇 분을 만들어내거든.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.