그 폴더는 Git 저장소가 아니라 $HOME 안쪽이었다

평범한 다운로드 폴더에서 Git command를 실행했다.

실패할 줄 알았다.

성공했다.

git rev-parse --is-inside-work-tree는 true를 출력했다.

다운로드 폴더가 어느새 저장소가 된 것은 아니었다.

Git이 상위 디렉터리의 저장소를 찾은 것이다.

그 상위 디렉터리는 $HOME이었다.

command 성공과 대상 정체성은 다른 문제다

Git command가 성공하면 지금 폴더가 의도한 저장소라고 생각하기 쉽다.

그 전제가 틀릴 수 있다.

현재 폴더에 독립된 worktree가 없어도 상위 디렉터리 안에 있으면 Git은 그 저장소 문맥에서 동작할 수 있다.

그래서 다음 출력만으로는 충분하지 않다.

true

이 값은 “어떤 worktree 안쪽이다”를 말한다.

“내가 의도한 worktree다”를 말하지 않는다.

top-level을 먼저 읽는다

현재 git-rev-parse(1) manual은 --show-toplevel을 worktree의 top-level 디렉터리 절대 경로를 보여 주는 option으로 설명한다.

worktree가 없으면 error를 보고한다.

그러니 repository 작업의 첫 질문은 다음과 같다.

git -C <repo> rev-parse --show-toplevel

출력은 기대한 <repo>와 정확히 같아야 한다.

target_repo=<absolute-repository-path>
resolved_repo=$(git -C "$target_repo" rev-parse --show-toplevel)

[[ "$resolved_repo" == "$target_repo" ]]

단순히 command가 성공했는지만 보지 않는다.

어느 저장소가 응답했는지 비교한다.

git -C는 문맥을 고정한다

현재 git(1) manual은 -C <path>를 Git이 현재 working directory 대신 해당 경로에서 시작된 것처럼 실행하는 option으로 설명한다.

자동화에서는 이 모양이 좋다.

git -C <absolute-repository-path> status --short

script가 어느 폴더에서 실행되든 repository 후보가 흔들리지 않는다.

하지만 -C만 붙였다고 정체성이 검증된 것은 아니다.

지정한 경로 자체가 다른 상위 저장소 안의 평범한 하위 폴더일 수 있다.

그래서 -C와 --show-toplevel 비교를 함께 쓴다.

-C: Git이 탐색을 시작할 위치를 고정한다.
--show-toplevel: Git이 실제로 선택한 worktree root를 드러낸다.
exact comparison: 선택된 root가 의도와 같은지 판정한다.

셋 중 하나만 빠져도 독자가 다른 질문을 읽을 수 있다.

틀린 검사를 먼저 실패시켰다

2026년 8월 26일의 privacy-sanitized local fixture에서 $HOME 아래의 평범한 하위 폴더를 독립 저장소라고 가정했다.

expected_repo=<nested-directory>
actual_repo=$(git -C "$expected_repo" rev-parse --show-toplevel)

[[ "$actual_repo" == "$expected_repo" ]]

검사는 exit 1로 실패했다.

actual_repo는 하위 폴더가 아니라 $HOME root를 가리켰다.

이 실패가 필요했다.

fixture가 정말로 upward discovery 함정을 만들 수 있다는 뜻이기 때문이다.

그다음 독립 저장소의 절대 경로를 넣어 같은 비교를 실행했다.

이번에는 통과했다.

git status부터 읽으면 늦을 수 있다

git status는 상태를 잘 보여 준다.

문제는 어느 저장소의 상태인지 확인하기 전에 그 출력을 해석하는 일이다.

상위 $HOME 저장소를 읽고 있다면 의도한 작은 project와 전혀 다른 파일 목록이 나올 수 있다.

더 위험한 command에서는 scope 착각의 비용이 커진다.

add
restore
clean
reset
commit

이 글은 destructive command를 실행하지 않았다.

그 command를 안전하게 만드는 첫 번째 경계만 다룬다.

대상 root가 다르면 다음 단계로 진행하지 않는다.

historical fact를 current fact로 쓰지 않는다

이 확인에서는 오래된 운영 기록과 현재 shell 결과가 하나 충돌했다.

과거에 broad development parent가 Git root로 기록돼 있었지만, 현재 rev-parse는 그 위치를 worktree로 판정하지 않았다.

그래서 공개 글은 과거 상태를 현재 사실처럼 반복하지 않는다.

현재 다시 확인된 $HOME 사례만 사용한다.

경로 경계는 configuration이다.

configuration은 바뀔 수 있다.

안전 규칙은 유지하되 현재 repository identity는 매번 다시 읽어야 한다.

script에는 fail-closed 경계를 둔다

repository identity가 다르면 warning만 출력하고 계속하지 않는다.

즉시 종료한다.

target_repo=<absolute-repository-path>
resolved_repo=$(git -C "$target_repo" rev-parse --show-toplevel) || exit 1

if [[ "$resolved_repo" != "$target_repo" ]]; then
  print -u2 -r -- "repository identity mismatch"
  exit 1
fi

그 뒤에야 status, diff, add 같은 command를 놓는다.

문자열 비교는 symlink나 path normalization 정책을 따로 정해야 할 수 있다.

이 fixture에서는 이미 확인한 절대 경로끼리 비교했다.

일반화할 때는 project의 path policy를 명시해야 한다.

실전 규칙

Git command의 exit 0은 repository identity 증명이 아니다.

절대 경로로 git -C를 쓴다.

rev-parse --show-toplevel로 Git이 고른 root를 읽는다.

그 root가 기대한 repository와 정확히 같은지 비교한다.

다르면 멈춘다.

상태를 읽기 전에 대상부터 읽는다.

그 순서 하나가 “Git이 잘 작동했다”와 “Git이 엉뚱한 저장소에서 잘 작동했다”를 갈라놓는다.