여러 브랜치를 운영하다 보면 로컬과 원격(origin)이 어긋나는 경우가 자주 생긴다.
을 실행했더니 아래와 같은 에러가 발생했다.
error: Your local changes to the following files would be overwritten by merge:
data/code/multiom/insilico-core-multiom/...
Please commit your changes or stash them before you merge.
Aborting
원인: 로컬에서 수정된 파일이 있는데, origin에서도 같은 파일이 변경되어 merge 시 충돌이 발생한 것이다.
1 현재 상태 파악: git branch -vv
먼저 각 브랜치가 origin 대비 얼마나 밀려있는지 확인한다.
* feat/chatbot 9c9b5d2 [origin/feat/chatbot] # 최신
feat/standardizer a954edd [origin/feat/standardizer: behind 47] # 47개 뒤처짐
main ce0acf0 [origin/main: ahead 1, behind 163] # 1개 앞서고 163개 뒤처짐
| 표시 | 의미 |
|---|---|
behind N |
origin에 N개 커밋이 더 있다 (로컬이 뒤처짐) |
ahead N |
로컬에만 N개 커밋이 있다 (origin에 안 올라감) |
| 표시 없음 | origin과 동일 |
2 해결 전략: origin을 기준으로 강제 동기화
로컬 변경사항을 버리고 origin을 정답으로 맞추기로 결정했다.
2.1 핵심 명령어: git reset --hard
이 명령어는 로컬 브랜치의 HEAD를 origin의 최신 커밋으로 강제 이동시킨다.
- 로컬에만 있던 커밋 -> 사라짐
- 로컬 파일 수정사항 -> 사라짐
- origin과 완전히 동일한 상태가 됨
git reset --hard는 되돌릴 수 없다. 로컬 변경사항이 영구 삭제되므로, 중요한 작업이 있다면 반드시 먼저 백업(stash, 별도 브랜치)해야 한다.
2.2 실제 수행 과정
2.2.1 작업 중인 변경사항 임시 저장
현재 feat/chatbot에서 작업 중인 파일(parent_retriever.py)이 있으므로 stash로 보관한다.
Saved working directory and index state WIP on feat/chatbot: 9c9b5d2 feat: add parent retriever
2.2.2 각 브랜치를 origin 기준으로 reset
HEAD is now at 00b4565 Merge pull request #4
HEAD is now at 60e88d5 docs: update chatbot prompts
2.2.3 원래 브랜치로 돌아와서 작업 복원
On branch feat/chatbot
Changes not staged for commit:
modified: src/parent_retriever.py
Dropped stash@{0} (7d9b3a2c4f1e8b5d6c7a8f9e0b1d2c3a)
parent_retriever.py 수정사항이 복원되고 stash가 목록에서 제거된다.
2.2.4 한 줄로 실행
위 과정을 한 줄로 연결하면:
2.3 결과 확인
* feat/chatbot 9c9b5d2 [origin/feat/chatbot] # 최신
feat/standardizer 60e88d5 [origin/feat/standardizer] # 최신
main 00b4565 [origin/main] # 최신
모든 브랜치가 origin과 일치한다.
3 git pull이 안 되는 다른 경우들
3.1 git stash로도 해결 안 될 때
git stash는 tracked 파일의 변경만 저장한다. 인덱스(staging area)가 꼬인 경우에는 stash 후에도 merge가 실패할 수 있다.
3.2 untracked 파일이 충돌할 때
origin에 새로 추가된 파일과 로컬 untracked 파일이 겹치는 경우:
Removing data/code/multiom/insilico-core-multiom/
Updating 9c9b5d2..00b4565
Fast-forward
data/code/multiom/insilico-core-multiom/config.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
| 옵션 | 의미 |
|---|---|
-f |
강제 삭제 |
-d |
디렉토리도 삭제 |
-n |
dry-run (삭제 대상만 미리 확인) |
git clean -nd로 먼저 삭제 대상을 확인한 후, -fd로 실행하는 것이 안전하다.
4 사례 연구: 빌드 산출물이 pull을 막을 때
포스트 서두와 동일한 would be overwritten by merge 에러를 다른 맥락에서 만난 적이 있다. Quarto 블로그 프로젝트에서 로컬이 origin/main 대비 80커밋 뒤처진 상태에서 git pull을 실행하자 에러가 떴다. 처음엔 “머지 충돌이 났다”고 판단했는데, 실제로는 머지 충돌이 아니었다. 이 사례는 에러 메시지만 보고 원인을 속단하지 않는 진단 프레임의 중요성을 보여준다.
4.1 증상
error: Your local changes to the following files would be overwritten by merge:
.quarto/xref/INDEX
_site/docs/blog/index.html
_site/docs/blog/posts/Math/linear_algebra/mit-08-*.html
_site/search.json
_site/sitemap.xml
...
Please commit your changes or stash them before you merge.
Aborting
4.2 1단계: git status로 실제 상태 확인 (진단)
“충돌”이라는 단어를 그대로 믿지 않고 git status를 먼저 실행한다. 이 한 번의 확인으로 머지 충돌과 로컬 변경 덮어쓰기 위험을 구분할 수 있다.
Your branch is behind 'origin/main' by 80 commits, and can be fast-forwarded.
Changes not staged for commit:
modified: .quarto/xref/INDEX
modified: _site/docs/blog/index.html
...
출력에서 두 가지 사실을 읽어낸다.
can be fast-forwarded→ 머지 충돌 없음. 단순히 앞으로 당기기만 하면 되는 상태. 히스토리가 분기되지 않았다Changes not staged for commit→ 커밋되지 않은 로컬 수정분 존재
즉 실제 원인은 “pull이 덮어쓸 파일과 로컬 수정분이 겹쳐 있어 안전장치가 작동한 것”이지 머지 충돌이 아니다.
| 구분 | 머지 충돌 (실제 conflict) | 덮어쓰기 위험 (이번 사례) |
|---|---|---|
| 원인 | 같은 줄을 양쪽이 다르게 수정 | 커밋 안 된 로컬 수정분 존재 |
git status 힌트 |
Unmerged paths 표시 |
Changes not staged for commit + can be fast-forwarded |
| 해결 방향 | 수동 충돌 해소 | 로컬 수정분 처리 (커밋·버리기·stash) |
4.3 2단계: 수정된 파일의 성격 파악
수정된 파일이 모두 두 디렉터리에 속한다는 점이 결정적이었다.
_site/→ Quarto가 렌더링한 빌드 산출물 (정적 HTML, JSON, sitemap).quarto/xref/INDEX→ Quarto의 내부 캐시
두 디렉터리 모두 소스가 아니라 생성물이다. 사용자가 직접 수정한 파일이 아니라 과거 quarto render 실행 결과일 뿐이다. 버려도 데이터 손실이 없다. 이 성격 판단이 해결책 선택을 바꾼다 — 재생성 가능 파일이면 공격적으로 덮어써도 된다.
4.4 3단계: 시도 1 — stash + pull (실패)
처음엔 안전하게 stash로 치워둔 뒤 pull을 시도했다.
git stash push -u -m "local build artifacts before ff-pull" -- .quarto/xref/INDEX _site/
git pull --ff-only결과: stash는 됐지만 어떤 이유로 인덱스에 같은 변경분이 다시 staged로 남아 pull이 여전히 막혔다. .gitignore에 .quarto가 포함돼 있어 pathspec + -u 조합이 꼬인 것으로 추정된다. stash의 untracked 모드와 pathspec이 .gitignore 엔트리를 건드릴 때 일관성이 깨지는 에지 케이스다.
4.5 4단계: 시도 2 — git checkout HEAD --로 강제 되돌리기 (성공)
빌드 산출물이라 버려도 되는 파일이므로, HEAD 기준으로 워킹트리를 강제로 되돌렸다.
이 명령은 “현재 커밋(HEAD)에 저장된 버전으로 .quarto, _site 디렉터리의 모든 파일을 덮어쓰라”는 뜻이다. 로컬 수정분이 사라지고 워킹트리가 커밋된 상태와 일치한다.
삭제된 파일 중 일부(_site/docs/blog/index-listing.json 등)는 checkout만으로 복구되지 않아 restore로 인덱스와 워킹트리를 동시에 복원했다.
git restore --source=HEAD --staged --worktree -- _site/docs/blog/index-listing.json _site/docs/blog/index.html이후 git status가 “nothing to commit, working tree clean”을 보인 뒤 pull을 실행했다.
결과: c8ca05fd..f015a038로 fast-forward 완료. 80커밋이 로컬에 반영됐다.
4.6 checkout HEAD -- vs reset --hard 비교
이번 사례처럼 특정 경로만 되돌리고 싶을 때는 checkout HEAD -- 또는 restore가 적합하다. 전체 워킹트리를 되돌리는 reset --hard는 필요 이상으로 공격적이다.
| 명령 | 범위 | 장점 | 단점 |
|---|---|---|---|
git reset --hard origin/<branch> |
브랜치 전체 | 한 번에 원격에 맞춤 | 로컬에만 있는 다른 의미 있는 변경도 날아감 |
git checkout HEAD -- <path> |
특정 경로 | 필요한 파일만 되돌림 | 삭제된 파일은 경우에 따라 복구 안 됨 |
git restore --source=HEAD --staged --worktree -- <path> |
특정 경로 | 인덱스 + 워킹트리 동시 복원 | 명령이 길고 옵션이 많음 |
본 사례에서는 소스 파일(.qmd, 가이드 .md)에 의미 있는 로컬 변경이 섞여 있을 수 있어 reset --hard가 부적절했다. .quarto·_site만 되돌리는 checkout HEAD --가 정확한 선택이었다.
4.7 핵심 교훈
- “충돌” 에러 메시지를 머지 충돌로 속단하지 않는다.
git status로 실제 상태를 확인하는 1단계가 필수다 - pull을 막는 두 가지 원인 구분: (1) 실제 머지 충돌 — 수동 해소 필요, (2) 로컬 변경분이 덮어쓰일 위험 — 로컬 수정분 처리 방식 선택
- 빌드 산출물은 소스가 아니다:
_site/·.quarto/·__pycache__/처럼 재생성 가능한 파일이 pull을 막으면,checkout HEAD -- <path>로 버리고 재생성하는 것이 가장 단순하다 - 근본 해결은
.gitignore정비: 빌드 산출물을 처음부터 추적하지 않으면 동일 문제가 재발하지 않는다. 현재.quarto는 무시되지만_site/일부가 커밋되어 있어 재발 가능성이 남아 있다
5 정리
| 상황 | 해결 명령어 |
|---|---|
| 로컬 변경 버리고 origin 따라가기 | git reset --hard origin/<branch> |
| 로컬 변경 보관 후 pull | git stash && git pull && git stash pop |
| 인덱스 꼬임 | git read-tree HEAD && git reset --hard origin/<branch> |
| untracked 파일 충돌 | git clean -fd <dir> && git pull |
| 특정 경로(빌드 산출물)만 되돌리고 pull | git checkout HEAD -- <path> && git pull --ff-only |
| 삭제된 파일까지 복원 후 pull | git restore --source=HEAD --staged --worktree -- <path> && git pull --ff-only |
핵심 원칙: origin이 기준이면 git reset --hard origin/<branch>로 맞추고, 보존할 작업이 있으면 먼저 git stash로 빼놓는다. 특정 경로(빌드 산출물 등)만 문제라면 git checkout HEAD -- <path>가 더 안전하다.
6 관련 주제
- Git Stash – stash 상세 사용법
- Git 되돌리기 완전 가이드 – reset, revert, restore 비교
- Git 머지 충돌 해결: 개념과 전략 – 브랜치 병합 시 충돌 해소
- Git 머지 충돌 해결: 실전 케이스 – 실제 PR 충돌 해결 사례