EROKE ORIGINAL WEBBOOK

현장에서 살아남는 Git

현장에서 살아남는 Git 웹북 표지
전체 목차20개 장
  1. 현장에서 살아남는 Git: 1장 저장이 아니라 역사를 만든다
  2. 현장에서 살아남는 Git: 2장 작업 폴더·스테이징·커밋을 손으로 구분한다
  3. 현장에서 살아남는 Git: 3장 설치와 초기 설정을 팀의 언어로 맞춘다
  4. 현장에서 살아남는 Git: 4장 init·clone·status로 작업 장소를 확정한다
  5. 현장에서 살아남는 Git: 5장 작고 설명 가능한 커밋을 만든다
  6. 현장에서 살아남는 Git: 6장 브랜치는 복사본이 아니라 움직이는 이름이다
  7. 현장에서 살아남는 Git: 7장 merge는 두 역사의 합류 기록이다
  8. 현장에서 살아남는 Git: 8장 fetch·pull·push를 하나로 뭉개지 않는다
  9. 현장에서 살아남는 Git: 9장 원격 추적과 upstream을 이해한다
  10. 현장에서 살아남는 Git: 10장 rebase는 내 커밋의 기반을 다시 쓴다
  11. 현장에서 살아남는 Git: 11장 CLI는 가장 정확한 공통 언어다
  12. 현장에서 살아남는 Git: 12장 SourceTree에서 버튼을 상태로 번역한다
  13. 현장에서 살아남는 Git: 13장 IntelliJ에서 코드 맥락과 Git 상태를 함께 본다
  14. 현장에서 살아남는 Git: 14장 충돌은 기호를 지우는 일이 아니라 의도를 합치는 일이다
  15. 현장에서 살아남는 Git: 15장 사고 직후에는 멈추고 증거부터 남긴다
  16. 현장에서 살아남는 Git: 16장 restore·reset·revert를 상태별로 고른다
  17. 현장에서 살아남는 Git: 17장 reflog로 사라진 커밋을 다시 붙잡는다
  18. 현장에서 살아남는 Git: 18장 현장에서 자주 만나는 12가지 고장
  19. 현장에서 살아남는 Git: 19장 협업 규칙이 복구 비용을 줄인다
  20. 현장에서 살아남는 Git: 20장 최종 실전: 금요일 배포 사고를 복구한다

기초부터 CLI·SourceTree·IntelliJ·사고 복구까지

ReleaseDesk 하나로 Git 기초, CLI, SourceTree, IntelliJ와 충돌·reset·reflog·push 거절 복구를 익힌다.

상태: 출간 후보 교정쇄 · 베타 리딩 대기 · 20개 장

웹북 초판 기준일 2026년 8월 2일 · 공통 실습 저장소 ReleaseDesk

작업 폴더, 스테이징, 로컬 커밋, 원격 저장소의 관계
작업 폴더, 스테이징, 로컬 커밋, 원격 저장소의 관계

Git을 쓸 줄 안다는 말은 커밋 버튼의 위치를 안다는 뜻이 아니다. 지금 변경이 어디에 있고, 다음 명령이 어느 역사를 바꾸며, 실수했을 때 무엇을 보존해야 하는지 설명할 수 있다는 뜻이다. 이 책은 명령어 사전이 아니라 판단 훈련서다.

하나의 ReleaseDesk 프로젝트를 CLI, SourceTree, IntelliJ에서 반복한다. 세 화면은 서로 다른 Git이 아니다. CLI의 status는 SourceTree의 파일 상태와 IntelliJ의 Commit 도구 창으로, log --graph는 두 도구의 Log 화면으로 번역된다. 화면 이름은 버전에 따라 조금 달라도 작업 폴더·스테이징·커밋·원격이라는 상태는 같다.

각 장은 네 단계로 진행한다. 먼저 상태를 예측하고, 명령이나 화면 조작을 실행하고, git statusgit log로 결과를 증명하고, 탈출 명령을 확인한다. 실습은 자신의 업무 저장소가 아니라 이 책이 생성하는 샌드박스에서 한다.

이 책은 Git 프로젝트, Atlassian, JetBrains의 공식 문서를 사실 확인에 사용했지만 문장과 도식, 예제 저장소와 화면 설명은 독자적으로 작성했다. Git, SourceTree, IntelliJ는 각 권리자의 상표다. 제품을 식별하기 위한 설명 외에 제휴나 보증을 뜻하지 않는다.

1장 저장이 아니라 역사를 만든다

문서를 저장하면 현재 모습 하나가 남는다. Git 커밋은 선택한 파일들의 한 시점, 부모 커밋, 작성자, 설명을 묶어 프로젝트의 역사를 만든다. 그래서 Git의 질문은 “저장했나?”가 아니라 “어떤 변경을 한 덩어리로 설명할 것인가?”다.

