포스트

Git 핵심 개념과 명령어 정리: 스냅샷, 세 영역, 그리고 되돌리기

Git이 스냅샷을 저장한다는 것의 의미부터 working directory, staging area, repository의 세 영역 모델, 브랜치와 원격, 그리고 상황별 되돌리기(restore, reset, revert)까지 명령어와 개념을 함께 정리한 스터디 노트입니다.

Git 핵심 개념과 명령어 정리: 스냅샷, 세 영역, 그리고 되돌리기

Git의 핵심 개념과 실무에서 자주 쓰는 명령어를 정리한 스터디 노트입니다.

Git은 무엇을 저장하는가: diff가 아니라 스냅샷

Git을 명령어 암기로 시작하면 금방 벽에 부딪힙니다. 명령어는 수십 개인데 밑에 깔린 모델을 모르면 각각이 무슨 일을 하는지 연결이 안 되기 때문입니다. 모델부터 잡겠습니다.

흔한 오해와 달리 Git은 “바뀐 부분(diff)”을 저장하지 않습니다. 커밋 하나는 그 시점 프로젝트 전체의 스냅샷입니다. git log에서 보는 커밋 목록은 “변경 내역의 목록”이 아니라 “시점별 전체 사진첩”이고, diff는 두 사진을 비교해서 그때그때 계산해 보여주는 것입니다.

이 모델에서 Git의 성격이 다 나옵니다.

  • 어떤 커밋으로든 완전한 상태로 즉시 이동할 수 있습니다 (사진을 꺼내면 끝).
  • 커밋은 부모 커밋을 가리키는 연결 리스트를 이루고, 브랜치는 그 사슬 위의 포스트잇(포인터)일 뿐입니다.
  • 한 번 커밋된 내용은 사실상 사라지지 않습니다. 되돌리기가 무섭지 않은 이유입니다.

그리고 Git은 분산 버전 관리입니다. clone을 받는 순간 전체 이력이 내 컴퓨터에 통째로 복사되므로, 커밋, 브랜치, 이력 조회는 전부 네트워크 없이 로컬에서 일어납니다. 서버(GitHub)와 통신하는 것은 push/pull/fetch 딱 세 계열뿐입니다.

세 영역 모델: Git 이해의 절반

Git의 거의 모든 명령은 세 영역 사이에서 파일을 옮기는 일입니다. 이 그림 하나가 Git 이해의 절반입니다.

1
2
working directory  --add-->  staging area  --commit-->  repository
   (작업 폴더)                (커밋 대기석)                (이력 보관소)
  • working directory: 내가 실제로 편집하는 파일들
  • staging area: “다음 커밋에 넣을 것”을 골라두는 대기석
  • repository: 커밋(스냅샷)이 영구 보관되는 곳 (.git 폴더)

staging area가 왜 있는지가 처음엔 의아합니다. 답은 커밋을 편집 단위가 아니라 의미 단위로 만들기 위해서입니다. 세 파일을 고쳤어도 “버그 수정”에 해당하는 두 파일만 골라 커밋하고, “오타 수정”인 나머지 하나는 따로 커밋할 수 있습니다. 커밋 하나 = 의미 있는 변경 하나라는 원칙이 가능해지는 장치입니다.

기본 사이클은 네 개 명령이 전부입니다.

1
2
3
4
5
git status                  # 지금 세 영역의 상태. 막히면 무조건 이것부터
git add src/train.py        # working에서 staging으로 (파일 지정)
git add -p                  # 파일 안에서도 덩어리(hunk) 단위로 골라 담기
git commit -m "학습 루프에 조기 종료 추가"
git log --oneline --graph   # 이력을 그래프로 확인

git status는 아무것도 바꾸지 않는 안전한 명령이고, 지금 상태뿐 아니라 “다음에 뭘 하면 되는지”까지 안내문으로 알려줍니다. 뭔가 꼬였다 싶으면 반사적으로 git status. 이것이 Git에서 가장 중요한 습관입니다.

변경 내용 확인은 diff인데, 세 영역 모델을 알면 옵션이 자연스럽습니다.

