Git Pull 과 Git Reset ..합치느냐 덮어쓰느냐..
서버-로컬 다중 PC로 작업하다가 git fetch와 git pull, 그리고 git reset --hard origin/main의 차이가 헷갈려서 파고든 대화를 정리했다. “오리진/메인이 도대체 뭘 가리키는 건지”부터 “왜 어떤 구조에서는 Git이 사고를 막아주고, 어떤 구조에서는 조용히 데이터가 사라지는지”까지, 개념이 잡히는 순서대로 정리해본다.
1. 로컬 저장소의 3개 포인터부터 이해하기
Git을 다루다 헷갈리는 대부분의 원인은 로컬 저장소 안에 성격이 다른 3가지 상태가 동시에 존재한다는 걸 놓치기 때문이다.
그림 1. origin/main · main · 워킹트리는 서로 갱신 시점이 다른 별개의 상태다.
git fetch는 ①번 “사진”만 최신화할 뿐, ②번 내 작업 포인터나 ③번 실제 파일은 건드리지 않는다. 이 갭을 어떻게 메울지가 pull이냐 reset --hard냐의 갈림길이다.
2. git pull vs git reset –hard — 동작 방식 차이
git pull = fetch + merge
git pull은 fetch로 origin/main을 최신화한 뒤, 그 내용을 로컬 main에 병합한다. 내 로컬 히스토리와 원격 히스토리를 둘 다 유지하며 합치는 방식이라, 같은 부분을 양쪽에서 건드렸으면 충돌이 나고 사람이 수동으로 풀어야 한다.
git reset –hard origin/main = 강제 동기화
반면 이건 병합을 시도하지 않는다. 로컬 main 포인터를 origin/main 지점으로 강제 이동시키고, 워킹트리도 그 지점 내용으로 강제 덮어쓰기한다. 이 과정에서 로컬에만 있던 커밋이나 저장 안 한 변경사항은 흔적 없이 사라진다.
그림 2. pull은 “합친다”, reset –hard는 “덮어쓴다”.
| git pull | git reset –hard origin/main | |
|---|---|---|
| 로컬 커밋/변경사항 | 보존하며 병합 시도 | 전부 버림 |
| 충돌 가능성 | 있음 (수동 해결 필요) | 없음 (애초에 합치지 않음) |
| 결과 히스토리 | 병합 커밋이 남을 수 있음 | origin과 완전히 동일 |
| 안전성 | 로컬 작업 보존됨 | 되돌리기 어려움 — 위험 |
fetch && reset --hard로 완전히 새로 시작하는 편이 안전하다. 단, 워킹트리가 clean한 상태인지 먼저 확인하는 게 전제다.
3. origin/main은 GitHub가 아니라 “로컬에 찍어둔 사진”이다
가장 헷갈리기 쉬운 지점이다. origin/main은 GitHub 서버에 실시간으로 접속해서 확인하는 게 아니라, .git 폴더 안에 저장된 스냅샷이다.
비유하면 이렇다 — GitHub 서버의 진짜 main은 친구 집 냉장고 안의 실제 내용물이고, 내 로컬의 origin/main은 친구가 마지막으로 보내준 냉장고 사진이다. 이 사진은 내가 fetch/pull/push를 실행하는 순간에만 갱신된다.
그림 3. fetch는 사진만 새로 찍어오는 동작이다.
git reset --hard origin/main이 두 단계로 이뤄진다는 점도 짚어둘 만하다.
- 포인터 이동: 로컬 main을 origin/main이 가리키는 커밋 지점으로 옮김 (아직 파일은 안 건드림)
- 워킹트리 동기화: 그 지점 내용에 맞춰 실제 폴더 파일을 강제로 덮어씀 (없던 파일은 삭제, 있던 파일은 교체)
4. 일반적인 다중 PC 구조에서 벌어지는 일
여러 PC가 각자 GitHub에 직접 commit·push하는 구조에서, 한 PC가 fetch/pull을 깜빡하고 작업 후 push를 시도하면 어떻게 될까.
push 시점에 Git이 “내가 알던 origin/main과 실제 origin/main이 다르다(non-fast-forward)”는 걸 자동으로 감지해서 push를 거부한다.
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs
hint: Updates were rejected because the tip of your current branch is behind
경고 후 그대로 진행되는 게 아니라, 애초에 반영 자체가 안 된다. 해결하려면 fetch 후 pull(merge/rebase)로 원격 커밋을 먼저 반영하고, 충돌을 풀고 나서 다시 push하면 된다. (단, --force를 쓰면 이 안전장치를 무시하고 강제로 덮어쓸 수 있다 — 이건 예외적으로 위험한 경우다.)
5. 서버 단일 push 구조에서 벌어지는 일 (진짜 위험 구간)
그런데 로컬이 작업장, 서버가 배포·commit·push 지점인 구조 — 즉 여러 PC(A, B)가 각자 서버에 배포하고, 서버 한 곳에서만 commit+push하며, 다른 PC들은 fetch && reset --hard origin/main으로 동기화만 하는 구조 — 에서는 이야기가 완전히 달라진다.
그림 4. 일반 구조는 push가 “거부”되지만, 이 구조는 조용히 “덮어써진다”.
이게 바로 “새 작업 시작 전 반드시 fetch && reset --hard로 동기화”를 원칙으로 못박아야 하는 이유다. B PC가 서버에 배포하기 전에 먼저 origin/main 최신 상태로 맞춰뒀다면, 애초에 A의 변경사항이 담긴 상태 위에서 작업했을 것이고 이런 사고 자체가 성립하지 않는다.
정리
git fetch는 로컬의 origin/main “사진”만 최신화한다 — 내 작업 포인터나 파일은 그대로다.git pull은 로컬 변경사항을 보존하며 병합한다 — 안전하지만 충돌·병합 커밋이 생길 수 있다.git reset --hard origin/main은 로컬 상태를 전부 버리고 origin 기준으로 강제 동기화한다 — 깨끗하지만 되돌리기 어렵다.- 여러 명이 직접 GitHub에 push하는 일반 구조는 non-fast-forward 거부로 안전이 보장된다.
- 서버가 유일한 push 지점인 구조에서는 이 안전장치가 작동하지 않으므로, 작업 시작 전 동기화를 습관화하는 것 외엔 답이 없다.