Git은 중앙 서버에 접속해야만 작동하는 동기화 앱이 아니다. 로컬 저장소만으로도 커밋과 브랜치와 복구가 된다. GitHub, GitLab, Bitbucket 같은 원격 호스팅은 그 역사를 팀과 교환하는 장소다. 이 구분을 모르면 인터넷이 끊겼을 때 커밋을 못 한다고 생각하거나, 커밋했으니 팀원도 보았다고 오해한다.

첫 실습 환경

Git과 Node.js 20 이상이 설치된 터미널에서 책 프로젝트로 이동한다.


cd git-field-guide
git --version
node --version
npm run lab:setup
cd sandbox/releasedesk
git status
git log --oneline --graph --decorate --all

마지막 그래프에는 mainfeature/search가 보인다. WORKSPACE.txt는 아직 추적되지 않은 파일이다. 이 상태가 앞으로 모든 실습의 출발점이다. 다시 시작하고 싶으면 책 루트에서 npm run lab:setup을 실행한다. 이 명령은 오직 git-field-guide/sandbox만 다시 만든다.

처음부터 지킬 세 가지

  1. 명령을 복사하기 전에 현재 경로와 브랜치를 확인한다.
  2. 파괴적인 명령 전에 status, diff, log를 기록한다.
  3. 실제 고객 저장소에서 복구 명령을 연습하지 않는다.

↑ 목차로 돌아가기

2장 작업 폴더·스테이징·커밋을 손으로 구분한다

Git 초보가 가장 많이 놓치는 것은 파일이 아니라 영역이다. 작업 폴더는 편집기의 현재 내용, 스테이징 영역은 다음 커밋에 넣기로 고른 내용, HEAD는 현재 브랜치가 가리키는 커밋이다. 같은 파일도 작업 폴더와 스테이징에 서로 다른 변경을 가질 수 있다.


printf '검토자: 미정\n' >> notes/v1.md
git status --short
git add notes/v1.md
printf '발행일: 금요일\n' >> notes/v1.md
git status --short
git diff
git diff --staged

git status --short의 두 칸은 각각 스테이징과 작업 폴더다. MM notes/v1.md라면 일부 변경은 staged, 그 뒤의 변경은 아직 unstaged다. git diff는 작업 폴더와 스테이징의 차이, git diff --staged는 스테이징과 HEAD의 차이다. “diff가 비었다”는 변경이 없다는 뜻이 아닐 수 있다. 어느 두 영역을 비교했는지 먼저 말해야 한다.

전체가 아니라 줄 일부만 고르려면 CLI에서 git add -p notes/v1.md를 쓴다. SourceTree에서는 파일의 hunk 또는 line 단위 스테이징, IntelliJ에서는 Commit 도구 창의 변경 목록에서 원하는 변경만 포함한다. 세 방법 모두 스테이징 영역을 편집한다.

되돌리기 연습


git restore --staged notes/v1.md
git restore notes/v1.md
git status --short

첫 명령은 스테이징만 취소하고 작업 내용은 남긴다. 두 번째는 작업 폴더의 변경을 HEAD 상태로 덮는다. 순서를 거꾸로 하면 staged 내용이 남을 수 있다. 실행 전후 diff 두 종류를 확인한다.

↑ 목차로 돌아가기

3장 설치와 초기 설정을 팀의 언어로 맞춘다

설치 직후 전역 이름과 이메일을 설정한다. 회사와 개인 계정이 다르면 모든 저장소에 같은 전역 이메일을 쓰지 말고 회사 저장소에서 로컬 설정으로 덮는다.


git config --global user.name "홍길동"
git config --global user.email "me@example.com"
git config --global init.defaultBranch main
git config --global core.autocrlf input
git config --list --show-origin

Windows 팀의 줄바꿈 정책은 조직에 맞춰야 한다. 무조건 위 값을 복사하지 말고 .gitattributes로 저장소 정책을 명시하는 편이 재현 가능하다.


* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.png binary

--show-origin은 같은 설정이 시스템·전역·저장소 중 어디에서 왔는지 보여 준다. 설정이 이상할 때 값을 다시 입력하기보다 출처부터 찾는다. 프록시, credential helper, 서명 설정도 같은 원칙이다.

SourceTree와 IntelliJ의 Git 실행 파일

SourceTree 설정에서 Git 버전과 내장/시스템 Git 선택을 확인한다. IntelliJ의 Settings 또는 Preferences에서 Version Control > Git으로 들어가 실행 파일 경로와 Test를 확인한다. 터미널과 GUI의 결과가 다르면 두 도구가 서로 다른 Git과 설정을 쓰는지 git --version, 실행 경로, 사용자 설정부터 비교한다.

↑ 목차로 돌아가기

4장 init·clone·status로 작업 장소를 확정한다

git init은 현재 폴더에 새 저장소를 만든다. git clone은 원격의 객체와 참조를 받고 작업 폴더를 만든다. 기존 프로젝트가 이미 원격에 있다면 빈 폴더를 init한 뒤 파일을 복사하는 대신 clone한다.


