흰색 유령 의상 수선사가 옷을 보존한 채 관리표를 떼어내며 Git 추적 해제를 표현하는 3D 일러스트

.DS_Store 때문에 git pull이 막힐 때: 로컬 변경 복구와 추적 해제

개발 Git·GitLab 2026년 11월 13일

git pull을 실행했는데 다음 오류가 나오면, 먼저 메시지에 적힌 경로를 확인하자.

error: Your local changes to the following files would be overwritten by merge:
        src/assets/images/.DS_Store
Please commit your changes or stash them before you merge.
Aborting

이 글에서는 .DS_Store가 이미 저장소에 추적되고 있고, 로컬 변경이 원격 변경으로 덮어써질 수 있어 작업이 중단된 경우를 다룬다. 위 문구는 별도의 임시 저장소에서도 재현했다.

처리 순서는 상태 확인 → 해당 경로의 변경을 보관하거나 폐기 → pull 재개 → 추적 해제와 ignore 규칙 공유다. .gitignore에 한 줄 추가하는 것만으로는 이미 추적 중인 파일 문제가 해결되지 않는다. Git 공식 gitignore 문서도 기존 추적 파일에는 ignore 규칙이 적용되지 않는다고 설명한다.

1. 코드 변경과 .DS_Store 변경을 먼저 나눈다

아래 예시는 저장소 최상위 폴더에서 실행한다. 실제 오류 경로가 다르면 src/assets/images/.DS_Store를 바꿔 사용한다.

git status
git status --short
git diff --stat
git diff --cached --stat
git ls-files --error-unmatch -- src/assets/images/.DS_Store
git branch -vv

git ls-files --error-unmatch가 경로를 출력하면 현재 index에 있는 파일이다. 경로를 찾지 못한다면 오타, 작업 위치, 추적 상태부터 다시 확인한다. git ls-files 공식 문서

git status --short의 두 칸은 보통 첫 번째가 스테이징 상태, 두 번째가 작업 파일 상태를 나타낸다. 병합 충돌 중에는 의미가 달라지므로 아래 표는 병합이나 rebase가 진행 중이지 않은 일반 파일 기준이다.

표에서 ·는 공백 한 칸을 뜻한다. 실제 Git 출력에는 점 대신 공백이 나타난다. 파일 경로는 생략하고 상태 두 칸만 표시했다.

상태 두 칸 현재 상태 다음 판단
·M 작업 파일만 수정됨 해당 변경을 보관할지 폐기할지 선택
M· 수정이 스테이징됨 일반 restore만으로는 스테이징 변경이 없어지지 않음
MM 스테이징한 뒤 다시 수정함 index와 작업 파일을 둘 다 확인
?? 추적하지 않는 파일 이 글의 추적 파일 복구 절차를 바로 적용하지 않음
UU 등 unmerged 상태 충돌 해결이 필요한 상태 진행 중인 merge/rebase 처리부터 확인

git status 출력 형식과 git diff 비교 대상을 보면 두 상태를 따로 확인하는 이유를 알 수 있다. git diff는 작업 파일과 index, git diff --cached는 index와 기본적으로 HEAD를 비교한다.

오류 목록에 .java, .vue, .js, 설정 파일이 함께 있다면 각각의 변경을 검토한다. 파일명이 비슷하다는 이유로 전체 작업을 되돌리지 않는다. 기본 작업 흐름은 Git 기본 명령어와 사용법에서 이어서 볼 수 있다.

2. 방법 A: 필요 없는 변경만 경로를 지정해 되돌린다

.DS_Store의 로컬 변경을 버려도 된다고 확인한 경우다. 보관할 필요가 있으면 먼저 저장소 밖의 별도 위치에 파일 사본을 만들거나 다음 절의 stash 방법을 선택한다.

스테이징하지 않은 변경만 있고 현재 index의 내용으로 돌아가려면 다음 명령을 사용한다.

git restore -- src/assets/images/.DS_Store
git status --short

이 명령은 기본적으로 index를 기준으로 작업 파일을 복구한다. 이미 스테이징된 변경까지 지우지는 않는다. MM 상태에서 실행하면 작업 파일이 스테이징 버전으로 돌아갈 수 있다.

해당 파일의 스테이징 변경과 작업 파일 변경을 모두 폐기하고 마지막 커밋 내용으로 맞추려는 경우에는 범위를 명확히 적는다.

git restore --source=HEAD --staged --worktree -- src/assets/images/.DS_Store
git status --short

두 번째 명령은 폐기 동작이다. 같은 위치에 소스 코드 경로나 .을 대신 넣지 않는다. 복구 대상과 원본의 의미는 git restore 공식 문서에서 확인할 수 있다.

이후 upstream이 올바른지 확인한 상태에서 pull을 재시도한다.

git pull --ff-only
git status --short

--ff-only는 로컬과 원격 이력이 갈라진 경우 중단한다. 로컬 변경을 강제로 덮어쓰는 옵션이 아니다. 다른 경로가 다시 오류에 나타나면 그 변경을 별도로 검토한다. git pull 공식 문서

