여러 사람이 같은 코드를 함께 고치다 보면 누가 언제 무엇을 왜 바꿨는지 금방 뒤섞인다. 이걸 기록하고 관리해 주는 프로그램을 버전 관리 시스템이라고 부른다. Git은 그중 가장 널리 쓰이는 분산 버전 관리 시스템으로(「분산」은 기록을 서버 한 곳에만 두지 않고, 저장소를 받아 간 사람마다 자기 컴퓨터에 전체 기록을 통째로 가진다는 뜻이다), 소스 코드의 변경 이력을 추적하고 여러 개발자가 협업할 수 있게 해 준다. 나는 이 글에 Git의 필수 명령어를 빠르게 찾아보고 실무에 바로 쓸 수 있게 정리했다.

공식 문서: https://git-scm.com/docs

이 글의 명령은 전부 터미널에 한 줄씩 쳐서 실행한다(Windows라면 Git을 깔 때 같이 들어오는 Git Bash를 쓰면 된다). 표를 읽는 법도 먼저 적어 둔다.

  • <file>, <url>, <commit>처럼 꺾쇠로 감싼 자리는 그대로 치는 글자가 아니라 실제 값으로 바꿔 넣는 자리다. git add <file>이면 git add index.html처럼 친다
  • [<stash>]처럼 대괄호로 한 번 더 감싼 자리는 빼도 된다는 뜻이다. 빼면 그 명령이 정한 기본값을 쓴다
  • 명령 뒤에 붙는 -m, --oneline처럼 -나 --로 시작하는 말은 옵션이다. 같은 명령이 일하는 방식을 바꾼다. 옵션마다 뜻은 그 명령이 나오는 자리에서 적었다

Git의 4가지 영역과 기본 명령어 흐름

Git은 파일을 네 군데에 걸쳐 옮겨가며 관리한다. 지금 편집기로 열어 고치는 작업 디렉토리가 있고, 커밋하기 전에 이번에 포함할 파일만 따로 골라 두는 스테이징 영역이 있고, 커밋한 기록이 쌓이는 내 컴퓨터 안의 저장소가 있고(이걸 로컬 저장소라 부른다), GitHub 같은 곳에 올려 둔 원격 저장소가 있다. git add는 작업 디렉토리의 파일을 스테이징 영역으로 옮기고, git commit은 스테이징 영역에 올라간 내용을 커밋으로 저장소에 기록하고, git push는 그 기록을 원격 저장소로 푸시한다. 아래 그림의 화살표가 이 흐름을 나타낸다.

sequenceDiagram participant WD as 작업 디렉토리
(Working Directory) participant SA as 스테이징 영역
(Staging Area) participant LR as 로컬 저장소
(Local Repository) participant RR as 원격 저장소
(Remote Repository) Note over WD,RR: 파일 변경 → 스테이징 → 커밋 → 푸시 WD->>SA: git add SA->>LR: git commit LR->>RR: git push Note over WD,RR: 원격 저장소에서 가져오기 RR->>LR: git fetch RR->>WD: git pull (fetch + merge) RR->>WD: git clone (최초) Note over WD,RR: 변경사항 되돌리기 LR->>WD: git checkout SA->>WD: git restore --staged LR->>SA: git reset

가운데 묶음은 반대 방향, 원격 저장소에서 내 컴퓨터로 오는 길이다. git fetch는 원격 저장소에 새로 생긴 커밋을 로컬 저장소로 받아오기만 하고 작업 디렉토리의 파일은 건드리지 않는다. git pull은 그 fetch에 이어서 받아온 내용을 지금 브랜치(코드의 갈래, 뒤의 「브랜치 관리」에서 다룬다)에 병합(두 갈래를 하나로 합치기)까지 하는 풀이라, 작업 디렉토리의 파일이 바로 바뀐다. git clone은 원격 저장소를 내 컴퓨터로 처음 통째로 복사해 올 때 한 번 쓴다. 그래서 그림에 「최초」라고 적었다. 맨 아래 되돌리기 세 줄은 뒤의 「변경사항 되돌리기」에서 하나씩 다룬다.

기본 명령어

저장소 초기화 및 기본 작업

명령어 설명 예제
git init 새 Git 저장소 생성 git init
git clone <url> 원격 저장소 복제 git clone https://github.com/user/repo.git
git status 작업 디렉토리 상태 확인 git status
git add <file> 파일을 스테이징 영역에 추가 git add index.html
git add . 모든 변경사항 스테이징 git add .
git commit -m "<message>" 스테이징된 변경사항 커밋 (-m 뒤에 커밋 메시지를 적는다) git commit -m "Add new feature"
git log 커밋 이력 조회 git log
git log --oneline 커밋 이력을 한 줄로 조회 git log --oneline
git diff 작업 디렉토리에서 고쳤지만 아직 스테이징하지 않은 변경사항 확인 git diff
git show <commit> 특정 커밋 내용 확인 git show abc1234

