본문 바로가기
빌드노트 검색

빌드노트

git fetch는 성공했는데 내 파일이 그대로다

fetch 뒤 파일이 그대로라면 HEAD·로컬 main·origin/main의 위치를 나눠 확인한다. 로컬 bare 저장소와 두 clone에서 fetch 전후 파일 보존, fast-forward 반영, 깨끗하지만 갈라진 이력의 --ff-only 거절과 로컬 커밋 보존을 비교했다.

git fetch origin이 성공했는데 편집기에서 연 파일이 그대로라면, 먼저 main과 origin/main이 같은 커밋을 가리키는지 확인한다. 일반적인 clone 설정에서 fetch는 받은 커밋과 원격 추적 브랜치를 갱신하지만, 체크아웃한 로컬 브랜치와 작업 파일을 자동으로 통합하지 않는다. 파일을 바꾸는 단계는 그다음이다.

여기서 main은 내가 작업하는 로컬 브랜치이고, origin/main은 원격 main의 위치를 내 저장소에 기록한 원격 추적 브랜치다. origin/main도 로컬에 있는 참조이므로 서버를 실시간으로 조회하는 이름은 아니다. main을 체크아웃한 상태에서는 HEAD가 main을 통해 현재 커밋을 가리킨다. fetch로 origin/main이 앞으로 가더라도 이 연결은 그대로 남는다.

이 설명은 기본 clone의 fetch 매핑으로 git fetch origin을 실행하는 경우다. git-fetch 공식 문서의 원격 추적 브랜치 설명 (새 탭에서 열림)도 어떤 참조를 갱신할지 설정된 매핑에 따라 정한다고 설명한다. 특수 refspec을 지정한 fetch까지 모두 같은 동작이라고 일반화하지 않는다.

받은 커밋과 지금 보는 파일을 따로 확인한다

Windows 11, Git 2.45.1.windows.1, PowerShell 7.6.6에서 로컬 bare 저장소 하나와 두 clone을 새로 만들었다. 한쪽 clone은 새 커밋을 보내고, 다른 clone은 받는 역할로 썼다. 외부 Git 서버나 실제 프로젝트의 파일은 사용하지 않았다.

처음에는 두 clone의 main이 모두 59203e2였고, note.txt에는 base 한 줄이 있었다. 보내는 쪽에서 파일을 remote-v2로 고쳐 커밋 e2e6875를 bare 저장소에 보냈다. 받는 쪽은 아직 fetch하지 않았으므로 HEAD, main, origin/main이 모두 59203e2에 남아 있었다. 이때 상태 조회에는 ## main...origin/main이 나왔다. 로컬에 기록된 두 브랜치가 같다는 뜻이지, 서버에도 새 커밋이 없다는 뜻은 아니다.

받는 clone의 최상위에서 다음 명령을 실행했다. 자기 저장소에서 따라 읽을 때도 Git이 설치된 터미널에서 해당 저장소를 열고, 원격 이름과 비교할 브랜치를 먼저 확인한다. 여기서는 현재 브랜치가 main, 원격이 origin, 비교 대상이 origin/main인 조건이다. 다른 브랜치에 서 있거나 첫 조회가 빈 이름을 반환하는 detached HEAD라면 아래 통합 명령으로 바로 넘어가지 않는다.

git fetch origin

fetch는 종료 코드 0으로 끝났고, 출력에는 main -> origin/main 갱신이 표시됐다. 이어 현재 브랜치, 세 참조의 커밋, 파일 상태와 내용을 같은 순서로 읽었다. note.txt는 이 실험의 합성 파일이므로 자기 저장소에서는 변경을 기대한 파일 경로로 바꾼다.

git branch --show-current
git rev-parse --short=7 HEAD
git rev-parse --short=7 refs/heads/main
git rev-parse --short=7 refs/remotes/origin/main
git status --short --branch
Get-Content -LiteralPath './note.txt'
git rev-list --left-right --count HEAD...origin/main

실제 출력은 다음과 같았다. 커밋 해시는 실험에서 생성된 값이며, 자기 저장소에서는 값의 일치 관계를 비교한다.

main
59203e2
59203e2
e2e6875
## main...origin/main [behind 1]
base
0	1

새 커밋을 받은 위치는 origin/main이다. HEAD와 로컬 main은 이전 커밋을 가리키고, 작업 파일도 여전히 base였다. fetch 전후 파일의 바이트와 인덱스에 기록된 파일 트리도 같았다. 이 결과에서는 파일이 그대로인 것이 fetch 실패의 증거가 아니다.

status의 behind 1은 추적 대상으로 설정된 origin/main에만 커밋이 하나 있다는 표시다. 파일 변경을 뜻하는 M이나 새 파일을 뜻하는 ??와는 다르다. git-status 공식 문서 (새 탭에서 열림)는 브랜치 추적 정보와 파일별 상태를 구분한다. 추적 대상 설정이 다른 저장소에서는 이 줄만 믿지 말고 명시적으로 지정한 두 참조를 비교한다.

마지막 줄의 두 수는 탭으로 나뉜다. HEAD...origin/main의 왼쪽인 HEAD에만 있는 커밋 수, 오른쪽인 origin/main에만 있는 커밋 수다. git-rev-list의 --left-right·--count 설명 (새 탭에서 열림)에서 이 순서를 확인할 수 있다. 이 실험의 0 1은 로컬에만 있는 커밋이 없고, 받은 쪽에만 하나가 있다는 뜻이다.

작업 파일이 깨끗하고 한 방향으로 이어질 때 통합한다

파일에 반영하려는 브랜치가 현재 main인지 확인한 다음, 먼저 미커밋 변경을 읽는다. 다음 명령은 성공하면서 출력이 비어 있어야 이 글의 통합 조건에 해당한다.

git status --porcelain=v1 --untracked-files=all

실험에서는 출력이 없었다. 이는 인덱스·작업 파일의 변경과 추적하지 않는 파일이 표시되지 않았다는 뜻이다. ignored 파일은 이 조회에 포함하지 않는다. 출력에 파일이 있으면 내용을 확인해 커밋하거나 별도로 보관한 뒤 다시 판단한다. behind 1이 보인다는 이유만으로 미커밋 변경까지 정리됐다고 간주하지 않는다.

받은 커밋의 변경을 적용하기로 했다면 다음을 실행한다. 앞의 조회가 오류로 끝났다면 먼저 그 오류를 해결한다.

git merge --ff-only origin/main

이 실험에서는 종료 코드 0과 함께 다음 출력이 나왔다.

Updating 59203e2..e2e6875
Fast-forward
 note.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

이후 같은 조회를 반복하자 HEAD, main, origin/main이 모두 e2e6875가 됐고, 파일도 remote-v2로 바뀌었다. 양쪽 전용 커밋 수는 0 0이었다. 새 merge commit을 만들지 않고 로컬 브랜치의 끝을 받은 커밋으로 전진시킨 fast-forward다. git-merge 공식 문서 (새 탭에서 열림)는 현재 커밋이 대상 커밋의 조상일 때 이 전진이 가능하다고 설명한다.

같은 비교에서 0 0이면 두 끝 커밋이 같고, 1 0처럼 왼쪽만 양수라면 로컬이 앞서 있으므로 받은 쪽에서 새로 통합할 커밋이 없다. 양쪽 모두 양수라면 다음 경우처럼 이력이 갈라진 상태다. 파일이 깨끗한지와 별개로 확인해야 한다.

로컬 커밋이 있으면 깨끗해도 --ff-only가 거절될 수 있다

첫 통합 뒤 받는 clone에서 local.txt에 local-only를 쓰고 커밋 6e7befd를 만들었다. 보내는 clone에서는 별도로 note.txt를 remote-v3로 바꾼 커밋 47c4357을 보냈다. 두 변경은 서로 다른 파일에 들어갔다.

받는 clone에서 다시 fetch하자 origin/main은 47c4357로 바뀌었지만, HEAD와 main은 로컬 커밋 6e7befd에 남았다. note.txt는 remote-v2, local.txt는 local-only였고 미커밋 변경 조회는 여전히 비어 있었다. 양쪽 전용 커밋 수는 1 1이었다.

어느 커밋이 갈라져 있는지는 다음 명령으로 읽었다. 첫 조회와 같은 HEAD...origin/main 순서다.

git log --oneline --left-right HEAD...origin/main
> 47c4357 D another remote update
< 6e7befd C local note

<는 HEAD 쪽에만, >는 origin/main 쪽에만 있는 커밋이다. 이 상태에서 앞의 git merge --ff-only origin/main을 실행하자 종료 코드 128로 끝났다. 안내문 뒤의 오류 줄은 다음과 같았다.

fatal: Not possible to fast-forward, aborting.

이는 파일 내용의 충돌을 해결하다 멈춘 결과가 아니다. 브랜치 끝을 한 방향으로 옮기는 것만으로 두 커밋을 모두 포함할 수 없어, 통합을 거절한 결과다. 파일이 서로 달라도 이력은 갈라질 수 있다. git-merge의 --ff-only 옵션 (새 탭에서 열림)은 fast-forward가 불가능하면 비정상 종료한다고 명시한다.

거절 뒤에도 세 참조, 인덱스의 파일 트리, 두 파일의 내용과 해시는 거절 전과 같았다. 로컬 커밋도 main에서 계속 도달할 수 있었다. 실험에서는 이 분기를 reset으로 없애지 않았다. 자기 저장소에서 양쪽에 남길 커밋이 있다면 여기서 각 변경을 읽고 팀의 merge·rebase 방침에 따라 통합 방법을 정한다. 이 오류만 없애려고 로컬 브랜치를 강제로 원격 위치에 맞추면, 남겨야 할 로컬 커밋을 브랜치에서 놓칠 수 있다.

직접 확인한 범위는 표준 fetch 매핑을 쓰는 로컬 텍스트 저장소에서의 수신, 깨끗한 fast-forward, 깨끗하지만 갈라진 이력의 거절까지다. 미커밋 변경이 있는 통합, shallow clone, 강제 갱신된 원격 이력, 서브모듈, 실제 네트워크 서버와 인증은 시험하지 않았다. 두 이력을 merge나 rebase로 합치는 후속 단계도 실행하지 않았다. fetch 뒤 파일이 그대로라면 먼저 세 참조와 작업 상태를 비교하고, 지금 브랜치에서 fast-forward로 반영할 수 있는 상태인지 판단하면 된다.

← 전체 글 보기