Git: 코드의 타임머신
💡 학습 가이드: 이 장은 Git을 전혀 사용해 보지 않은 분들을 위해 작성되었습니다. 처음부터 명령어를 외우게 하지 않고, 먼저 "Git이 대체 어떤 문제를 해결해 주는지"를 파악한 다음, 명령어와 개념을 하나씩 연결해 나갑니다. 다 읽고 나면 로컬 커밋, 브랜치 생성, GitHub에 푸시하는 것을 독립적으로 할 수 있을 것입니다.
0. 먼저 한 가지 질문: 이런 악몽을 겪어본 적 없나요?
시나리오 1: 버전 지옥
논문이나 코드를 작성하다가 중간에 잘못 수정한 것을 발견하고 3일 전 버전으로 돌아가고 싶은데 — 이미 없어졌습니다.
프로젝트_v1.zip
프로젝트_v2_수정본.zip
프로젝트_v3_최종본.zip
프로젝트_v3_최종본_진짜최종본.zip
프로젝트_v3_최종본_절대안고침.zip새 사본을 저장할 때마다 하드디스크는 점점 어지러워지고, 어느 버전에서 무엇을 수정했는지 전혀 기억나지 않습니다.
시나리오 2: 협업 악몽
팀원과 같은 파일을 동시에 수정:
- 여러분은 10번째 줄을 수정하고 로그인 기능을 추가
- 팀원도 10번째 줄을 수정하고 버그를 고침
- 이메일로 코드를 주고받았더니 병합할 때 한 사람의 수정이 다른 사람에 의해 덮어씌워짐
- 어느 코드가 정답인지 아무도 모름
시나리오 3: "후회약"이 없음
프로덕션 환경에 새 코드를 배포했더니 버그가 발생하여 긴급히 이전 안정 버전으로 되돌리고 싶은데 — 어떻게 되돌리는지 모르고, 허둥지둥 백업을 찾습니다.
Git은 바로 이 세 가지 문제를 해결하기 위해 탄생했습니다.
Git은 버전 관리 시스템(Version Control System)입니다. 본질은: 여러분의 모든 "저장" 조작을 기록하여 완전한 역사 타임라인을 형성하고, 언제든 어떤 역사적 노드로든 돌아갈 수 있게 합니다.
과장 없이 Git은 현대 소프트웨어 개발에서 가장 중요한 도구 중 하나입니다. 거의 모든 회사와 모든 오픈소스 프로젝트가 사용하고 있습니다.
1. Git과 GitHub는 같은 건가요?
많은 초보자가 이 두 개념을 혼동합니다. 먼저 정리해 봅시다:
| Git | GitHub | |
|---|---|---|
| 무엇인가 | 컴퓨터에서 실행되는 버전 관리 도구 | Git 저장소를 호스팅하는 웹사이트(클라우드) |
| 어디에 있나 | 로컬 컴퓨터 | 인터넷상 |
| 독립 사용 가능 | ✅ 가능, 로컬 히스토리만 관리 | ❌ Git과 함께 사용해야 함 |
| 비유 | 로컬의 일기장 | 일기를 저장하는 클라우드 스토리지 |
간단히 말해: Git은 도구이고 GitHub는 호스팅 서비스입니다. Word가 도구이고 OneDrive가 클라우드인 것처럼, 둘은 함께 사용하지만 같은 것은 아닙니다.
GitHub 외에도 비슷한 서비스로 GitLab, Gitee(국내) 등이 있습니다.
2. 핵심 개념: 세 가지 영역
이것이 Git에서 가장 중요한 설계입니다. 이 세 가지 영역을 이해하면 Git의 영혼을 이해하게 됩니다.
Git은 파일 상태를 세 가지 계층으로 나눕니다:
작업 디렉토리 (Working Directory) 여러분의 일반 폴더입니다. 지금 보이는, 편집 중인 모든 파일이 여기에 있습니다. 마음대로 수정하세요. Git은 무엇을 수정했는지 감지하지만, 어떤 기록도 남기지 않습니다.
스테이징 영역 (Staging Area / Index) 이것은 "제출 준비" 중간 역할입니다. 작업 디렉토리에서 저장하고 싶은 파일을 스테이징 영역에 "넣을" 수 있습니다. 택배 상자에 물건을 담는 것과 같습니다 — 아직 보내지 않았지만, 무엇을 보낼지 이미 선택한 상태입니다.
저장소 (Repository) 이것은 영구 보관되는 히스토리 기록 저장소이며, .git 폴더 안에 숨겨져 있습니다. git commit을 실행할 때마다 스테이징 영역의 내용이 저장소에 봉인되어 조작 불가능한 히스토리 레코드가 됩니다.
👇 직접 해보기: 명령 버튼을 순서대로 클릭하여 파일이 세 가지 영역 사이를 어떻게 이동하는지 관찰하세요.
files you are editing
login.jsunstagedstyle.cssunstageddebug.logunstagedgit addfiles prepared for this commit
git commitversions saved permanently
9f3e1b2init: initialize projectHEAD왜 "2단계"(add + commit)인가?
많은 초보자가 묻습니다: 왜 원클릭 저장이 아니라 먼저 add 후 commit을 해야 하나요?
실제 개발에서는 모든 수정을 함께 커밋하고 싶지 않은 경우가 많기 때문입니다.
예를 들어, 오늘 5개의 파일을 수정했습니다:
login.js: 로그인 기능 완성 (커밋하고 싶음)style.css: 로그인 페이지 스타일 조정 (커밋하고 싶음)debug.log: 임시 디버그 출력 (커밋하고 싶지 않음)experiment.js: 테스트 중인 새 기능, 아직 미완성 (커밋하고 싶지 않음)todo.txt: 개인 메모 (커밋하고 싶지 않음)
스테이징 영역이 없다면, 5개 파일을 모두 커밋하거나(커밋 기록이 지저분해짐) 하나도 커밋하지 못합니다.
스테이징 영역이 있으면 정밀하게 제어할 수 있습니다: git add login.js style.css로 이 두 파일만 택배 상자에 넣은 다음 commit하면, 이번 커밋은 "로그인 기능 완성"이라고 명확하게 기록됩니다.
3. Git 처음 사용하기: 초기화와 기본 워크플로우
3.1 설치와 초기화
Git을 설치한 후(macOS는 기본 포함, Windows는 git-scm.com에서 다운로드), 터미널을 열고 프로젝트 폴더로 이동합니다:
# 현재 폴더에 Git 저장소 초기화
git init
# Git이 숨겨진 .git 폴더를 생성하고, 모든 히스토리 기록이 그 안에 저장됩니다
# 출력: Initialized empty Git repository in .../your-project/.git/처음 사용할 때는 Git에게 당신이 누구인지 알려야 합니다 (이 정보는 각 커밋 기록에 첨부됩니다):
git config --global user.name "Your Name"
git config --global user.email "your@email.com"3.2 일상 워크플로우: 3단계 저장
초기화 후, 일상 개발의 90%는 다음 세 단계를 반복하는 것입니다:
첫 번째: 상태 확인
git status이것은 가장 많이 사용하는 명령어입니다. 다음을 알려줍니다:
- 현재 어떤 브랜치에 있는지
- 어떤 파일이 수정되었는지 (빨간색 = 미스테이징)
- 어떤 파일이 스테이징 영역에 있는지 (초록색 = 스테이징됨, 커밋 대기 중)
두 번째: 파일을 스테이징 영역에 추가
# 단일 파일 추가
git add login.js
# 여러 파일 추가
git add login.js style.css
# 현재 폴더의 수정된 모든 파일 추가 (.은 "전부"를 의미)
git add .⚠️ 초보자 일반적인 오해:
git add .은 매우 편리하지만, 커밋하고 싶지 않은 임시 파일까지 모두 추가합니다. 정확하게 add하는 습관을 들이거나,.gitignore를 사용하여 추적하지 않을 파일을 제외하세요 (나중에 설명).
세 번째: 커밋하고 설명 작성
git commit -m "feat: 사용자 로그인 기능 추가"-m 뒤의 따옴표 안의 내용을 커밋 메시지(commit message)라고 합니다. 미래의 자신과 팀원을 위해 의미 있게 작성해야 합니다.
3.3 커밋 메시지는 어떻게 써야 전문적인가?
# ❌ 쓸모없는 작성법 — 봐도 무엇을 했는지 모름
git commit -m "update"
git commit -m "fix"
git commit -m "몇 가지 수정"
# ✅ 좋은 작성법: 유형 + 콜론 + 한 줄 설명
git commit -m "feat: 사용자 로그인 기능 추가"
git commit -m "fix: iOS Safari에서 홈페이지 흰 화면 문제 수정"
git commit -m "docs: README의 배포 설명 업데이트"
git commit -m "refactor: UserService를 독립 모듈로 분리"
git commit -m "style: 코드 들여쓰기를 2칸으로 통일"자주 쓰는 접두어 의미:
| 접두어 | 의미 |
|---|---|
feat: | 새 기능 (feature) |
fix: | 버그 수정 |
docs: | 문서 변경 |
style: | 코드 형식 조정 (기능에 영향 없음) |
refactor: | 코드 리팩토링 (기능 변경 없이 구조 최적화) |
chore: | 빌드, 도구, 의존성 관련 |
test: | 테스트 관련 |
이 습관을 들이면 몇 달 후 히스토리를 찾아볼 때 한눈에 각 커밋에서 무엇을 했는지 알 수 있습니다. 이것은 팀 협업에서 특히 중요합니다.
3.4 히스토리 기록 보기
# 상세 형식 (각 커밋의 전체 정보)
git log
# 간결 형식 (한 줄에 하나, 일상 사용 권장)
git log --oneline
# 출력 예시:
# a1b2c3d (HEAD -> main) feat: 사용자 로그인 기능 추가
# 9f3e1b2 init: 프로젝트 초기화4. 평행 우주: 브랜치 (Branch)
브랜치는 Git에서 가장 강력하면서도 초보자를 가장 혼란스럽게 하는 기능입니다. 하지만 이해하고 나면 이 설계가 매우 우아하다는 것을 알게 될 것입니다.
4.1 브랜치란? "평행 우주"로 이해하기
롤플레잉 게임을 하고 있다고 상상해 보세요. 게임에 중요한 선택이 있습니다:
- 선택 A: 대 보스에게 도전 (새 기능 개발)
- 선택 B: 안정局面 유지 (메인 라인 변경 없음)
메인 세이브 파일에서 바로 선택 A를 하면, 실패했을 때 전체 게임 진행이 망가집니다.
하지만 세이브 파일을 복사해서 복사본에서 보스에게 도전한다면:
- 이겼나요? 복사본의 성과를 메인 세이브에 병합
- 졌나요? 메인 세이브는 전혀 영향 없고, 복사본을 삭제하고 다시
Git 브랜치가 바로 이 "복사본 세이브" 메커니즘입니다.
Git에서 main(또는 master) 브랜치는 "메인 세이브"로, 항상 안정적으로 유지해야 합니다. 새 기능을 개발할 때 main에서 새 브랜치를 만들어 거기서 개발하고 테스트한 후, 완료되면 main에 병합합니다.
4.2 브랜치 시각화 데모
👇 직접 해보기: 명령 버튼을 순서대로 클릭하고 아래 브랜치 그래프가 어떻게 분기, 확장, 최종적으로 병합되는지 관찰하세요. HEAD 레이블의 위치 변화에 특히 주목하세요 — 항상 "현재 어디에 있는지"를 가리킵니다.
4.3 브랜치 조작 상세 설명
새 브랜치 생성 및 전환:
# 방법 1: 먼저 생성한 후 전환 (2단계)
git branch feature-login # 브랜치 생성
git checkout feature-login # 전환
# 방법 2: 한 번에 (권장)
git checkout -b feature-login
# 출력: Switched to a new branch 'feature-login'브랜치 생성 후, 명령줄 프롬프트에 현재 브랜치 이름이 표시됩니다:
user@mac ~/project (feature-login) $모든 브랜치 보기:
git branch
# 출력 (*은 현재 브랜치를 나타냄):
# * feature-login
# main브랜치에서 정상적으로 개발:
# feature-login 브랜치에서 코드 수정, add, commit은 평소와 완전히 동일
git add login.js
git commit -m "feat: 로그인 폼 HTML 구조 추가"
git add login.js api.js
git commit -m "feat: 로그인 API 연동 완료"이 커밋들은 feature-login 브랜치에만 있으며, main 브랜치는 무엇을 했는지 전혀 모릅니다.
메인 브랜치로 돌아가서 병합:
# main으로 전환
git checkout main
# feature-login의 모든 수정 병합
git merge feature-login
# 병합 완료 후, 이 브랜치 삭제 (선택 사항)
git branch -d feature-login4.4 언제 브랜치를 만들어야 하나요?
| 시나리오 | 제안 | 이유 |
|---|---|---|
| 새 기능 개발 | ✅ 브랜치 생성 | 기능 완성 전까지 메인에 영향 없고, 언제든 포기 가능 |
| 프로덕션 긴급 버그 수정 | ✅ main에서 hotfix-xxx 브랜치 생성 | 수정 후 바로 병합하여 배포, 미완성 기능이 포함되지 않음 |
| 팀원과 병렬 개발 | ✅ 각자 브랜치 생성 | 서로 간섭 없이, 완료 후 Pull Request로 통합 병합 |
| 오타 하나 수정 | ❌ main에서 직접 수정 | 위험이 극히 낮아 굳이 브랜치 생성 불필요 |
4.5 팀에서 자주 쓰는 브랜치 전략
실제 프로젝트에서는 팀이 브랜치 이름과 용도를 미리 약속합니다:
| 브랜치명 | 용도 | 특징 |
|---|---|---|
main / master | 프로덕션 환경의 안정 코드 | 테스트를 통과한 코드만 들어올 수 있고, 직접 푸시 불가 |
dev / develop | 일상 통합 브랜치 | 모든 기능 브랜치가 먼저 여기로 병합, 테스트 통과 후 main으로 |
feature/xxx | 구체적인 기능 개발 | 예: feature/user-login, 완료 후 dev로 병합 |
hotfix/xxx | 긴급 수정 | main에서 생성, 수정 후 main과 dev에 바로 병합 |
5. 팀원과 협업하기: 원격 저장소
지금까지 배운 것은 모두 로컬 Git 조작입니다 — 모든 히스토리가 여러분의 컴퓨터에만 저장됩니다. 팀원과 코드를 공유하려면 원격 저장소, 즉 GitHub, GitLab 같은 클라우드 저장소가 필요합니다.
5.1 원격 저장소의 작동 원리
원격 저장소는 팀 공용 "공용 세이브"로 이해할 수 있습니다:
- 각자 로컬에서 코드를 작성하고 커밋합니다
- 완료 후
push(업로드)하여 원격 저장소로 보냅니다 - 팀원이
pull(다운로드)하여 원격 저장소의 최신 내용을 로컬로 가져옵니다 - 이렇게 모두의 코드가 동기화됩니다
👇 직접 해보기: 명령을 순서대로 클릭하여 원격 저장소 연결, 푸시, 팀원 업데이트 풀의 전체 과정을 체험하세요.
9f3e1b2init: initialize projectc4d8a31feat: homepage layout5.2 처음으로 프로젝트를 GitHub에 푸시하기
첫 번째: GitHub에서 새 저장소 생성 (오른쪽 상단 + -> New repository), 초기화 옵션은 체크하지 마세요.
두 번째: 로컬 터미널로 돌아가 원격 저장소 연결:
# 로컬 저장소와 GitHub의 저장소를 연결
# "origin"은 원격 저장소의 별명으로, 관례상 사용하는 이름 (변경 가능하지만 필요 없음)
git remote add origin https://github.com/사용자명/저장소명.git
# 연결 성공 확인
git remote -v
# 출력:
# origin https://github.com/사용자명/저장소명.git (fetch)
# origin https://github.com/사용자명/저장소명.git (push)세 번째: 로컬 내용을 원격으로 푸시:
# 첫 번째 푸시, -u는 "이후 git push 시 기본적으로 origin의 main 브랜치로 푸시"를 의미
git push -u origin main
# 이후에는 매번 푸시할 때:
git push5.3 일상 협업 명령어
푸시 (내가 수정한 것을 팀원이 볼 수 있게):
git push풀 (팀원이 수정한 것을 동기화):
git pullgit pull은 사실 두 명령어의 조합입니다:
git fetch: 원격 저장소에서 최신 커밋 기록 다운로드git merge: 다운로드한 내용을 현재 브랜치에 병합
처음으로 GitHub에서 다른 사람의 프로젝트 가져오기:
# 전체 원격 저장소를 로컬로 복사 (한 번만 하면 됨)
git clone https://github.com/누군가/어떤프로젝트.git
# clone은 자동으로 원격과의 연결을 설정하므로, 이후 push/pull만 하면 됨5.4 push와 pull의 방향
내 컴퓨터(로컬 저장소) ←→ GitHub(원격 저장소)
git push: 로컬 → 원격 (내가 수정한 것을 팀원에게 업로드)
git pull: 원격 → 로컬 (팀원이 수정한 것을 내 컴퓨터에 다운로드)
git clone: 원격 → 로컬 (처음에 전체 저장소 복사)모범 사례: 매일 작업 시작 전
git pull로 최신 코드를 받고, 퇴근하거나 기능 완료 후git push하여 백업하고 팀원에게 진행 상황을 공유하세요.
6. 심화: 충돌 해결
충돌은 협업에서 피할 수 없는 것이지만, 그렇게 무섭지도 않습니다.
6.1 충돌은 어떻게 발생하는가?
팀원과 같은 파일의 같은 줄을 동시에 수정했을 때, 병합할 때 Git이 어느 버전을 사용해야 할지 모르면 충돌이 발생합니다.
예를 들어:
- 여러분이
login.js5번째 줄에:const timeout = 3000이라고 작성 - 팀원이 같은 줄에:
const timeout = 5000이라고 작성 git pull또는git merge시 Git이 이 모순을 발견하고 "일시 중지"하며 알려줍니다: 어느 것을 사용할지 결정해 주세요.
6.2 충돌 파일은 어떻게 생겼는가?
Git은 충돌 부분에 특수 마커를 삽입합니다:
function login() {
const url = '/api/login'
<<<<<<< HEAD
const timeout = 3000 // 나의 버전
=======
const timeout = 5000 // 팀원의 버전
>>>>>>> feature/update-timeout
return fetch(url, { timeout })
}<<<<<<< HEAD에서=======사이: 현재 브랜치의 내용=======에서>>>>>>> xxx사이: 병합해 온 내용
6.3 충돌을 어떻게 해결하는가?
첫 번째: 충돌 파일을 열고 모든 <<<<<<< 마커를 찾습니다 (보통 VS Code 등 편집기가 자동으로 하이라이트)
두 번째: 어느 코드를 유지할지 결정하고, 파일을 직접 편집하여 모든 마커 기호(<<<<<<<, =======, >>>>>>>)를 삭제합니다.
예를 들어 5000(팀원의 버전)을 사용하기로 결정:
function login() {
const url = '/api/login'
const timeout = 5000 // 팀원의 수정 채택
return fetch(url, { timeout })
}세 번째: 다시 커밋
# 충돌 해결 완료 표시
git add login.js
# 병합 커밋 완료 (Git이 자동으로 병합 커밋 메시지 생성)
git commit6.4 충돌을 줄이는 좋은 습관
- 자주 pull: 작업 전 최신 코드 동기화, "너무 많이 뒤처지는" 상황 방지
- 작은 단위로 커밋: 일주일 치 코드를 한 번에 커밋하지 말고, 잦은 소규모 커밋이 충돌 발견과 해결에 유리
- 브랜치 격리: 다른 기능은 다른 브랜치로, 같은 줄에 대한 경쟁 감소
- 소통: 공용 파일(예:
config.js) 수정 전 팀원에게 미리 알리기
7. 자주 쓰는 명령어 빠른 참조
Save this table and check it whenever a Git command slips your mind:
8. 실전: 팀 프로젝트에 참여하는 전체 과정
새 팀이나 새 프로젝트에 합류할 때의 표준 작업 절차이며, 그대로 따라 하시면 됩니다:
# ① 첫날: 프로젝트를 로컬에 clone (한 번만)
git clone https://github.com/team/project.git
cd project
# ② 매일 작업 시작: 먼저 최신 코드 풀
git pull origin main
# ③ 자신의 기능 브랜치 생성 (main에서 직접 수정하지 마세요)
git checkout -b feature/user-profile
# ④ 정상 개발... 코드 작성...
# ⑤ 작은 기능 단위 완성 후 즉시 커밋 (모아두지 마세요)
git add src/UserProfile.vue
git commit -m "feat: 사용자 프로필 이미지 업로드 기능 완료"
git add src/UserProfile.vue src/api/user.js
git commit -m "feat: 사용자 프로필 편집 API 연동 완료"
# ⑥ 자신의 브랜치를 원격으로 푸시하여 팀원이 볼 수 있게
git push origin feature/user-profile
# ⑦ GitHub에서 Pull Request(PR) 생성, main에 병합 요청
# (이 단계는 GitHub 웹페이지에서 조작)
# ⑧ 팀원의 코드 리뷰를 기다리고, 피드백에 따라 수정, 계속 commit + push
# ⑨ PR 병합 후, main으로 돌아가 로컬 업데이트, 기능 브랜치 삭제
git checkout main
git pull
git branch -d feature/user-profile9. .gitignore: 어떤 파일을 추적하지 않아야 하나요?
다음 파일들은 Git 저장소에 커밋하지 않아야 합니다:
node_modules/: 의존 패키지, 용량이 매우 크고npm install로 재생성 가능.env: 환경 변수 파일, 데이터베이스 비밀번호, API Key가 포함될 수 있어 공개 저장소에 절대 업로드 금지*.log: 로그 파일.DS_Store: macOS가 자동 생성하는 숨김 파일dist/,build/: 빌드 산출물, 다시 빌드 가능
프로젝트 루트 디렉토리에 .gitignore 파일을 만들고 추적하지 않을 파일 규칙을 작성합니다:
# 의존 패키지
node_modules/
# 환경 변수 (중요! 비밀번호는 커밋하면 안 됨)
.env
.env.local
# 빌드 산출물
dist/
build/
# 시스템 파일
.DS_Store
Thumbs.db
# 로그
*.logGitHub에는 다양한 언어와 프레임워크의 .gitignore 템플릿이 있습니다: github.com/github/gitignore
용어 빠른 참조 표
| 용어 | 영어 | 설명 |
|---|---|---|
| 저장소 | Repository (Repo) | 프로젝트의 모든 버전 히스토리를 저장하는 데이터베이스, .git 폴더 안에 있음 |
| 커밋 | Commit | 완전한 버전 기록 한 번, 게임 세이브 포인트와 같으며, 설명과 타임스탬프가 있음 |
| 브랜치 | Branch | 독립적인 개발 라인, 평행 타임라인처럼 서로 영향 없음 |
| 병합 | Merge | 한 브랜치의 수정을 다른 브랜치에 통합 |
| 충돌 | Conflict | 같은 줄의 코드를 여러 사람이 수정하여 Git이 어느 것을 사용할지 모름, 수동 해결 필요 |
| 스테이징 | Stage / Index | 수정 사항을 "커밋 준비" 목록에 넣는 조작 |
| 원격 | Remote | 클라우드의 저장소 사본 (GitHub / GitLab / Gitee) |
| 클론 | Clone | 전체 원격 저장소를 로컬에 완전히 복사 |
| 푸시 | Push | 로컬 커밋을 원격 저장소에 업로드 |
| 풀 | Pull | 원격의 최신 내용을 다운로드하여 로컬에 병합 |
| HEAD | HEAD | 현재 있는 브랜치/커밋의 포인터, "지금 어디에 있는지"를 나타냄 |
| origin | origin | 원격 저장소의 기본 별명 (관례상 사용하는 이름) |
| stash | Stash | 아직 commit하지 않은 수정을 임시 저장, 작업 전환 시 사용 |
| PR / MR | Pull Request / Merge Request | 자신의 브랜치를 메인 브랜치에 병합해 달라고 요청, 보통 팀원의 리뷰 필요 |