git clone에 넣는 주소는 GitHub 저장소 페이지에서 복사해 오고, 끝의 .git까지 그대로 붙여 쓴다.

abc1234는 커밋마다 붙는 커밋 해시의 앞 몇 자리다. git log --oneline을 치면 커밋마다 맨 앞에 이 값이 찍히니, 거기서 복사해 쓴다. 뒤에 나오는 예제의 abc1234, def5678도 같은 자리다.

사용 예제

# 프로젝트 디렉토리에서 Git 저장소 초기화
cd project
git init

# 모든 파일을 스테이징하고 첫 커밋
git add .
git commit -m "Initial commit"

# 현재 상태 확인
git status

# 마지막 5개의 커밋 확인
git log --oneline -5

한 줄씩 따라가기. cd project는 Git 명령이 아니라 터미널 명령으로, 터미널이 지금 들어가 있는 폴더를 project 폴더(관리하고 싶은 코드가 든 폴더)로 옮긴다. git init은 그 폴더 안에 .git이라는 숨은 폴더를 만들어 빈 저장소를 세운다. 이후의 커밋 기록은 전부 이 폴더에 쌓인다. git add .의 .은 「지금 폴더 전체」라는 뜻이라 모든 파일을 스테이징하고, git commit -m "Initial commit"은 그 내용을 「Initial commit」이라는 메시지를 붙여 첫 커밋으로 남긴다. 마지막 줄의 -5는 가장 최근 커밋 5개까지만 보여 달라는 옵션이다.

커밋에는 누가 만들었는지(작성자 이름과 이메일)가 같이 기록된다. Git은 이 값을 user.name, user.email 설정에서 읽으니, 첫 커밋 전에 한 번 git config --global user.name "내 이름", git config --global user.email "내 이메일"로 정해 둔다(git config와 --global은 글 끝 「별칭」에서 다시 설명한다). 뒤에 나오는 git log --author가 찾는 「작성자」가 이 값이다.

git status를 실행하면 지금 작업 디렉토리와 스테이징 영역이 어떤 상태인지 화면에 글로 보여준다. 예를 들어 index.html을 고친 뒤 실행하면 이런 식으로 나온다.

On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
        modified:   index.html

첫 줄 On branch main은 지금 main이라는 브랜치에 있다는 뜻이다. 브랜치는 바로 다음 절에서 다루는데, main은 git init 때 처음 생기는 기본 브랜치다. Git 설정에 따라 이 이름이 master로 나오기도 한다. Changes not staged for commit 아래에 뜬 파일은 고치긴 했지만 아직 git add로 스테이징하지 않은 파일이라는 뜻이다. git add index.html을 실행한 뒤 다시 git status를 보면 같은 파일이 Changes to be committed(커밋될 변경사항) 아래로 옮겨 간다.

브랜치 관리

새 기능을 만들다가 코드를 망가뜨릴 수도 있다. 이럴 때 원래 코드는 그대로 둔 채 따로 떨어져 나가 고칠 수 있게 만든 갈래가 브랜치다. 기능을 다 만든 뒤에는 그 갈래에서 고친 내용을 원래 갈래로 다시 합치는데, 이 일을 병합(머지)이라고 부른다.

브랜치 동작 시각화

gitGraph commit id: "Initial commit" commit id: "Add feature A" branch feature checkout feature commit id: "Start feature B" commit id: "Implement feature B" checkout main commit id: "Fix bug" checkout feature commit id: "Complete feature B" checkout main merge feature tag: "v1.0" commit id: "Release"

그림은 왼쪽에서 오른쪽으로 시간이 흐른다. 점 하나가 커밋 하나다. 맨 위 줄이 처음부터 있던 main 브랜치이고, branch feature에서 feature라는 갈래가 아래로 갈라져 나온다. checkout feature/checkout main은 「지금부터 그 갈래에 커밋을 쌓는다」는 뜻이라, 두 갈래에 커밋이 따로 쌓인다. 끝의 merge feature에서 feature 갈래가 main으로 합쳐지고, 그 합친 커밋에 v1.0이라는 태그(버전 이름표, 뒤의 「태그 관리」에서 다룬다)를 붙였다.