3. 방법 B: 판단이 어려우면 해당 파일만 stash에 보관한다

지금 변경을 버릴지 결정하기 어렵다면 경로를 제한해 잠시 보관한다.

git stash push -m "ds-store-before-pull" -- src/assets/images/.DS_Store
git stash list
git stash show --stat 'stash@{0}'
git status --short

새 stash 항목이 실제로 생성됐는지 메시지와 목록을 확인한다. 새 항목이 없다면 stash@{0}은 이전의 다른 작업일 수 있다. 경로를 지정한 stash는 지정하지 않은 파일의 변경을 그대로 남긴다. 원격 변경과 겹치는 다른 로컬 수정이 있다면 이 조치만으로 pull이 성공한다고 보장할 수 없다.

git pull --ff-only
git status --short

pull이 끝났다고 곧바로 stash pop을 실행할 필요는 없다. 원격에서도 .DS_Store가 바뀌었다면 이전 상태를 다시 적용할 때 충돌할 수 있다. 내용을 복원해야 할 때는 저장해 둔 항목을 식별하고 검토한 뒤 적용한다. stash apply는 항목을 남겨두며, stash pop도 충돌하면 항목을 자동 삭제하지 않는다. git stash 공식 문서

이번 재현에서는 .DS_Store만 stash에 넣고 별도의 JavaScript 수정은 작업 폴더에 남겨 둔 상태로 pull이 완료됐다. 이후 서로 다른 바이너리 변경을 다시 적용하자 충돌했고 stash 항목은 보존됐다. 이는 Git 동작을 확인한 모의 실험이며, 실제 프로젝트에서 같은 결과가 난다고 단정하는 것은 아니다.

4. 재발을 막으려면 ignore 규칙과 추적 해제를 함께 반영한다

앞 단계는 현재 pull을 재개하기 위한 복구다. 저장소가 .DS_Store를 계속 추적하면 이후에도 문제가 반복될 여지가 있다.

팀에서 이 파일을 버전 관리하지 않기로 정했다면, 복구 후 별도의 정리 커밋을 준비한다. 기존 스테이징 영역에 다른 작업이 섞여 있지 않은 상태에서 진행해야 정리 커밋에 무관한 수정이 포함되지 않는다. 먼저 확인한다.

git status --short
git diff --cached --name-status

기존 .gitignore 내용을 덮어쓰지 말고 편집기로 다음 규칙을 추가한다. 이미 규칙이 있으면 중복으로 넣지 않는다.

.DS_Store

루트 .gitignore의 이 패턴은 하위 디렉터리의 같은 이름에도 적용된다. 하위 .gitignore나 예외 패턴이 있으면 실제 매칭 결과를 확인해야 한다. gitignore 패턴 규칙

다음은 오류에 나온 한 파일만 추적에서 제외하는 예다.

git rm -n --cached -- src/assets/images/.DS_Store
git rm --cached -- src/assets/images/.DS_Store
git add -- .gitignore
git diff --cached --name-status
git diff --cached -- .gitignore

-n은 실행 예정 결과만 확인한다. --cached를 붙인 실제 명령은 index에서 경로를 제거하고, 실행한 사람의 작업 파일은 남겨 둔다. 조건이 맞지 않아 명령이 거부되면 즉시 -f를 붙이지 말고 스테이징 상태부터 확인한다. git rm 공식 문서

스테이징 목록에 .gitignore 변경과 의도한 .DS_Store 삭제만 있는지 확인한 뒤 커밋한다.

git commit -m "Stop tracking .DS_Store"

커밋 후에는 팀의 브랜치·리뷰 절차에 따라 공유한다. 로컬 커밋만으로 팀 저장소에 반영되지는 않는다. .DS_Store가 여러 경로에 있다면 추적 목록에서 대상 경로를 확인해 하나씩 추가한다. 이 한 파일을 처리하기 위해 저장소 전체에 git rm -r --cached .을 실행할 필요는 없다.

5. 내 파일은 남아도 팀원의 파일은 제거될 수 있다

git rm --cached를 설명할 때 가장 혼동하기 쉬운 부분이다. 이 명령을 실행한 작업 폴더에 파일이 남는 것과, 정리 커밋에 파일 삭제가 기록되는 것은 동시에 성립한다.

이번 임시 저장소 실험에서는 다음 순서로 확인했다.

  1. 정리 담당 복제본에서 git rm --cached 후 커밋했다. 이 복제본의 파일은 남았다.
  2. 로컬 테스트용 bare 저장소에 정리 커밋을 전달했다.
  3. 해당 파일을 수정하지 않은 다른 복제본에서 pull했다. 파일은 삭제됐다.
  4. 그 복제본에 파일을 다시 만들었다. 공유된 .gitignore 규칙에 따라 무시됐다.

따라서 “모든 팀원의 파일을 그대로 둔 채 추적만 끈다”는 안내는 정확하지 않다. 팀원에게 정리 내용을 알리고, 필요한 로컬 사본은 먼저 보관하게 한다. 팀원의 파일에 수정이 있으면 pull이 중단되는 등 상태에 따라 결과가 달라질 수 있다.

