Git Commands 가이드: 개념부터 실전 명령어까지
Git이란?
Git은 분산 버전 관리 시스템(DVCS)이다. 코드의 변경 이력을 추적하고, 여러 사람이 동시에 작업할 수 있게 해준다.
핵심 특징:
- 분산형: 각 개발자가 전체 히스토리의 복사본을 로컬에 가지고 있음
- 빠름: 대부분의 작업이 로컬에서 이루어짐
- 브랜치: 가볍고 빠른 브랜치 생성/전환/병합
공식 문서: Git Reference
기본 개념
Working Directory / Staging Area / Repository
Working Directory → Staging Area (Index) → Local Repository → Remote Repository
(작업 공간) (git add) (git commit) (git push)
- Working Directory: 실제 파일을 편집하는 공간
- Staging Area (Index): 다음 커밋에 포함시킬 변경사항을 모아두는 공간
- Local Repository: 커밋된 히스토리가 저장되는
.git디렉토리 - Remote Repository: GitHub, GitLab 등 원격 저장소
파일의 상태
Untracked → Staged → Committed
↑ ↓
Modified ←───┘
- Untracked: Git이 추적하지 않는 새 파일
- Modified: 추적 중인 파일이 변경된 상태
- Staged:
git add로 스테이징 영역에 올린 상태 - Committed: 로컬 저장소에 기록된 상태
HEAD
HEAD는 현재 체크아웃된 커밋을 가리키는 포인터다. 보통 브랜치의 최신 커밋을 가리킨다.
HEAD → main → commit C
commit B
commit A
초기 설정
사용자 정보 설정
git config --global user.name "이름"
git config --global user.email "이메일"
설정 확인
git config --list
git config user.name
공식 문서: git-config
저장소 생성 및 복제
새 저장소 초기화
git init
현재 디렉토리에 .git 폴더를 생성하고 Git 저장소로 만든다.
원격 저장소 복제
git clone <url>
git clone <url> <디렉토리명>
원격 저장소를 로컬에 복사한다. origin이라는 이름으로 자동 등록된다.
상태 확인 명령어
git status
git status
git status -s # 짧은 형식
작업 디렉토리와 스테이징 영역의 상태를 보여준다.
M file.txt # 수정됨 (unstaged)
M file.txt # 수정됨 (staged)
MM file.txt # staged 후 다시 수정됨
?? newfile.txt # untracked
A newfile.txt # 새 파일 (staged)
D deleted.txt # 삭제됨
git log
git log # 전체 로그
git log --oneline # 한 줄 요약
git log --oneline --graph # 브랜치 그래프 포함
git log -5 # 최근 5개 커밋
git log --author="이름" # 특정 작성자의 커밋
git log -- path/to/file # 특정 파일의 변경 이력
git diff
git diff # unstaged 변경사항
git diff --staged # staged 변경사항
git diff HEAD # 모든 변경사항 (staged + unstaged)
git diff branch1..branch2 # 브랜치 간 차이
git diff commit1..commit2 # 커밋 간 차이
공식 문서: git-status / git-log / git-diff
변경사항 기록
git add
git add file.txt # 특정 파일 스테이징
git add . # 현재 디렉토리의 모든 변경사항
git add -A # 저장소 전체의 모든 변경사항
git add -p # 변경사항을 하나씩 확인하며 스테이징
git add *.js # 패턴 매칭
-p (patch) 모드는 하나의 파일 안에서도 원하는 부분만 골라 스테이징할 수 있어 유용하다.
git commit
git commit -m "커밋 메시지"
git commit -am "메시지" # add + commit (tracked 파일만)
git commit --amend # 직전 커밋 수정 (메시지 또는 내용)
좋은 커밋 메시지 작성법
타입: 제목 (50자 이내)
본문 (선택사항, 72자 줄바꿈)
- 왜 변경했는지
- 어떤 영향이 있는지
흔히 사용하는 타입: feat, fix, refactor, docs, test, chore
git rm / git mv
git rm file.txt # 파일 삭제 + 스테이징
git rm --cached file.txt # Git 추적만 제거 (파일은 보존)
git mv old.txt new.txt # 파일 이름 변경
공식 문서: git-add / git-commit
브랜치
브랜치 기본
git branch # 브랜치 목록
git branch -a # 원격 브랜치 포함
git branch <이름> # 새 브랜치 생성
git branch -d <이름> # 브랜치 삭제 (병합 완료된 것만)
git branch -D <이름> # 브랜치 강제 삭제
브랜치 전환
git checkout <브랜치> # 브랜치 전환
git checkout -b <브랜치> # 생성 + 전환
git switch <브랜치> # 브랜치 전환 (Git 2.23+)
git switch -c <브랜치> # 생성 + 전환 (Git 2.23+)
git switch는git checkout의 브랜치 전환 기능만 분리한 명령어다. 더 명확하고 안전하다.
병합 (Merge)
git merge <브랜치> # 현재 브랜치에 대상 브랜치를 병합
git merge --no-ff <브랜치> # fast-forward 없이 병합 커밋 생성
git merge --abort # 충돌 시 병합 취소
Fast-forward vs Merge commit:
- Fast-forward: 분기 없이 포인터만 앞으로 이동 (히스토리가 직선)
- Merge commit: 두 브랜치를 합치는 별도의 커밋 생성 (
--no-ff)
리베이스 (Rebase)
git rebase <브랜치> # 현재 브랜치를 대상 브랜치 위로 재배치
git rebase -i HEAD~3 # 최근 3개 커밋을 인터랙티브 리베이스
git rebase --abort # 리베이스 취소
git rebase --continue # 충돌 해결 후 계속
리베이스는 히스토리를 깔끔하게 만들지만, 이미 push한 커밋은 리베이스하지 않는 것이 원칙이다.
공식 문서: git-branch / git-merge / git-rebase
원격 저장소
remote 관리
git remote -v # 원격 저장소 목록
git remote add origin <url> # 원격 저장소 추가
git remote remove <이름> # 원격 저장소 제거
git remote set-url origin <new-url> # URL 변경
push / pull / fetch
git push origin main # 로컬 → 원격
git push -u origin main # 업스트림 설정 + push
git push --force # 강제 push (주의!)
git pull # fetch + merge
git pull --rebase # fetch + rebase
git fetch # 원격 변경사항만 가져오기 (병합 X)
git fetch --all # 모든 원격의 변경사항 가져오기
pull vs fetch:
fetch: 원격의 변경사항을 가져오기만 함. 로컬 브랜치에 영향 없음pull: fetch + merge (또는 rebase). 로컬 브랜치에 바로 반영
공식 문서: git-remote / git-push / git-pull / git-fetch
되돌리기
변경사항 취소
git checkout -- file.txt # 특정 파일 변경사항 취소 (unstaged)
git restore file.txt # 위와 동일 (Git 2.23+)
git restore --staged file.txt # 스테이징 취소 (변경사항 보존)
커밋 되돌리기
git revert <commit> # 해당 커밋을 취소하는 새 커밋 생성
git reset --soft HEAD~1 # 커밋 취소 (변경사항은 staged 상태 유지)
git reset --mixed HEAD~1 # 커밋 취소 (변경사항은 unstaged 상태)
git reset --hard HEAD~1 # 커밋 취소 + 변경사항 완전 삭제 (위험!)
revert vs reset:
revert: 안전함. 히스토리를 보존하면서 변경을 취소. 공유된 브랜치에서 사용reset: 히스토리를 변경함. 로컬에서만 사용하는 것이 안전
공식 문서: git-restore / git-revert / git-reset
임시 저장 (Stash)
git stash # 현재 변경사항 임시 저장
git stash -m "메시지" # 메시지와 함께 저장
git stash list # stash 목록
git stash pop # 가장 최근 stash 적용 + 삭제
git stash apply # stash 적용 (삭제하지 않음)
git stash drop # 가장 최근 stash 삭제
git stash clear # 모든 stash 삭제
브랜치를 전환해야 하는데 현재 작업을 커밋하기엔 애매할 때 유용하다.
공식 문서: git-stash
태그
git tag # 태그 목록
git tag v1.0.0 # 태그 생성 (lightweight)
git tag -a v1.0.0 -m "설명" # 태그 생성 (annotated)
git push origin v1.0.0 # 특정 태그 push
git push origin --tags # 모든 태그 push
git tag -d v1.0.0 # 로컬 태그 삭제
- Lightweight tag: 단순 포인터 (이름만)
- Annotated tag: 작성자, 날짜, 메시지 포함 (릴리스에 권장)
공식 문서: git-tag
기타 유용한 명령어
cherry-pick
git cherry-pick <commit> # 특정 커밋만 현재 브랜치에 적용
blame
git blame file.txt # 각 줄을 누가, 언제 수정했는지 확인
reflog
git reflog # HEAD 이동 이력 (reset으로 잃어버린 커밋 복구 시 유용)
clean
git clean -n # 삭제 대상 미리 보기 (dry run)
git clean -f # untracked 파일 삭제
git clean -fd # untracked 파일 + 디렉토리 삭제
공식 문서: git-cherry-pick / git-blame / git-reflog / git-clean
.gitignore
프로젝트 루트에 .gitignore 파일을 만들어 추적하지 않을 파일을 지정한다.
# 빌드 결과물
node_modules/
dist/
build/
# 환경 설정
.env
.env.local
# OS 파일
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
# 로그
*.log
공식 문서: gitignore
자주 쓰는 조합
새 기능 개발 흐름
git switch -c feature/new-feature # 브랜치 생성 + 전환
# ... 작업 ...
git add .
git commit -m "feat: 새 기능 추가"
git push -u origin feature/new-feature
# PR 생성 → 코드 리뷰 → 병합
충돌 해결
git merge feature-branch # 충돌 발생
# 충돌 파일 편집 (<<<<<<, ======, >>>>>> 마커 해결)
git add . # 해결 완료 표시
git commit # 병합 커밋 생성
실수로 커밋한 파일 제거
git rm --cached secrets.env # Git 추적만 제거 (파일은 남김)
echo "secrets.env" >> .gitignore # .gitignore에 추가
git commit -m "chore: remove tracked secret file"
이전 커밋 상태 확인
git log --oneline # 커밋 해시 확인
git show <commit> # 해당 커밋의 변경 내용 보기
git checkout <commit> -- file.txt # 특정 파일만 해당 커밋 상태로 복원
브랜치 병합 전략 가이드: 작업 브랜치에서 main으로
실제 개발에서 가장 많이 하는 작업 중 하나가 "작업 브랜치에서 완성된 코드를 main에 합치는 것"이다. 같은 결과를 내더라도 어떤 방식으로 합치느냐에 따라 히스토리의 모양과 추적 가능성이 크게 달라진다.
실전 시나리오: drafts → main
drafts 브랜치에서 작업하다가 완료 후 main에 반영하는 기본 흐름:
# 1. main을 최신 상태로 업데이트
git switch main
git pull origin main
# 2. drafts를 main에 병합 (방법은 아래에서 선택)
git merge drafts
# 3. (선택) 원격에 push
git push origin main
핵심은 2번 단계에서 어떤 병합 방법을 쓰느냐다. 아래에서 각 방법을 비교한다.
방법 1: Fast-Forward Merge (기본 동작)
main에 drafts 분기 이후 새로운 커밋이 없으면, Git은 단순히 포인터만 앞으로 이동한다.
git switch main
git merge drafts
# Before
main: A --- B
\
drafts: C --- D
# After (fast-forward)
main: A --- B --- C --- D
- merge 커밋이 생성되지 않음
- 히스토리가 완전한 직선
- 단, drafts 브랜치가 존재했다는 기록이 남지 않음
적합한 상황: 혼자 작업하는 프로젝트, 커밋 1~2개짜리 단순 수정
방법 2: Merge Commit (`--no-ff`)
Fast-forward가 가능한 상황에서도 강제로 merge 커밋을 생성한다.
git switch main
git merge --no-ff drafts
# After (merge commit M 생성)
main: A --- B --- E --- M
\ /
drafts: C --- D
- "drafts 브랜치가 여기서 합쳐졌다"는 기록이 히스토리에 명확히 남음
git revert -m 1 <merge-commit>한 번으로 기능 전체를 되돌릴 수 있음- 팀 프로젝트에서 "이 기능은 언제 main에 합쳐졌는가?"를 추적하기 좋음
--no-ff vs 기본 merge:
- 기본
git merge: fast-forward 가능하면 FF, 불가능하면 merge 커밋 생성 git merge --no-ff: 항상 merge 커밋 생성
적합한 상황: 팀 프로젝트, 기능 단위 추적이 필요한 경우, Git Flow 등 브랜칭 전략 사용 시
방법 3: Squash Merge
drafts의 모든 커밋을 하나의 새 커밋으로 합쳐서 main에 적용한다.
git switch main
git merge --squash drafts
git commit -m "feat: 새 기능 추가" # 별도로 commit 필요!
# Before
drafts: C --- D --- E (WIP 커밋 여러 개)
# After (squash)
main: A --- B --- S (C+D+E가 하나의 커밋 S로)
--squash는 자동 커밋하지 않음. 반드시git commit을 별도로 실행해야 함- main 히스토리가 매우 깔끔해짐 (1 기능 = 1 커밋)
- "fix typo", "WIP", "oops" 같은 중간 커밋이 main에 남지 않음
- 커밋 메시지를 새로 정리해서 작성할 수 있음
주의: Git이 drafts가 "merge되었다"고 인식하지 않기 때문에, squash merge 후에는 반드시 브랜치를 삭제하는 것이 좋다. 삭제하지 않고 다시 merge하면 충돌이 발생할 수 있다.
적합한 상황: 실험적/임시 커밋이 많은 브랜치, PR 단위로 깔끔한 히스토리를 원할 때
방법 4: Rebase 후 Fast-Forward Merge
drafts를 main의 최신 상태 위로 "재배치"한 뒤 fast-forward merge한다.
# Step 1: drafts에서 main 위로 rebase
git switch drafts
git rebase main
# (충돌 시 해결 후 git rebase --continue)
# Step 2: main에서 fast-forward merge
git switch main
git merge drafts # 이제 반드시 fast-forward됨
# Before
main: A --- B --- E
\
drafts: C --- D
# After rebase
main: A --- B --- E
\
drafts: C' --- D' (새로운 hash)
# After merge (fast-forward)
main: A --- B --- E --- C' --- D'
- 완벽한 직선형(linear) 히스토리
- merge 커밋 없이 깔끔한
git log - 단, rebase는 커밋 hash를 변경함 (C → C', D → D')
- 이미 push한 브랜치를 rebase하면 force push 필요 (
git push --force-with-lease) - 다른 사람이 같은 브랜치에서 작업 중이면 문제 발생
적합한 상황: 개인 브랜치, 직선형 히스토리를 선호하는 경우, push 전 로컬 정리
방법 5: Cherry-Pick (선택적 반영)
브랜치 전체가 아니라 특정 커밋만 골라서 main에 적용한다.
git switch main
git cherry-pick <commit-hash>
# 여러 커밋
git cherry-pick <hash1> <hash2>
# 연속 범위 (hash1 제외, hash2까지 포함)
git cherry-pick <hash1>..<hash2>
# cherry-pick C와 E만 (D는 제외)
main: A --- B --- C' --- E'
\
drafts: C --- D --- E (원본은 그대로)
- 원하는 커밋만 선택적으로 가져올 수 있음
- 새로운 커밋 hash로 복사됨 (원본은 유지)
- 많은 커밋을 cherry-pick하면 관리가 어려워지므로, merge/rebase의 대체 수단으로 쓰면 안 됨
적합한 상황: hotfix를 여러 브랜치에 적용할 때, 특정 커밋만 먼저 main에 반영해야 할 때
전략 비교
| 방법 | merge 커밋 | 개별 커밋 보존 | 직선형 히스토리 | 기능 전체 revert |
|---|---|---|---|---|
| Fast-Forward | X | O | O | 어려움 |
--no-ff |
O | O | X | 쉬움 |
| Squash | X | X (1개로 합침) | O | 쉬움 |
| Rebase + FF | X | O | O | 어려움 |
| Cherry-Pick | X | 선택적 | O | 어려움 |
어떤 전략을 선택할까?
작업 브랜치를 main에 합치려 한다
│
├─ 커밋이 1~2개이고 단순한 수정?
│ └─ Fast-Forward 또는 Squash
│
├─ 팀에서 기능 단위 추적이 필요?
│ └─ git merge --no-ff
│
├─ main 히스토리를 최대한 깔끔하게?
│ ├─ 개별 커밋 이력 불필요 → Squash
│ └─ 개별 커밋 이력 필요 → Rebase 후 FF
│
├─ 특정 커밋만 선택적으로 가져와야?
│ └─ Cherry-Pick
│
└─ 혼자 작업 + 직선형 히스토리 선호?
└─ Rebase 후 FF
GitHub PR 병합 옵션
GitHub에서 Pull Request를 merge할 때 세 가지 버튼이 표시된다. 각각 위에서 설명한 로컬 명령어와 대응된다.
Create a merge commit
git merge --no-ff와 동일한 동작.
main: A --- B --- M
\ /
feature: C --- D
- 모든 커밋이 보존됨 + merge 커밋 추가
- 커밋 메시지에 PR 번호 자동 포함:
Merge pull request #42 from ... - GitHub의 기본 옵션
Squash and merge
git merge --squash + git commit과 동일한 동작.
main: A --- B --- S
- 모든 커밋이 하나로 합쳐짐
- GitHub UI에서 최종 커밋 메시지 편집 가능
- PR description이 커밋 body에 자동 포함
- merge 후 브랜치 삭제를 권장 (Git이 merge 여부를 인식하지 못하므로)
Rebase and merge
git rebase main + git merge (fast-forward)와 유사한 동작.
main: A --- B --- C' --- D'
- 각 커밋이 main 위에 개별적으로 추가됨
- merge 커밋 없는 직선형 히스토리
- 주의: GitHub은 항상 새로운 커밋 SHA를 생성함 (로컬 hash와 달라짐)
참고: GitHub 저장소 Settings > General > "Automatically delete head branches"를 체크하면 PR merge 시 브랜치가 자동 삭제된다.
병합 전 체크리스트
실수를 줄이기 위해 병합 전에 확인할 것들.
1. 반드시 main을 최신화하라
git switch main
git pull origin main
main이 최신 상태가 아니면 원격과 로컬의 히스토리가 어긋나서 push 시 충돌이 발생한다.
2. 작업 브랜치와 main의 차이를 확인하라
git log main..drafts --oneline # drafts에만 있는 커밋
git diff main..drafts --stat # 변경된 파일 요약
3. main이 앞서 나간 경우 처리
작업 중에 다른 사람(또는 다른 브랜치에서의 자신)이 main에 커밋을 추가한 경우:
# 방법 A: merge로 동기화 (안전, 히스토리 복잡해짐)
git switch drafts
git merge main
# 방법 B: rebase로 동기화 (깔끔, 개인 브랜치일 때만)
git switch drafts
git rebase main
4. 병합 후 브랜치 정리
# merge된 브랜치 확인
git branch --merged main
# 로컬 브랜치 삭제
git branch -d drafts
# 원격 브랜치 삭제
git push origin --delete drafts
# 원격에서 삭제된 브랜치의 로컬 tracking 참조 정리
git fetch --prune
삭제 기준:
- 삭제: merge 완료된 feature 브랜치, squash merge된 브랜치
- 유지:
main,develop등 장기 브랜치, 아직 작업 중인 브랜치
알아두면 좋은 최신 기능
git rerere (충돌 해결 재사용)
git config --global rerere.enabled true
merge 충돌을 한 번 해결하면 Git이 해결 방법을 기록해두고, 같은 충돌이 다시 발생하면 자동으로 동일하게 해결한다. rebase 중 반복되는 충돌에 특히 유용하다.
--force-with-lease (안전한 Force Push)
# 위험: 원격의 모든 변경을 덮어씀
git push --force
# 안전: 원격이 예상한 상태일 때만 push
git push --force-with-lease
rebase 후 push할 때 --force 대신 --force-with-lease를 쓰면, 다른 사람이 그 사이에 push한 내용이 있을 경우 push를 거부한다.
merge-ort 전략 (Git 2.34+ 기본값)
기존 merge-recursive 전략을 대체하는 새로운 merge 엔진. 대규모 rename이 있는 merge에서 수백~수천 배의 성능 향상이 보고되었다. Git 2.34 이상이면 자동 적용되므로 별도 설정이 필요 없다.
공식 문서: git-rerere / merge strategies