2. Git에 관한 두번째 이야기 – 개념 심화
Git 기초 ②
add·commit·checkout·amend를 직접 만져보며 이해하기
첫 번째 글에서 Git의 정체와 .git 폴더, 그리고 Git과 GitHub의 차이를 살펴봤다면,
이번 글에서는 실제로 파일을 수정하면서 Git의 핵심 동작을 하나씩 확인해 본다.
명령어를 외우기보다 “지금 어느 상태가 바뀌고 있는가?”를 이해하는 데 초점을 맞췄다.
git add, git commit, git checkout 같은 명령어가
제각각 따로 노는 것처럼 보인다. 그래서 초반에는 “이 명령은 이럴 때 쓴다” 식으로 외우기 쉽다.
그런데 직접 실습을 해보니 대부분의 명령이 결국
Working Directory, Staging Area, Commit이라는 세 상태 사이에서
파일을 옮기거나 기준점을 바꾸는 작업이라는 사실을 알 수 있었다.
이 구조만 잡히면 이후에 나오는 reset, restore, branch, merge도 훨씬 덜 낯설게 느껴진다.
1. 먼저 실습용 index.html 파일을 만들었다
Git의 변화를 눈으로 확인하려면 내용이 너무 복잡한 파일보다 단순한 파일 하나가 오히려 좋다.
그래서 gitstudy라는 폴더에 아주 간단한 index.html 파일을 만들었다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title</title>
</head>
<body>
<h1>안녕하세요</h1>
<h2>무척 더운 날씨네요</h2>
<h3>언제 가을이 오려나요?</h3>
</body>
</html>
여기서 중요한 것은 HTML 문법이 아니다. 이 파일을 A라는 상태로 한번 저장하고, 그 뒤 한 줄을 더 추가해 B 상태로 만들고, 다시 또 수정해 C 상태로 만들면서 Git이 A·B·C를 어떻게 구분하는지 보는 것이 목적이다.
2. Git을 이해하는 핵심: 세 공간
초보자에게 가장 낯선 것은 git add가 왜 필요한지다.
그냥 파일을 수정하고 바로 commit하면 될 것 같은데, 왜 굳이 중간 단계를 하나 더 두는지 이해되지 않는다.
이 의문을 풀려면 Git이 파일 상태를 세 곳으로 나누어 본다는 사실부터 알아야 한다.
Working Directory는 우리가 평소 파일을 수정하는 실제 폴더다.
VS Code나 메모장으로 index.html을 열어서 문장을 하나 추가하면 바로 이 영역이 바뀐다.
Staging Area는 “다음 커밋에 어떤 상태를 넣을 것인지” 미리 준비해 두는 공간이다. 여기에는 파일 자체를 복사해 둔다고 단순하게 생각하기보다, “이번 커밋 후보로 선택한 상태”가 올라간다고 이해하는 편이 좋다.
마지막으로 Local Repository는 커밋이 실제로 쌓이는 곳이다.
우리가 첫 글에서 살펴본 .git 폴더 안에서 이런 이력들이 관리된다.
3. 첫 번째 커밋을 만들면 무엇이 생기는가
처음 만든 index.html을 Git이 다음 커밋에 포함하도록 먼저 staging했다.
git add index.html
그리고 commit을 실행했다.
git commit
커밋 메시지를 입력하고 저장하자 다음과 같은 출력이 나타났다.
[master (root-commit) fee8354] 첫 커밋 인덱스.html로 테스트 커밋 작성
1 file changed, 13 insertions(+)
create mode 100644 index.html
여기서 root-commit은 이 저장소에서 만들어진 최초의 커밋이라는 뜻이다.
fee8354는 이 커밋을 식별하는 해시값의 일부다.
중요한 것은 이 순간부터 Git이 단순히 “현재 index.html 파일”만 갖고 있는 것이 아니라, “첫 번째 커밋 당시 index.html이 어떤 상태였는가”라는 시점을 하나 갖게 됐다는 것이다.
4. 파일을 수정하면 commit이 바뀌는 게 아니라 Working Directory만 바뀐다
첫 번째 커밋 후 index.html에 다음 한 줄을 추가했다.
<h4>모르겠네요.. 곧 오겠죠</h4>
파일을 저장했다고 해서 Git의 마지막 커밋이 즉시 바뀌는 것은 아니다. Git이 보관하고 있던 첫 번째 커밋은 그대로 있고, 내 작업 폴더의 파일만 달라진 상태다.
HEAD = A
Staging = A
Working = B
여기서 A는 마지막 커밋 당시의 상태, B는 내가 방금 수정한 현재 파일 상태라고 생각하면 된다.
5. git status는 “지금 어디가 다른가?”를 알려주는 명령
이 상태에서 다음 명령을 실행했다.
git status
결과는 다음과 같았다.
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: index.html
no changes added to commit
이 메시지는 처음 보면 길어 보이지만 뜻은 단순하다.
Git은 index.html이 바뀌었다는 사실을 알고 있다.
다만 그 바뀐 상태가 아직 Staging Area에 올라가지 않았다는 뜻이다.
modified — 마지막 커밋과 비교했을 때 파일이 달라졌다.
not staged — 하지만 아직 이번 커밋에 포함할 변경으로 선택하지 않았다.
git add — 이 변경을 다음 커밋 후보에 올리는 명령이다.
그래서 git status는 단순히 오류가 있나 확인하는 명령이 아니라,
현재 Working Directory와 Staging Area가 어떤 상태인지 요약해 보여주는 안내판이라고 생각하면 이해하기 쉽다.
6. git diff는 “정확히 어느 줄이 달라졌는가?”를 보여준다
git status가 “index.html이 수정됐다”는 사실을 알려준다면,
git diff는 그보다 한 단계 더 자세히 들어간다.
git diff
@@ -9,5 +9,6 @@
<h1>안녕하세요</h1>
<h2>무척 더운 날씨네요</h2>
<h3>언제 가을이 오려나요?</h3>
+ <h4>모르겠네요.. 곧 오겠죠</h4>
새로 추가된 줄 앞에는 +가 붙는다.
반대로 삭제된 줄이 있다면 -가 붙는다.
diff = 그 파일의 어느 부분이 어떻게 달라졌는지 확인
실제 개발에서는 commit 전에 git diff를 한 번 보는 습관이 매우 유용하다.
의도하지 않은 수정이 섞였는지 확인할 수 있기 때문이다.
7. git add는 “파일 등록”보다 “현재 상태 선택”에 가깝다
초보자 입장에서 git add는 가장 오해하기 쉬운 명령 중 하나다.
이름만 보면 “새 파일을 Git에 추가한다”는 의미처럼 보인다.
물론 새 파일을 처음 추적할 때도 사용하지만, 그것만이 전부는 아니다.
이미 추적 중인 index.html을 수정한 뒤에도 다시 git add를 사용한다.
git add index.html
그 이유는 현재 수정된 B 상태를 Staging Area에 올리기 위해서다.
실행 전
HEAD = A
Staging = A
Working = B
git add index.html
실행 후
HEAD = A
Staging = B
Working = B
즉 git add는 “이 파일을 앞으로 영원히 관리해라”라는 뜻보다
“지금 이 순간의 변경 상태를 다음 commit 후보로 올려라”에 더 가깝다.
git add할 수 있다.
Git은 파일 이름만 보는 것이 아니라 어떤 상태를 staging했는지를 구분한다.
8. commit을 해야 HEAD가 새로운 시점으로 이동한다
Staging Area에 B가 올라간 상태에서 commit을 실행했다.
git commit -m "두번째 커밋.. 스테이징과 애드의 개념잡기"
그러면 B라는 상태가 새 커밋으로 확정된다.
HEAD = B
Staging = B
Working = B
이때 처음으로 HEAD가 A에서 B 쪽으로 이동한 것처럼 보인다.
정확히는 새 커밋이 만들어지고, 현재 브랜치인 master가 그 새 커밋을 가리키게 되며,
HEAD는 master를 가리키고 있기 때문에 현재 기준점도 새 커밋이 된다.
9. git log는 단순 기록표가 아니라 커밋의 연결 구조를 보여준다
커밋을 여러 번 만들고 git log를 실행했다.
git log
commit e342724... (HEAD -> master)
한줄문구로 간편하게 커밋메시지등록 테스트
commit 19d884f...
두번째 커밋.. 스테이징과 애드의 개념잡기
commit fee8354...
첫 커밋 인덱스.html로 테스트 커밋 작성
가장 위에 있는 것이 최신 커밋이다.
그리고 (HEAD -> master)라는 표시는 현재 HEAD가 master 브랜치를 가리키고 있고,
master는 최신 커밋을 가리키고 있다는 뜻이다.
로그가 많아지면 최근 몇 개만 보는 것이 편하다.
git log -5
이때 git log 5처럼 쓰면 안 된다.
Git은 숫자 5를 “5개만 보여줘”로 해석하지 않고 revision 이름이나 경로 이름일 가능성이 있는 값으로 해석하기 때문이다.
한 줄씩 압축해서 보고 싶다면 다음 명령이 편하다.
git log --oneline -5
10. checkout으로 아직 commit하지 않은 수정사항을 되돌려 보았다
이번에는 마지막 커밋 이후 파일을 다시 수정했다. 하지만 이 수정은 마음에 들지 않는다고 가정해 보자. 아직 commit하지 않았으니 “방금 한 수정만 버리고 마지막 상태로 돌아가고 싶다”는 상황이다.
책에서는 다음 명령을 사용했다.
git checkout -- index.html
처음에는 git checkout --index.html처럼 붙여 썼다가 오류가 발생했다.
--와 파일명 사이에는 공백이 필요하다.
--는 구분자다.“여기까지는 옵션이나 revision에 관한 이야기이고, 이 뒤부터는 파일 경로다”라는 뜻으로 이해하면 된다.
정상적으로 실행한 뒤 다시 git status를 해보니
nothing to commit, working tree clean이 나타났고,
실제 파일에서도 방금 추가했던 내용이 사라져 있었다.
11. 여기서 핵심: checkout은 무조건 HEAD로 돌아가는 명령이 아니다
이 부분은 직접 실습하지 않으면 오해하기 쉽다.
파일을 대상으로 한 git checkout -- index.html은
단순히 “HEAD의 파일을 가져온다”라고 외우면 이후 상황에서 헷갈릴 수 있다.
Staging Area에 무엇이 들어 있는지에 따라 결과가 달라 보이기 때문이다.
① git add를 하기 전
HEAD = A
Staging = A
Working = B
Working만 B로 수정된 상태다. 이때 checkout을 실행하면 Working Directory를 Staging의 A와 맞춘다.
결과
HEAD = A
Staging = A
Working = A
그래서 방금 수정한 B가 사라진다.
② git add를 한 후
HEAD = A
Staging = B
Working = B
이번에는 이미 B 상태를 staging해 두었다. 같은 checkout을 실행해도 Working Directory를 Staging의 B와 맞추기 때문에 겉으로는 아무 변화가 없는 것처럼 보인다.
12. add 후 다시 수정하면 Staging Area의 존재가 더 또렷하게 보인다
다음 상황은 초보자가 Staging Area를 이해하기에 아주 좋은 실험이다.
HEAD = A
Staging = B
Working = C
B까지는 git add해 둔 상태인데,
그 뒤 파일을 다시 수정해서 Working Directory만 C가 된 것이다.
이때 checkout을 실행하면 어떻게 될까?
git checkout -- index.html
결과
HEAD = A
Staging = B
Working = B
C에서 추가로 수정한 부분은 사라지지만, 이미 Staging Area에 올라가 있던 B는 그대로 남는다.
13. git commit -a는 “모든 파일”이라는 뜻이 아니다
Git에는 git add와 git commit을 따로 하지 않고
조금 더 빠르게 commit하는 방법도 있다.
git commit -a
이름만 보면 -a가 all이니 “모든 파일을 add한 뒤 commit하라”는 뜻처럼 보인다.
하지만 실제로는 이미 Git이 추적 중인 파일의 변경과 삭제만 자동으로 staging한다.
예를 들어 다음과 같은 상태라고 하자.
index.html ← 이미 추적 중, 수정됨
README.md ← 이미 추적 중, 수정됨
style.css ← 방금 새로 만든 파일, 아직 untracked
git commit -a를 실행하면 앞의 두 파일은 반영할 수 있지만 새 파일인 style.css는 자동으로 포함되지 않는다.
| 파일 상태 | git commit -a 동작 |
|---|---|
| Tracked 파일 수정 | 자동 staging 후 commit 가능 |
| Tracked 파일 삭제 | 자동 반영 가능 |
| 새 Untracked 파일 | 자동 포함되지 않음 |
왜 이렇게 만들었을까? 프로젝트 폴더에는 임시파일, 백업파일, 로그파일, DB 파일처럼 “폴더에는 존재하지만 Git에 넣고 싶지는 않은 파일”이 얼마든지 생길 수 있기 때문이다.
git commit -a의 all은
“폴더 안의 모든 파일”보다 “이미 추적 중인 파일에서 생긴 모든 변경사항”에 가깝다.
14. git add . 과 git commit -a의 차이
이번 실습에서는 다음 명령도 사용했다.
git add .
여기서 .은 현재 디렉터리를 뜻한다.
현재 디렉터리를 기준으로 변경사항을 staging하라는 의미다.
| 명령 | Tracked 수정 | 새 Untracked 파일 |
|---|---|---|
git add . | 포함 가능 | 포함 가능 |
git commit -a | 자동 반영 | 포함 안 됨 |
둘 다 “여러 변경을 한꺼번에 처리한다”는 점에서는 비슷해 보이지만, 새 파일을 어떻게 다루는지에서 차이가 있다.
15. commit -m은 Vim을 열지 않고 바로 메시지를 넣는 방법
처음 git commit만 입력했을 때는 Vim 편집기가 열렸다.
Vim에 익숙하지 않다면 이것만으로도 Git이 어렵게 느껴질 수 있다.
짧은 커밋 메시지는 다음처럼 명령줄에서 바로 넣을 수 있다.
git commit -m "한줄문구로 간편하게 커밋메시지등록 테스트"
-m은 message의 약자라고 생각하면 이해하기 쉽다.
짧고 명확한 커밋 메시지라면 이 방식이 매우 편하다.
16. git commit –amend는 단순한 메시지 수정 명령이 아니다
이번에는 일부러 직전 커밋의 메시지를 바꾸기 위해 git commit --amend를 사용해 보았다.
처음 접하면 흔히 “마지막 커밋 메시지를 고치는 명령”이라고 이해하게 된다.
그런데 실제 동작은 그보다 더 크다. Git은 기존 커밋을 열어서 그 안의 글자를 직접 고치는 식으로 동작하지 않는다.
예를 들어 현재 커밋 역사가 다음과 같다고 하자.
A → B → C
↑
HEAD
C의 메시지만 바꿨다고 하더라도 amend 후에는 개념적으로 다음처럼 된다.
A → B → C'
C와 C’는 파일 내용이 거의 같을 수 있다. 커밋 메시지 한 줄만 달라질 수도 있다. 하지만 Git 입장에서는 서로 다른 커밋 객체다. 그래서 커밋 해시도 달라진다.
17. amend는 파일 내용까지 함께 고칠 수 있다
amend가 단순 메시지 수정 명령이 아니라는 사실은 “방금 commit했는데 파일 하나를 빼먹었다”는 상황에서 더 잘 드러난다.
파일 수정
git add index.html
git commit --amend
이 경우 Git은 새로 staging한 파일 내용까지 포함해서 직전 커밋을 대신할 새 커밋을 만든다.
| 일반 commit | amend |
|---|---|
A → B → C → D | A → B → C' |
| C는 그대로 남고 D가 새로 추가됨 | 현재 브랜치에서 C 대신 C’가 마지막 커밋이 됨 |
| 새로운 역사를 뒤에 추가 | 직전 역사를 다시 작성하는 성격 |
그래서 이미 GitHub에 push한 커밋을 amend하는 것은 조심해야 한다. 원격 저장소에는 C가 있는데 내 로컬은 C’로 바뀌기 때문에 서로의 이력이 달라질 수 있기 때문이다.
18. 이번에도 GitHub는 전혀 필요하지 않았다
지금까지 사용한 명령을 다시 보면 다음과 같다.
git status
git diff
git add
git commit
git log
git checkout
git commit --amend
이 모든 작업은 내 PC 안의 .git 저장소만으로 이루어졌다.
GitHub에 로그인할 필요도 없고 인터넷 연결도 필요하지 않다.
이것은 첫 번째 글에서 살펴본 “Git은 로컬에서 독립적으로 동작하는 분산 버전 관리 시스템이고, GitHub는 별도의 원격 서비스다” 라는 내용을 실제 실습으로 확인한 셈이다.
19. 공개 GitHub 저장소를 clone해 보면서 권한의 차이도 이해했다
공개 GitHub 저장소를 다음처럼 clone해 보았다.
git clone https://github.com/jinyphp/jiny
공개 저장소라면 누구나 clone해서 소스코드와 Git 이력을 자신의 PC로 가져올 수 있다.
clone한 뒤에는 내 로컬 저장소이므로 파일을 수정하고,
git add를 하고, git commit을 하는 것도 자유롭다.
하지만 원본 GitHub 저장소로 git push하려면 그 저장소에 쓰기 권한이 있어야 한다.
20. 이번 실습에서 가장 중요한 건 명령어 암기가 아니었다
처음에는 status, diff, add, commit,
checkout, amend가 서로 다른 기능처럼 보였다.
하지만 세 공간을 기준으로 보면 대부분의 동작이 하나의 흐름으로 연결된다.
git status → 현재 어느 상태가 다른지 확인한다.
git diff → 아직 staging하지 않은 변경 내용을 자세히 본다.
git add → 현재 변경 상태를 Staging Area에 올린다.
git commit → 새로운 스냅샷을 만들고 현재 브랜치가 앞으로 이동한다.
git checkout — 파일 → Working Directory를 Staging 상태로 되돌린다.
git commit –amend → 직전 커밋을 수정한 새 커밋으로 교체한다.
결국 Git은 “파일을 저장하는 프로그램”이라기보다 프로젝트의 여러 시점과 그 사이의 변화를 구조적으로 관리하는 시스템이라고 보는 편이 더 정확하다.
이번 글 핵심 정리
- 파일을 수정하면 우선 Working Directory만 달라진다.
git status는 현재 어느 파일이 어떤 상태인지 요약해 보여준다.git diff는 수정된 줄을 구체적으로 보여준다.git add는 단순 파일 등록이 아니라 현재 변경 상태를 staging하는 명령이다.- commit을 해야 새로운 스냅샷이 생기고 현재 브랜치가 앞으로 이동한다.
- HEAD는 파일 수정이나 add만으로 바뀌지 않는다.
git checkout -- 파일의 결과는 Staging Area의 상태에 따라 다르게 보일 수 있다.git commit -a는 새 untracked 파일을 자동으로 포함하지 않는다.git commit --amend는 기존 커밋을 직접 수정하는 것이 아니라 새 커밋으로 교체한다.- 이 모든 기본 동작은 GitHub 없이 로컬 Git만으로 가능하다.
다음 단계에서는 이 커밋들이 여러 개 쌓인 상태에서 과거 시점으로 이동하거나, 브랜치를 새로 만들면 프로젝트의 시간이 어떻게 갈라지는지 살펴볼 수 있다. 지금까지 익힌 HEAD·Staging·Working Directory 개념이 그때 다시 핵심이 된다.