기술 · 실용 가이드
clone 후 Git LFS 포인터 파일만 남았나요? 다시 clone하기 전에 속성과 객체 감사하기
clone한 작업 트리에 포인터 텍스트가 보인다는 것은 증상이지 근본 원인은 아닙니다. 로컬 변경을 보존하고 정확한 LFS 객체를 식별한 뒤, 속성과 필터 설정을 다운로드, 인증, 로컬 객체, checkout 실패와 분리해 판단하세요.

Git LFS는 Git에 작은 포인터를 저장하고 큰 객체는 별도 저장소에 둡니다. 작업 트리에 포인터가 나타나는 이유는 파일을 펼칠 때 Git LFS가 설치되지 않았거나, smudge를 의도적으로 건너뛰었거나, 속성이 해당 경로와 일치하지 않거나, 객체를 가져오지 못했거나, checkout이 수정된 파일을 보호하려고 교체를 보류했기 때문일 수 있습니다. 포인터가 보인다는 사실만으로 저장소가 손상되었다고 단정할 수 없습니다.
가장 안전한 진단은 포인터에서 속성, 필터, 로컬 캐시, 원격 fetch, 작업 트리 checkout까지 객체의 경로를 순서대로 추적하는 것입니다. 단계마다 증거와 해결 방법이 다릅니다. 무작정 다시 clone하면 같은 설정 오류가 반복되고 대역폭을 낭비하며 유용한 로컬 상태를 잃을 수 있습니다.
이 가이드는 읽기 중심입니다. 이력을 다시 쓰거나 hard reset 또는 clean을 실행하지 않고, 인증을 우회하거나 기존 clone을 삭제하지도 않습니다. 최종 결과는 짧은 증거 기록과 범위를 최소화한 안전한 다음 조치입니다.
1. 작업 트리를 보존하고 실제 포인터인지 확인하기
먼저 `git status --short`를 실행해 출력을 보관합니다. 테스트 전에 로컬에서 편집했을 수 있는 포인터 형태의 파일과 추적되지 않은 작업을 저장소 밖에 복사하세요. 진단 중에는 `git reset --hard`, `git clean`, 이력 마이그레이션, clone 삭제를 실행하지 않습니다.
비정상적으로 작은 텍스트 파일을 읽기 전용으로 엽니다. 정상적인 Git LFS v1 포인터에는 사양 줄, `oid sha256:` 줄, 선언된 `size`가 있습니다. 상대 경로, OID, 크기를 기록하세요. 포인터처럼 보여도 형식대로 파싱되지 않으면 별도의 콘텐츠 손상 분기로 다룹니다.
사용 중인 버전이 지원한다면 `git lfs pointer --check --file="PATH"`를 실행하거나 문서의 포인터 구조와 비교합니다. 저장소 자격 증명, 서명된 다운로드 URL, 비공개 원격 세부 정보는 공개 증거에 넣지 마세요.
2. 해당 경로가 실제로 LFS 속성의 적용 대상인지 확인하기
영향받는 커밋에서 `git check-attr -a -- "PATH"`를 실행합니다. LFS로 추적되어야 하는 경로에 대해 Git이 실제로 읽은 filter, diff, merge, text 속성을 확인하세요. 작업 트리의 파일 이름만으로 현재 속성 규칙이 일치한다고 증명할 수는 없습니다.
커밋되지 않은 로컬 사본만 보지 말고 해당 Git 버전에 기록된 `.gitattributes`를 확인합니다. 속성 패턴은 경로에 의존합니다. 상위 프로젝트 FAQ는 플랫폼 차이로 잘못된 패턴이 드러나거나, 추적 규칙이 적용되기 전에 커밋된 과거 파일이 다르게 동작할 수 있다고 설명합니다.
그 커밋의 경로에 LFS filter 속성이 없다면 여기서 멈추세요. Git LFS 설치나 객체 fetch만으로 속성 불일치를 해결할 수는 없습니다. 어떤 파일과 커밋을 바꿔야 하는지 별도로 검토한 다음 renormalization이나 migration을 고려해야 합니다.
3. 로컬 Git LFS 필터와 선택 상태 기록하기
`git lfs version`과 민감 정보를 제거한 `git lfs env`를 기록합니다. 현재 사용자와 저장소에 Git LFS가 설치되어 있는지 확인하고, 작업 트리를 만들 때 `GIT_LFS_SKIP_SMUDGE=1`, `git lfs install --skip-smudge` 또는 동등한 설정을 사용했는지 기록하세요.
모든 LFS 객체를 다운로드해야 한다고 먼저 가정하지 말고 include와 exclude 규칙을 확인합니다. pull 매뉴얼은 경로 필터링을 설명하며, partial clone이나 sparse checkout도 작업 트리에 들어오는 경로를 의도적으로 제한할 수 있습니다.
sparse-checkout 상태와 모든 partial-clone filter도 기록합니다. 기존 Git LFS issue에는 partial checkout 및 include 필터와 관련해 포인터만 남는 실제 사례가 있습니다. 그러나 속성, 필터, fetch, checkout이라는 일반 경계를 확인한 뒤에만 해당 회귀 분기를 고려해야 합니다.
4. 객체 fetch와 작업 트리 checkout을 분리해 판단하기
`git lfs pull`은 fetch와 checkout을 결합합니다. 반면 `git lfs checkout`만 실행하면 누락된 콘텐츠를 다운로드하지 않고, 로컬 LFS 저장소에 이미 있는 객체만 사용해 해당 포인터를 교체합니다. 그래서 checkout을 반복해도 변화가 없을 수 있습니다.
대규모 다운로드 전에 영향받는 ref와 경로로 fetch 범위를 제한합니다. 저장소 구성을 확인한 뒤, 예를 들어 `git lfs fetch REMOTE REF --include="PATH"`를 실행하고 정확한 원격, ref, 민감 정보를 제거한 첫 오류를 기록하세요.
fetch가 인증, 권한, 객체 누락 또는 할당량 오류를 명확히 반환하면 그 경계에서 멈춥니다. 일반 Git clone이 성공했다고 해서 별도의 LFS 객체 엔드포인트가 요청을 받아들였거나 해당 OID를 아직 보관한다고 증명되지는 않습니다.
5. checkout 전에 로컬 저장소의 정확한 OID 확인하기
fetch가 성공했다면 기록한 SHA-256 OID에 해당하는 객체가 로컬 LFS 저장소에 있고, 바이트 길이가 포인터에 선언된 size와 같은지 확인합니다. 같은 이름의 파일을 보고 추측하지 말고 Git LFS 명령과 저장소 진단을 사용하세요.
현재 저장소에 적합하다면 `git lfs fsck --objects`를 실행해 출력을 보관합니다. 로컬 객체의 크기나 해시가 다르면 무결성 문제입니다. 객체 자체가 없다면 fetch 또는 원격 가용성 문제가 아직 남아 있습니다.
파일 이름이 같다는 이유로 임의의 바이너리를 포인터 위에 덮어쓰지 마세요. 콘텐츠 식별자는 포인터 OID입니다. 올바른 checkout은 정확한 객체로 해석되어야 하며 관련 없는 파일을 바꾸지 않습니다.
6. checkout의 변경 보호를 유지하며 결과 해석하기
정확한 객체가 로컬에 있음을 확인한 다음, 경로를 제한해 `git lfs checkout -- "PATH"`를 실행합니다. checkout 매뉴얼은 수정된 파일을 덮어쓰지 않는다고 설명합니다. 따라서 포인터 형태의 파일이 인덱스와 다를 때 변화가 없는 것은 설치 실패가 아니라 의도한 보호일 수 있습니다.
`git status --short`, `git diff -- "PATH"`, 인덱스에 기록된 포인터를 비교합니다. 깨끗한 자리표시자로 되돌리기 전에 모든 로컬 변경을 백업하고, 차이가 사용자 작업인지 줄바꿈 손상인지 비정상 checkout 결과인지 판단하세요.
checkout이 성공하면 작업 트리 파일이 더 이상 포인터로 파싱되지 않고 크기가 선언값과 같은지 확인합니다. 일반적인 Git LFS 구성에서 가능하다면 해시도 OID와 대조합니다.
증거 | 실패 경계 | 최소한의 안전 조치 경로에 LFS filter 속성이 없음 | 속성 | 추적 규칙을 별도로 수정·검토하고 무차별 fetch는 하지 않음 LFS가 미설치이거나 smudge를 의도적으로 건너뜀 | 필터 설치 | LFS를 설치 또는 설정한 뒤 범위를 제한해 pull fetch가 인증 또는 객체 누락 오류를 반환 | 원격 접근 또는 객체 가용성 | 접근을 수정하거나 정확한 OID를 복원하고 재clone은 하지 않음 객체는 로컬에 있지만 checkout이 수정된 포인터를 남김 | 작업 트리 보호 | 변경을 백업하고 인덱스와 비교한 뒤 경로 제한 checkout 일반 점검 후에도 partial/sparse 경로만 실패 | checkout 선택 또는 회귀 | issue로 올리기 전에 정확한 버전과 최소 필터 조건으로 재현
7. 복구를 검증하고 지원용 증거 정리하기
합격 조건을 구체적으로 정하세요. 포인터가 파싱되고 OID와 선언 크기가 기록되어 있어야 합니다. fetch 뒤 같은 OID가 로컬에 있어야 하고, 작업 트리 경로에는 해석된 콘텐츠가 들어가며, 관련 없는 변경은 그대로여야 합니다. 마지막으로 예상한 애플리케이션에서 파일이 열리는지 실용적인 추가 확인을 합니다.
지원 요청에는 민감 정보를 제거한 Git 및 Git LFS 버전, 운영체제, 포인터 OID 접두부와 크기, `check-attr` 결과, 필터와 include/exclude 상태, 원격 이름, fetch 결과, 로컬 객체 결과, sparse/partial checkout 상태, 경로 제한 checkout 결과를 포함하세요. 관찰 사실과 결론은 분리해서 작성합니다.
속성, 설정, 원격 접근, 객체 존재, 변경 보호를 모두 배제한 뒤에도 최소 재현이 실패할 때만 버전별 회귀 가능성을 보고합니다. 포인터만 남는 모든 checkout을 하나의 기존 issue로 단정하지 마세요.
자주 묻는 질문
Git clone은 성공했는데 왜 Git LFS 파일이 다운로드되지 않나요? Git 저장소와 LFS 객체 서비스는 서로 다른 경계입니다. LFS가 미설치이거나 smudge를 건너뛰었거나 경로가 제외되었거나 자격 증명이 실패했거나 원격 객체가 누락되어도 Git은 포인터 파일 clone에 성공할 수 있습니다.
`git lfs checkout`은 누락된 객체를 다운로드하나요? 아니요. checkout은 로컬 LFS 저장소에 이미 있는 객체만 사용합니다. 누락된 객체는 먼저 fetch 또는 pull해야 합니다.
포인터를 교체하려고 `git reset --hard`를 실행해야 하나요? 진단 중에는 실행하지 마세요. hard reset은 관련 없는 작업을 잃게 할 수 있고, 필요한 LFS 객체의 가용성도 증명하지 못합니다. 먼저 상태, 속성, OID, fetch 증거를 기록하세요.
포인터 텍스트가 보이면 파일이 손상된 것인가요? 반드시 그렇지는 않습니다. 포인터는 정상적인 저장소 데이터입니다. 문제는 일치하는 큰 객체가 작업 트리에 채워지지 않았다는 것이며, 서로 다른 여러 경계가 원인일 수 있습니다.