git rev-parse --show-toplevel
git branch --show-current
git remote -v
git status

첫 명령은 저장소 루트를 알려 준다. “분명 맞는 파일을 수정했는데 Git이 못 본다”면 다른 복제본이나 하위 저장소에 있는 경우가 많다. 현재 경로, 저장소 루트, 원격 URL, 브랜치를 한 세트로 확인한다.

SourceTree에서는 Add Working Copy 또는 Clone으로 저장소를 연다. IntelliJ에서는 Get from Version Control로 복제하거나 기존 폴더를 열어 Git 루트가 감지되는지 확인한다. IDE 프로젝트와 Git 저장소 루트가 반드시 같지는 않다. 모노레포나 중첩 저장소에서는 Settings > Version Control의 directory mapping을 확인한다.

↑ 목차로 돌아가기

5장 작고 설명 가능한 커밋을 만든다

좋은 커밋은 나중에 독립적으로 이해하고 되돌릴 수 있다. 기능, 포맷 전체 변경, 의존성 업데이트를 한 커밋에 섞지 않는다. 먼저 변경을 읽고 그 다음 선택한다.


printf '\n검토 완료: 1.0 릴리스 후보\n' >> notes/v1.md
git diff --check
git diff
git add -p notes/v1.md
git diff --staged
git commit -m "docs: 1.0 릴리스 검토 정보 추가"
git show --stat --oneline HEAD

git diff --check는 불필요한 공백 같은 기계적 문제를 먼저 찾는다. git add -p는 hunk마다 포함 여부를 묻는다. 커밋 메시지는 “수정함”보다 변경의 의도와 범위를 적는다. 팀이 Conventional Commits를 쓰지 않는다면 docs: 형식을 억지로 도입할 필요는 없다. 중요한 것은 검색 가능한 동사와 대상이다.

잘못 빠뜨린 파일이 있고 아직 공유하지 않았다면 추가한 뒤 git commit --amend --no-edit로 마지막 커밋을 고칠 수 있다. 이미 push한 커밋을 amend하면 커밋 ID가 바뀐다. 공유 브랜치에서는 새 커밋을 만드는 편이 안전하다.

2부 혼자 만든 역사를 팀과 연결한다

↑ 목차로 돌아가기

6장 브랜치는 복사본이 아니라 움직이는 이름이다

브랜치는 커밋을 가리키는 가벼운 이름이다. git switch -c feature/filter는 새 브랜치를 현재 커밋에 만들고 이동한다. 이후 커밋할 때 그 이름이 앞으로 움직인다.


git switch -c feature/reviewer main
printf '# 검토 절차\n\n1. 사실 확인\n2. 발행 승인\n' > REVIEW.md
git add REVIEW.md
git commit -m "docs: 검토 절차 추가"
git log --oneline --graph --decorate --all

SourceTree에서는 Branch로 이름과 기준 커밋을 선택한다. IntelliJ에서는 오른쪽 아래 branch widget 또는 Git > Branches에서 New Branch를 선택한다. 만들기 전에 기준 브랜치와 최신 커밋을 확인한다. 잘못된 기준에서 만들었다면 당황해서 파일을 복사하지 말고, 필요한 커밋을 cherry-pick하거나 아직 커밋 전이면 안전한 브랜치로 이동할 전략을 세운다.

브랜치 이름은 작업 단위를 드러내게 한다. 오래 유지되는 개인 브랜치보다 작은 기능 브랜치를 자주 통합한다. 삭제는 커밋을 즉시 지우는 행위가 아니라 이름을 제거하는 일이다. 그래도 미병합 커밋의 마지막 이름일 수 있으니 git branch -d의 보호를 먼저 이용한다.

↑ 목차로 돌아가기

7장 merge는 두 역사의 합류 기록이다

브랜치가 갈라지지 않았다면 merge는 이름만 앞으로 옮기는 fast-forward가 될 수 있다. 양쪽에 커밋이 있으면 공통 조상을 기준으로 세 방향 병합하고 보통 merge commit을 만든다.


git switch main
git merge --no-ff feature/reviewer
git log --oneline --graph --decorate --all

--no-ff가 항상 우월하지는 않다. 짧은 브랜치의 경계를 기록하려는 팀 규칙이라면 쓴다. 선형 이력을 선호하면 fast-forward나 rebase를 택한다. 도구가 아니라 팀의 리뷰와 복구 요구가 기준이다.

SourceTree에서 대상 브랜치를 먼저 checkout한 뒤 Merge를 선택한다. IntelliJ에서도 현재 브랜치로 무엇을 합칠지 읽는다. “A를 B에 병합”이라는 문장이 헷갈리면 CLI 문장으로 번역한다. mainfeature/reviewer를 합치려면 현재 브랜치는 main, 명령 인수는 feature/reviewer다.

↑ 목차로 돌아가기

8장 fetch·pull·push를 하나로 뭉개지 않는다

fetch는 원격 상태를 가져와 origin/main 같은 remote-tracking branch를 갱신하지만 내 main을 합치지 않는다. pull은 fetch 뒤에 merge 또는 설정에 따른 rebase를 수행한다. push는 로컬 참조와 객체를 원격으로 보낸다.


git fetch --prune origin
git log --oneline --left-right --graph main...origin/main
git diff main..origin/main

--prune은 원격에서 삭제된 브랜치의 오래된 추적 이름을 정리한다. 가져오기 전에 무슨 일이 합쳐질지 보고 싶다면 pull보다 fetch가 선명하다. 팀의 pull 전략을 설정으로 고정할 수 있다.


git config pull.rebase false   # merge
# 또는 팀 합의가 있다면 true

주석까지 한 줄로 복사하지 않는다. 어떤 정책을 골랐는지 저장소 문서에 적는다. SourceTree의 Fetch, Pull, Push 버튼과 IntelliJ Git 메뉴도 같은 동작이다. 체크박스의 “rebase”, “force”, “prune”을 읽지 않고 기본 버튼만 누르지 않는다.

↑ 목차로 돌아가기

9장 원격 추적과 upstream을 이해한다

origin은 관례적인 원격 별칭일 뿐 서버 자체가 아니다. origin/main은 마지막 fetch 때 관찰한 원격 main이다. 로컬 main과 다른 개체다.


git remote -v
git branch -vv
git push -u origin feature/reviewer

-u는 로컬 브랜치의 upstream을 설정한다. 그 뒤 git pushgit pull이 어느 원격 브랜치를 상대할지 알 수 있다. git branch -vv에서 ahead/behind와 upstream을 함께 본다.

원격 URL을 바꿀 때는 git remote set-url origin <새 URL>을 쓴다. 인증 토큰을 URL에 박아 넣으면 셸 기록과 설정 파일에 남을 수 있다. HTTPS credential helper나 SSH agent 등 호스팅 서비스의 공식 절차를 이용한다.

↑ 목차로 돌아가기

10장 rebase는 내 커밋의 기반을 다시 쓴다

rebase는 내 브랜치의 커밋을 새 기반 위에 다시 적용한다. 내용이 같아 보여도 부모가 달라지므로 새 커밋 ID가 생긴다. 공유되지 않은 기능 브랜치를 최신 main 위에 정리할 때 유용하다.


git switch feature/search
git rebase main
git log --oneline --graph --decorate --all

충돌이 나면 파일을 고치고 git add한 뒤 git rebase --continue한다. 해당 커밋이 불필요하면 이유를 확인하고 --skip, 전체 시도를 취소하려면 --abort다. 종료 명령이 막히면 먼저 git status가 말하는 현재 작업을 읽는다.

이미 여러 사람이 기반으로 삼은 커밋을 rebase해 force push하면 동료의 역사가 갈라진다. 공유 브랜치에서는 하지 않는 것을 기본으로 한다. 꼭 갱신해야 하는 개인 원격 브랜치라면 --force보다 --force-with-lease로 내가 마지막으로 본 원격 상태가 바뀌지 않았는지 확인한다. 그래도 리뷰 시스템과 팀 규칙을 먼저 따른다.

3부 세 도구로 같은 상태를 읽는다

↑ 목차로 돌아가기

11장 CLI는 가장 정확한 공통 언어다

CLI를 외우는 목적은 GUI를 버리기 위해서가 아니다. 장애 상황을 복사 가능한 명령과 텍스트로 재현하기 위해서다. 다음 여섯 줄이면 대부분의 첫 진단이 된다.

ReleaseDesk의 feature/search 변경을 CLI, SourceTree, IntelliJ에서 동일하게 관찰한 개념 화면
ReleaseDesk의 feature/search 변경을 CLI, SourceTree, IntelliJ에서 동일하게 관찰한 개념 화면

pwd
git rev-parse --show-toplevel
git status
git branch -vv
git log --oneline --graph --decorate --all -12
git remote -v

업무 시작에는 status, 수정 뒤에는 두 종류의 diff, 공유 전에는 log와 테스트를 본다. 별칭은 팀 문서에서 원래 명령을 숨기지 않는 범위로 쓴다.


git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "restore --staged --"

파일명이 -로 시작하면 옵션으로 오해할 수 있다. git add -- -draft.md처럼 --로 옵션의 끝을 표시한다. 공백이 있는 경로는 따옴표로 감싼다. 비밀값이 인수나 URL에 들어가면 셸 기록에 남으므로 입력 자체를 피한다.

CLI 하루 흐름


git switch main
git fetch origin
git pull --ff-only
git switch -c docs/release-1-1
# 편집과 테스트
git diff --check
git add -p
git diff --staged
git commit -m "docs: 1.1 릴리스 공지 추가"
git push -u origin docs/release-1-1

--ff-only는 로컬과 원격이 갈라졌을 때 자동 merge를 만들지 않고 멈춘다. 멈춤은 실패가 아니라 판단 기회다.

↑ 목차로 돌아가기

12장 SourceTree에서 버튼을 상태로 번역한다

SourceTree의 장점은 파일 변화와 커밋 그래프를 한 화면에서 보는 것이다. 단점은 버튼 한 번이 여러 옵션을 포함할 수 있다는 점이다. 제품 버전과 운영체제에 따라 명칭과 배치가 달라질 수 있으므로 이 책은 2026년 8월 기준 개념을 설명한다.

하려는 일 SourceTree에서 찾을 것 실행 전 CLI 번역 실행 후 증거
변경 확인 File Status / Working Copy git status, git diff staged/unstaged 분리
일부 선택 hunk·line stage git add -p staged diff
새 브랜치 Branch git switch -c 이름 기준 그래프의 새 라벨
병합 Merge 현재 브랜치에서 git merge 상대 merge graph와 status
가져오기 Fetch git fetch --prune remote-tracking 갱신
되돌리기 Reverse commit 계열 git revert 커밋 새 반대 커밋

실습 저장소를 SourceTree에 추가하고 feature/search를 checkout한다. notes/search.md를 수정하면 File Status에 나타난다. 한 줄만 stage하고 커밋한다. History 또는 Log에서 커밋을 선택해 부모와 diff를 확인한다. 같은 폴더에서 CLI git show HEAD를 실행해 결과가 같은지 대조한다.

SourceTree에서 사고를 줄이는 습관

커밋 화면의 “Push immediately” 같은 연속 동작은 처음에는 끈다. Pull 대화상자의 rebase 옵션, Push 대화상자의 대상 브랜치와 force 옵션을 매번 읽는다. Reset 대화상자의 soft/mixed/hard는 결과가 전혀 다르다. 의미가 확실하지 않으면 취소하고 17장의 선택표로 돌아간다.

↑ 목차로 돌아가기

13장 IntelliJ에서 코드 맥락과 Git 상태를 함께 본다

IntelliJ의 Commit 도구 창은 변경 목록, diff, 검사 결과를 코드 편집과 연결한다. Git 도구 창의 Log는 브랜치 그래프와 커밋을 보여 준다. Project 창의 파일 색상은 편리한 신호지만 최종 증거는 아니다. git status와 Commit 목록을 함께 본다.

동일 실습

  1. sandbox/releasedesk를 프로젝트로 연다.
  2. 오른쪽 아래 branch widget에서 feature/search를 checkout한다.
  3. notes/search.md를 편집하고 Commit 도구 창을 연다.
  4. 변경 diff를 보고 포함할 파일이나 변경을 선택한다.
  5. 분석 경고를 읽은 뒤 Commit만 실행한다. Push는 별도 단계로 둔다.
  6. Git > Show Git Log 또는 Git 도구 창 Log에서 새 커밋과 부모를 확인한다.

IntelliJ의 changelist는 작업 변경을 논리적으로 묶는 IDE 기능이다. Git 브랜치나 커밋이 아니다. changelist로 옮겼다고 다른 브랜치에 저장된 것은 아니다. 마찬가지로 Shelf는 IDE가 관리하는 임시 보관이고 Git stash와 다르다. 다른 도구에서도 복구해야 한다면 Git stash나 임시 브랜치를 고려한다.

2026.2 계열 IntelliJ는 Git worktree 지원 문서를 제공한다. 기능이 보이지 않으면 에디션과 버전, Git 실행 파일을 확인하고 CLI worktree를 사용할 수 있다. 특정 메뉴보다 git worktree list 결과를 정본으로 삼는다.

↑ 목차로 돌아가기

14장 충돌은 기호를 지우는 일이 아니라 의도를 합치는 일이다

충돌은 Git이 자동 선택을 못 했다는 뜻이지 누군가 잘못했다는 판결이 아니다. 먼저 merge 중인지 rebase 중인지 확인한다.


git status
git diff --name-only --diff-filter=U

파일의 <<<<<<<, =======, >>>>>>>는 두 후보 경계다. 경계만 삭제하고 양쪽 내용을 무조건 붙이면 문법은 통과해도 의미가 깨질 수 있다. 요구사항, 테스트, 작성자 의도를 보고 최종 결과 한 개를 만든다.


# 파일 수정 후
git add <해결한-파일>
git diff --check
git status
git merge --continue    # merge 중일 때
# 또는 git rebase --continue

SourceTree의 외부/내장 merge 도구나 IntelliJ의 3-way merge 화면도 공통 조상, 현재 쪽, 들어오는 쪽을 보여 준다. “Accept yours/theirs”는 rebase에서 직관과 다르게 느껴질 수 있다. 버튼 이름만 믿지 말고 결과 pane을 읽고 테스트한다.

재현 실습


npm run lab:setup
cd sandbox/releasedesk
git switch -c conflict/a main
printf '담당: 민지\n' >> notes/v1.md
git add notes/v1.md && git commit -m "docs: 담당자 민지 지정"
git switch main
printf '담당: 준호\n' >> notes/v1.md
git add notes/v1.md && git commit -m "docs: 담당자 준호 지정"
git merge conflict/a

한 사람을 고르는 문제가 아니라 공동 담당 규칙을 정해야 할 수도 있다. 최종 문장을 직접 만들고 커밋하라. 다시 시작하려면 git merge --abort 후 파일 상태를 확인한다.

4부 실수를 데이터 손실 없이 복구한다

↑ 목차로 돌아가기

15장 사고 직후에는 멈추고 증거부터 남긴다

실수 직후 가장 위험한 행동은 검색 결과의 reset --hard를 맥락 없이 복사하는 것이다. 아래 진단을 텍스트 파일이나 이슈에 남긴다. 비밀이 들어간 원격 URL은 가린다.


git status
git log --oneline --graph --decorate --all -20
git reflog -20
git diff
git diff --staged

그 다음 세 질문에 답한다.

  1. 변경은 커밋 전인가, 로컬 커밋인가, 이미 공유됐는가?
  2. 파일 내용을 보존할 것인가, 커밋 구조만 바꿀 것인가?
  3. merge, rebase, cherry-pick 같은 진행 중 작업이 있는가?

모르면 먼저 저장소 폴더를 별도 위치에 복사할 수 있다. 단, 비밀과 대용량 파일 취급 정책을 지킨다. untracked 파일은 reflog가 보호하지 않는다. 중요하지만 아직 추적되지 않은 파일은 별도 백업이 우선이다.

↑ 목차로 돌아가기

16장 restore·reset·revert를 상태별로 고른다

세 명령은 이름이 비슷하지만 대상이 다르다.

상황 보존하려는 것 기본 선택 핵심 효과
staged만 취소 파일 내용 git restore --staged 파일 index를 되돌림
커밋 전 수정 취소 취소 안 함 git restore 파일 작업 폴더를 덮음
로컬 마지막 커밋 취소 파일 내용 git reset --mixed HEAD~1 브랜치와 index 이동
로컬 커밋 취소, staged 유지 staged 내용 git reset --soft HEAD~1 브랜치만 이동
공유된 잘못된 커밋 취소 공유 역사 git revert 커밋 반대 변경의 새 커밋

git reset --hard는 브랜치, index, 작업 폴더를 맞추며 로컬 수정 손실을 일으킬 수 있다. “깨끗하게 만든다”는 이유만으로 쓰지 않는다. git clean -fd는 untracked 파일과 폴더를 삭제하고 Git 역사로 복구하기 어렵다. 필요하다면 먼저 git clean -nd로 삭제 후보만 미리 본다.

merge commit을 revert할 때는 mainline 부모를 정하는 -m이 필요하다. 어느 부모를 기준으로 되돌리는지 그래프에서 확인하지 못했다면 멈추고 리뷰한다.

↑ 목차로 돌아가기

17장 reflog로 사라진 커밋을 다시 붙잡는다

reflog는 로컬에서 HEAD와 브랜치 참조가 어디로 이동했는지 기록한다. reset, rebase, branch delete 뒤 커밋 이름을 잃었을 때 유용하다.


git reflog --date=local
git show <찾은-커밋ID>
git branch rescue/incident-2026-08 <찾은-커밋ID>

첫 단계에서 바로 reset하지 않는다. git show로 찾은 커밋이 맞는지 확인하고 임시 rescue 브랜치를 만들어 이름을 붙인다. 그 다음 원래 브랜치에 merge, cherry-pick, reset 중 무엇이 맞는지 결정한다.

reflog는 로컬 기록이다. 다른 컴퓨터의 reflog나 서버의 영구 백업이 아니다. 정리 정책과 시간이 지나면 객체가 제거될 수 있다. 그래서 중요한 작업은 작은 커밋과 안전한 원격 브랜치로 일찍 공유한다.

↑ 목차로 돌아가기

18장 현장에서 자주 만나는 12가지 고장

1. push가 non-fast-forward로 거절된다

원격에 내가 모르는 커밋이 있다는 뜻이다. git fetch origingit log --left-right --graph HEAD...origin/<branch>로 양쪽을 본다. 팀 정책에 따라 merge 또는 내 미공유 커밋 rebase를 한다. 무작정 force push하지 않는다.

2. detached HEAD에서 커밋했다

창을 닫지 말고 git switch -c rescue/my-work로 현재 커밋에 이름을 붙인다. 이후 올바른 브랜치로 합친다.

3. 잘못된 브랜치에서 작업했지만 아직 커밋 전이다

변경과 대상 브랜치의 충돌 가능성을 확인한다. 안전하면 git switch -c feature/right-place로 현재 위치에서 새 브랜치를 만든다. 기존 대상 브랜치가 있다면 임시 커밋 또는 stash 후 이동한다. 중요한 untracked 파일을 먼저 확인한다.

4. 잘못된 브랜치에 이미 커밋했다

올바른 브랜치에서 git cherry-pick <commit>으로 가져온다. 잘못된 브랜치가 공유됐으면 그곳은 revert, 로컬 전용이면 검토 후 reset으로 정리한다.

5. .gitignore에 넣었는데 계속 보인다

이미 tracked인 파일은 ignore가 추적을 취소하지 않는다. git check-ignore -v 파일로 규칙을 확인하고, 저장소에서만 제거하려면 git rm --cached 파일을 사용한다. 비밀이 이미 커밋됐다면 추적 취소만으로 비밀이 폐기되지 않는다. 키를 즉시 회전하고 이력 정리는 별도 사고 절차로 한다.

6. 인증이 갑자기 실패한다

git remote -v로 SSH/HTTPS 방식을 확인하고 git ls-remote origin으로 읽기 연결을 시험한다. 토큰 만료, 계정 권한, SSO 승인, SSH agent와 키 등록을 해당 호스팅의 최신 공식 문서로 확인한다. 자격 증명을 채팅이나 이슈에 붙이지 않는다.

7. 파일명 대소문자만 바꿨는데 반영되지 않는다

대소문자를 구분하지 않는 파일 시스템에서는 중간 이름을 거친다.


git mv Readme.md temp-name.md
git mv temp-name.md README.md

8. 파일 전체가 변경된 것처럼 보인다

줄바꿈, 포매터, 파일 권한을 의심한다. git diff --ignore-space-at-eol, git diff --summary, .gitattributes, core.filemode을 확인한다. 원인을 고친 뒤 의도한 변경과 기계적 변경을 분리한다.

9. 큰 파일을 실수로 커밋해 push가 막힌다

아직 공유 전이면 커밋에서 파일을 제거하고 적절한 저장소나 Git LFS 정책을 정한다. 이미 공유했으면 단순 삭제 커밋은 과거 객체 크기를 줄이지 않는다. 호스팅 서비스와 팀의 이력 정리 절차, 백업, 강제 갱신 공지를 갖춘 뒤 수행한다.

10. rebase가 계속 멈춘다

각 과거 커밋을 새 기반에 순서대로 적용하므로 충돌이 여러 번 날 수 있다. 매번 status, 해결, add, rebase --continue를 반복한다. 방향을 잃으면 rebase --abort로 시작 전 상태로 돌아간다.

11. stash를 만들었는데 사라진 것 같다

git stash list, git stash show -p stash@{0}로 내용을 확인한다. pop은 적용 뒤 성공하면 항목을 제거하고, apply는 남긴다. 충돌 가능성이 있으면 apply 후 확인하고 수동 drop한다. untracked 포함 여부는 만들 때 옵션에 좌우된다.

12. IDE와 터미널 상태가 다르다

프로젝트 경로, Git root mapping, 선택 브랜치, Git executable, submodule/worktree 여부를 비교한다. IDE 새로고침 전에 CLI rev-parse --show-toplevelstatus로 실제 저장소를 확정한다.

↑ 목차로 돌아가기

19장 협업 규칙이 복구 비용을 줄인다

도구 교육만으로 사고를 막을 수 없다. 팀 저장소에 다음 운영 계약을 둔다.

  • main 직접 push 금지와 보호 규칙
  • 최소 리뷰 수와 필수 CI 검사
  • merge/rebase/squash 정책
  • 커밋과 브랜치 이름 예시
  • 비밀·대용량 파일·생성물 정책
  • force push 허용 범위
  • 사고 시 연락 채널과 복구 책임자

작업은 작게 나누고 자주 fetch한다. 리뷰 요청에는 변경 이유, 테스트, 위험, 되돌리기 방법을 적는다. “LGTM”만 남기지 않고 권한, 데이터 마이그레이션, 호환성처럼 실패 비용이 큰 지점을 본다.

worktree는 하나의 저장소에서 여러 브랜치를 별도 작업 폴더로 동시에 열 때 유용하다.


git worktree add ../releasedesk-hotfix -b hotfix/login main
git worktree list
# 작업 종료와 커밋 확인 후
git worktree remove ../releasedesk-hotfix

같은 브랜치를 두 worktree에 checkout할 수 없다는 보호를 이해한다. 폴더를 파일 탐색기에서 먼저 지우지 말고 Git 명령으로 정리한다.

↑ 목차로 돌아가기

20장 최종 실전: 금요일 배포 사고를 복구한다

상황은 이렇다. 금요일 오후, release/1.1에서 공지와 설정을 수정했다. 동료 변경을 받지 않고 push해 거절되었다. 서둘러 rebase하다 충돌이 났고, 중요한 공지 한 줄이 사라진 것처럼 보인다. 실제 서비스 저장소가 아니라 샌드박스에서 다음 절차를 문서화한다.