명령어 설명 예제
git branch 로컬 브랜치 목록 조회 git branch
git branch <name> 새 브랜치 생성 git branch feature
git branch -d <name> 브랜치 삭제 git branch -d feature
git branch -D <name> 브랜치 강제 삭제 git branch -D feature
git switch <branch> 브랜치 전환 (Git 2.23+) git switch main
git switch -c <branch> 브랜치 생성 및 전환 git switch -c feature
git checkout <branch> 브랜치 전환 (구버전) git checkout main
git checkout -b <branch> 브랜치 생성 및 전환 (구버전) git checkout -b feature
git merge <branch> 현재 브랜치로 병합 git merge feature
git branch --no-merged 병합되지 않은 브랜치 조회 git branch --no-merged
  • -d와 -D. -d는 그 브랜치의 커밋이 다 병합돼 있을 때만 지운다. 기준은 그 브랜치에 업스트림(짝이 되는 원격 브랜치, 「원격 저장소」에서 다룬다)이 정해져 있으면 그 업스트림, 없으면 지금 서 있는 브랜치다. 아직 병합 안 된 커밋이 남아 있으면 거절한다. 그 커밋을 버려도 된다고 확신할 때만 -D로 강제로 지운다
  • switch와 checkout. 원래는 checkout 하나가 브랜치 전환과 파일 되돌리기를 다 맡았다. Git 2.23 버전부터 이 둘을 갈라 브랜치 전환은 switch, 파일 되돌리기는 restore로 나눠 놓았다. 하는 일은 같으니 표의 「(Git 2.23+)」가 「2.23 버전부터 있다」는 뜻이다. 내 Git 버전은 git --version으로 본다. 새로 배운다면 switch/restore를 쓰고, checkout은 옛 글이나 스크립트에서 만났을 때 읽을 줄만 알면 된다
  • 「현재 브랜치」 는 마지막으로 switch(또는 checkout)로 옮겨 간 브랜치다. git status 첫 줄 On branch ...에 찍힌다. git merge feature는 그 현재 브랜치로 feature를 끌어와 합친다

사용 예제

# 새 기능 개발을 위한 브랜치 생성 및 전환
git switch -c feature/user-login

# 작업 완료 후 main 브랜치로 전환
git switch main

# feature 브랜치를 main으로 병합
git merge feature/user-login

# 병합 완료 후 feature 브랜치 삭제
git branch -d feature/user-login

feature/user-login은 브랜치 이름 하나다. Git은 브랜치 이름에 /를 넣어 폴더처럼 묶는 것을 허용해서, feature/ 뒤에 무슨 기능인지 붙이는 식으로 이름을 짓는다. 두 번째 줄에서 main으로 먼저 옮겨 가는 이유는, git merge가 「현재 브랜치로」 합치기 때문이다. main에 있어야 feature/user-login이 main으로 들어온다.

원격 저장소

내 컴퓨터의 로컬 저장소와 GitHub 같은 곳의 원격 저장소를 잇는 명령들이다. 원격 저장소는 주소가 길어서 짧은 이름을 붙여 부른다. 표의 <remote>, <name> 자리가 그 이름이고, 흔히 보는 origin은 origin이라는 관례 이름이다. git clone으로 받아 오면 Git이 원래 주소에 자동으로 origin을 붙여 준다. 직접 git remote add로 붙일 때는 다른 이름을 지어도 된다.

명령어 설명 예제
git remote 원격 저장소 목록 조회 git remote
git remote -v 원격 저장소 URL 포함 조회 git remote -v
git remote add <name> <url> 원격 저장소 추가 git remote add origin https://github.com/user/repo.git
git fetch <remote> 원격 저장소에서 변경사항 가져오기 git fetch origin
git pull <remote> <branch> 원격 저장소에서 가져오고 병합 git pull origin main
git push <remote> <branch> 로컬 브랜치를 원격으로 푸시 git push origin main
git push -u <remote> <branch> 푸시하고 업스트림 설정 git push -u origin main
git push --force 강제 푸시 (주의) git push --force
git push --tags 태그 푸시 (태그는 뒤의 「태그 관리」에서 다룬다) git push --tags

-u가 설정하는 업스트림은 「내 로컬 브랜치가 원격의 어느 브랜치와 짝인가」를 적어 두는 기록이다. git push -u origin main을 한 번 해 두면 로컬 main의 짝이 origin의 main으로 기억된다. 그래서 그다음부터는 git push, git pull만 쳐도 Git이 어디로 보내고 어디서 받을지 안다.

git push --force는 force push다 — 원격 저장소에 이미 올라가 있는 커밋을 지우고 내 기록으로 덮어써 버린다. 다른 사람이 그 커밋을 이미 받아 갔다면 그 사람의 기록과 영영 어긋나므로, 여러 명이 같이 쓰는 브랜치에서는 되도록 쓰지 않는다. 자세한 주의사항은 글 끝 「팁 & 주의사항」에 다시 정리했다.

사용 예제

# 원격 저장소 추가 (GitHub 사이트에서 빈 저장소를 먼저 만들고 그 주소를 쓴다)
git remote add origin https://github.com/user/repo.git

# 로컬 커밋을 원격으로 푸시 (처음)
git push -u origin main

# 이후 푸시는 간단하게
git push

# 원격 변경사항 가져오기
git fetch origin

# 원격 변경사항 가져오고 병합
git pull origin main

변경사항 되돌리기

Reset 옵션 비교

HEAD는 「지금 내가 서 있는 커밋」을 가리키는 이름표다. 여기서 체크아웃은 어떤 브랜치나 커밋으로 옮겨 가 그 시점의 파일을 작업 디렉토리에 꺼내 놓는 일이고, switch로 브랜치를 옮기면 HEAD도 그 브랜치의 마지막 커밋을 가리키게 된다. HEAD~1은 HEAD가 가리키는 커밋에서 한 단계 전 커밋을 뜻한다. git reset ... HEAD~1은 지금 위치를 그 한 단계 전 커밋으로 되돌려서 마지막 커밋을 기록에서 빼는 명령이다. 그 커밋에 담겨 있던 변경 내용을 어디까지 남길지는 옵션(--soft/--mixed/--hard)이 정한다. 옵션을 안 붙이면 --mixed로 돈다.

flowchart TD Start["마지막 커밋 하나에
변경 내용이 담겨 있다"] Start -->|git reset --soft HEAD~1| Soft["커밋만 취소
변경 내용은 스테이징 영역에 남음
(바로 다시 커밋할 수 있다)"] Start -->|git reset --mixed HEAD~1| Mixed["커밋과 스테이징을 취소
변경 내용은 작업 디렉토리에만 남음
(다시 git add 부터 한다)"] Start -->|git reset --hard HEAD~1| Hard["커밋 · 스테이징 · 작업 디렉토리 전부 취소
변경 내용이 사라짐"] style Start fill:#2d3748,stroke:#a0aec0,stroke-width:2px,color:#e2e8f0 style Soft fill:#2d3748,stroke:#48bb78,stroke-width:2px,color:#e2e8f0 style Mixed fill:#2d3748,stroke:#ecc94b,stroke-width:2px,color:#e2e8f0 style Hard fill:#2d3748,stroke:#f56565,stroke-width:2px,color:#e2e8f0
명령어 설명 예제
git reset <file> 스테이징 취소 git reset index.html
git reset --soft HEAD~1 마지막 커밋 취소 (변경사항 유지) git reset --soft HEAD~1
git reset --mixed HEAD~1 마지막 커밋 취소 (스테이징 해제) git reset --mixed HEAD~1
git reset --hard HEAD~1 마지막 커밋 취소 (변경사항 삭제) git reset --hard HEAD~1
git revert <commit> 특정 커밋을 되돌리는 새 커밋 생성 git revert abc1234
git checkout -- <file> 파일 변경사항 취소 git checkout -- index.html
git restore <file> 파일 변경사항 취소 (Git 2.23+) git restore index.html
git restore --staged <file> 스테이징 취소 (Git 2.23+) git restore --staged index.html
  • 같은 reset인데 하는 일이 다른 이유. 뒤에 파일 이름을 주면(git reset index.html) 그 파일의 스테이징만 되돌리고 커밋 기록은 건드리지 않는다. 커밋(HEAD~1)을 주면 HEAD 자체를 그 커밋으로 옮긴다. 무엇을 넘기느냐로 일이 갈린다
  • reset과 revert. reset은 커밋을 기록에서 빼 버린다. 이미 원격에 푸시해서 다른 사람이 받아 간 커밋을 이렇게 빼면 그 사람의 기록과 어긋난다. revert는 기록을 건드리지 않고, 그 커밋이 한 일을 거꾸로 하는 새 커밋을 하나 더 쌓는다. 그래서 이미 푸시한 커밋은 revert로, 아직 내 컴퓨터에만 있는 커밋은 reset으로 되돌린다
  • git restore <file>이 돌려놓는 상태는 스테이징 영역에 올라가 있는 내용이다. 그 파일을 스테이징한 적이 없으면 마지막 커밋 상태와 같다. git checkout -- <file>도 같은 일을 한다. 아직 스테이징하지 않은 고친 내용은 사라진다
  • git checkout -- <file>의 -- 는 「여기부터는 옵션이나 브랜치 이름이 아니라 파일 이름이다」라는 구분선이다. 파일 이름과 브랜치 이름이 같을 때 헷갈리지 않게 붙인다

사용 예제

# 실수로 스테이징한 파일 취소
git restore --staged config.json

# 파일의 변경사항 완전히 취소
git restore config.json

# 마지막 커밋 취소 (변경사항은 유지)
git reset --soft HEAD~1

# 특정 커밋을 되돌리는 새로운 커밋 생성
git revert abc1234

Stash (임시 저장)

작업을 반쯤 해놓은 채로 급히 다른 브랜치로 가야 할 때가 있다. 커밋하기엔 아직 다 끝나지 않았고, 그렇다고 변경사항을 버리기도 아깝다. 이럴 때 지금까지 고친 내용을 잠깐 다른 곳에 치워 두는 것이 stash다. 치워 둔 내용은 쌓이는 순서대로 번호가 붙는다 — 가장 최근에 치운 것이 stash@{0}이고, 그 전 것은 stash@{1}처럼 번호가 하나씩 밀린다.

Stash 동작 시각화

stateDiagram-v2 WorkingDirectory: 작업 디렉토리 (고친 내용이 있음) CleanState: 작업 디렉토리 깨끗 (마지막 커밋 상태) StashStack: stash 목록 WorkingDirectory --> CleanState: git stash CleanState --> WorkingDirectory: git stash pop WorkingDirectory --> StashStack: 고친 내용이 옮겨 가 쌓임 note right of StashStack stash@{0}: 가장 최근 stash@{1}: 이전 stash@{2}: 더 이전 end note

그림은 이렇게 읽는다. git stash를 치면 작업 디렉토리는 마지막 커밋 상태로 돌아가 깨끗해지고(그림의 「작업 디렉토리 깨끗」), 고친 내용은 「stash 목록」에 쌓인다. 그 상태로 다른 브랜치에 다녀온 뒤 git stash pop을 치면 치워 둔 내용이 작업 디렉토리로 돌아온다. 목록에는 여러 번 쌓을 수 있다.

명령어 설명 예제
git stash 현재 작업 임시 저장 git stash
git stash save "<message>" 메시지와 함께 임시 저장 git stash save "WIP: feature"
git stash list 저장된 stash 목록 조회 git stash list
git stash show [<stash>] stash 내용 확인 git stash show stash@{0}
git stash apply [<stash>] stash 적용 (목록에 유지) git stash apply stash@{0}
git stash pop [<stash>] stash 적용 후 삭제 git stash pop
git stash drop [<stash>] 특정 stash 삭제 git stash drop stash@{0}
git stash clear 모든 stash 삭제 git stash clear
git stash push -u 추적되지 않은 파일 포함 git stash push -u
git stash push -p 대화형으로 선택 저장 git stash push -p
  • save와 push. 둘 다 메시지를 붙여 치워 둔다(표의 WIP는 Work In Progress, 「작업 중」을 줄인 말로 메시지에 흔히 붙인다). save가 먼저 있던 명령이고, 지금 공식 문서는 save를 더 쓰지 말고 push를 쓰라고 적어 두었다(deprecated). 메시지는 git stash push -m "<message>"로 붙인다. 아래 예제의 save는 옛 글에서 만났을 때 알아보라고 남겼다
  • 추적되지 않은 파일은 Git에 한 번도 git add해 본 적 없는 새 파일이다. Git이 아직 그 파일의 변화를 기록하지 않는다. git stash는 기본으로 이런 파일을 치우지 않아서, 같이 치우려면 -u를 붙인다
  • 대화형(-p) 은 Git이 고친 부분을 덩어리(hunk)마다 하나씩 보여 주며 「이것도 치울까?」를 묻고, 내가 y(예) · n(아니오)로 답해 고르는 방식이다. 파일 하나 안에서도 일부만 고를 수 있다

사용 예제

# 급하게 다른 브랜치로 이동해야 할 때
git stash save "현재 작업 중인 기능"

# 다른 작업 완료 후 돌아와서 적용
git stash list  # 저장된 stash 확인
git stash pop   # 가장 최근 stash 적용 및 삭제

# 추적되지 않은 파일도 포함하여 stash
git stash push -u

태그 관리

태그에는 두 종류가 있다. 경량 태그는 커밋에 이름표만 붙이는 것이고, 주석 태그는 이름표에 더해 누가 언제 왜 만들었는지 설명까지 같이 남긴다. 릴리스처럼 기록이 중요한 경우에는 주석 태그를 쓴다.

명령어 설명 예제
git tag 태그 목록 조회 git tag
git tag <name> 경량 태그 생성 git tag v1.0.0
git tag -a <name> -m "<message>" 주석 태그 생성 git tag -a v1.0.0 -m "Release 1.0.0"
git tag -d <name> 로컬 태그 삭제 git tag -d v1.0.0
git push origin <tag> 특정 태그 푸시 git push origin v1.0.0
git push --tags 모든 태그 푸시 git push --tags
git push origin :refs/tags/<tag> 원격 태그 삭제 git push origin :refs/tags/v1.0.0

마지막 줄이 낯설게 생긴 이유가 있다. git push는 원래 <보낼 것>:<원격에서 받을 자리> 모양으로도 쓴다. refs/tags/v1.0.0은 원격에서 v1.0.0 태그가 놓인 자리의 정식 이름이다. : 앞의 「보낼 것」을 비워 두면 「빈 것을 그 자리로 보낸다」, 즉 그 자리를 지우라는 뜻이 된다.

사용 예제

# 릴리즈 버전 태그 생성
git tag -a v1.0.0 -m "First stable release"

# 태그를 원격 저장소에 푸시
git push origin v1.0.0

# 모든 태그 푸시
git push --tags

고급 활용

Rebase

리베이스(rebase)는 한 브랜치에서 쌓은 커밋들을 다른 브랜치의 최신 지점 위로 옮겨 다시 쌓는 일이다. 머지처럼 두 기록을 합친 흔적을 남기지 않고, 마치 처음부터 그 지점에서 시작해 순서대로 작업한 것처럼 기록을 한 줄로 만든다.

# feature 브랜치를 main의 최신 상태로 재배치
git switch feature
git rebase main

# 대화형 rebase로 여러 커밋 정리
git rebase -i HEAD~3

# rebase 중 충돌을 해결한 뒤 계속
git rebase --continue

# rebase 중단
git rebase --abort

한 줄씩 따라가기. git rebase -i HEAD~3을 실행하면 대화형(interactive) 모드로 들어간다. 편집기가 열리고 최근 3개 커밋이 한 줄씩 목록으로 뜨는데, 각 줄 앞에 붙은 pick이라는 단어를 squash(바로 위 커밋과 합치기)나 drop(그 커밋 버리기) 같은 단어로 바꾸고 저장하면 그 지시대로 커밋 목록이 정리된다. 목록은 위쪽이 더 오래된 커밋이라, squash는 바로 윗줄의 더 오래된 커밋에 합쳐진다. 열리는 편집기는 Git 설정(core.editor)에 정한 프로그램이고, 그 편집기에서 파일을 저장하고 창을 닫으면 Git이 이어서 진행한다. 정한 적이 없으면 공식 문서 말로 「대개 vi」인 기본 편집기가 터미널 안에서 열린다. vi에서는 i를 눌러야 글을 고칠 수 있고, 다 고쳤으면 Esc를 누른 뒤 :wq를 치고 Enter를 누르면 저장하고 닫힌다.

--continue와 --abort는 리베이스가 도중에 멈췄을 때 쓴다. 옮겨 쌓던 커밋이 대상 브랜치와 같은 곳을 다르게 고쳤으면 충돌이 나서 멈춘다. 충돌난 파일을 고치고 git add한 뒤 --continue로 이어 가거나, --abort로 리베이스를 시작하기 전 상태로 돌아간다. 충돌난 파일이 어떻게 생겼는지는 뒤의 「충돌 해결」에서 본다.

Cherry-pick

다른 브랜치에 있는 커밋 중 딱 하나(또는 몇 개)만 골라, 그 커밋이 한 변경을 지금 브랜치에 똑같이 적용해 새 커밋으로 쌓는 명령이다. 브랜치 전체를 병합하기는 이르고 버그 수정 하나만 먼저 가져오고 싶을 때 쓴다. 넘기는 값은 가져올 커밋의 해시로, 그 브랜치에서 git log --oneline을 쳐서 찾는다.

# 다른 브랜치의 특정 커밋만 가져오기
git cherry-pick abc1234

# 여러 커밋 가져오기
git cherry-pick abc1234 def5678

Clean

git clean은 추적되지 않은 파일(앞의 Stash 절에서 적은, 한 번도 git add해 본 적 없는 새 파일)을 지운다. .gitignore는 「Git이 신경 쓰지 말 파일」의 이름을 적어 두는 설정 파일인데, 기본으로는 여기 적힌 파일은 지우지 않고 -x를 붙여야 같이 지운다. -n의 dry-run은 「실제로는 아무것도 안 지우고, 지울 목록만 보여 준다」는 뜻이다. 지우기 전에 늘 -n으로 먼저 본다. -f(force)는 「정말 지운다」는 확인이다. Git은 기본 설정에서 -f 없이는 지우기를 거절한다. -d는 추적되지 않은 폴더 안까지 들어가 폴더째 지우라는 뜻이다. 없으면 폴더는 건드리지 않는다.

# 추적되지 않은 파일 보기 (dry-run)
git clean -n

# 추적되지 않은 파일 삭제
git clean -f

# 디렉토리도 포함하여 삭제
git clean -fd

# .gitignore에 적힌 파일까지 삭제
git clean -fdx

자주 사용하는 패턴

1. Feature 브랜치 워크플로우

feature 브랜치를 원격 저장소에 올린 뒤에는 바로 main에 병합하지 않고, GitHub 같은 사이트에서 Pull Request(PR)를 만든다. PR은 Git 명령어가 아니라 그 사이트가 제공하는 기능으로, 다른 사람이 코드를 먼저 리뷰할 수 있는 자리를 만들어 준다. 리뷰에서 문제가 없으면 그때 머지해서 main에 반영한다. 머지하는 길은 둘이다. 사이트의 PR 화면에서 머지 버튼을 누르거나, 내 컴퓨터에서 직접 git merge로 합친 뒤 푸시한다. 아래 그림과 명령은 뒤쪽, 내 컴퓨터에서 합치는 길을 보여 준다.

워크플로우 시각화

sequenceDiagram participant WD as 작업 디렉토리 participant Main as main 브랜치 participant Feature as feature 브랜치 participant Remote as 원격 저장소 WD->>Feature: git switch -c feature/new Note over Feature: 새 기능 개발 WD->>Feature: git add & commit Feature->>Remote: git push -u origin feature/new Note over Remote: Pull Request 생성 Main->>Main: git pull (최신 상태 확인) Feature->>Main: git merge (리뷰 완료 후) Main->>Remote: git push Feature->>Feature: git branch -d (로컬 삭제) Remote->>Remote: git push origin --delete (원격 삭제)

명령어 순서

# 새 기능 개발 시작
git switch -c feature/new-feature

# 작업 및 커밋
git add .
git commit -m "Implement new feature"

# main 브랜치의 최신 변경사항 반영
git switch main
git pull origin main
git switch feature/new-feature
git rebase main

# 원격에 푸시
git push -u origin feature/new-feature

# PR 생성 및 리뷰 후 main에 병합
git switch main
git merge feature/new-feature
git push origin main

# feature 브랜치 삭제
git branch -d feature/new-feature
git push origin --delete feature/new-feature

한 줄씩 따라가기. 「main 브랜치의 최신 변경사항 반영」 네 줄은, 내가 기능을 만드는 사이 다른 사람이 main에 먼저 올린 커밋을 받아 와서(git pull) 내 커밋들을 그 위로 옮겨 쌓는(git rebase main) 일이다. 이렇게 해 두면 내 브랜치가 최신 main에서 출발한 모양이 되어, 나중에 합칠 때 충돌이 나도 내 컴퓨터에서 먼저 보고 풀 수 있다. 맨 끝의 git push origin --delete는 원격 저장소에 있는 브랜치를 지우는 명령이다. push가 지우기까지 맡는 이유는 앞의 「태그 관리」에서 본 :(빈 것을 보내 지우기)와 같은 일을 하는 옵션이기 때문이다.

2. 실수한 커밋 수정

# 마지막 커밋 메시지 수정
git commit --amend -m "Correct commit message"

# 마지막 커밋에 파일 추가
git add forgotten-file.txt
git commit --amend --no-edit

# 여러 커밋 전 상태로 되돌리기
git reset --hard HEAD~3

--amend는 새 커밋을 쌓지 않고 방금 만든 마지막 커밋을 고쳐 쓴다. -m을 주면 메시지를 바꾸고, --no-edit를 주면 메시지는 그대로 두고 그사이 스테이징한 파일만 그 커밋에 더한다. 이미 푸시한 커밋을 이렇게 고치면 원격의 기록과 어긋나니, 아직 푸시하지 않은 커밋에만 쓴다.

3. 충돌 해결

git pull이나 git merge가 충돌로 멈추면, 충돌난 파일 안에 Git이 이런 표시를 넣어 둔다.

<<<<<<< HEAD
내 쪽에서 고친 줄
=======
합치려는 쪽에서 고친 줄
>>>>>>> other

<<<<<<< HEAD부터 =======까지가 내 쪽(지금 브랜치), =======부터 >>>>>>>까지가 합치려는 쪽 내용이다. >>>>>>> 뒤에는 합치려는 쪽의 이름이 붙는다(위는 other 브랜치를 합칠 때 찍힌 모양이다). 편집기로 이 파일을 열어 둘 중 하나를 남기거나 둘을 섞어 원하는 모양으로 고치고, 표시 세 줄을 지운 뒤 저장한다. 그다음이 아래 명령이다.

# pull 중 충돌 발생
git pull origin main

# 충돌 파일을 열어 고친 뒤
git add conflicted-file.txt
git commit -m "Resolve merge conflict"

# 또는 merge 취소
git merge --abort

팁 & 주의사항

별칭 · 로그 조회 · 부분 스테이징으로 반복 작업을 던다

별칭(Alias) 설정으로 생산성 향상

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

git config는 Git 설정을 바꾸는 명령이고, --global은 이 저장소 하나가 아니라 내 컴퓨터 사용자 계정의 모든 저장소에 적용되는 설정 파일(~/.gitconfig)에 적으라는 뜻이다. ~는 내 사용자 계정의 홈 폴더(Windows라면 C:\Users\<사용자 이름>)를 가리킨다. alias.co checkout은 co를 checkout의 줄임말로 등록한다. 등록한 뒤에는 git co main이 git checkout main과 같다.

유용한 로그 조회

# 그래프 형태로 브랜치 보기
git log --oneline --graph --all --decorate

# 특정 파일의 변경 이력
git log --follow filename.txt

# 특정 작성자의 커밋
git log --author="John"

--graph는 브랜치가 갈라지고 합쳐진 모양을 글자로 그린 선과 함께 보여 주고, --all은 지금 브랜치뿐 아니라 모든 브랜치와 태그의 커밋을 같이 보여 준다. --decorate는 커밋 옆에 그 커밋을 가리키는 브랜치 · 태그 이름을 붙여 준다. --follow는 파일 이름이 중간에 바뀌었어도 바뀌기 전의 이력까지 이어서 보여 준다(파일 하나에만 쓸 수 있다).

대화형 스테이징

# 파일의 일부만 선택적으로 스테이징
git add -p

실행하면 고친 부분을 덩어리(hunk)마다 하나씩 보여 주며 스테이징할지 묻는다. y는 이 덩어리를 스테이징, n은 건너뛰기, q는 그만두기다. 한 파일 안에서 고친 것 중 일부만 이번 커밋에 넣고 싶을 때 쓴다.

reset –hard · push –force · clean -fdx · 공유 브랜치 rebase를 조심한다

  • git reset --hard: 작업 내용이 완전히 삭제되므로 신중하게 사용
  • git push --force: 공유 브랜치에서는 절대 사용하지 말 것 (다른 사람의 작업을 덮어쓸 수 있음)
  • git clean -fdx: .gitignore에 적힌 파일까지 삭제하므로 빌드 결과물(컴파일해서 만들어진 .class 파일처럼 소스에서 다시 만들어 내는 파일)이나 설정 파일 등이 삭제될 수 있음
  • Rebase vs Merge: 공유 브랜치에서는 rebase보다 merge 사용 권장. rebase는 커밋을 옮겨 쌓으면서 새 커밋으로 다시 만든다. 그 브랜치를 받아 가 그 위에 작업한 사람은 원래 커밋을 들고 있어서, 기록이 어긋난 것을 손으로 맞춰야 한다. merge는 기존 커밋을 그대로 두고 합친 커밋 하나를 더할 뿐이라 이 문제가 없다

Windows와 Linux · Mac은 줄바꿈 설정이 다르다

파일 안에서 줄이 바뀌는 자리에는 눈에 보이지 않는 줄바꿈 문자가 들어 있다. CR(Carriage Return)과 LF(Line Feed)라는 두 문자인데, Windows는 줄바꿈 문자로 CRLF(두 글자를 이어서)를 쓰고, Linux/Mac은 LF(한 글자)만 쓴다. 같은 파일을 서로 다른 운영체제에서 커밋하면 실제로는 안 바뀐 줄까지 전부 바뀐 것으로 Git이 인식할 수 있다. core.autocrlf는 Git이 커밋할 때와 받을 때 줄바꿈 문자를 서로 맞춰 주도록 설정하는 옵션이다.

Windows

  • 줄바꿈 문자 설정: git config --global core.autocrlf true — 커밋할 때는 CRLF를 LF로 바꿔 저장소에 넣고, 파일을 꺼낼 때는 LF를 CRLF로 바꿔 준다. 저장소 안에는 LF만 남는다
  • 파일명 대소문자: core.ignorecase는 손대지 않는다. Windows의 파일 시스템은 Makefile과 makefile을 같은 파일로 보는데, Git은 저장소를 만들 때(git init, git clone) 이걸 알아보고 core.ignorecase를 알아서 true로 맞춘다. 공식 문서는 이 값을 바꾸면 예상하지 못한 동작이 날 수 있다고 적어 둔다

Linux/Mac

  • 줄바꿈 문자 설정: git config --global core.autocrlf input — 커밋할 때만 CRLF를 LF로 바꾸고, 꺼낼 때는 바꾸지 않는다. 이미 LF를 쓰는 운영체제라 꺼낼 때 바꿀 것이 없다
  • 파일의 실행 권한(이 파일을 프로그램처럼 실행해도 되는가 하는 표시)이 바뀐 것도 Git이 변경으로 잡는다(core.fileMode). 따로 설정하라는 말이 아니라 알아 두라는 말이다

더 읽을 공식 문서


자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기