Git 브랜치 동기화

로컬 브랜치를 원격(origin) 기준으로 강제 동기화하기

로컬과 원격(origin) 브랜치가 어긋났을 때 git reset –hard, git stash, git clean으로 강제 동기화하는 방법을 상황별로 정리한다. git branch -vv로 상태를 파악하고, 안전하게 동기화하는 실전 워크플로를 다룬다.

Engineering
Git
DevOps
저자

Kwangmin Kim

공개

2026년 04월 03일

여러 브랜치를 운영하다 보면 로컬과 원격(origin)이 어긋나는 경우가 자주 생긴다.

git pull

을 실행했더니 아래와 같은 에러가 발생했다.

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 대비 얼마나 밀려있는지 확인한다.

git branch -vv
* 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

git reset --hard origin/<branch-name>

이 명령어는 로컬 브랜치의 HEAD를 origin의 최신 커밋으로 강제 이동시킨다.

  • 로컬에만 있던 커밋 -> 사라짐
  • 로컬 파일 수정사항 -> 사라짐
  • origin과 완전히 동일한 상태가 됨
경고

git reset --hard되돌릴 수 없다. 로컬 변경사항이 영구 삭제되므로, 중요한 작업이 있다면 반드시 먼저 백업(stash, 별도 브랜치)해야 한다.

2.2 실제 수행 과정

2.2.1 작업 중인 변경사항 임시 저장

현재 feat/chatbot에서 작업 중인 파일(parent_retriever.py)이 있으므로 stash로 보관한다.

git stash
Saved working directory and index state WIP on feat/chatbot: 9c9b5d2 feat: add parent retriever

2.2.2 각 브랜치를 origin 기준으로 reset

# main 브랜치 동기화
git checkout main
git reset --hard origin/main
HEAD is now at 00b4565 Merge pull request #4
# standardizer 브랜치 동기화
git checkout feat/standardizer
git reset --hard origin/feat/standardizer
HEAD is now at 60e88d5 docs: update chatbot prompts

2.2.3 원래 브랜치로 돌아와서 작업 복원

git checkout feat/chatbot
git stash pop
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 한 줄로 실행

위 과정을 한 줄로 연결하면:

git stash \
  && git checkout main && git reset --hard origin/main \
  && git checkout feat/standardizer && git reset --hard origin/feat/standardizer \
  && git checkout feat/chatbot && git stash pop

2.3 결과 확인

git branch -vv
* 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가 실패할 수 있다.

# 인덱스 재구성 후 reset
git read-tree HEAD
git reset --hard origin/<branch>

3.2 untracked 파일이 충돌할 때

origin에 새로 추가된 파일과 로컬 untracked 파일이 겹치는 경우:

# untracked 파일 정리 후 pull
git clean -fd <충돌-디렉토리>/
Removing data/code/multiom/insilico-core-multiom/
git pull
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이 덮어쓸 파일과 로컬 수정분이 겹쳐 있어 안전장치가 작동한 것”이지 머지 충돌이 아니다.

머지 충돌 vs 덮어쓰기 위험 구분표
구분 머지 충돌 (실제 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 기준으로 워킹트리를 강제로 되돌렸다.

git checkout HEAD -- .quarto _site

이 명령은 “현재 커밋(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을 실행했다.

git pull --ff-only

결과: 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 관련 주제

Subscribe

Enjoy this blog? Get notified of new posts by email: