> ## Content Index
> Fetch the complete content index at: https://mlog.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# .DS_Store 때문에 git pull이 막힐 때: 로컬 변경 복구와 추적 해제
- URL: https://mlog.me/git-pull-ds-store-local-changes-gitignore/
- Published: 2026-11-13T06:30:17.000Z
- Updated: 2026-11-13T08:07:28.000Z
- Description: 로컬 코드 변경을 보존하면서 .DS_Store의 추적 상태와 스테이징을 확인하고, pull 복구와 팀 저장소의 재발 방지를 진행합니다.
- Author: mLog
- Tags: 개발, Git·GitLab, 저장소 관리

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

```text
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 문서](https://git-scm.com/docs/gitignore?ref=mlog.me)도 기존 추적 파일에는 ignore 규칙이 적용되지 않는다고 설명한다.

## 1\. 코드 변경과 .DS\_Store 변경을 먼저 나눈다

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

```bash
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 공식 문서](https://git-scm.com/docs/git-ls-files?ref=mlog.me)

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

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

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

[git status 출력 형식](https://git-scm.com/docs/git-status?ref=mlog.me)과 [git diff 비교 대상](https://git-scm.com/docs/git-diff?ref=mlog.me)을 보면 두 상태를 따로 확인하는 이유를 알 수 있다. `git diff`는 작업 파일과 index, `git diff --cached`는 index와 기본적으로 HEAD를 비교한다.

오류 목록에 `.java`, `.vue`, `.js`, 설정 파일이 함께 있다면 각각의 변경을 검토한다. 파일명이 비슷하다는 이유로 전체 작업을 되돌리지 않는다. 기본 작업 흐름은 [Git 기본 명령어와 사용법](https://mlog.me/git-basic-commands-beginner-guide/)에서 이어서 볼 수 있다.

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

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

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

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

```

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

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

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

```

두 번째 명령은 폐기 동작이다. 같은 위치에 소스 코드 경로나 `.`을 대신 넣지 않는다. 복구 대상과 원본의 의미는 [git restore 공식 문서](https://git-scm.com/docs/git-restore?ref=mlog.me)에서 확인할 수 있다.

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

```bash
git pull --ff-only
git status --short

```

`--ff-only`는 로컬과 원격 이력이 갈라진 경우 중단한다. 로컬 변경을 강제로 덮어쓰는 옵션이 아니다. 다른 경로가 다시 오류에 나타나면 그 변경을 별도로 검토한다. [git pull 공식 문서](https://git-scm.com/docs/git-pull?ref=mlog.me)

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

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

```bash
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이 성공한다고 보장할 수 없다.

```bash
git pull --ff-only
git status --short

```

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

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

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

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

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

```bash
git status --short
git diff --cached --name-status

```

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

```gitignore
.DS_Store

```

루트 `.gitignore`의 이 패턴은 하위 디렉터리의 같은 이름에도 적용된다. 하위 `.gitignore`나 예외 패턴이 있으면 실제 매칭 결과를 확인해야 한다. [gitignore 패턴 규칙](https://git-scm.com/docs/gitignore?ref=mlog.me)

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

```bash
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 공식 문서](https://git-scm.com/docs/git-rm?ref=mlog.me)

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

```bash
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 용량이 그대로인 이유](https://mlog.me/git-remote-remove-repository-size-cleanup/)에서 별도로 다룬다.

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

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

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

```

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

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

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

```

여기서 `--no-index`는 진단할 때 index를 보지 않겠다는 뜻이다. 추적을 해제하는 옵션이 아니다. 일반 `git check-ignore`는 추적 파일을 기본적으로 표시하지 않으므로, 출력이 없다고 곧바로 ignore 규칙이 틀렸다고 결론 내리지 않는다. [git check-ignore 공식 문서](https://git-scm.com/docs/git-check-ignore?ref=mlog.me)

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

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

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

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

여러 저장소를 한 번에 갱신하는 스크립트에서는 이 오류가 난 저장소를 건너뛰고 기록하는 편이 안전하다. 자동으로 로컬 변경을 폐기하지 않도록 한다. [여러 Git 저장소 한 번에 Pull하기](https://mlog.me/pull-multiple-git-repositories-at-once/)의 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 규칙을 정리하면, 불필요한 전체 초기화 없이 같은 문제의 재발을 줄일 수 있다.