1
2
git diff             # working vs staging, 아직 add 안 한 변경
git diff --staged    # staging vs 마지막 커밋, 커밋될 내용 미리보기

.gitignore: 추적하지 않을 것을 선언한다

데이터 파일, 가상환경, 자격 증명은 저장소에 들어가면 안 됩니다. 프로젝트 루트의 .gitignore에 패턴을 선언합니다.

.venv/
data/
*.parquet
.env
__pycache__/

데이터를 다루는 프로젝트에서 특히 중요합니다. 수백 MB짜리 데이터나 모델 가중치를 실수로 커밋하면 저장소가 영구히 무거워지고(스냅샷 모델이라 이력에서 빼기 어렵습니다), .env의 API 키를 커밋하면 보안 사고입니다. 저장소에는 코드와 설정만, 데이터는 경로와 받는 방법만 남기는 것이 원칙입니다. 이미 추적 중인 파일은 .gitignore에 추가해도 무시되지 않으므로 git rm --cached 파일로 추적을 먼저 끊어야 합니다.

브랜치: 포인터 하나의 가격으로 얻는 평행우주

브랜치는 커밋 사슬을 통째로 복사하는 게 아니라 특정 커밋을 가리키는 이름표 하나입니다. 그래서 생성이 즉각적이고 공짜에 가깝습니다. 새 실험, 새 기능마다 부담 없이 만들라고 있는 것입니다.

1
2
3
4
5
git branch                      # 브랜치 목록
git switch -c feat/early-stop   # 만들면서 이동 (checkout -b의 현대식 표기)
git switch main                 # 다른 브랜치로 이동
git merge feat/early-stop       # 현재 브랜치로 대상 브랜치를 합침
git branch -d feat/early-stop   # 합친 브랜치 정리

merge에서 두 브랜치가 같은 파일의 같은 부분을 다르게 고쳤을 때만 충돌(conflict)이 납니다. 충돌은 에러가 아니라 “둘 다 맞을 수 있으니 사람이 골라라”는 정상적인 질문입니다. 파일을 열면 두 버전이 표시되어 있고,

1
2
3
4
5
<<<<<<< HEAD
lr = 0.001          # 내 브랜치의 내용
=======
lr = 0.0005         # 합치려는 브랜치의 내용
>>>>>>> feat/early-stop

원하는 쪽을 남기고 표식(<<<<<<<, =======, >>>>>>>)을 지운 뒤 add, commit 하면 끝입니다. 처음 만나면 당황하지만 절차는 이게 전부입니다.

원격: push, pull, 그리고 fetch와의 차이

GitHub에 있는 저장소는 origin이라는 이름의 원격(remote)으로 등록됩니다. 원격과의 통신은 세 계열입니다.

1
2
3
git push                 # 내 커밋을 원격으로 올림
git fetch                # 원격의 새 커밋을 받아만 옴 (내 브랜치는 그대로)
git pull                 # fetch + merge, 받아와서 내 브랜치에 합침

pull = fetch + merge라는 등식을 기억해 두면, “pull 했더니 충돌이 났다”가 이해됩니다. 받아오기는 문제없었고 합치기에서 충돌한 것입니다. 원격의 상태를 부담 없이 확인만 하고 싶을 때는 fetchgit log origin/main으로 구경하면 됩니다.

협업의 기본 리듬은 단순합니다. 작업 시작 전에 pull, 의미 단위마다 commit, 하루가 끝나기 전에 push. 그리고 GitHub에서의 협업은 브랜치를 push한 뒤 Pull Request로 리뷰를 받아 main에 합치는 흐름으로 확장됩니다.

되돌리기: 상황별로 도구가 다르다

Git이 어렵다는 인상의 대부분은 되돌리기 명령이 여러 개라서입니다. 하지만 “무엇을, 어디까지 되돌리는가”로 나누면 각자 자리가 명확합니다.