이 작업은 이후 커밋의 추적 대상에서 제외하는 것이다. 과거 커밋에 저장된 내용까지 지우지는 않는다. 실험에서도 직전 커밋의 파일 객체를 계속 읽을 수 있었다. 저장소 용량이나 이전 이력을 정리하는 문제는 Git remote 삭제 후 .git 용량이 그대로인 이유에서 별도로 다룬다.

6. .gitignore가 적용됐는지 두 단계로 확인한다

추적 해제 뒤 현재 index와 ignore 매칭을 따로 검사한다.

git ls-files -- src/assets/images/.DS_Store
git check-ignore -v -- src/assets/images/.DS_Store
git status --short

첫 번째 명령에는 경로가 나오지 않아야 한다. 두 번째 명령은 적용되는 규칙의 파일·줄 번호·패턴을 보여 준다. !로 시작하는 예외 규칙이 표시되면 무시하겠다는 의미가 아니므로 패턴을 다시 확인한다.

아직 추적 중인 파일의 패턴만 진단하려면 다음을 사용한다.

git check-ignore -v --no-index -- src/assets/images/.DS_Store

여기서 --no-index는 진단할 때 index를 보지 않겠다는 뜻이다. 추적을 해제하는 옵션이 아니다. 일반 git check-ignore는 추적 파일을 기본적으로 표시하지 않으므로, 출력이 없다고 곧바로 ignore 규칙이 틀렸다고 결론 내리지 않는다. git check-ignore 공식 문서

7. 다른 오류와 구분하고 변경 범위를 넓히지 않는다

확인한 상황 다음 행동
.DS_Store 외에 코드 수정도 표시됨 각 변경을 검토하고 경로를 제한해 보관 또는 커밋
untracked working tree files would be overwritten 추적되지 않는 파일의 덮어쓰기 문제로 분리해 보관·대조
--ff-only가 분기된 이력 때문에 거부됨 커밋 이력과 팀의 merge/rebase 방침 확인
stash pop 뒤 충돌 발생 반복 실행하지 말고 unmerged 상태와 남은 stash 확인
인증·연결 오류가 발생함 GitLab clone 연결 오류 점검처럼 네트워크·인증 단계로 분리

reset --hard, clean -fdx 같은 전체 정리 명령은 이번 파일 하나의 문제를 진단하는 과정에 넣지 않았다. 다른 작업이나 무시 중인 로컬 파일까지 영향을 받을 수 있기 때문이다.

assume-unchanged나 skip-worktree를 영구적인 해결책으로 제안하지도 않는다. Git 공식 문서는 이 플래그로 추적 파일의 변경을 무시하려는 사용 방식이 기대대로 동작하지 않을 수 있다고 설명한다. 팀 공통 제외 규칙과 추적 해제를 분명히 하는 편이 목적에 맞다. git update-index 주의사항

여러 저장소를 한 번에 갱신하는 스크립트에서는 이 오류가 난 저장소를 건너뛰고 기록하는 편이 안전하다. 자동으로 로컬 변경을 폐기하지 않도록 한다. 여러 Git 저장소 한 번에 Pull하기의 dirty 상태·upstream 검사와 연결해서 적용할 수 있다.

8. 재현한 범위와 적용 전 확인

2026-11-13, Linux 환경의 Git 2.52.0에서 임시 bare 저장소와 여러 복제본을 만들고 20개 검사를 통과했다. 실제 사용자 저장소나 원격 서비스에는 연결하지 않았다. 외부 패키지 설치도 필요하지 않았다.

검사에는 ignore만으로 해결되지 않는 경우, 경로 제한 restore/stash, 스테이징 변경의 잔존, 다른 코드 수정 보존, stash 충돌 뒤 항목 보존, fast-forward 거부, 추적 해제 후 로컬 파일 유지, 다른 복제본에서의 삭제 반영이 포함됐다.

.DS_Store는 NUL 바이트를 포함한 모의 바이너리 파일로 만들었다. macOS Finder가 실제 파일을 생성·갱신하는 과정, GUI Git 클라이언트, 팀 저장소의 보호 브랜치·서버 훅은 검증하지 않았다. 적용할 때는 파일 경로, 보관할 변경, 현재 스테이징 상태, upstream을 먼저 확인한다.

급하게 pull을 재개할 때도 파일 하나의 변경을 어떻게 처리할지부터 결정하면 된다. 그 뒤 공유된 추적 목록과 ignore 규칙을 정리하면, 불필요한 전체 초기화 없이 같은 문제의 재발을 줄일 수 있다.

태그

mLog

Spring Boot와 PostgreSQL로 서비스를 개발하고, Oracle Cloud에서 Docker로 mLog를 운영합니다. 쿼리가 느려지거나 컨테이너가 뜨지 않을 때 어떤 로그와 명령으로 원인을 찾았는지 기록합니다. 직접 확인한 결과와 공식 문서를 참고한 예제를 구분해 적습니다.