1단계 동작을 멈춘다

status, 그래프, reflog, diff를 incident/initial-state.md에 기록했다고 가정한다. 진행 중 작업 종류와 사라진 것이 tracked인지 확인한다.

2단계 안전한 탈출점을 만든다

rebase 중이고 판단이 어렵다면 git rebase --abort로 시작 전 상태로 돌아간다. 현재 커밋이 중요하면 먼저 ID를 기록한다. 정상 상태에서 backup/friday-before-sync 브랜치를 만든다.

3단계 원격을 관찰한다

git fetch origin 후 로컬과 origin/release/1.1을 그래프로 비교한다. 원격 커밋을 읽고 내 커밋이 미공유인지 확인한다.

4단계 통합 전략을 선택한다

공유 release 브랜치라면 팀 규칙에 따라 merge를 우선할 수 있다. 개인 기능 브랜치라면 최신 기반으로 rebase할 수 있다. 충돌 해결 후 파일 문법뿐 아니라 ReleaseDesk의 공지 의미를 검토한다.

5단계 증명하고 공유한다

테스트, diff --check, 최종 그래프, 원격과의 ahead/behind를 확인한다. force가 필요하다면 혼자 결정하지 않는다. 사고 기록에는 원인, 보존한 증거, 선택한 명령, 대안, 재발 방지 규칙을 남긴다.

졸업 기준

독자는 다음 질문에 명령어 없이도 먼저 답할 수 있어야 한다.

  1. 지금 변경은 어느 영역에 있는가?
  2. 이 커밋은 다른 사람이 이미 보았는가?
  3. 되돌릴 때 파일 내용과 공유 역사 중 무엇을 보존하는가?
  4. 실행 뒤 무엇으로 성공을 증명하는가?
  5. 실패하면 시작 상태로 돌아가는 명령은 무엇인가?

부록 A 증상에서 시작하는 90초 진단표

책 루트에서 npm run build를 실행하면 dist/lab/index.htmlGit 사고 진단실이 만들어진다. 이 파일을 브라우저로 열고 증상·공유 여부·보존 대상을 선택하면 첫 관찰 명령과 피해야 할 행동을 비교할 수 있다. 진단실은 명령을 대신 실행하지 않으며, 실제 저장소에서는 결과를 복사하기 전에 git status와 현재 경로를 다시 확인한다.

화면 또는 오류 첫 명령 확인할 것 피할 행동
변경 파일이 안 보임 git rev-parse --show-toplevel 다른 복제본·ignore·tracked 상태 다시 init
pull이 갈라짐 경고 git fetch ahead/behind, 팀 전략 임의 설정 복사
push rejected git fetch 원격 새 커밋 push --force
충돌 표시 git status merge/rebase/cherry-pick 종류 기호만 삭제
커밋이 사라짐 git reflog reset/rebase 전 ID 즉시 gc/clean
비밀을 커밋함 키 회전 노출 범위·공유 여부 ignore만 추가
파일 전체 diff git diff --summary 줄바꿈·권한·포매터 그대로 commit
IDE만 이상함 git status root와 executable 캐시만 반복 삭제

부록 B 명령별 탈출구

  • merge 중단: git merge --abort
  • rebase 중단: git rebase --abort
  • cherry-pick 중단: git cherry-pick --abort
  • revert 시퀀스 중단: git revert --abort
  • 스테이징 취소: git restore --staged <file>
  • 삭제 후보 미리 보기: git clean -nd
  • 잃은 커밋 탐색: git reflog

중단 명령도 현재 작업 종류가 맞을 때만 쓴다. git status가 안내하는 명령을 우선 읽는다.

부록 C 공식 자료와 버전 원칙

공식 문서는 제품 업데이트에 따라 바뀐다. 이 책은 변하기 쉬운 버튼 위치보다 Git 상태와 실행 전후 증거를 중심으로 썼다. SourceTree 화면은 운영체제와 버전에 따라 이름이 달라질 수 있고, IntelliJ 설명은 2026.2 문서 계열을 기준으로 검토했다.

부록 D 저작권·상표·보안

명령 이름과 제품 기능이라는 사실은 공식 자료로 검증하되 설명은 독자적으로 서술했다. 공식 문서 문단, 타 도서의 도표, 블로그의 명령 모음을 복제하지 않았다. 실습 저장소와 fixture는 가상의 프로젝트이며 example.invalid 주소를 사용한다. 제품 로고와 공식 UI 스크린샷을 배포물에 포함하지 않는다.

업무 저장소의 코드나 오류 로그를 외부 서비스에 올릴 때 회사 정책과 비밀정보를 먼저 확인한다. 인증 정보가 노출되면 Git 이력 수정 전에 자격 증명 회전이 우선이다. 이 책은 특정 호스팅 서비스의 보안 절차를 대신하지 않는다.

↑ 목차로 돌아가기