소프트웨어 개발은 단순히 코드를 작성하는 행위에서 그치지 않습니다. 수많은 수정과 실험, 그리고 팀원들과의 협업이 동반되는 복잡하고 유기적인 과정입니다. 이 과정 속에서 우리는 필연적으로 다음과 같은 질문들과 마주하게 됩니다. '어제까지 잘 동작하던 코드가 왜 갑자기 안 되지?', '새로운 기능을 추가하다가 기존 코드를 망가뜨렸는데 어떻게 되돌리지?', '동료가 수정한 내용과 내가 수정한 내용이 겹치는데 어떻게 합쳐야 할까?'
이러한 혼돈 속에서 질서를 부여하고, 코드의 변경 이력을 체계적으로 관리하며, 안정적인 협업 환경을 구축해 주는 도구가 바로 버전 관리 시스템(Version Control System, VCS)입니다. 그리고 오늘날 가장 압도적으로 많이 사용되는 버전 관리 시스템이 바로 Git입니다.
Git은 리눅스 커널을 개발한 리누스 토르발스가 2005년에 직접 개발한 분산 버전 관리 시스템입니다. '분산'이라는 특징은 중앙 서버에만 모든 변경 이력이 저장되는 중앙 집중식 시스템(예: SVN)과 달리, 모든 개발자가 각자의 컴퓨터에 완전한 저장소(Repository)의 복제본을 가지고 작업할 수 있음을 의미합니다. 이는 네트워크에 연결되지 않은 상태에서도 작업이 가능하게 하고, 중앙 서버에 문제가 생겨도 각 개발자의 로컬 저장소를 통해 복구할 수 있는 강력한 안정성을 제공합니다.
이 글에서는 Git의 철학과 기본 개념부터 시작하여, 개발자라면 반드시 알아야 할 핵심 명령어들(add, commit, push, pull 등)의 사용법과 그 이면에 숨겨진 원리까지 깊이 있게 다룰 것입니다. 단순히 명령어 목록을 나열하는 것을 넘어, 각 명령어가 어떤 상황에서 왜 필요한지, 그리고 어떻게 유기적으로 연결되어 하나의 강력한 워크플로우를 만들어내는지에 초점을 맞출 것입니다. Git을 처음 접하는 입문자부터, 어렴풋이 사용은 하고 있지만 개념이 확실하지 않았던 개발자까지 모두에게 튼튼한 기반을 다져줄 이정표가 되기를 바랍니다.
1. Git, 첫 만남: 설치와 최초 설정
모든 여정의 시작은 첫걸음부터입니다. Git을 사용하기 위해서는 먼저 자신의 컴퓨터에 Git을 설치하고, Git이 '나'를 인식할 수 있도록 최소한의 정보를 설정해주어야 합니다. 이 과정은 앞으로의 모든 Git 활동에 대한 기본 환경을 구축하는 중요한 단계입니다.
운영체제별 설치 방법
Git은 윈도우, macOS, 리눅스 등 대부분의 운영체제를 지원하며, 설치 방법 또한 매우 간단합니다.
- Windows: Git for Windows 공식 웹사이트에서 설치 파일을 다운로드하여 실행합니다. 설치 과정에서 나타나는 여러 옵션들은 대부분 기본값을 유지해도 무방하지만, 'Git Bash'와 'Git GUI'를 함께 설치하는 것이 좋습니다. Git Bash는 윈도우 환경에서 리눅스 스타일의 명령어 인터페이스를 제공하여 Git을 더욱 편리하게 사용할 수 있게 해줍니다.
-
macOS: macOS에는 두 가지 편리한 설치 방법이 있습니다. 첫 번째는 공식 웹사이트에서 설치 파일을 직접 받는 것입니다. 두 번째이자 더 권장되는 방법은 Homebrew 패키지 매니저를 이용하는 것입니다. 터미널을 열고
brew install git명령어를 입력하면 간단하게 설치가 완료됩니다. 만약 Homebrew가 설치되어 있지 않다면, 먼저 Homebrew를 설치해야 합니다. 또는, Xcode Command Line Tools를 설치하면 Git이 함께 설치되기도 합니다. 터미널에git --version을 입력했을 때 설치 여부를 묻는 팝업이 뜬다면 해당 팝업의 안내에 따라 설치를 진행할 수 있습니다. -
Linux: 대부분의 리눅스 배포판은 자체 패키지 매니저를 통해 Git을 손쉽게 설치할 수 있습니다.
- Debian/Ubuntu 계열:
sudo apt-get install git - Fedora/CentOS/RHEL 계열:
sudo yum install git또는sudo dnf install git
- Debian/Ubuntu 계열:
설치가 완료되었다면, 터미널(Windows에서는 Git Bash)을 열고 다음 명령어를 입력하여 설치된 Git의 버전을 확인해보세요. 버전 정보가 정상적으로 출력된다면 성공적으로 설치된 것입니다.
$ git --version
git version 2.39.2 (Apple Git-143)
최초 설정: 사용자 정보 등록
Git은 누가 어떤 변경사항을 만들었는지 추적하기 위해 모든 커밋(Commit, 변경사항의 기록 단위)에 작성자 정보를 함께 기록합니다. 따라서 Git을 처음 설치했다면, 당신의 이름과 이메일 주소를 Git에게 알려주어야 합니다. 이 설정은 --global 옵션을 사용하여 컴퓨터 전체에 한 번만 설정하면 됩니다.
$ git config --global user.name "Your Name"
$ git config --global user.email "youremail@example.com"
여기서 입력하는 이름과 이메일은 GitHub와 같은 원격 저장소 계정과 반드시 일치할 필요는 없지만, 협업 시 누가 커밋했는지 명확하게 식별하기 위해 일반적으로 원격 저장소 계정과 동일한 정보를 사용하는 것이 좋습니다.
설정된 정보를 확인하고 싶다면 다음 명령어를 사용합니다.
$ git config --global user.name
Your Name
$ git config --global user.email
youremail@example.com
또는 전체 설정 목록을 보고 싶다면 git config --list 명령어를 사용할 수 있습니다. 이 외에도 기본 브랜치 이름 설정(init.defaultBranch), 기본 텍스트 에디터 설정(core.editor) 등 다양한 설정을 할 수 있지만, 사용자 정보 등록은 모든 작업을 위한 필수적인 첫 단계입니다.
2. 내 컴퓨터에서 시작하는 버전 관리: 로컬 저장소 다루기
Git의 가장 큰 매력 중 하나는 인터넷 연결 없이, 오직 내 컴퓨터만으로도 완벽한 버전 관리를 시작할 수 있다는 점입니다. 이를 가능하게 하는 것이 바로 로컬 저장소(Local Repository)입니다. 이제 새로운 프로젝트를 시작하거나 기존 프로젝트에 버전 관리를 도입하는 방법을 알아보겠습니다.
새로운 저장소 만들기: `git init`
버전 관리를 시작하고 싶은 프로젝트 폴더가 있다면, 해당 폴더로 이동한 뒤 git init 명령어를 실행하면 됩니다. 이 명령어는 그 폴더를 Git이 관리하는 공간, 즉 '저장소'로 만들어줍니다.
# 프로젝트를 위한 새 폴더를 만들고 이동합니다.
$ mkdir my-project
$ cd my-project
# 현재 폴더를 Git 저장소로 초기화합니다.
$ git init
Initialized empty Git repository in /Users/username/my-project/.git/
git init을 실행하면 폴더 내에 .git이라는 숨김 폴더가 생성됩니다. 이 .git 디렉토리 안에는 버전 관리와 관련된 모든 정보, 즉 변경 이력, 설정, 브랜치 정보 등이 저장됩니다. 이 폴더는 Git의 심장과도 같으므로, 절대로 수동으로 수정하거나 삭제해서는 안 됩니다. 이 폴더가 존재하는 한, 해당 디렉토리와 그 하위 디렉토리들은 모두 Git의 관리하에 놓이게 됩니다.
Git의 3가지 공간: 작업 디렉토리, 스테이징 영역, 저장소
Git의 핵심 워크플로우를 이해하기 위해서는 반드시 세 가지 논리적인 공간의 개념을 알아야 합니다. 많은 초심자들이 Git을 어려워하는 이유 중 하나가 이 공간들의 역할과 상호작용을 명확히 이해하지 못하기 때문입니다.
- 작업 디렉토리 (Working Directory): 우리가 실제로 파일을 생성하고, 수정하고, 삭제하는 프로젝트 폴더 그 자체입니다. 우리 눈에 보이는 파일들이 존재하는 공간입니다.
- 스테이징 영역 (Staging Area, 또는 Index): 작업 디렉토리에서 발생한 변경사항들 중에서, 다음 커밋에 포함시키고 싶은 것들만 선별하여 올려놓는 '준비 영역'입니다. 장바구니에 물건을 담는 것에 비유할 수 있습니다. 모든 변경사항을 한 번에 커밋하는 것이 아니라, 관련 있는 변경사항들끼리 묶어서 의미 있는 단위로 커밋하기 위해 존재하는 매우 중요한 공간입니다.
-
저장소 (Repository, .git 디렉토리): 스테이징 영역에 준비된 파일들의 스냅샷을 영구적으로 기록하는 곳입니다. 커밋된 파일들은
.git디렉토리 안에 안전하게 저장되며, 각 커밋은 고유한 식별자(해시 값)를 가집니다.
Git의 기본 작업 흐름은 이 세 공간 사이를 오가는 과정입니다.
작업 디렉토리에서 파일 수정 → 스테이징 영역으로 변경사항 추가 (`git add`) → 저장소에 스냅샷 기록 (`git commit`)
이 흐름을 이해하는 것이 Git 명령어들을 제대로 사용하는 첫걸음입니다.
현재 상태 확인하기: `git status`
git status는 Git을 사용하면서 가장 자주 사용하게 될 명령어입니다. 이 명령어는 세 가지 공간의 현재 상태를 종합적으로 보여줍니다. 어떤 파일이 수정되었는지, 어떤 파일이 스테이징 영역에 추가되었는지, 아직 Git이 관리하지 않는 파일은 무엇인지 등을 명확하게 알려주기 때문에, 다음 행동을 결정하기 전에 항상 git status를 확인하는 습관을 들이는 것이 좋습니다.
예를 들어, README.md 파일을 새로 만들고 git status를 실행해 보겠습니다.
$ touch README.md
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
출력 메시지를 보면, `README.md`가 'Untracked files'(추적하지 않는 파일) 목록에 있는 것을 볼 수 있습니다. 이는 Git이 이 파일의 존재는 인지했지만, 아직 버전 관리 대상에 포함시키지는 않았다는 의미입니다.
변경사항을 장바구니에 담기: `git add`
작업 디렉토리에서 만든 변경사항을 커밋에 포함시키려면, 먼저 `git add` 명령어를 사용하여 스테이징 영역으로 옮겨야 합니다. Git에게 '이 변경사항을 다음 커밋에 포함시켜줘'라고 알려주는 과정입니다.
특정 파일만 추가하기
$ git add README.md
이제 다시 git status를 실행해 보면 상태가 바뀐 것을 확인할 수 있습니다.
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
이제 `README.md`는 'Changes to be committed'(커밋될 변경사항) 목록에 나타납니다. 즉, 스테이징 영역에 성공적으로 추가된 것입니다. 만약 스테이징을 취소하고 싶다면, 메시지에 친절하게 안내된 대로 git rm --cached README.md를 사용하면 됩니다.
모든 변경사항 추가하기
작업 디렉토리 내의 모든 변경사항(새 파일, 수정된 파일, 삭제된 파일)을 한 번에 스테이징 영역에 추가하고 싶을 때는 `.`을 사용합니다.
# 현재 디렉토리 이하의 모든 변경사항을 추가
$ git add .
이 명령어는 매우 편리하지만, 의도치 않은 파일까지 스테이징 영역에 포함될 수 있으므로 사용 전에 git status로 변경 내역을 확인하는 습관이 중요합니다.
대화형으로 추가하기: `git add -p`
하나의 파일 내에 여러 가지 논리적인 수정사항이 섞여 있을 때, 그중 일부만 선택하여 스테이징하고 싶을 때가 있습니다. 예를 들어, 함수 하나를 리팩토링하고 동시에 간단한 주석 오타를 수정했을 때, 이 두 변경사항을 별개의 커밋으로 분리하고 싶을 수 있습니다. 이때 `git add -p` (또는 `--patch`) 옵션이 유용합니다.
이 명령어를 실행하면 Git은 변경된 코드 덩어리(hunk)를 하나씩 보여주면서 스테이징할 것인지(y), 안 할 것인지(n), 더 작은 덩어리로 나눌 것인지(s) 등을 묻습니다. 이를 통해 매우 세밀하게 커밋할 내용을 제어할 수 있습니다.
의미 있는 기록 남기기: `git commit`
스테이징 영역에 커밋할 변경사항들이 준비되었다면, `git commit` 명령어를 사용하여 저장소에 영구적인 스냅샷으로 기록할 차례입니다. 커밋은 프로젝트의 특정 시점을 사진 찍어 저장하는 것과 같습니다. 각 커밋은 타임캡슐처럼 그 시점의 코드 상태와 '왜' 이런 변경이 있었는지에 대한 설명(커밋 메시지)을 함께 담고 있습니다.
가장 기본적인 커밋 방법은 `-m` 옵션을 사용하여 커밋 메시지를 함께 전달하는 것입니다.
$ git commit -m "Add README.md for project introduction"
[main (root-commit) a1b2c3d] Add README.md for project introduction
1 file changed, 1 insertion(+)
create mode 100644 README.md
커밋이 성공하면, 커밋이 적용된 브랜치(여기서는 `main`), 커밋의 고유 식별자(예: `a1b2c3d`), 그리고 변경된 파일의 요약 정보가 출력됩니다.
만약 `-m` 옵션 없이 `git commit`만 실행하면, Git 설정에 지정된 기본 텍스트 에디터(예: Vim, Nano)가 실행되며, 여기서 더 상세한 커밋 메시지를 작성할 수 있습니다.
좋은 커밋 메시지의 중요성
커밋 메시지는 단순히 변경사항을 기록하는 것을 넘어, 미래의 나 자신과 동료들을 위한 중요한 소통 수단입니다. 잘 작성된 커밋 메시지는 다음과 같은 장점을 가집니다.
- 코드 변경의 의도와 맥락을 파악하기 쉽습니다.
- 특정 변경사항을 추적하거나 버그의 원인을 찾을 때 시간을 절약해 줍니다.
- 코드 리뷰 과정을 원활하게 만듭니다.
- 프로젝트의 변경 이력을 바탕으로 릴리즈 노트를 자동으로 생성할 수 있습니다.
일반적으로 좋은 커밋 메시지는 '무엇을' 변경했는지보다는 '왜' 변경했는지를 설명하는 데 초점을 맞춥니다. 흔히 사용되는 좋은 커밋 메시지 형식 중 하나는 다음과 같습니다.
<타입>: <제목>
<본문 (선택 사항)>
<꼬리말 (선택 사항)>
- 제목: 50자 이내의 현재형 동사로 시작하는 문장으로 변경 내용을 요약합니다. (예: `Feat: Add user login functionality`)
- 본문: 어떻게보다는 무엇을, 왜 변경했는지에 대한 상세한 설명을 작성합니다.
- 타입 예시:
Feat: 새로운 기능 추가Fix: 버그 수정Docs: 문서 수정Style: 코드 포맷팅, 세미콜론 누락 등 (코드 변경 없음)Refactor: 코드 리팩토링Test: 테스트 코드 추가/수정Chore: 빌드 관련 파일 수정, 패키지 매니저 설정 변경 등
이러한 규칙(이를 'Conventional Commits'라고도 합니다)을 팀 내에서 공유하고 일관성 있게 사용하면, 프로젝트의 이력을 훨씬 더 명확하고 전문적으로 관리할 수 있습니다.
3. 협업의 시작: 원격 저장소와 연동하기
지금까지 우리는 로컬 컴퓨터에서 혼자 버전 관리를 하는 방법을 배웠습니다. 하지만 Git의 진정한 힘은 '분산'이라는 특징을 활용한 '협업'에서 나옵니다. 여러 개발자가 함께 프로젝트를 진행하려면, 각자의 로G컬 저장소에 있는 변경 내용을 공유하고, 다른 사람의 변경 내용을 내 로컬 저장소로 가져올 수 있는 중앙 거점이 필요합니다. 이 역할을 하는 것이 바로 원격 저장소(Remote Repository)입니다.
대표적인 원격 저장소 서비스로는 GitHub, GitLab, Bitbucket 등이 있습니다. 이들은 단순한 코드 저장 공간을 넘어, 코드 리뷰, 이슈 트래킹, CI/CD(지속적 통합/배포) 연동 등 개발 협업에 필요한 다양한 기능을 제공합니다.
원격 저장소 연결하기: `git remote`
먼저 GitHub와 같은 서비스에 접속하여 새로운 빈 저장소(empty repository)를 생성해야 합니다. 이때, README 파일이나 .gitignore 파일을 자동으로 생성하는 옵션은 체크하지 않는 것이 좋습니다. 로컬에 이미 커밋 내역이 있는 상태에서 원격 저장소에 다른 커밋 내역이 존재하면, 나중에 push 할 때 충돌이 발생할 수 있기 때문입니다.
원격 저장소가 준비되었다면, 로컬 저장소에게 원격 저장소의 주소를 알려주어야 합니다. 이 때 `git remote add` 명령어를 사용합니다.
# git remote add <별명> <원격 저장소 주소>
$ git remote add origin https://github.com/your-username/my-project.git
여기서 `origin`은 원격 저장소 주소에 대한 '별명'입니다. 다른 이름(예: `upstream`, `github`)을 사용할 수도 있지만, 관례적으로 기본 원격 저장소에는 `origin`이라는 이름을 가장 널리 사용합니다. 긴 주소를 매번 입력하는 대신 `origin`이라는 짧은 이름으로 원격 저장소를 참조할 수 있게 되는 것입니다.
연결된 원격 저장소의 목록과 주소를 확인하려면 다음 명령어를 사용합니다.
$ git remote -v
origin https://github.com/your-username/my-project.git (fetch)
origin https://github.com/your-username/my-project.git (push)
`fetch`(가져오기)와 `push`(올리기)에 대한 주소가 모두 `origin`이라는 별명으로 잘 등록된 것을 확인할 수 있습니다.
로컬 변경사항을 원격으로: `git push`
로컬 저장소에 쌓인 커밋들을 이제 원격 저장소에 업로드하여 다른 팀원들과 공유할 차례입니다. `git push` 명령어는 로컬의 커밋 내역을 원격 저장소로 전송하는 역할을 합니다.
# git push <원격 저장소 별명> <로컬 브랜치 이름>
$ git push origin main
이 명령어는 '`origin`이라는 원격 저장소에, 내 로컬의 `main` 브랜치의 내용을 올려줘'라는 의미입니다.
처음으로 `push`를 할 때는 `-u` 또는 `--set-upstream` 옵션을 함께 사용하는 것이 좋습니다.
$ git push -u origin main
이 옵션은 로컬의 `main` 브랜치가 원격 저장소 `origin`의 `main` 브랜치를 추적하도록 설정하는 역할을 합니다. 이렇게 한번 상류 브랜치(upstream branch)를 설정해두면, 그 다음부터는 해당 브랜치에서 작업할 때 `git push` 라고만 입력해도 Git이 자동으로 `origin`의 `main` 브랜치로 push를 시도합니다. 협업의 효율성을 높여주는 매우 유용한 설정입니다.
push가 성공적으로 완료되면, 이제 GitHub의 프로젝트 페이지에 접속했을 때 로컬에서 커밋했던 파일과 변경 이력이 그대로 보이는 것을 확인할 수 있습니다.
4. 동료의 작업을 내게로: 원격 저장소와 동기화하기
협업은 단순히 내 작업을 공유하는 것뿐만 아니라, 다른 사람의 작업을 내 로컬 환경으로 가져와서 통합하는 과정이 반드시 필요합니다. 원격 저장소에는 내가 작업하는 동안 다른 팀원들이 push한 새로운 커밋들이 쌓여있을 수 있습니다. 내 로컬 저장소를 최신 상태로 유지하고, 다른 사람의 작업을 기반으로 내 작업을 이어가기 위한 명령어들을 알아봅시다.
프로젝트를 처음 시작할 때: `git clone`
이미 원격 저장소에 존재하는 프로젝트에 내가 새로 참여하게 되었다면, `git init`과 `git remote add`를 순서대로 할 필요가 없습니다. `git clone` 명령어 하나면 원격 저장소의 모든 내용을 내 컴퓨터로 복제해오고, 자동으로 원격 저장소 연결(`origin`)까지 설정해 줍니다.
# git clone <원격 저장소 주소>
$ git clone https://github.com/some-team/existing-project.git
이 명령어를 실행하면 `existing-project`라는 이름의 폴더가 생성되고, 그 안에는 원격 저장소의 모든 파일과 전체 커밋 히스토리가 담긴 `.git` 디렉토리가 포함됩니다. 즉, 프로젝트에 참여하기 위한 가장 간편하고 완벽한 시작 방법입니다.
원격 저장소의 변경사항 가져오기: `git pull`
이미 프로젝트를 진행하고 있는 상황에서, 다른 팀원이 원격 저장소에 새로운 내용을 push했다는 소식을 들었다고 가정해 봅시다. 이제 그 최신 내용을 내 로컬 저장소로 가져와야 합니다. 이때 사용하는 명령어가 `git pull`입니다.
# git pull <원격 저장소 별명> <원격 브랜치 이름>
$ git pull origin main
만약 이전에 `git push -u origin main`을 통해 업스트림 브랜치를 설정해두었다면, 간단하게 `git pull`만 입력해도 동일하게 동작합니다.
`git pull` 명령어는 사실 내부적으로 두 가지 작업을 순차적으로 수행합니다.
- `git fetch`: 원격 저장소의 최신 내역(커밋, 브랜치 등)을 로컬 저장소로 가져옵니다. 하지만 이때, 내 작업 디렉토리의 파일들을 변경하지는 않습니다. 단순히 원격 저장소의 정보만 업데이트하여 로컬의 `.git` 디렉토리 안에 `origin/main`과 같은 원격 추적 브랜치(remote-tracking branch)에 저장해 둡니다.
- `git merge`: `fetch`로 가져온 원격 브랜치(`origin/main`)의 내용을 현재 내가 작업 중인 로컬 브랜치(`main`)와 병합(merge)합니다. 이 과정에서 내 작업 디렉토리의 파일들이 실제로 변경됩니다.
즉, `git pull` = `git fetch` + `git merge` 입니다.
대부분의 경우 `git pull`은 편리하고 직관적으로 동작하지만, 때로는 `fetch`와 `merge`를 분리해서 사용하는 것이 더 안전하고 명확할 수 있습니다. 예를 들어, 원격 저장소에 어떤 변경사항이 있는지 먼저 확인만 하고, 병합은 나중에 신중하게 결정하고 싶을 때가 있습니다. 이런 경우 `git fetch`를 먼저 실행하고, `git log origin/main..main` 같은 명령어로 로컬과 원격의 차이를 확인한 뒤, 준비가 되었을 때 `git merge origin/main`을 실행하는 워크플로우를 사용할 수 있습니다.
충돌(Conflict) 해결하기
만약 내가 수정한 파일의 특정 라인을 다른 팀원도 똑같이 수정하여 원격 저장소에 먼저 push했다면, 내가 `git pull`을 시도했을 때 Git은 두 변경사항 중 어떤 것을 선택해야 할지 자동으로 결정할 수 없습니다. 이런 상황을 충돌(Conflict)이라고 합니다.
충돌이 발생하면 `git pull`은 실패하고, 충돌이 발생한 파일 목록을 알려줍니다. 해당 파일을 열어보면, Git이 충돌 부분을 다음과 같은 형식으로 표시해 줍니다.
<<<<<<< HEAD
내 로컬 브랜치(main)의 변경 내용
=======
원격 저장소(origin/main)에서 가져온 변경 내용
>>>>>>> a1b2c3d4e5f6g7h8 (커밋 해시)
개발자의 역할은 이 표시자들(`<<<<<<<`, `=======`, `>>>>>>>`)을 모두 제거하고, 두 변경사항을 비교하여 최종적으로 어떤 코드를 남길지 직접 수정하는 것입니다. 두 내용을 모두 남기거나, 하나만 선택하거나, 혹은 완전히 새로운 코드로 합칠 수도 있습니다. 수정을 완료한 후, 해당 파일을 다시 `git add`하고 `git commit`을 실행하여 충돌 해결을 완료했다는 새로운 커밋을 만들어주어야 합니다. (이때 `git commit`을 실행하면 충돌 해결을 위한 기본 커밋 메시지가 자동으로 작성됩니다.)
충돌은 협업 과정에서 자연스럽게 발생하는 현상이므로 두려워할 필요가 없습니다. 중요한 것은 `push`를 하기 전에 항상 `pull`을 먼저 하여 로컬 저장소를 최신 상태로 만들고, 충돌이 발생하면 침착하게 해결하는 습관을 들이는 것입니다. "Pull before you push"는 Git 협업의 황금률 중 하나입니다.
5. 시간 여행과 코드 탐색: 히스토리 활용하기
Git의 핵심 가치는 모든 변경사항을 '기록'하는 데 있습니다. 이 기록, 즉 히스토리를 효과적으로 조회하고 활용하는 것은 문제 해결, 코드 이해, 그리고 실수를 되돌리는 데 매우 중요합니다. Git은 과거로 돌아가거나 특정 시점의 코드를 살펴보는 강력한 시간 여행 도구들을 제공합니다.
커밋 기록 조회하기: `git log`
git log는 저장소의 커밋 히스토리를 시간의 역순(최신 커밋이 가장 위)으로 보여주는 명령어입니다. 각 커밋은 고유한 해시 값, 작성자, 날짜, 그리고 커밋 메시지를 포함하고 있습니다.
$ git log
commit a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 (HEAD -> main, origin/main)
Author: Your Name <youremail@example.com>
Date: Wed Feb 15 11:30:00 2023 +0900
Feat: Implement user authentication
commit 9z8y7x6w5v4u3t2s1r0q9p8o7n6m5l4k3j2i1h0g
Author: Your Name <youremail@example.com>
Date: Tue Feb 14 18:00:00 2023 +0900
Fix: Correct calculation error in payment module
...
기본 출력은 정보가 너무 많아 한눈에 파악하기 어려울 수 있습니다. `git log`는 다양한 옵션을 통해 출력을 원하는 형태로 가공할 수 있습니다.
--oneline: 각 커밋을 한 줄로 요약하여 보여줍니다. 커밋 해시의 앞 7자리와 커밋 메시지 제목만 간결하게 출력되어 전체적인 흐름을 파악하기 좋습니다.
$ git log --oneline
a1b2c3d (HEAD -> main, origin/main) Feat: Implement user authentication
9z8y7x6 Fix: Correct calculation error in payment module
...
--graph: 브랜치와 병합(merge) 히스토리를 아스키 그래프로 시각화하여 보여줍니다. 브랜치가 어떻게 분기되고 합쳐졌는지 직관적으로 이해할 수 있어 복잡한 히스토리 분석에 필수적입니다.--all: 현재 브랜치뿐만 아니라, 로컬에 존재하는 모든 브랜치의 커밋 히스토리를 함께 보여줍니다.-p 또는 --patch: 각 커밋에 포함된 실제 코드 변경 내용(diff)까지 함께 보여줍니다. 어떤 코드가 추가되고 삭제되었는지 상세하게 확인할 수 있습니다.--author="Author Name": 특정 작성자가 만든 커밋만 필터링하여 보여줍니다.--grep="Keyword": 커밋 메시지에 특정 키워드가 포함된 커밋만 필터링합니다.이 옵션들은 조합하여 사용할 수 있습니다. 예를 들어, git log --oneline --graph --all은 모든 브랜치의 히스토리를 한 줄 그래프 형태로 보여주는 매우 유용한 조합입니다.
실수 되돌리기: `reset`과 `revert`
개발을 하다 보면 실수는 필연적으로 발생합니다. 잘못된 코드를 커밋했거나, 불필요한 파일을 커밋에 포함시켰을 수 있습니다. Git은 이러한 실수를 안전하게 되돌릴 수 있는 여러 방법을 제공하며, 그중 가장 대표적인 것이 `git reset`과 `git revert`입니다.
히스토리 자체를 수정하는 `git reset`
`git reset`은 현재 브랜치가 가리키는 커밋 위치(HEAD)를 과거의 특정 커밋으로 되돌리는 강력한 명령어입니다. 하지만 강력한 만큼 위험성도 따르기 때문에, 그 원리를 정확히 이해하고 사용해야 합니다. `reset`은 주로 아직 원격 저장소에 push하지 않은 로컬 커밋들을 정리할 때 사용합니다.
`reset`에는 세 가지 주요 옵션이 있으며, 이 옵션에 따라 작업 디렉토리와 스테이징 영역의 처리 방식이 달라집니다.
-
--soft: HEAD 포인터만 지정한 커밋으로 이동시킵니다. 즉, 과거로 돌아가지만, 그동안 작업했던 변경 내용들은 모두 스테이징 영역(Staging Area)에 그대로 남아있습니다. 여러 개의 커밋을 하나로 합치고 싶을 때(squash) 유용하게 사용됩니다.# 가장 최신 커밋 1개를 취소하고, 해당 변경 내용은 스테이징 상태로 둠 $ git reset --soft HEAD~1 -
--mixed(기본값): HEAD 포인터와 스테이징 영역을 모두 지정한 커밋 상태로 되돌립니다. 변경 내용들은 작업 디렉토리(Working Directory)에 unstaged 상태로 남아있습니다. 최신 커밋 내용이 마음에 들지 않아 다시 작업하고 싶을 때 사용합니다.# 가장 최신 커밋 2개를 취소하고, 해당 변경 내용은 작업 디렉토리에 보존 $ git reset HEAD~2 -
--hard: HEAD 포인터, 스테이징 영역, 작업 디렉토리를 모두 지정한 커밋 상태로 완전히 되돌립니다. 즉, 최신 커밋 이후의 모든 변경 내용이 완전히 삭제됩니다. 복구하기 어렵기 때문에 정말로 해당 변경 내용을 버려도 될 때만 신중하게 사용해야 합니다.# 가장 최신 커밋 3개를 완전히 삭제 (주의!) $ git reset --hard HEAD~3
중요한 경고: `git reset`은 기존의 커밋 히스토리를 직접 수정(삭제)하는 명령어입니다. 따라서, 이미 원격 저장소에 push하여 다른 팀원과 공유된 커밋에 대해서는 절대로 `git reset`을 사용해서는 안 됩니다. 공유된 히스토리를 수정하면 다른 팀원들의 로컬 저장소와 히스토리가 꼬여서 큰 혼란을 야기할 수 있습니다.
실수를 되돌리는 새로운 커밋을 만드는 `git revert`
`git revert`는 과거의 실수를 안전하게 되돌리는 방법입니다. `reset`처럼 기존 히스토리를 삭제하는 대신, 특정 커밋에서 이루어졌던 변경 내용을 정확히 반대로 수행하는 새로운 커밋을 생성합니다.
예를 들어, 'a1b2c3d' 커밋에서 특정 기능을 추가했는데, 이 기능에 버그가 있어서 없애고 싶다고 가정해 봅시다.
# 'a1b2c3d' 커밋의 변경 내용을 되돌리는 새로운 커밋을 생성
$ git revert a1b2c3d
이 명령어를 실행하면, 'a1b2c3d' 커밋에서 추가했던 라인은 삭제하고, 삭제했던 라인은 다시 추가하는 내용의 새로운 커밋이 만들어집니다. 기존의 'a1b2c3d' 커밋 자체는 히스토리에 그대로 남아있기 때문에, '어떤 변경이 있었고, 그 변경이 나중에 되돌려졌다'는 모든 기록이 투명하게 관리됩니다.
이러한 방식 덕분에 `git revert`는 이미 원격 저장소에 공유된 커밋의 실수를 바로잡을 때 매우 안전하고 이상적인 방법입니다. 히스토리를 덮어쓰지 않고 새로운 커밋을 추가하는 방식이므로, 다른 팀원들과의 협업에 아무런 문제를 일으키지 않습니다.
| 구분 | git reset |
git revert |
|---|---|---|
| 작업 방식 | 기존 커밋 히스토리를 수정/삭제하여 과거로 되돌아감 | 특정 커밋의 변경사항을 취소하는 새로운 커밋을 생성 |
| 히스토리 | 과거 커밋이 사라짐 (재작성됨) | 과거 커밋과 되돌린 커밋이 모두 남음 |
| 주요 사용처 | 아직 push하지 않은 로컬 커밋 정리 | 이미 push하여 공유된 커밋의 실수를 되돌릴 때 |
| 안전성 | 공유된 히스토리에 사용 시 매우 위험 | 협업 환경에서 안전하게 사용 가능 |
6. 안전하고 효율적인 협업의 핵심: 브랜치(Branch)
Git을 다른 버전 관리 시스템과 차별화하는 가장 강력한 기능을 꼽으라면 단연 브랜치(Branch)입니다. 브랜치는 마치 평행 우주처럼, 기존의 안정적인 코드(예: `main` 브랜치)에 아무런 영향을 주지 않으면서 새로운 기능 개발, 버그 수정, 실험 등을 자유롭게 할 수 있는 독립적인 작업 공간을 제공합니다.
브랜치를 사용하면 다음과 같은 이점을 얻을 수 있습니다.
- 독립적인 작업: 여러 기능을 동시에 개발할 때, 각 기능별로 브랜치를 만들어 작업하면 서로의 코드가 섞여 충돌하는 것을 방지할 수 있습니다.
- 안정적인 버전 관리: 사용자가 직접 사용하는 안정적인 버전은 `main` 브랜치에서 관리하고, 새로운 기능 개발은 별도의 `feature` 브랜치에서 진행합니다. 개발이 완료되고 충분한 테스트를 거친 후에만 `main` 브랜치에 병합(merge)함으로써, 메인 코드의 안정성을 항상 유지할 수 있습니다.
- 신속한 버그 수정: 긴급한 버그가 발생했을 때, `hotfix` 브랜치를 즉시 생성하여 빠르게 수정한 뒤 `main` 브랜치에 병합하고 배포할 수 있습니다. 진행 중이던 다른 기능 개발 작업에 전혀 방해를 받지 않습니다.
브랜치 다루기: 생성, 전환, 삭제
브랜치 생성 및 목록 확인: `git branch`
새로운 브랜치를 생성하려면 `git branch <브랜치 이름>` 명령어를 사용합니다.
# 'feature/login' 이라는 이름의 새 브랜치를 생성
$ git branch feature/login
이 명령어는 브랜치를 만들기만 할 뿐, 현재 작업 위치를 해당 브랜치로 옮겨주지는 않습니다. 현재 로컬 저장소에 있는 모든 브랜치의 목록을 보려면 `git branch` 명령어를 그냥 실행하면 됩니다. * 기호가 붙어있는 브랜치가 현재 내가 작업하고 있는 브랜치(HEAD가 가리키는 브랜치)입니다.
$ git branch
feature/login
* main
브랜치 전환하기: `git checkout` 또는 `git switch`
생성한 브랜치로 작업 공간을 옮기려면 `git checkout <브랜치 이름>` 명령어를 사용합니다.
$ git checkout feature/login
Switched to branch 'feature/login'
이제부터의 모든 `commit`은 `main` 브랜치가 아닌 `feature/login` 브랜치에 기록됩니다. 작업 디렉토리의 파일들도 해당 브랜치의 최신 커밋 상태로 변경됩니다.
브랜치를 생성함과 동시에 해당 브랜치로 전환하고 싶을 때는 `-b` 옵션을 사용할 수 있습니다.
# 'feature/signup' 브랜치를 만들고 바로 그 브랜치로 전환
$ git checkout -b feature/signup
Switched to a new branch 'feature/signup'
최신 버전의 Git(2.23 이상)에서는 역할이 더 명확한 `git switch` 명령어를 사용하는 것을 권장합니다. `checkout`은 브랜치 전환 외에도 특정 커밋 시점의 파일 복원 등 여러 기능을 가지고 있어 혼란을 줄 수 있기 때문입니다.
# 'feature/signup' 브랜치를 만들고 전환
$ git switch -c feature/signup
# 기존 'main' 브랜치로 전환
$ git switch main
브랜치 삭제하기: `git branch -d`
기능 개발이 완료되어 `main` 브랜치에 병합된 후에는, 더 이상 필요 없어진 기능 브랜치를 삭제하여 저장소를 깔끔하게 유지하는 것이 좋습니다.
# 'feature/login' 브랜치를 삭제
$ git branch -d feature/login
Deleted branch feature/login (was a1b2c3d).
만약 아직 병합되지 않은 변경사항이 있는 브랜치를 강제로 삭제하고 싶다면 대문자 `-D` 옵션을 사용해야 하지만, 작업 내용을 잃을 수 있으므로 신중해야 합니다.
나뉜 작업을 하나로 합치기: `git merge`
독립된 브랜치에서 기능 개발이나 버그 수정이 완료되었다면, 그 변경 내용을 `main`과 같은 주축 브랜치에 통합해야 합니다. 이 과정을 병합(Merge)이라고 하며, `git merge` 명령어를 사용합니다.
예를 들어, `feature/login` 브랜치의 작업을 `main` 브랜치로 가져오고 싶다고 가정해 봅시다. 과정은 다음과 같습니다.
- 먼저, 병합의 대상이 되는 브랜치(여기서는 `main`)로 이동합니다.
- `pull`을 통해 `main` 브랜치를 최신 상태로 유지합니다. (협업 시 중요)
- `git merge` 명령어 뒤에 합치고 싶은 브랜치 이름을 적어 실행합니다.
$ git switch main
$ git pull origin main
$ git merge feature/login
병합 과정에서 `pull`을 할 때와 마찬가지로 코드 충돌(Conflict)이 발생할 수 있습니다. `main` 브랜치와 `feature/login` 브랜치에서 같은 파일의 같은 부분을 다르게 수정했다면, Git은 자동으로 병합할 수 없어 사용자에게 해결을 요청합니다. 해결 과정은 앞서 설명한 충돌 해결 방법과 동일합니다.
병합이 성공적으로 완료되면, `feature/login` 브랜치에서 작업했던 모든 커밋과 변경 내용이 `main` 브랜치의 히스토리에 통합됩니다. 이제 `main` 브랜치를 원격 저장소에 `push`하여 팀원들과 공유할 수 있습니다.
마치며
지금까지 우리는 Git이라는 강력한 도구를 사용하여 소스 코드의 버전을 관리하고 협업하는 기본적인 워크플로우를 살펴보았습니다. init으로 저장소를 만들고, add와 commit으로 내 컴퓨터에서 변경사항을 기록하며, push와 pull을 통해 동료들과 작업을 공유하는 이 흐름은 모든 Git 작업의 근간을 이룹니다.
또한, `log`를 통해 과거의 기록을 탐색하고, `reset`과 `revert`로 실수를 되돌리며, `branch`를 활용해 복잡한 작업을 체계적으로 관리하는 방법을 배웠습니다. 이 명령어들은 단순한 기능의 나열이 아니라, 소프트웨어 개발이라는 혼돈의 과정에 질서와 안정성을 부여하는 철학을 담고 있습니다.
Git의 세계는 이 글에서 다룬 내용보다 훨씬 더 깊고 넓습니다. 대화형 리베이스(Interactive Rebase), 체리픽(Cherry-pick), 스태시(Stash) 등 더 고급 기능들이 여러분을 기다리고 있습니다. 하지만 오늘 배운 핵심 개념과 명령어들을 꾸준히 사용하며 손에 익히는 것이 무엇보다 중요합니다. 모든 복잡한 기술은 결국 탄탄한 기본기 위에 세워지기 때문입니다. 이제 여러분은 코드의 시간을 지배하고, 더 자신감 있게 협업하며, 더 나은 개발자로 성장할 수 있는 튼튼한 첫걸음을 내디뎠습니다.
0 개의 댓글:
Post a Comment