깃 커밋의 작동원리
깃, 처음 만나던 날
비개발자가 클로드와 함께 배운 Git의 모든 것
어느 일요일 오후, 사무실에서 작업한 코드와 집에서 작업한 코드가 서로를 덮어쓰는 사고를 겪었다. 며칠치 작업이 흔적도 없이 사라졌고, 왜 그런 일이 벌어졌는지 도무지 알 수가 없었다. 깃(Git)이라는 것을 쓰고 있다고 생각했는데, 정작 깃이 무엇이고 어떻게 동작하는지는 한 번도 제대로 들여다본 적이 없었던 것이다.
그날 밤 클로드에게 차근차근 묻기 시작했다. 깃이란 무엇인가, 커밋이란 무엇인가, 푸시는 정확히 어떤 일을 하는가. 비개발자에게는 마치 외국어 같은 단어들의 진짜 의미를 한 겹씩 벗겨내자, 그동안 막연하게 무서워하던 깃이 사실은 꽤나 합리적이고 친절한 도구였음을 깨닫게 되었다.
이 글은 그 학습의 기록이다. 같은 어려움을 겪고 있는 누군가에게, 깃이 더 이상 두려운 존재가 아니기를 바라며.
깃이란 무엇인가
깃(Git)은 한마디로 “파일의 시간을 되돌릴 수 있게 해주는 도구”다. 좀 더 정확히 말하면, 파일이 시간에 따라 어떻게 변해왔는지를 기록하고, 원하는 시점으로 되돌리거나 여러 사람의 변경사항을 합칠 수 있게 해주는 버전 관리 시스템이다.
워드 문서를 작업하다가 최종.docx, 최종_수정.docx, 진짜최종.docx, 진짜진짜최종.docx처럼 파일을 여러 개 만들어본 경험이 있다면, 그것이 가장 원시적인 버전 관리다. 깃은 이걸 똑똑하게 자동화해주는 도구라고 생각하면 된다.
깃이 해주는 것
1. 파일의 변경 이력을 시점별로 기록한다
2. 어느 시점으로든 되돌릴 수 있다
3. 여러 사람이 동시에 작업한 결과를 안전하게 합쳐준다
4. 누가, 언제, 무엇을, 왜 바꿨는지 추적할 수 있다
비개발자에게도 깃이 유용한 이유는 단순하다. 코드뿐 아니라 글, 문서, 설정 파일 등 “버전이 중요한 모든 텍스트”에 깃을 쓸 수 있기 때문이다. 그리고 어쩌면 가장 중요한 효용은, “되돌릴 수 있다”는 안심감 그 자체일지도 모른다.
스냅샷의 비밀 — 깃이 저장하는 것
깃을 처음 배우는 사람이 가장 헷갈리는 부분이 바로 여기다. “커밋을 하면 무엇이 저장되는가?”
흔히 떠올리는 직관은 이렇다. “수정된 부분(diff)만 저장되어서, 차곡차곡 변경 이력이 쌓이는 것 아닐까?” 그러나 깃의 실제 동작은 정반대다.
깃은 변경사항이 아니라 매 시점의 전체 사진을 저장한다.
커밋을 할 때마다 깃은 그 시점 프로젝트 전체의 모습을 통째로 “찰칵” 찍어서 보관한다. 마치 사진첩에 사진을 한 장씩 추가하는 것과 같다. 다만 그 사진첩이 무한히 들어가도 무겁지 않도록, 깃은 똑똑한 트릭을 쓴다.
위 그림에서 볼 수 있듯, 각 커밋은 그 시점 모든 파일의 모습을 모두 담고 있다. 다만 안 바뀐 파일은 이전 커밋의 데이터를 그대로 참조하므로, 실제로 새로 저장되는 건 달라진 파일뿐이다.
그래서 깃 저장소는 수천 개의 커밋이 쌓여도 생각만큼 무겁지 않다. 그리고 어느 시점이든 “그날의 사진”을 꺼내볼 수 있다. git checkout 명령으로 과거 커밋으로 돌아가면, 깃은 그 시점의 사진을 그대로 보여준다. 시간 여행이 가능한 이유다.
오해와 진실
오해: “커밋은 변경된 부분(diff)만 저장한다.”
진실: “커밋은 전체 스냅샷이다. git diff로 보이는 변경사항은 깃이 두 스냅샷을 비교해서 즉석에서 계산해 보여주는 결과다.”
로컬과 원격 — 두 개의 저장소
깃을 본격적으로 쓰기 시작하면 반드시 마주치는 개념이 로컬(local)과 원격(remote)의 구분이다. 이 두 단어가 헷갈리면 깃의 모든 명령이 흐려진다.
로컬 저장소는 내 컴퓨터 안에 있는 깃 저장소다. 프로젝트 폴더 안의 .git이라는 숨은 폴더가 그 정체다. 내가 커밋을 하면, 그 사진은 일단 이 .git 안에 저장된다. 인터넷 연결 없이도 커밋은 얼마든지 가능하다.
원격 저장소는 인터넷 어딘가에 있는 깃 저장소다. GitHub, GitLab, Bitbucket 같은 서비스가 대표적이다. 여러 사람이 함께 작업하거나, 내 PC가 망가져도 안전하게 백업을 두기 위한 공간이다.
여기서 가장 중요한 통찰은 이것이다. 두 저장소는 서로 자동으로 동기화되지 않는다. 내가 로컬에서 커밋을 백 번 해도, git push를 치기 전까지는 GitHub에 아무것도 올라가지 않는다. 반대로 다른 누군가가 GitHub에 코드를 올렸어도, 내가 git pull을 치기 전까지는 내 로컬은 그 사실을 모른다.
이 단순한 사실이, 뒤에 나올 모든 사고와 해결책의 근원이 된다.
커밋과 푸시 — 두 단계 작업의 의미
이제 우리는 깃의 작업이 왜 두 단계로 나뉘는지 이해할 수 있다.
📸 git commit
“지금 이 순간의 사진을 내 컴퓨터 사진첩에 저장한다”
로컬 저장소에 새로운 스냅샷을 추가하는 명령. 인터넷 연결 불필요. 메시지와 함께 기록되어 나중에 무엇을 했는지 알 수 있다.
☁️ git push
“내 사진첩의 최근 사진들을 인터넷 사진첩에도 올려둔다”
로컬에만 있는 커밋들을 원격으로 전송하는 명령. 인터넷 필요. 여기까지 해야 다른 사람도 내 작업을 볼 수 있다.
왜 두 단계로 나누어 두었을까? 이유는 여럿이지만 가장 큰 것은 작업의 흐름과 공유의 흐름을 분리하기 위해서다.
작업하다 보면 작은 단위로 자주 커밋하고 싶을 때가 있다. 한 줄 고쳤다고 매번 GitHub에 올리는 건 비효율적이다. 그래서 일단 로컬에 차곡차곡 커밋만 쌓아두고, 어느 정도 정리가 되면 한꺼번에 push하는 식으로 작업한다. 또한 인터넷이 안 되는 환경에서도 작업을 멈추지 않을 수 있다.
실제 작업 흐름의 예
1. 파일 수정
2. git add . — 무엇을 커밋할지 선택
3. git commit -m "메시지" — 로컬에 사진 저장
4. (1~3 반복 가능)
5. git push — 쌓인 커밋들을 한꺼번에 원격으로 전송
여기서 git add가 등장하는데, 이 단계는 “커밋할 파일을 골라서 준비 영역(스테이징)에 올리는” 과정이다. 모든 변경사항을 다 커밋하지 않고 일부만 골라 커밋할 수 있게 해주는 장치다. 일단 익숙해지기 전까지는 git add .(모든 변경 추가)로 시작해도 무방하다.
풀(Pull)의 중요성과 협업의 함정
여기서부터가 진짜 중요한 이야기다. 푸시의 짝꿍인 git pull은 단순히 “남이 올린 거 받아오기” 정도로 이해되곤 한다. 하지만 그 본질은 훨씬 무겁다.
풀은 “내 로컬을 다른 사람들과 같은 시점에 맞춘다”는 약속이다.
이 약속이 깨질 때 사고가 난다. 가장 흔한 시나리오는 이렇다.
⚠️ 전형적인 사고 시나리오
월요일 사무실 PC에서 작업한 후 커밋하고 푸시했다. GitHub에는 최신 버전이 올라가 있다.
화요일 집 PC를 켰다. 이 컴퓨터의 로컬 저장소는 며칠 전 상태 그대로다. 월요일 작업을 모른다. 그런데 git pull을 하지 않고 바로 작업을 시작한다.
작업을 마치고 푸시를 시도한다. 깃은 거부한다(“non-fast-forward”). 그래서 어떻게든 푸시하기 위해 강제 푸시(git push --force)를 한다. 그 순간, GitHub에 있던 월요일 작업이 흔적도 없이 사라진다.
이 사고의 본질은 단순하다. “내 로컬이 원격보다 뒤처져 있다는 사실을 무시한 채 덮어썼다”는 것. 깃은 사실 이런 사고를 막기 위해 일반 푸시를 거부한다. 하지만 강제 푸시는 그 안전장치마저 무력화시킨다.
그래서 깃을 처음 쓰는 사람에게 가장 먼저 들려줘야 할 말은 이것이다.
작업을 시작하기 전, 무조건 git pull.
이 한 줄이 90%의 사고를 막아준다. 1초 걸리는 명령이지만, 며칠치 작업을 지키는 보험이다.
사고는 어떻게 일어나는가
앞에서 살펴본 시나리오는 가장 극적인 예지만, 깃 사고가 일어나는 경로는 더 다양하다. 몇 가지 유형을 알아두면 미리 피할 수 있다.
유형 1 — 강제 푸시(force push)
가장 위험하다. git push --force는 원격의 안전장치를 모두 무시하고 내 로컬 상태로 덮어쓴다. 다른 사람이 올린 커밋들이 통째로 사라질 수 있다. 처음 깃을 쓰는 사람은 이 명령을 절대 직접 치지 않는 것이 좋다.
유형 2 — 충돌 해결을 잘못한 경우
풀을 했더니 충돌이 났다. 깃이 “어느 쪽을 살릴지 정해달라”고 묻는다. 자기 버전만 살리고 상대 버전을 모두 버리면, 결과적으로 상대 작업이 사라진 채로 푸시된다. 충돌은 천천히 살펴보고 양쪽을 모두 검토해야 한다.
유형 3 — 여러 곳에서 동시 작업
서버에서 직접 수정, 집 PC에서 수정, 사무실 PC에서 수정 — 이런 분산 작업이 가장 위험하다. 어느 곳도 “진짜 최신”이 아니게 되면서 모든 상태가 어긋난다.
유형 4 — 커밋만 하고 푸시를 잊음
로컬에 커밋만 잔뜩 쌓아두고 깜빡 잊었다가, 다른 PC에서 작업한 내용이 충돌하면 정리가 까다로워진다. 작업 종료 시 푸시를 잊지 말자.
사고를 막는 4가지 습관
1. 작업 시작 = git pull — 무조건 첫 동작
2. 작업 종료 = git push — 잊지 말고 끝내기
3. –force 금지 — 절대 직접 치지 않기
4. 작업 장소 통일 — 가능한 한 한 곳에서 일하기
단일 진실의 원천 — 워크플로우 설계
일반적인 깃 교본은 보통 “내 컴퓨터(로컬)와 GitHub(원격)”이라는 두 지점 모델로 설명을 시작한다. 그림도 두 개의 박스를 화살표로 잇는 정도다. 깔끔하고 직관적이다. 그런데 나의 실제 작업 환경은 그렇게 단순하지 않았다. 자기만의 작은 서비스나 운영 환경을 함께 다루는 사람이라면, 비슷한 풍경을 마주하게 될 것이다.
내 환경에는 세 곳의 “장소”가 있었다.
나의 세 지점
① 내 PC — 집과 사무실, 두 군데에 한 대씩. PC는 두 대지만 본질은 같음. 코드를 만지는 작업 도구.
② 라이브 서버 — AWS Lightsail 위에서 실제로 서비스가 돌아가는 곳. 사용자가 접속하는 진짜 코드는 거기에 있다.
③ GitHub — 원격 저장소. 코드의 백업이자 협업의 허브.
세 곳 모두에 깃 저장소가 있을 수 있고, 각자 다른 시점의 코드를 들고 있을 수 있다. 그런데 교본에서 말하는 “로컬”이 내 경우에는 정확히 무엇인가? 내 PC인가, 아니면 코드가 실제로 돌아가는 서버인가? 이 질문에 답을 내리지 못한 채 작업을 시작했고, 그것이 모든 혼란의 출발이었다.
가장 자주 빠지던 함정
구체적인 시나리오는 이랬다. 내 PC에서 클로드 코드(AI 코딩 도우미)를 켠다. 클로드 코드가 작업을 하려고 보니 PC 로컬에는 필요한 파일이 일부 없다. 그래서 서버에서 해당 파일을 다운받아 와 PC 위에서 수정한다. 수정이 끝나면 서버에 다시 업로드(배포)한다. 그리고 — 커밋과 푸시는 PC에서 한다.
여기에 결정적인 함정이 있다. GitHub로 올라가는 것은 “내 PC가 그 순간 보유한 일부 파일의 상태”이지, “서버에서 실제로 돌고 있는 전체 상태”가 아니다. 서버에는 그동안 다른 경로로 추가된 파일들, 직접 만진 설정들, 누적된 변경들이 있을 수 있는데, PC는 그것을 알지 못한 채 자기가 가진 일부만 깃에 박는다. GitHub는 서버의 완전한 사본이 아니라, 조각난 사본이 된다.
여기에 두 번째 PC까지 더해지면 상황은 더 복잡해진다. 사무실 PC와 집 PC가 각자 다른 시점에 같은 패턴으로 일하면, 각자 다른 “조각난 사본”이 GitHub로 올라간다. 그러는 동안 진짜로 서비스를 돌리고 있는 서버는, 자기 상태가 깃에 어떻게 기록되고 있는지 알 길이 없다. 서버의 진짜 상태와 깃에 기록된 상태가 점점 어긋나기 시작한다.
어느 순간부터 머릿속이 흐릿해진다. “지금 진짜는 어디에 있지?” 누구도 자신 있게 답하지 못하는 상태. 그것이 내가 빠져 있던 함정이었다. 그리고 이 흐릿함이 곧 사고로 이어졌다. 어딘가에서 한 작업이 또 어딘가의 작업을 덮어쓰고, 정신을 차려보면 며칠치 작업이 사라져 있는 식이다.
해결의 실마리는 의외로 단순한 한 문장에서 왔다.
세 지점이 같은 위계로 존재하면 안 된다.
진실을 어느 한 곳으로 정해야 한다.
이 한 마디가 모든 것을 정리했다. 협업이 많고 다수의 작업자가 있다면 GitHub가 진실이 될 수도 있고, 작은 개인 프로젝트라면 내 노트북이 진실이 될 수도 있다. 어느 쪽이든 좋다. 다만 그 한 곳을 명확히 정하고, 나머지는 그 한 곳을 위한 도구로 격하시킬 것. 이것이 핵심이다.
내 경우의 결론
내 경우는 자명했다. 라이브 서버가 곧 사용자가 만나는 진짜 서비스이고, 그 서버에 정확히 어떤 코드가 돌고 있는지가 결국 가장 중요한 정보다. 그렇다면 라이브 서버를 진실의 원천으로 두는 것이 가장 자연스럽다.
해결책은 단 하나의 원칙으로 정리된다.
“라이브 서버가 유일한 진실의 원천이다.”
모든 수정은 서버에서 일어나고, 모든 커밋과 푸시도 서버에서 일어난다. 집 PC와 사무실 PC는 그저 “서버에 접속하기 위한 창구”일 뿐이다. GitHub는 서버의 백업 역할을 한다. 이렇게 정리하면 머릿속에 한 가지만 남는다. “진실은 서버에 있다.”
이 모델의 우아함은 단순함에 있다. “지금 어디서 작업해야 하지?”, “어디가 최신이지?”, “어디에 커밋하지?” 같은 질문이 사라진다. 모두 서버다.
물론 모든 사람에게 운영 서버가 있는 것은 아니다. 하지만 원리는 같다. “진실을 한 곳에 둔다”는 원칙은, 그 한 곳이 무엇이든 — 자기 노트북이든, 회사 PC든, 클라우드든 — 깃 사고를 막는 가장 근본적인 처방이다.
.gitignore — 깃에 넣지 말아야 할 것들
모든 파일을 깃에 넣어야 할 것 같지만, 사실 그렇지 않다. 어떤 파일들은 절대 깃에 넣지 않는 것이 원칙이다.
① 데이터베이스 파일 (.sqlite, .db 등)
DB 파일은 작업할 때마다 매번 변하는 바이너리 덩어리다. 깃은 매번 통째로 새 사진을 찍어 저장하니, 저장소가 빠르게 부풀어 오른다. 게다가 DB에는 종종 민감한 정보(고객 데이터, 비밀번호 등)가 들어 있어 GitHub에 올리면 보안 사고로 직결될 수 있다.
② 비밀 정보 (.env, API 키, 인증서)
API 키, 비밀번호, 토큰 같은 것은 깃에 들어가는 순간 사실상 영원히 남는다. 나중에 지워도 깃 히스토리에는 그대로 남는다. 처음부터 .gitignore에 넣어 추적되지 않게 하는 것이 정답이다.
③ 의존성 폴더 (node_modules, vendor 등)
패키지 매니저가 설치하는 라이브러리 폴더는 보통 수십~수백 MB에 달한다. 이건 package.json 같은 설정 파일만 깃에 두고, 라이브러리 자체는 각 환경에서 새로 설치하는 것이 표준이다.
④ 빌드 산출물 (dist, build, *.log 등)
소스 코드로부터 자동 생성되는 결과물도 보통 깃에서 제외한다. 필요할 때마다 다시 만들면 되니까.
.gitignore 파일 예시
# 데이터베이스 *.sqlite *.sqlite-shm *.sqlite-wal # 비밀 정보 .env *.pem secrets/ # 의존성 node_modules/ vendor/ # 빌드 산출물 dist/ build/ *.log
그런데 한 가지 함정이 있다. .gitignore는 아직 추적되지 않는 파일만 무시한다. 이미 한 번이라도 커밋해서 깃이 추적 중인 파일은 .gitignore에 적어도 계속 추적된다. 이 경우는 별도 명령으로 추적을 해제해야 한다.
한 가지 더 중요한 사실. git rm --cached로 추적을 해제해도, 과거 커밋에는 그 파일이 여전히 남아 있다. 깃의 모든 히스토리에서 완전히 지우려면 git filter-branch나 BFG Repo-Cleaner 같은 도구로 히스토리를 다시 써야 하는데, 이건 협업자가 있다면 매우 신중하게 결정해야 한다.
그러니 정답은 명확하다. 처음부터 .gitignore에 적어두고 커밋하지 말 것.
나만의 운영 원칙 만들기
지금까지의 모든 깨달음을 한 곳에 모아두는 것이 좋다. 프로젝트 루트에 CLAUDE.md(또는 OPERATIONS.md 같은 이름)를 만들어, 그 프로젝트의 깃 운영 원칙을 명문화해두자. 협업자가 있으면 모두가 보고, 혼자 작업하더라도 미래의 자신을 위한 안전벨트가 된다.
운영 원칙 예시
핵심 원칙: 라이브 서버를 진실의 원천으로 둔다.
작업 위치: 모든 수정은 서버에서 직접 한다. 부득이하게 로컬에서 작업한 경우 반드시 서버에 배포 후 서버에서 커밋한다.
Git 규칙: commit과 push는 오직 서버에서만 한다. 로컬에서는 절대 push하지 않는다.
작업 시작 전: 무조건 git pull을 먼저 실행한다.
금지 사항: 강제 푸시(–force) 금지. DB 파일·비밀 정보 커밋 금지.
물리적 안전장치
규칙으로만 막는 것은 한계가 있다. 사람은 실수하기 마련이고, 자동화된 도구도 가끔 잘못된 판단을 한다. 그래서 가능한 한 물리적으로 막아두는 장치를 함께 두는 것이 좋다.
예를 들어, 로컬 PC에서는 절대 푸시하지 않는다는 원칙이 있다면, 다음 명령으로 그 PC의 푸시 자체를 차단할 수 있다.
이렇게 해두면 실수로 푸시를 시도해도 깃이 거부한다. 한 번만 실행해두면 그 폴더에 영구 적용되며, 다른 프로젝트나 다른 PC에는 전혀 영향을 주지 않는다. 되돌리려면 원래 GitHub 주소로 다시 설정하면 된다.
운영 원칙의 효과
이런 원칙들을 정해두고 나면, 깃은 더 이상 두려운 존재가 아니게 된다. “지금 무엇을 해야 하지?”, “어디서 커밋하지?”, “푸시해도 되나?” 같은 망설임이 사라지고, 매번 같은 흐름으로 안전하게 작업할 수 있다. 깃의 진짜 효용은 안심하고 작업할 수 있다는 데 있다.
상태 확인의 기본 명령들
깃은 무언가를 하기 전에 “지금 상태가 어떤지” 확인하는 것이 가장 중요하다. 다음 명령들은 마치 자동차 운전 전에 거울을 확인하는 것과 같다. 매번 확인하는 습관을 들이면 사고 가능성이 현저히 줄어든다.
git status
가장 자주 쓰는 명령. 현재 어떤 파일이 수정됐는지, 어떤 파일이 스테이징됐는지, 내 브랜치가 원격과 비교해 앞서 있는지 뒤처져 있는지를 한눈에 보여준다. 무언가 하기 전에 일단 이것부터 친다.
git fetch
원격 저장소에 새로운 변경사항이 있는지 “확인만” 한다. 가져와서 합치지는 않는다. 풀을 하기 전에 미리 살펴보는 용도로 좋다.
git log –oneline -5
최근 커밋 5개를 한 줄씩 보여준다. “최근 무슨 일이 있었지?”를 빠르게 확인할 때 쓴다. 숫자는 원하는 만큼 조정할 수 있다.
git diff
아직 커밋되지 않은 변경사항을 보여준다. “내가 뭘 바꿨더라?”를 점검할 때 유용하다.
git diff origin/main HEAD
내 로컬 main 브랜치와 원격 main 브랜치의 차이를 보여준다. 푸시하기 전에 “올라갈 게 정확히 뭐지?”를 확인할 때 쓴다.
이 다섯 가지만 익혀두면 거의 모든 상황에서 “지금 깃이 어떤 상태인지”를 파악할 수 있다. 깃을 두려워하지 않게 되는 가장 빠른 길은, 이 명령들로 자주 들여다보는 것이다. 들여다볼수록 친해진다.
동기화 확인 패턴
특히 push 후 “서버와 GitHub가 완전히 일치하는지” 확인하고 싶을 때 쓰는 패턴이다.
마지막 명령의 결과가 비어 있으면, 내 로컬과 GitHub가 비트 단위까지 일치한다는 뜻이다. 이걸 확인하고 나면 “푸시는 정말로 완료됐다”는 안심을 가질 수 있다.
맺으며
그날 밤 클로드와 길고 긴 대화를 마치고 노트북을 덮을 때, 나는 한 가지를 깨달았다. 깃이 어려웠던 게 아니라, 깃의 세계관을 모른 채로 명령어만 외우고 있었던 것이다.
스냅샷, 로컬과 원격, 커밋과 푸시, 풀의 의미, 진실의 원천 — 이 개념들이 머릿속에 자리 잡자 깃은 갑자기 친근해졌다. 외계어 같던 명령들이 의미를 갖기 시작했고, 두려움도 줄어들었다. 처음으로 안심하고 코드를 만질 수 있게 되었다.
비개발자가 깃을 배워야 하는가? 무조건 그렇다고 말하지는 않겠다. 하지만 코드든 글이든, “버전이 중요한 무언가”를 만들고 있다면, 깃은 분명히 도움이 된다. 그리고 한 번 익히면 평생을 쓰는 도구다.
이 글이 누군가에게는 깃과 친해지는 첫 걸음이 되었기를 바란다. 두려워하지 말고, 작은 프로젝트 하나에 깃을 적용해보라. 처음엔 어색하겠지만, 머지않아 깃 없이 작업하던 시절이 어떻게 가능했는지 의아해질 것이다.
기록한다는 것은 안심한다는 것이다.
되돌릴 수 있는 자유, 그것이 깃이 주는 가장 큰 선물이다.
― 어느 일요일, 클로드와의 긴 대화를 마치고