상황명령파괴적인가
파일 수정을 마지막 커밋 상태로git restore 파일예 (수정 내용이 사라짐)
add를 취소 (staging에서 내리기)git restore --staged 파일아니오
방금 커밋의 메시지나 내용 수정git commit --amend이력 수정
커밋 자체를 없던 일로 (로컬)git reset --soft HEAD~1이력 수정, 변경은 보존
커밋을 “취소하는 커밋”을 추가git revert 커밋아니오 (이력에 추가만)

reset의 세 모드는 세 영역 모델로 읽으면 됩니다. --soft는 repository만 되감고(커밋만 취소, 파일은 staged 상태로), --mixed(기본값)는 staging까지, --hard는 working directory까지 되감습니다. 즉 --hard만이 작업 내용을 실제로 삭제합니다.

어떤 것을 쓸지 가르는 기준은 하나입니다.

push해서 공유된 이력은 reset이 아니라 revert로 되돌린다. reset은 이력을 다시 쓰는 것이라, 이미 그 이력을 받아간 동료의 저장소와 어긋나게 됩니다. revert는 “취소했다는 사실”까지 이력에 남기므로 공유 상태에서 안전합니다. 로컬에만 있는 커밋은 reset으로 자유롭게 정리해도 됩니다.

그리고 최후의 안전망으로 git reflog가 있습니다. HEAD가 움직인 모든 기록이 남아 있어서, reset --hard로 날린 커밋조차 대부분 git reset --hard HEAD@{1}로 복구할 수 있습니다. “커밋까지 갔던 것은 웬만해선 살릴 수 있다”는 뜻이고, 그래서 자주 커밋하는 습관이 곧 백업 습관입니다.

자주 쓰는 조합 몇 가지

1
2
3
4
5
6
git stash                    # 작업 중인 변경을 임시 보관하고 working을 깨끗하게
git stash pop                # 보관한 변경을 다시 꺼냄. 급히 브랜치를 옮길 때
git log --oneline -10        # 최근 10개 커밋 요약
git diff main..feat/x        # 두 브랜치 차이
git blame src/train.py       # 각 줄을 누가 어느 커밋에서 수정했는지
git checkout 커밋해시 -- 파일   # 특정 시점의 파일 하나만 가져오기

stash는 “커밋하기엔 어중간한데 브랜치는 옮겨야 할 때”의 표준 해법입니다. 다만 stash가 쌓이기 시작하면 그냥 임시 커밋을 하는 편이 안전합니다. 커밋은 reflog에 남지만 날린 stash는 찾기 어렵습니다.

커밋을 잘 만드는 습관

도구 사용법보다 오래가는 것은 커밋을 만드는 기준입니다.

  • 하나의 커밋 = 하나의 의미. “모델 수정 + 리팩토링 + 오타”를 한 커밋에 넣으면 나중에 그중 하나만 되돌릴 수 없습니다. add -p가 이걸 돕는 도구입니다.
  • 메시지는 “무엇을”이 아니라 “왜”. 무엇이 바뀌었는지는 diff가 보여줍니다. 메시지에는 diff가 말해주지 않는 의도를 남깁니다. “lr 0.001로 변경”보다 “발산 방지를 위해 lr 인하”가 좋은 메시지입니다.
  • 동작하는 상태에서 커밋. 커밋은 “언제든 돌아갈 수 있는 지점”인데, 깨진 상태의 스냅샷은 돌아갈 가치가 없습니다.

정리

개념한 줄 요약
스냅샷 모델커밋은 diff가 아니라 전체 사진. 브랜치는 그 위의 포인터
세 영역working, staging, repository. add와 commit이 그 사이를 옮긴다
git status막히면 무조건 이것부터. 다음 할 일까지 알려준다
pullfetch(받기) + merge(합치기). 충돌은 merge 쪽 사정
되돌리기로컬은 reset, 공유된 이력은 revert, 최후엔 reflog
.gitignore데이터, 가상환경, 비밀키는 처음부터 추적 밖으로

명령어보다 오래가는 것은 모델입니다. 스냅샷과 세 영역, 이 두 그림만 잡혀 있으면 처음 보는 명령도 “어느 영역에서 어느 영역으로 무엇을 옮기는가”로 유추할 수 있고, 꼬였을 때도 git status와 reflog가 돌아갈 길을 알려줍니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.