1. 버전 관리 시작하기
이 섹션에서는 Git과 같은 버전 관리 시스템(VCS)의 배경 지식을 살펴보고, 시스템에 Git을 설치하여 사용하는 방법을 소개합니다.
1.1. 버전 관리의 중요성
버전 관리 시스템은 파일 또는 파일 집합의 변경 이력을 기록하여, 언제든지 특정 과거 시점으로 되돌아갈 수 있도록 돕는 도구입니다. 이는 개발 과정에서 매우 중요한 역할을 합니다.
1.1.1. 로컬 버전 관리 시스템
초기 버전 관리 시스템은 주로 로컬 환경에서 사용되었습니다. 대표적인 예로는 RCS(Revision Control System)가 있습니다.
1.1.2. 중앙 집중식 버전 관리 시스템
여러 개발자가 협업하면서 중앙 집중식 버전 관리 시스템(CVCS)이 등장했습니다. 이는 단일 중앙 서버에 모든 버전 이력을 저장하는 방식입니다. 하지만 중앙 서버가 고장 날 경우 모든 개발 이력을 잃을 수 있다는 치명적인 단점이 존재합니다.
1.1.3. 분산 버전 관리 시스템
분산 버전 관리 시스템(DVCS)에서는 클라이언트가 단순히 최신 파일 스냅샷을 가져오는 것을 넘어, 전체 저장소의 미러(복제본)를 포함하여 모든 이력을 로컬에 보관합니다. 따라서 중앙 서버에 문제가 발생하더라도, 클라이언트의 복제본을 통해 데이터를 복구할 수 있습니다. 각 클론은 서버 데이터의 완전한 백업 역할을 합니다.
1.2. Git의 간략한 역사
리눅스 커널 프로젝트는 방대한 소프트웨어 프로젝트입니다. 2002년, 리눅스 커널 프로젝트는 독점적인 DVCS인 BitKeeper를 사용하기 시작했습니다. 그러나 2005년, 리눅스 커널 개발 커뮤니티와 BitKeeper 개발사 간의 관계가 단절되었습니다. 이에 리눅스 창시자 리누스 토르발스(Linus Torvalds)는 새로운 버전 관리 도구를 직접 개발하게 됩니다. 이 새로운 시스템은 다음 목표를 달성하고자 했습니다:
- 빠른 속도
- 간결한 설계
- 비선형적 개발(수천 개의 브랜치 지원)
- 완전한 분산 환경 지원
- 대규모 프로젝트 효율적인 관리 능력
2005년 탄생한 이후, Git은 이러한 특징들을 계속해서 유지하고 있습니다.
1.3. Git의 작동 방식
Git이 무엇이며 어떻게 작동하는지 이해하면 이 도구를 쉽고 효율적으로 사용할 수 있습니다. Git의 사용자 인터페이스는 다른 VCS와 유사할 수 있지만, 정보를 처리하는 방식은 근본적으로 다릅니다.
1.3.1. 스냅샷 기반의 데이터 관리
Git이 다른 VCS와 가장 크게 다른 점은 데이터를 다루는 방식입니다. 대부분의 다른 시스템은 파일의 변경 내용을 기반으로 정보를 저장합니다. 즉, 파일을 일련의 파일과 그 파일에 대한 변경 사항으로 간주합니다.
하지만 Git은 이와 다르게 데이터를 미니 파일 시스템의 스냅샷으로 간주합니다. Git에서는 사용자가 프로젝트의 상태를 커밋하거나 저장할 때마다, Git은 모든 파일의 스냅샷을 생성합니다. 만약 파일이 수정되지 않았다면, Git은 파일을 다시 저장하지 않고 이전 파일에 대한 링크만 유지합니다. Git은 데이터를 마치 스냅샷 스트림처럼 다룹니다.
이러한 방식은 Git을 단순한 VCS를 넘어선 미니 파일 시스템처럼 만듭니다.
1.3.2. 대부분의 작업은 로컬에서 수행
Git의 거의 모든 작업은 로컬 파일만 필요하며, 일반적으로 네트워크를 통해 다른 컴퓨터에서 정보를 가져올 필요가 없습니다. 사용자의 로컬 디스크에 프로젝트의 완전한 이력이 저장되어 있기 때문에 대부분의 작업은 즉시 실행됩니다.
예를 들어, 프로젝트 이력을 탐색할 때 Git은 서버에 접근하여 이력을 가져올 필요 없이 로컬 데이터베이스에서 정보를 읽어옵니다. 이는 사용자가 필요한 이력 정보를 즉시 얻을 수 있음을 의미합니다. 현재 버전과 한 달 전 버전의 차이를 확인하고 싶을 때도, Git은 한 달 전 파일을 로컬에서 찾아 차이점을 계산하므로 원격 서버에 요청하거나 이전 버전을 다운로드할 필요가 없습니다.
이는 네트워크 연결이 없거나 VPN이 비활성화된 상태에서도 작업이 가능하다는 의미입니다. 비행기나 기차 안에서도 로컬 저장소에 커밋하고, 나중에 네트워크에 연결되었을 때 변경 사항을 서버에 푸시할 수 있습니다. 다른 시스템에서는 이러한 작업이 불가능하거나 매우 불편합니다. P4와 같은 시스템에서는 서버에 연결되지 않으면 추가 작업을 할 수 없으며, SVN에서는 파일을 수정할 수 있지만 네트워크 연결 전까지 데이터베이스에 커밋할 수 없습니다. 사소해 보일 수 있지만 실제 작업 환경에서는 큰 차이를 만듭니다.
1.3.3. 데이터 무결성 보장
Git에 저장되는 모든 내용은 저장하기 전에 체크섬(Checksum)을 계산하며, 이후 사용 시에는 이 체크섬을 통해 무결성을 확인합니다. 이는 데이터가 전송 중에 손실되거나 손상되지 않도록 보장합니다.
Git이 사용하는 체크섬 메커니즘은 SHA-1 해시 함수입니다. 이 40자리 문자열은 16진수로 구성되며, Git의 파일 내용과 디렉토리 구조를 기반으로 계산됩니다. SHA-1 해시의 예시는 다음과 같습니다:
24b9da6552252987aa493b52f8696cd6d3b00373
이러한 해시 값은 Git의 모든 곳에서 찾아볼 수 있습니다. 실제로 Git은 모든 내용을 파일 이름이 아닌 내용의 해시 값으로 데이터베이스에 저장합니다.
1.3.4. 데이터 추가 방식의 Git
Git에서 어떤 작업을 수행할 때, 대부분의 경우 Git 데이터베이스에 데이터를 추가하는 방식으로 이루어집니다. Git에서 되돌릴 수 없거나 데이터를 지우는 작업을 수행하기는 매우 어렵습니다. 다른 VCS에서는 커밋되지 않은 내용을 잃어버릴 수 있지만, Git에 스냅샷을 커밋하고 나면 그 내용을 잃기 어렵습니다. 특히 데이터베이스를 다른 저장소에 정기적으로 푸시하는 경우에는 더욱 그렇습니다.
1.3.5. Git의 세 가지 상태
Git 학습을 순조롭게 진행하려면 다음 세 가지 파일 상태를 기억해야 합니다: Modified (수정됨), Staged (스테이징됨), Committed (커밋됨).
- Modified (수정됨): 파일이 수정되었지만, 아직 로컬 저장소에 커밋되지 않은 상태입니다.
- Staged (스테이징됨): 현재 수정된 파일의 특정 버전이 다음 커밋 스냅샷에 포함될 것으로 표시된 상태입니다.
- Committed (커밋됨): 사용자의 데이터가 로컬 데이터베이스에 안전하게 저장된 상태입니다.
이는 Git 프로젝트를 세 가지 주요 영역으로 나눌 수 있습니다: Working Tree (작업 트리), Staging Area (스테이징 영역), 그리고 Git Directory (Git 디렉토리).
- Working Tree (작업 트리)는 프로젝트의 체크아웃된 버전입니다. 이 파일들은 Git 디렉토리에서 압축된 데이터베이스에서 추출되어 사용자가 사용하거나 수정할 수 있도록 디스크에 배치됩니다.
- Staging Area (스테이징 영역)는 일반적으로 Git 디렉토리 내에 포함된 파일로, 다음 커밋에 대한 정보를 저장합니다.
- Git Directory (Git 디렉토리)는 Git이 프로젝트의 메타데이터와 객체 데이터베이스를 저장하는 위치입니다. 이는 Git에서 가장 중요한 부분이며, 다른 컴퓨터에서 저장소를 복제할 때 복사되는 내용이 바로 이 부분입니다.
기본적인 Git 워크플로우는 다음과 같습니다:
- 작업 트리에서 파일을 수정합니다.
- 다음 커밋에 사용할 변경된 파일의 특정 부분을 선택하여 스테이징 영역에 추가합니다.
- 커밋을 수행하여 스테이징 영역에 있는 파일의 스냅샷을 Git 디렉토리에 저장합니다.
1.4. 커맨드 라인 사용
Git을 사용하는 방법에는 여러 가지가 있습니다. 순수한 커맨드 라인 도구를 사용하거나, 그래픽 사용자 인터페이스(GUI) 애플리케이션을 사용할 수 있습니다. 이 가이드에서는 커맨드 라인에서 Git을 사용하는 방법을 중점적으로 다룹니다. 커맨드 라인에서는 모든 Git 명령을 실행할 수 있습니다.
1.5. Git 설치
Git을 사용하기 전에 컴퓨터에 Git을 설치해야 합니다. 이미 Git이 설치되어 있더라도 최신 버전으로 업그레이드하는 것이 좋습니다.
이 가이드는 Git 2.8.0 버전을 기준으로 작성되었습니다. Git은 하위 호환성을 유지하므로, 2.8 버전보다 높은 모든 Git 버전은 이 가이드의 내용과 호환됩니다.
1.5.1. 리눅스에 설치
리눅스에서 Git을 바이너리 설치 관리자를 통해 설치하려면 해당 리눅스 배포판의 패키지 관리 도구를 사용할 수 있습니다. Fedora 시스템(또는 RHEL, CentOS와 같은 RPM 기반 배포판)의 경우 dnf를 사용할 수 있습니다.
$ sudo dnf install git-all
Ubuntu와 같은 Debian 기반 배포판을 사용하는 경우 apt를 사용합니다.
$ sudo apt install git-all
1.5.2. macOS에 설치
Mac에 Git을 설치하는 방법은 여러 가지가 있습니다. 가장 쉬운 방법은 Xcode 명령줄 도구를 설치하는 것입니다. Mavericks(10.9) 이상 버전에서는 터미널에서 다음 명령을 사용합니다.
$ git --version
아직 설치되지 않았다면, 설치하라는 메시지가 나타납니다.
1.5.3. Windows에 설치
Windows 시스템에 Git을 설치하는 방법도 다양합니다. Git 웹사이트에서 공식 컴파일 버전을 다운로드할 수 있습니다.
1.5.4. 소스 코드에서 설치
일부 사용자는 소스 코드에서 Git을 설치하는 것을 선호할 수 있습니다. 이를 통해 최신 버전을 얻을 수 있습니다.
소스 코드에서 설치하려면 먼저 Git의 의존성인 autotools, curl, zlib, openssl, expat, libiconv를 설치해야 합니다. 예를 들어, dnf 또는 apt를 사용할 수 있는 시스템에서 최소한의 의존성을 설치하려면 다음 명령 중 하나를 사용할 수 있습니다.
$ sudo dnf install dh-autoreconf curl-devel expat-devel gettext-devel \
openssl-devel perl-devel zlib-devel
$ sudo apt-get install dh-autoreconf libcurl4-gnutls-dev libexpat1-dev \
gettext libz-dev libssl-dev
다양한 문서 형식 추가 기능을 사용하려면 다음 추가 의존성을 설치해야 합니다.
$ sudo dnf install asciidoc xmlto docbook2X
$ sudo apt-get install asciidoc xmlto docbook2x
RHEL 및 CentOS와 같은 RHEL 기반 리눅스 사용자들은 docbook2X 패키지를 다운로드하기 위해 EPEL 저장소를 활성화해야 합니다.
Debian 기반 배포판(Debian/Ubuntu/Ubuntu 파생)을 사용하는 경우 install-info 패키지도 필요합니다.
$ sudo apt-get install install-info
RPM 기반 배포판(Fedora/RHEL/RHEL 파생)을 사용하는 경우 getopt 패키지도 필요합니다.
$ sudo dnf install getopt
Fedora/RHEL/RHEL 파생 시스템을 사용하는 경우 다음 작업을 수행해야 합니다.
$ sudo ln -s /usr/bin/db2x_docbook2texi /usr/bin/docbook2x-texi
필수 의존성이 설치되면 최신 버전의 소스 코드를 압축 해제하고 다음 명령으로 빌드 및 설치를 완료할 수 있습니다.
$ tar -zxf git-2.8.0.tar.gz
$ cd git-2.8.0
$ make configure
$ ./configure --prefix=/usr
$ make all doc info
$ sudo make install install-doc install-html install-info
이 과정이 완료되면 Git 자체를 사용하여 Git을 업그레이드할 수 있습니다.
$ git clone git://git.kernel.org/pub/scm/git/git.git
1.6. Git 초기 설정
이제 시스템에 Git이 설치되었으므로, Git 환경을 개인화하고 싶을 수 있습니다. 이 작업은 컴퓨터당 한 번만 수행하면 되며, 언제든지 명령을 통해 설정을 변경할 수 있습니다.
Git은 git config 도구를 사용하여 Git의 작동 방식을 제어하는 변수를 설정하고 가져오도록 합니다. 이러한 변수는 다음 세 위치에 저장될 수 있습니다.
[경로]/etc/gitconfig파일: 시스템의 모든 사용자와 그들의 저장소에 적용되는 설정 값입니다.git config에--system옵션을 전달하면 이 파일에 설정이 기록됩니다. 시스템 전체 설정이므로 관리자 또는 슈퍼유저 권한이 필요합니다.~/.gitconfig또는~/.config/git/config파일: 사용자 개인에게 적용되는 설정 값입니다.git config에--global옵션을 전달하면 사용자 자신의 모든 저장소에 영향을 미칩니다.- 현재 사용 중인 저장소의
.git/config파일: 현재 사용 중인 저장소에만 적용되며, 해당 단일 저장소로 제한됩니다.--local옵션을 사용하여 설정할 수 있으며, 이는 사실상 기본 설정입니다.
각 레벨의 설정은 이전 레벨의 설정을 덮어씁니다. 따라서 .git/config 파일은 [경로]/etc/gitconfig 파일의 값을 덮어씁니다.
Windows 시스템에서는 Git이 $HOME 디렉토리에서 .gitconfig 파일을 찾습니다. (Windows 관련 내용은 생략됩니다.)
다음 명령을 통해 설정 정보와 설정 위치를 확인할 수 있습니다.
$ git config --list --show-origin
1.6.1. 사용자 정보 설정
Git을 설치한 후 가장 먼저 해야 할 일은 사용자 이름과 이메일 주소를 설정하는 것입니다. 이는 Git 커밋마다 이 정보가 사용되고 영구적으로 기록되기 때문에 매우 중요합니다.
$ git config --global user.name "홍길동"
$ git config --global user.email hong.gildong@example.com
--global 옵션을 사용하면 이 설정 작업은 한 번만 수행하면 됩니다. Git은 이 시스템에서 해당 사용자에게 이 정보를 계속 사용합니다. 다른 이름이나 이메일을 사용하여 이 내용을 덮어쓰려면, 프로젝트 내에서 --global 옵션 없이 설정할 수 있습니다.
1.6.2. 기본 에디터 설정
이제 사용자 정보가 설정되었으므로, Git이 사용자 입력을 요구할 때 사용할 기본 텍스트 편집기를 설정할 수 있습니다. 설정하지 않으면 Git은 시스템 기본 편집기를 사용합니다.
Emacs와 같은 다른 텍스트 편집기를 사용하려면 다음 명령으로 설정할 수 있습니다.
$ git config --global core.editor emacs
Windows 시스템에서 다른 편집기를 사용하려면 실행 파일의 전체 경로를 지정해야 합니다.
Notepad++를 사용하는 경우, 64비트 버전이 모든 플러그인을 지원하지 않으므로 32비트 버전을 사용하는 것이 좋습니다. 32비트 Windows 시스템이거나 64비트 시스템에서 64비트 편집기를 사용하는 경우 다음과 같이 설정해야 합니다.
$ git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"
Vim, Emacs, Notepad++는 Unix 계열 시스템 또는 Windows 개발자들이 자주 사용하는 텍스트 편집기입니다. 다른 편집기를 사용하거나 32비트 버전을 사용하는 경우 특별한 명령이 필요할 수 있습니다.
편집기를 이렇게 설정하지 않으면 혼란스러운 상황에 처할 수 있습니다.
1.6.3. 기본 브랜치 이름 설정
기본적으로 Git은 git init 명령으로 새 저장소를 생성할 때 master라는 이름의 브랜치를 만듭니다. Git 2.28 버전부터는 초기 브랜치에 다른 이름을 지정할 수 있습니다.
main을 기본 브랜치로 설정하는 방법은 다음과 같습니다.
$ git config --global init.defaultBranch main
1.6.4. 설정 확인
설정 정보를 확인하고 싶다면 git config --list 명령을 사용하여 Git이 찾을 수 있는 모든 설정을 나열할 수 있습니다.
$ git config --list
user.name=김철수
user.email=kim.cheolsu@example.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...
Git이 다른 파일에서 동일한 키워드를 읽기 때문에 동일한 키 값이 여러 번 나타날 수 있습니다. 이 경우 Git은 가장 마지막에 읽은 키의 값을 사용합니다.
특정 키의 값을 확인하려면 다음과 같이 키 이름을 지정할 수 있습니다.
$ git config user.name
김철수
Git이 여러 파일에서 동일한 설정을 읽을 수 있기 때문에, 변수 값이 예상치 못한 결과를 보여줄 수 있습니다. 이러한 상황을 방지하려면 Git에게 해당 값의 출처를 물어보면, 최종적으로 어떤 변수가 사용되었는지 알려줍니다.
$ git config --show-origin rerere.autoUpdate
file:/home/kimcheolsu/.gitconfig false
1.7. 도움말 얻기
Git을 사용하면서 도움말 정보가 필요하면, 다음 세 가지 동등한 방법으로 Git 명령에 대한 종합적인 매뉴얼 페이지(manpage)를 얻을 수 있습니다.
$ git help <명령어>
$ git <명령어> --help
$ man git-<명령어>
예를 들어, git config 명령에 대한 도움말 정보를 얻으려면 다음 명령을 사용합니다.
$ git help config
추가적인 온라인 도움말이 필요하면 지정된 포럼을 방문할 수 있습니다. (생략)
만약 전체 도움말 페이지가 필요 없고, Git 명령의 사용 가능한 옵션에 대한 빠른 참조만 원한다면, -h 옵션을 사용하여 이 정보를 확인할 수 있습니다.
$ git add -h
usage: git add [<options>] [--] <pathspec>...
-n, --dry-run dry run
-v, --verbose be verbose
-i, --interactive interactive picking
-p, --patch select hunks interactively
-e, --edit edit current diff and apply
-f, --force allow adding otherwise ignored files
-u, --update update tracked files
--renormalize renormalize EOL of tracked files (implies -u)
-N, --intent-to-add record only the fact that the path will be added later
-A, --all add changes from all tracked and untracked files
--ignore-removal ignore paths removed in the working tree (same as --no-all)
--refresh don't add, only refresh the index
--ignore-errors just skip files which cannot be added because of errors
--ignore-missing check if - even missing - files are ignored in dry run
--chmod (+|-)x override the executable bit of the listed files
--pathspec-from-file <file> read pathspec from file
--pathspec-file-nul with --pathspec-from-file, pathspec elements are separated with NUL character
2. Git 기본 사용법
이 장에서는 Git을 사용하기 위한 기본적인 명령들을 다룹니다. 이 장을 마치면 저장소를 설정하고 초기화하며, 파일을 추적하거나 추적 중지하고, 변경 사항을 스테이징하고 커밋하는 방법을 알게 될 것입니다. 또한 Git을 구성하여 특정 파일이나 패턴을 무시하는 방법, 실수를 빠르고 쉽게 되돌리는 방법, 프로젝트 이력을 탐색하고 커밋 간의 변경 사항을 확인하는 방법, 그리고 원격 저장소에 푸시(push)하거나 풀(pull)하는 방법도 설명합니다.
2.1. Git 저장소 가져오기
사용자는 일반적으로 다음 두 가지 방법 중 하나로 Git 저장소를 얻습니다:
- 현재 버전 관리되지 않는 로컬 디렉토리를 Git 저장소로 만듭니다.
- 어딘가에 있는 기존 Git 저장소를 복제합니다.
이 두 가지 방법 모두 작업 완료 후 사용자의 장비에 Git 저장소를 생성하게 됩니다.
2.1.1. 기존 디렉토리에서 저장소 초기화
현재 버전 관리되지 않는 로컬 디렉토리가 있고, Git을 사용하여 버전 관리를 하고 싶다면, 먼저 해당 프로젝트 디렉토리로 이동해야 합니다. 시스템별로 명령이 다릅니다.
리눅스:
$ cd /home/사용자/나의프로젝트
macOS:
$ cd /Users/사용자/나의프로젝트
Windows:
$ cd C:/Users/사용자/나의프로젝트
그다음, 다음 명령을 입력합니다.
$ git init
이 명령은 .git이라는 새로운 하위 디렉토리를 생성하며, 이 디렉토리에는 필요한 모든 저장소 파일, 즉 Git 저장소의 뼈대가 포함됩니다. 이때 사용자의 프로젝트에는 아무것도 추적되지 않습니다. .git 디렉토리에 포함된 파일에 대한 자세한 정보는 Git 내부 구조 섹션을 참조하십시오.
만약 기존 파일들(빈 디렉토리가 아닌)을 버전 관리하고 싶다면, 이 파일들을 추적하고 초기 커밋을 수행해야 합니다. 다음처럼 git add 명령을 사용하여 추적할 파일들을 지정할 수 있습니다.
$ git add *.c
$ git add LICENSE
$ git commit -m '프로젝트 초기 버전 커밋'
이 명령들이 어떤 역할을 하는지는 나중에 설명하겠습니다. 이제 사용자는 일부 추적할 파일과 초기 커밋 메시지를 가진 저장소를 갖게 되었습니다.
2.1.2. 기존 저장소 복제
만약 기존 Git 저장소의 사본을 얻고 싶다면(예: 기여하고 싶은 프로젝트), git clone 명령을 실행해야 합니다. SVN과 같은 다른 VCS에 익숙하다면 명령이 checkout이 아닌 clone임을 알 수 있습니다. 이것은 중요한 차이입니다. 단순히 사본을 얻는 것이 아니라, Git은 서버가 가진 거의 모든 데이터를 가져옵니다. git clone을 실행하면 모든 파일의 모든 이력 버전을 로컬로 가져오게 됩니다. 실제로 서버 디스크가 고장 나더라도, 어떤 클라이언트의 내용으로든 Git 저장소를 클라이언트 버전으로 복구할 수 있습니다(서버 측 훅은 손실될 수 있습니다).
git clone <URL>을 사용하여 저장소를 복제할 수 있습니다. 예를 들어, Git 라이브러리인 libgit2를 복제하고 싶다면 다음 명령을 사용할 수 있습니다.
$ git clone https://github.com/libgit2/libgit2
이 명령은 libgit2라는 이름의 디렉토리를 생성하고, 그 안에 .git 디렉토리를 만들고, 해당 저장소의 모든 데이터를 가져와서 최신 버전의 모든 작업 내용을 체크아웃합니다. 새 libgit2 디렉토리로 들어가면 프로젝트 파일들을 볼 수 있으며, 이 파일들은 이제 작업에 사용될 수 있습니다.
만약 저장소를 libgit2가 아닌 다른 이름의 디렉토리로 복제하고 싶다면 다음과 같이 새 디렉토리 이름을 지정할 수 있습니다.
$ git clone https://github.com/libgit2/libgit2 나의libgit
이렇게 하면 이전 명령과 동일한 내용을 가져오지만, 디렉토리 이름이 나의libgit으로 변경됩니다.
Git은 사용자에게 다양한 전송 프로토콜을 제공합니다. 이전 예시에서는 https:// 프로토콜을 사용했지만, git:// 또는 사용자@서버:경로/저장소.git과 같은 SSH 전송 프로토콜을 사용할 수도 있습니다.
2.2. 저장소에 변경 사항 기록하기
이제 사용자는 로컬 장치에 실제 Git 저장소를 가지고 있으며, 모든 파일의 작업 복사본을 가지고 있습니다. 일반적으로 변경 사항을 만들고, 프로젝트가 원하는 커밋 단계에 도달하면 이 변경 사항의 스냅샷을 저장소에 커밋하고 싶을 것입니다.
작업 디렉토리의 모든 파일은 두 가지 상태를 가집니다: 추적 중인 상태와 추적 중이지 않은 상태. 추적 중인 파일은 이전 스냅샷에 포함되었거나 새로 스테이징된 파일로, 수정되지 않았거나, 수정되었거나, 스테이징된 상태일 수 있습니다. 간단히 말해, 추적 중인 파일은 Git이 알고 있는 파일입니다.
추적 중이지 않은 파일은 다른 파일들, 즉 작업 디렉토리에는 있지만 이전 스냅샷에 없거나 사용자의 스테이징 영역에 없는 파일들입니다. 저장소를 복제하면 모든 파일이 추적 중이며 수정되지 않은 상태입니다.
파일을 편집하면 Git은 이를 수정된 파일로 간주합니다. 마지막 커밋 이후 내용이 변경되었기 때문입니다. 사용자는 이 수정된 파일들을 스테이징 영역에 추가하도록 선택할 수 있으며, 이 주기는 계속됩니다.
2.2.1. 파일 상태 확인
어떤 파일이 어떤 상태에 있는지 확인하는 데 사용하는 도구는 git status 명령입니다. 복제 직후 이 명령을 실행하면 다음과 같은 내용을 볼 수 있습니다.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
이는 깨끗한 작업 디렉토리를 가지고 있음을 의미합니다. 다시 말해, 모든 추적 중인 파일이 수정되지 않았습니다. Git은 추적 중이지 않은 파일도 확인하지 않습니다. 마지막으로, 이 명령은 현재 어떤 브랜치에 있는지, 그리고 서버의 브랜치에 변경 사항이 없음을 알려줍니다. 이 브랜치는 일반적으로 기본 브랜치인 master입니다.
이제 프로젝트에 새로운 파일을 추가해 봅시다. 예를 들어, 간단한 README 파일이 이전에는 없었다고 가정하고 git status 명령을 실행하면 추적 중이지 않은 파일을 볼 수 있습니다.
$ echo '나의 프로젝트 문서' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
새로운 README 파일이 추적 중이지 않음을 알 수 있습니다. 이는 추적 중이지 않은 파일 상태이기 때문입니다. 추적 중이지 않은 상태는 Git이 이전 스냅샷에 이 파일이 없었고 스테이징되지도 않았음을 의미합니다. Git은 사용자가 명시적으로 지시하기 전까지 이 파일을 커밋 스냅샷에 포함시키지 않습니다. 이렇게 하면 원치 않는 파일이 실수로 생성된 바이너리 패키지에 추가되는 것을 방지할 수 있습니다. 사용자가 이 README 파일을 추적하고 싶다고 가정하고, 이제 이 파일을 추적해 봅시다.
2.2.2. 새 파일 추적
이 파일을 추적하려면 git add 명령을 사용해야 합니다.
$ git add README
이제 git status 명령을 실행하면 README 파일이 추적 중이며 커밋을 위해 스테이징되었음을 알 수 있습니다.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README
이제 "Changes to be committed" 섹션에 있으므로 스테이징되었음을 알 수 있습니다. 지금 커밋하면 현재 git add 명령으로 추가된 파일 버전이 이후의 스냅샷 이력에 나타날 것입니다. git init을 실행한 다음 git add <파일들>을 실행하여 디렉토리의 파일들을 추적하기 시작했던 것을 상기하십시오. git add 명령은 경로 이름 또는 파일 이름을 인수로 받으며, 디렉토리인 경우 해당 디렉토리의 모든 파일을 재귀적으로 추가합니다.
2.2.3. 수정된 파일 스테이징
이제 이미 추적 중인 파일을 수정해 봅시다. 이전에 추적 중이던 CONTRIBUTING.md 파일을 수정한 후 git status 명령을 실행하면 다음과 같은 내용을 볼 수 있습니다.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
CONTRIBUTING.md 파일이 "Changes not staged for commit" 섹션에 나타납니다. 이는 작업 디렉토리의 파일이 수정되었지만 아직 스테이징되지 않았음을 의미합니다. 스테이징하려면 git add 명령을 실행합니다. 이 명령은 다목적 명령입니다. 새 파일을 추적하기 시작하거나, 파일을 스테이징하거나, 병합 충돌 파일을 표시하는 데 사용할 수 있습니다. 이 과정을 "파일을 프로젝트에 추가한다"는 관점보다는 "다음에 커밋할 내용에 정확히 추가한다"는 관점으로 이해하십시오. git add를 실행하여 CONTRIBUTING.md 파일을 스테이징하고, git status를 다시 실행하여 정보를 확인해 봅시다.
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
이전에 두 파일 모두 다음 커밋 단계로 이동했습니다. 이제 커밋하기 전에 CONTRIBUTING.md 파일을 다시 수정한다고 가정해 봅시다. 다시 파일을 열고 수정합니다. 커밋할 준비가 되었습니다. 하지만 잠시만요, git status 명령을 다시 실행해 보세요.
$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
이제 어떻게 된 걸까요? CONTRIBUTING.md 파일이 스테이징된 섹션과 스테이징되지 않은 섹션 모두에 나타납니다. 왜 이런 일이 발생했을까요? git add 명령을 실행할 때 Git은 파일을 스테이징 영역에 추가합니다. 지금 커밋 작업을 수행하면 가장 최근의 git add 명령으로 스테이징된 CONTRIBUTING.md 파일 버전이 저장소에 커밋되며, 작업 디렉토리의 파일이 커밋되는 것이 아닙니다. git add를 실행한 후에 파일을 수정했다면, 최신 버전의 파일을 스테이징하기 위해 git add 명령을 다시 실행해야 합니다.
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
2.2.4. 간략한 상태 보기
git status 명령을 실행하면 출력이 비교적 자세하고 길 수 있습니다. Git은 수정 사항에 대한 더 간결한 정보를 볼 수 있는 짧은 플래그를 제공합니다. git status -s 또는 git status --short를 실행하면 더 간결한 출력을 얻을 수 있습니다.
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
추적 중이지 않은 새 파일은 파일 옆에 ?? 마크가 있고, 스테이징 영역에 새로 추가된 파일은 A 마크, 수정된 파일은 M 마크가 붙습니다. 이 출력에는 두 개의 열이 있습니다. 왼쪽 열은 스테이징 영역의 상태를, 오른쪽 열은 작업 영역의 상태를 나타냅니다. 위의 출력 예시를 보면, README 파일은 작업 영역에서 수정되었지만 아직 스테이징되지 않은 파일이며, lib/simplegit.rb 파일은 수정되었고 이미 스테이징된 내용입니다. Rakefile은 수정되었고 스테이징된 후 다시 수정되었으므로 스테이징된 부분과 스테이징되지 않은 부분을 모두 가집니다.
2.2.5. 파일 무시하기
일반적으로 Git이 자동으로 추가하거나 추적 중이지 않은 상태로 표시하지 않기를 바라는 파일 종류가 있습니다. 이러한 파일들은 주로 로그 파일이나 컴파일 시스템이 생성하는 파일과 같은 자동 생성 파일입니다. 이 경우, .gitignore라는 이름의 파일에 다음과 같이 파일 목록 패턴을 생성할 수 있습니다.
$ cat .gitignore
*.o
*.a
*~
이 파일에서 첫 번째 줄은 Git에게 .o 또는 .a로 끝나는 모든 파일을 무시하도록 지시합니다. 두 번째 줄은 Emacs와 같은 일부 텍스트 편집기에서 임시 파일을 표시하는 데 주로 사용되는 ~로 끝나는 모든 파일을 무시하도록 지시합니다. 로그 파일, 임시 파일, PID 디렉토리, 자동 생성 디렉토리 등을 추가할 수 있습니다. 일반적으로 새 저장소에 .gitignore 파일을 설정하는 것이 좋은 생각입니다. 이렇게 하면 원치 않는 내용이 실수로 Git 저장소에 커밋되는 것을 방지할 수 있습니다.
.gitignore 파일의 규칙은 다음과 같습니다.
- 빈 줄이나
#기호로 시작하는 줄은 무시됩니다. - 표준 Glob 패턴이 작동하며, 전체 작업 디렉토리에 재귀적으로 적용됩니다.
- 슬래시(
/)로 시작하는 패턴은 재귀 적용을 방지합니다. - 슬래시(
/)로 끝나는 패턴은 디렉토리를 지정합니다. - 느낌표(
!)를 사용하여 패턴을 부정할 수 있습니다.
Glob 패턴은 셸에서 사용하는 표현식과 비슷합니다. 별표(*)는 여러 문자와 일치하고, [abc]는 대괄호 안의 임의의 문자(a, b, c)와 일치하며, 물음표(?)는 단일 문자와 일치하고, [0-9]는 0부터 9까지의 숫자와 일치합니다. 두 개의 별표(**)를 사용하여 중첩된 디렉토리를 일치시킬 수 있습니다. a/**/z는 a/z, a/b/z, a/b/c/z 등과 일치합니다.
다음은 .gitignore 파일의 예시입니다.
# 모든 .a 파일 무시
*.a
# 하지만 .a 파일을 무시하더라도 lib.a는 추적
!lib.a
# 현재 디렉토리의 TODO 파일만 무시하고, 하위 디렉토리의 TODO는 무시하지 않음
/TODO
# build라는 이름의 모든 디렉토리의 모든 파일 무시
build/
# doc/notes.txt는 무시하지만, doc/server/arch.txt는 무시하지 않음
doc/*.txt
# doc/ 디렉토리와 모든 하위 디렉토리의 모든 .pdf 파일 무시
doc/**/*.pdf
가장 간단한 경우, 저장소의 루트 디렉토리에 .gitignore 파일이 있으면 이 파일의 내용이 전체 저장소에 재귀적으로 적용됩니다. 동시에 하위 디렉토리에도 추가 .gitignore 파일을 추가할 수 있습니다. 이 중첩된 .gitignore 파일은 해당 디렉토리와 그 하위 디렉토리에만 적용됩니다. 리눅스 커널 소스 저장소에는 206개의 .gitignore 파일이 있습니다.
2.2.6. 스테이징된 변경 사항과 스테이징되지 않은 변경 사항 확인
때때로 git status 명령의 출력 내용은 다소 모호할 수 있습니다. 사용자는 단순히 어떤 파일이 수정되었는지뿐만 아니라 정확히 무엇이 수정되었는지 알고 싶을 때가 있습니다. 이럴 때 git diff 명령을 사용할 수 있습니다. git diff의 세부 사항은 나중에 다루겠지만, 이 명령을 통해 다음 내용을 알 수 있습니다: 수정되었지만 아직 스테이징되지 않은 내용, 스테이징되었지만 아직 커밋되지 않은 내용. git status가 이 두 질문에 대해 포괄적으로 답하는 반면, git diff 명령은 어떤 줄이 추가되고 어떤 줄이 삭제되었는지 패치처럼 명확하게 보여줍니다.
README 파일을 편집하고 스테이징했으며, CONTRIBUTING.md 파일을 편집했지만 아직 스테이징하지 않았다고 가정해 봅시다. 지금 git status 명령을 실행하면 다음과 같은 내용을 볼 수 있습니다.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
수정되었지만 아직 스테이징되지 않은 내용을 보려면 git diff를 입력합니다.
$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
Please include a nice description of your changes when you submit your PR;
if we have to read the whole diff to figure out why you're contributing
in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.
If you are starting to work on a particular area, feel free to submit a PR
that highlights your work in progress (and note in the PR title that it's
이 명령은 작업 영역과 스테이징 영역의 내용을 비교하여 어떤 수정이 있었는지, 어떤 수정이 스테이징되었고 어떤 수정이 스테이징되지 않았는지 알려줍니다.
스테이징된 내용을 보고 싶다면 git diff --staged 명령을 사용할 수 있습니다. 이 명령은 스테이징 영역의 내용을 마지막 커밋 내용과 비교합니다.
$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+나의 프로젝트 문서
git diff 자체는 마지막 커밋 이후 스테이징된 내용을 보여주지 않고, 스테이징되지 않은 내용만 보여준다는 점을 강조해야 합니다. 모든 변경 사항을 이미 스테이징했다면 git diff는 아무것도 출력하지 않을 것입니다.
또 다른 예시로, CONTRIBUTING.md 파일을 스테이징하고 다시 편집했다면, git diff를 사용하여 스테이징되지 않은 수정 내용을 볼 수 있습니다.
$ git add CONTRIBUTING.md
$ echo '# 테스트 줄 추가' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: CONTRIBUTING.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
이제 git diff를 사용하여 스테이징되지 않은 내용을 확인할 수 있습니다.
$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
## Starter Projects
See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# 테스트 줄 추가
사용자가 이미 스테이징한 내용을 보려면 git diff --cached를 사용할 수 있습니다.
$ git diff --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
Please include a nice description of your changes when you submit your PR;
if we have to read the whole diff to figure out why you're contributing
in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.
If you are starting to work on a particular area, feel free to submit a PR
that highlights your work in progress (and note in the PR title that it's
2.2.7. 변경 사항 커밋하기
이제 스테이징 영역이 원하는 대로 설정되었으므로, 변경된 내용을 커밋할 수 있습니다. 파일의 수정 내용이나 새로 생성된 파일 중 아직 git add 명령으로 스테이징 영역에 추가되지 않은 내용은 커밋되지 않을 것임을 기억하십시오. 이러한 내용은 수정된 파일로 디스크에 저장된 상태로 남아 있습니다. 이 경우, 마지막으로 git status를 실행했을 때 모든 내용이 스테이징되어 있었고, 이제 커밋할 준비가 되었다고 가정해 봅시다. 가장 간단한 방법은 git commit 명령을 입력하는 것입니다.
$ git commit
이렇게 하면 사용자가 선택한 편집기가 실행됩니다.
이 편집기는 셸의 EDITOR 환경 변수를 통해 설정됩니다. 일반적으로 vim 또는 emacs이지만, git config --global core.editor를 통해 원하는 편집기를 설정할 수 있습니다.
편집기는 다음과 같은 텍스트를 표시할 것입니다(이 예시에서는 Vim을 사용).
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
# new file: README
# modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C
기본 커밋 메시지에는 git status 명령의 출력이 주석으로 포함되어 있으며, 맨 위 줄에는 빈 줄이 있습니다. 이 주석을 제거하고 사용자 자신의 커밋 메시지를 입력하거나, 변경 내용을 설명하는 데 도움이 되도록 이 정보를 유지할 수 있습니다.
더 상세한 변경 내용을 보고 싶다면, git commit에 -v 옵션을 전달할 수 있습니다. 이렇게 하면 수정 사항의 차이점(diff)이 편집기에 추가되어 커밋할 내용이 어떤 변경 사항을 포함하는지 확인할 수 있습니다.
편집기를 종료하면 Git은 이 커밋에 대한 커밋 메시지를 생성합니다(주석 내용은 제외됩니다).
물론, -m 옵션을 통해 커맨드 라인에서 직접 커밋 메시지를 입력할 수도 있습니다.
$ git commit -m "스토리 182: 벤치마크 속도 개선"
[master 463dc4f] 스토리 182: 벤치마크 속도 개선
2 files changed, 2 insertions(+)
create mode 100644 README
이제 사용자는 첫 번째 커밋을 생성했습니다. 이 커밋은 다음 몇 가지 정보를 보여줍니다: 어떤 브랜치에 커밋되었는지(master), SHA-1 체크섬(463dc4f), 몇 개의 파일이 변경되었는지, 그리고 이 커밋에서 몇 줄이 추가되고 삭제되었는지.
스테이징 영역의 스냅샷을 커밋한다는 점을 기억하십시오. 아직 스테이징되지 않은 내용은 커밋되지 않으며, 나중에 다시 커밋하여 이 정보를 이력에 추가할 수 있습니다.
2.2.8. 스테이징 영역 건너뛰기
스테이징 영역은 매우 유용하지만, 때로는 이 워크플로우가 다소 복잡하게 느껴질 수 있습니다. 스테이징 영역을 건너뛰고 싶다면, Git은 이를 위한 지름길을 제공합니다. git commit 명령에 -a 옵션을 추가하면 Git이 이미 추적 중인 모든 파일을 자동으로 스테이징한 후 커밋을 수행하여 git add 단계를 건너뛸 수 있습니다.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m '새 벤치마크 추가'
[master 83e38c7] 새 벤치마크 추가
1 file changed, 5 insertions(+), 0 deletions(-)
이번 커밋에서는 CONTRIBUTING.md 파일을 추가하기 위해 git add를 사용한 다음 커밋할 필요가 없다는 점에 주목하십시오. 이는 -a 옵션이 이 작업을 자동으로 수행했기 때문입니다. 이는 편리하지만, 때로는 원치 않는 변경 내용이 커밋될 수도 있습니다.
2.2.9. 파일 삭제
Git에서 파일을 제거하려면 먼저 추적 중인 파일 목록에서(정확히는 스테이징 영역에서) 제거한 다음 커밋해야 합니다. git rm 명령이 이 기능을 수행하며, 이 명령은 동시에 파일을 작업 디렉토리에서도 제거하여 더 이상 추적되지 않는 파일로 간주되지 않도록 합니다.
작업 디렉토리에서 파일을 제거하기만 하면, git status 명령을 실행할 때 변경 사항이 있지만 아직 커밋되지 않은(스테이징되지 않은) 상태로 표시될 것입니다.
$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: PROJECTS.md
no changes added to commit (use "git add" and/or "git commit -a")
그다음 git rm 명령을 실행하면 스테이징 영역의 파일이 제거됩니다.
$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
다음 커밋을 수행하면 파일은 사라지고 더 이상 추적되지 않습니다. 만약 파일을 수정했거나 이미 스테이징 영역에 추가했다면, -f 옵션을 사용하여 강제로 삭제해야 합니다. 이 명령은 커밋되지 않은 내용을 삭제하는 것을 방지하는 안전장치로, 이 경우 Git은 복구할 수 없습니다.
사용자는 로컬 디스크의 작업 디렉토리에는 파일을 유지하고, 스테이징 영역에서만 제거하고 싶을 수 있습니다. 다시 말해, Git이 해당 파일을 추적하지 않도록 하면서 로컬 디스크에는 유지하는 것입니다. 이러한 상황은 .gitignore에 설정하는 것을 잊었지만, 대용량 로그 파일이나 .a 컴파일 파일과 같은 특정 파일을 스테이징했을 때 발생합니다. --cached 옵션을 통해 이를 구현할 수 있습니다.
$ git rm --cached README
파일, 디렉토리, 와일드카드 등을 git rm 명령에 전달할 수 있습니다. 예를 들어, 다음과 같이 사용할 수 있습니다.
$ git rm log/\*.log
* 앞에 있는 백슬래시(\)는 셸의 파일명 확장 외에 Git이 자체적으로 파일명 확장을 수행할 수 있기 때문에 필요합니다. 이 명령은 log/ 디렉토리 아래의 모든 .log 확장자 파일을 제거합니다. 또는 다음과 같은 명령을 실행할 수 있습니다.
$ git rm \*~
이 명령은 이름이 ~로 끝나는 모든 파일을 제거합니다.
2.2.10. 파일 이동
다른 VCS와 달리 Git은 파일 이동을 정확하게 추적하지 않습니다. Git에서 파일 이름을 변경하더라도, Git에게 이 파일의 이름이 변경되었다고 알려주는 메타데이터는 없습니다. 실제로 Git은 이 내용을 매우 지능적으로 추론할 수 있습니다. 이는 나중에 설명하겠습니다.
Git에는 mv 명령이 있습니다. 파일 이름을 변경하고 싶다면 다음 명령을 실행할 수 있습니다.
$ git mv 이전_파일 이름 새_파일_이름
이 명령을 실행한 다음 상태를 확인하면, Git은 파일 이름이 변경되었다고 인식합니다.
$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
이는 다음 일련의 작업과 동일합니다.
$ mv README.md README
$ git rm README.md
$ git add README
Git은 이 작업을 이름 변경으로 암묵적으로 분석하므로, 위의 방식이나 mv 명령을 사용하는 것 모두 가능합니다. 유일한 차이점은 git mv가 하나의 명령이라는 점입니다.
2.3. 커밋 이력 보기
몇 번의 커밋을 수행했거나, 커밋 이력이 있는 저장소를 복제한 후에는 과거에 어떤 일이 일어났는지 확인하고 싶을 수 있습니다. 가장 기본적이고 강력한 명령은 git log입니다.
이 예시를 보여주기 위해, 먼저 simplegit 저장소를 복제해 봅시다.
$ git clone https://github.com/schacon/simplegit-progit
이 프로젝트에 대해 git log 명령을 실행하면 다음과 같은 출력을 볼 수 있습니다.
$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
Change version number
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
Remove unnecessary test
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
Initial commit
기본적으로 주석은 없으며, git log는 커밋 과정을 역시간 순서로 나열합니다. 즉, 최신 커밋이 가장 먼저 나옵니다. 이 명령은 각 커밋의 SHA-1 체크섬, 커밋 사용자 이름 및 이메일 주소, 작성 날짜, 커밋 메시지를 나열합니다.
찾고 있는 내용을 찾는 데 도움이 되는 많은 옵션이 있습니다.
그중 하나는 -p 또는 --patch 옵션으로, 이 옵션은 각 커밋의 차이점(패치 출력)을 보여줍니다. 표시할 커밋 로그 항목 수를 제한할 수 있으며, 예를 들어 -2를 사용하여 최신 두 개만 명시적으로 표시할 수 있습니다.
$ git log -p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
Change version number
diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "simplegit"
- s.version = "0.1.0"
+ s.version = "0.1.1"
s.author = "Scott Chacon"
s.email = "schacon@gee-mail.com"
s.summary = "A simple gem for using Git in Ruby code."
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
Remove unnecessary test
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
end
end
-
-if $0 == __FILE__
- git = SimpleGit.new
- puts git.show
-end
이 옵션은 diff 내용을 포함한 항목 정보를 표시합니다. 이는 코드 수정 내용을 확인하는 데 도움이 됩니다. git log에 많은 요약성 옵션을 사용할 수 있습니다. 예를 들어, 간결한 커밋 상태 정보를 보려면 --stat 옵션을 사용할 수 있습니다.
$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
Change version number
Rakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
Remove unnecessary test
lib/simplegit.rb | 5 -----
1 file changed, 5 deletions(-)
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
Initial commit
README | 6 ++++++
Rakefile | 23 +++++++++++++++++++++++
lib/simplegit.rb | 25 +++++++++++++++++++++++++
3 files changed, 54 insertions(+)
--stat 옵션은 커밋 항목별로 수정된 파일 목록, 수정된 파일 수, 추가되거나 삭제된 줄 수를 출력합니다. 마지막에는 요약 정보를 제공합니다.
또 다른 유용한 옵션은 --pretty입니다. 이 옵션은 기본 형식 대신 로그 출력 형식을 변경합니다. 몇 가지 미리 구성된 옵션 값을 사용할 수 있습니다. 예를 들어, oneline 값은 각 커밋을 한 줄로 출력하여 많은 커밋 내용을 볼 때 매우 유용합니다. 이 외에도 short, full, fuller 값을 선택할 수 있으며, 예시는 다음과 같습니다.
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 Change version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Remove unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 Initial commit
이 옵션 중 가장 흥미로운 값은 format으로, 사용자가 직접 로그 출력 형식을 지정할 수 있게 합니다. 이는 장치가 파싱하기 편리한 스크립트를 작성할 때 매우 유용합니다.
$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : Change version number
085bb3b - Scott Chacon, 6 years ago : Remove unnecessary test
a11bef0 - Scott Chacon, 6 years ago : Initial commit
다음 표는 설정 가능한 형식 출력 값 목록입니다.
| 지정자 | 출력 설명 |
|---|---|
%H |
커밋 해시 |
%h |
간결한 커밋 해시 |
%T |
트리 해시 |
%t |
간결한 트리 해시 |
%P |
부모 해시 |
%p |
간결한 부모 해시 |
%an |
작성자 이름 |
%ae |
작성자 이메일 |
%ad |
작성 날짜 (--date=option) |
%ar |
작성자 상대 날짜 |
%cn |
커미터 이름 |
%ce |
커미터 이메일 |
%cd |
커미터 날짜 |
%cr |
커미터 상대 날짜 |
%s |
주제 |
커미터와 작성자의 차이에 대해 궁금할 수 있습니다. 작성자는 최초 작업을 수행한 사람이고, 커미터는 작업을 적용한 사람입니다. 따라서 프로젝트에 패치를 보냈고 핵심 개발자가 그 패치를 적용했다면, 두 사람 모두 기록됩니다. 당신은 작성자이고, 핵심 개발자는 커미터입니다.
oneline 및 format 옵션은 다른 로그 옵션인 --graph에서도 매우 유용합니다. 이 옵션은 브랜치 및 병합 이력을 보여주는 ASCII 아트 그래프를 추가합니다.
$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 Ignore errors from SIGCHLD on trap
* 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Add method for getting the current branch
* | 30e367c Timeout code and tests
* | 5a09431 Add timeout protection to grit
* | e1193f8 Support for heads with slashes in them
|/
* d6016bc Require time for xmlschema
* 11d191e Merge branch 'defunkt' into local
이러한 유형의 출력은 다음 장에서 브랜치와 병합을 설명할 때 더 자세히 다룰 것입니다.
다음 표는 log 명령의 옵션 목록입니다.
| 옵션 | 설명 |
|---|---|
-p |
각 커밋의 패치 정보 표시 |
--stat |
각 커밋의 파일 상태 표시 |
--short-stat |
--stat에서 변경, 삽입, 삭제 내용만 간결하게 표시 |
--name-only |
커밋 메시지 후 파일 변경 정보 표시 |
--name-status |
추가, 수정, 삭제의 영향을 받은 파일 정보 표시 |
--abbrev-commit |
SHA-1 체크섬의 처음 몇 문자 표시 |
--relative-date |
상대적인 형식으로 날짜 표시 |
--graph |
ASCII 아트 그래프 표시 |
--pretty |
선택 가능한 형식으로 출력 표시 |
--oneline |
한 줄로 간결하게 표시 |
2.3.1. 로그 출력 제한
추가적인 출력 형식 옵션 외에도 git log 명령은 제한 옵션을 사용하여 커밋 정보의 하위 집합을 표시할 수 있습니다. 사용자는 -2 옵션을 통해 두 개의 커밋 항목을 표시하는 것을 보았습니다. 실제로 -<n>을 사용하여 표시할 n개의 항목 수를 지정할 수 있습니다. 일반적으로 Git은 출력 내용을 페이저(Pager)를 통해 파이프로 출력하므로, 사용자는 한 페이지 분량의 로그 출력만 보게 될 것이므로 이 옵션은 자주 사용되지 않을 수 있습니다.
하지만 --since 및 --until과 같은 시간 제한 옵션은 매우 유용합니다. 예를 들어, 다음 명령은 지난 두 주 동안의 커밋 정보를 가져옵니다.
$ git log --since=2.weeks
이 명령은 형식에 따라 작동하며, 사용자는 2008-01-15와 같은 특정 날짜를 지정하거나 2 years 1 day 3 minutes ago와 같은 상대 날짜를 제공할 수 있습니다.
특정 기준으로 커밋 정보를 필터링할 수도 있습니다. --author 옵션은 작성자를 지정할 수 있도록 하며, --grep 옵션은 커밋 메시지에서 키워드를 찾습니다.
--author와 --grep과 같은 여러 기준을 동시에 지정할 수 있으며, 이렇게 하면 지정된 작성자 또는 --grep 패턴을 만족하는 항목으로 출력 정보가 제한됩니다. --all-match 옵션을 추가하면 --grep 패턴과 일치하는 모든 항목을 만족하게 됩니다.
또 다른 필터링 옵션은 -S 옵션으로, 문자열을 입력으로 받아 커밋 메시지에서 해당 문자열의 출현 횟수를 변경한 항목을 찾습니다. 예를 들어, 특정 함수 호출의 추가 또는 삭제 항목을 찾고 싶다면 다음 명령을 사용할 수 있습니다.
$ git log -S 함수_이름
다음으로 유용한 필터링 옵션은 git log에 경로를 전달하는 것입니다. 디렉토리나 파일 이름을 지정하면, 해당 디렉토리나 파일을 수정한 항목으로 로그 출력을 제한할 수 있습니다. 이 내용은 항상 옵션의 맨 마지막에 위치하며, 옵션과 경로를 이중 대시(--)로 구분합니다.
$ git log -- 경로/파일_이름
다음 표는 필터링 옵션 표입니다.
| 옵션 | 설명 |
|---|---|
-<n> |
최신 n개 항목 표시 |
--since, --after |
시작 날짜 제한 |
--until, --before |
종료 날짜 제한 |
--author |
작성자 지정 |
--committer |
커미터 지정 |
--grep |
특정 문자열을 포함하는 항목 찾기 |
-S |
문자열을 추가하거나 삭제한 항목 찾기 |
예를 들어, Junio Hamano가 Git 소스에 커밋한 테스트 파일에 어떤 커밋이 변경을 가했는지, 그리고 로그를 2008년 10월 내로 제한하고, 병합 커밋이 아닌 것을 보고 싶다면 다음 명령을 실행할 수 있습니다.
$ git log --pretty="%h - %s" --author='Junio C Hamano' --since="2008-10-01" \
--before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attributes are in use
acd3b9e - Enhance hold_lock_file_for_{update,append}() API
f563754 - demonstrate breakage of detached checkout with symbolic link HEAD
d1a43f2 - reset --hard/read-tree --reset -u: remove unmerged new paths
51a94af - Fix "checkout --track -b newbranch" on detached HEAD
b0ad11e - pull: allow "git pull origin $something:$current_branch" into an unborn branch
이 명령은 40,000개의 커밋 항목 중에서 6개의 일치하는 정보를 찾아냈습니다.
2.4. 작업 되돌리기
어떤 단계에서든 사용자는 일부 작업을 되돌려야 할 수도 있습니다. 여기서는 변경 사항을 되돌리는 몇 가지 기본 도구를 살펴보겠습니다. 모든 되돌리기 작업을 항상 수행할 수 있는 것은 아닙니다. Git의 특정 영역에서 되돌리기 작업을 잘못 수행하면 일부 작업 내용을 잃을 수도 있다는 점에 유의해야 합니다.
가능한 되돌리기 시나리오 중 하나는 작업을 너무 일찍 커밋하여 커밋하고 싶었던 일부 파일을 추가하는 것을 잊었을 때입니다. 또는 커밋 메시지를 작성할 때 실수를 했을 수도 있습니다. 잊어버린 내용을 보완하기 위해 다시 커밋하고 싶다면 --amend 옵션을 사용할 수 있습니다.
$ git commit --amend
이 명령은 스테이징 영역의 내용을 사용하여 커밋 작업을 수행합니다. 마지막 커밋 이후 아무런 수정도 하지 않았다면(예: 마지막 커밋 직후 이 명령을 실행했다면), 새 스냅샷은 이전과 동일하며 변경되는 것은 커밋 메시지뿐입니다.
이전 커밋 메시지가 편집기에 나타나며, 이 정보를 다시 편집하면 이전 커밋 메시지를 덮어씁니다.
예를 들어, 커밋 후에 파일을 스테이징 영역에 넣는 것을 잊었다는 것을 발견했다고 가정해 봅시다. 다음 작업을 수행할 수 있습니다.
$ git commit -m '초기 커밋'
$ git add 잊어버린_파일
$ git commit --amend
세 가지 명령이 완료된 후에는 단 하나의 커밋만 남습니다. 두 번째 커밋이 첫 번째 커밋 내용을 대체했습니다.
커밋을 보완하는 것은 이전 커밋 위에 추가하는 것이 아니라, 이전 커밋을 제거하고 새로운 커밋 정보로 대체하는 것입니다. 이는 이전 커밋이 존재하지 않는 것처럼 만들며, 저장소의 커밋 이력에는 나타나지 않습니다.
보완 작업은 아직 로컬에서 원격으로 푸시되지 않은 경우에만 유효합니다. 이미 원격으로 푸시된 커밋을 보완하면 협업자와의 협업에 문제가 발생할 수 있습니다.
2.4.1. 스테이징된 파일 언스테이징
다음 두 섹션에서는 스테이징 영역 및 작업 디렉토리의 변경 사항을 되돌리는 방법을 보여줍니다. 이 두 영역의 상태를 확인하는 명령을 상기하고, 이제 이 두 영역에 대한 되돌리기 작업을 어떻게 수행할지 고려해 봅시다. 예를 들어, 사용자가 두 개의 수정된 파일을 개별적으로 커밋하고 싶었지만 git add *를 사용하여 두 파일을 동시에 스테이징했다고 가정해 봅시다. 이 중 하나를 스테이징 영역에서 어떻게 제거할 수 있을까요? git status 명령을 살펴보십시오.
$ git add *
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
modified: CONTRIBUTING.md
"Changes to be committed" 텍스트 아래에, git reset HEAD <file> 명령을 사용하여 스테이징된 파일들을 제거하라고 알려줍니다. 따라서, 이 제안을 사용하여 CONTRIBUTING.md 파일을 스테이징 영역에서 제거해 봅시다.
$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
명령이 다소 이상하게 보일 수 있지만, 실제로 작동합니다. git status 명령을 사용하여 CONTRIBUTING.md 파일이 실제로 스테이징 영역에서 제거되었음을 확인할 수 있습니다.
git reset 명령은 특히 --hard 플래그를 제공할 때 매우 위험할 수 있다는 점에 유의해야 합니다. 그러나 위 시나리오에서는 작업 디렉토리의 파일에 영향을 미치지 않으므로 비교적 안전합니다.
2.4.2. 수정된 파일 되돌리기
수정된 CONTRIBUTING.md 파일을 유지하고 싶지 않다면? 간단히 수정을 취소하고 마지막 커밋 이전 버전(또는 초기 복제 버전)으로 되돌릴 수 있는 방법은 무엇일까요? 다행히도 git status 명령은 이를 수행하는 방법을 알려줍니다. 이전 예시의 출력에서 스테이징되지 않은 내용 섹션은 다음과 같습니다.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
이것은 수행된 변경 사항을 어떻게 버릴지 명확하게 알려줍니다. 이 명령을 실행해 봅시다.
$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
git checkout -- <file>은 매우 위험한 명령이라는 점에 유의해야 합니다. 로컬에서 수정된 모든 내용은 사라질 것입니다. Git은 단순히 스테이징 영역의 내용이나 이전 커밋 버전의 내용으로 대체할 것입니다. 수행하려는 작업의 의미를 완전히 이해하지 못했다면 이 위험한 명령을 사용하지 마십시오.
만약 수정된 내용을 유지하고 싶지만 지금 당장 그 내용을 제외하고 싶다면, 나중에 스태싱 및 브랜치 작업에 대해 설명하는 "Git Branching" 장을 자세히 살펴보십시오.
Git에 커밋된 내용은 기본적으로 모두 복구 가능하다는 점을 기억하십시오. 이미 삭제된 브랜치나 --amend로 덮어쓴 커밋도 복구할 수 있습니다. 그러나 아직 커밋되지 않은 내용의 손실은 영원히 복구할 수 없습니다.
2.4.3. git restore로 작업 되돌리기
Git 버전 2.23.0부터 새로운 명령인 git restore가 도입되었습니다. 이는 git reset 명령의 대체 명령입니다. Git 2.23.0 이상 버전에서는 Git이 되돌리기 작업을 위해 git reset 대신 git restore를 사용하도록 권장합니다.
이제 git restore 명령을 사용하여 작업을 되돌려 봅시다.
2.4.3.1. git restore로 스테이징된 파일 언스테이징
다음 두 섹션에서는 git restore 명령을 사용하여 스테이징 영역 및 작업 디렉토리 변경 사항을 되돌리는 방법을 설명합니다. 앞서 언급된 두 영역의 상태를 확인하는 명령에서도 git restore를 통해 이 작업을 수행하는 방법이 제시됩니다. 예를 들어, 사용자가 두 파일을 수정했고 이들을 개별적으로 커밋하고 싶었지만 git add * 명령을 사용하여 두 파일을 동시에 스테이징 영역에 추가했다고 가정해 봅시다. 이 중 하나를 스테이징 영역에서 어떻게 제거할 수 있을까요? git status 명령이 이 정보를 알려줄 것입니다.
$ git add *
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: CONTRIBUTING.md
renamed: README.md -> README
"Changes to be committed" 아래에, git restore --staged <file>을 사용하여 파일을 스테이징 영역에서 제거하라고 제시됩니다. 이제 이 명령을 사용하여 CONTRIBUTING.md 파일을 스테이징 영역에서 제거해 봅시다.
$ git restore --staged CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: README.md -> README
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: CONTRIBUTING.md
이제 CONTRIBUTING.md 파일은 스테이징 영역에서 제거되었습니다.
2.4.3.2. git restore로 수정된 파일 되돌리기
이제 CONTRIBUTING.md 파일에서 수정한 내용이 더 이상 필요하지 않다면, git status 명령은 다시 이 작업을 수행하는 방법을 알려줍니다. 이전 예시의 출력에는 다음과 같은 내용이 있습니다.
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: CONTRIBUTING.md
이는 사용자가 수행한 변경 사항을 명확하게 버리는 방법을 알려줍니다.
$ git restore CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: README.md -> README
이 명령 역시 매우 위험합니다.
2.5. 원격 저장소와 작업하기
Git 프로젝트에 참여하려면 원격 저장소를 관리하는 방법을 이해해야 합니다. 원격 저장소는 네트워크에 호스팅된 프로젝트 버전입니다. 여러 프로젝트를 가질 수 있으며, 사용자에게는 읽기 전용이거나 읽기/쓰기 가능할 수 있습니다. 다른 사람들과 이러한 원격 저장소를 함께 관리하는 것은 공유 네트워크에 데이터를 푸시하거나 공유 네트워크에서 데이터를 가져오는 것을 포함합니다. 원격 저장소 관리에는 원격 저장소를 추가하고, 유효하지 않은 저장소를 제거하며, 다양한 원격 브랜치를 관리하고 이들이 추적되는지 여부를 결정하는 것이 포함됩니다. 이 섹션에서는 원격 관리를 위한 기술들을 소개합니다.
원격 저장소는 사용자 로컬 장치에 있을 수도 있다는 점에 유의하십시오.
2.5.1. 원격 저장소 확인
원격 서버 설정을 확인하려면 git remote 명령을 실행할 수 있습니다. 이 명령은 사용자가 지정한 각 원격 핸들의 짧은 이름을 나열합니다. 원격 프로젝트를 복제했다면, 적어도 origin을 볼 수 있을 것입니다. 이것은 Git이 서버에 부여하는 기본 이름입니다.
$ git clone https://github.com/schacon/ticgit
Cloning into 'ticgit'...
remote: Reusing existing pack: 1857, done.
remote: Total 1857 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1857/1857), 374.35 KiB | 268.00 KiB/s, done.
Resolving deltas: 100% (772/772), done.
Checking connectivity... done.
$ cd ticgit
$ git remote
origin
-v 옵션을 지정하면 Git 원격 저장소가 읽기 또는 쓰기에 사용하는 URL을 표시합니다.
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
여러 원격 저장소가 있는 경우 이 명령은 모든 내용을 나열할 것입니다. 예를 들어, 여러 협업자를 처리하기 위한 여러 원격 저장소는 다음과 같이 표시될 수 있습니다.
$ cd grit
$ git remote -v
bakkdoor https://github.com/bakkdoor/grit (fetch)
bakkdoor https://github.com/bakkdoor/grit (push)
cho45 https://github.com/cho45/grit (fetch)
cho45 https://github.com/cho45/grit (push)
defunkt https://github.com/defunkt/grit (fetch)
defunkt https://github.com/defunkt/grit (push)
koke git://github.com/koke/grit.git (fetch)
koke git://github.com/koke/grit.git (push)
origin git@github.com:mojombo/grit.git (fetch)
origin git@github.com:mojombo/grit.git (push)
이는 우리가 어떤 사용자 저장소에서든 가져올 수 있음을 의미합니다. 우리는 이러한 내용에 대한 가져오기 또는 푸시 권한을 가질 수 있습니다.
2.5.2. 원격 저장소 추가
git clone 명령이 로컬 저장소에 origin 원격 저장소를 암시적으로 추가하는 방법을 언급했습니다. 다음은 원격 저장소를 명시적으로 추가하는 방법입니다. 새 원격 Git 저장소를 간단히 참조할 수 있는 짧은 이름으로 추가하려면 git remote add <짧은_이름> <URL>을 실행합니다.
$ git remote
origin
$ git remote add pb https://github.com/paulboone/ticgit
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
pb https://github.com/paulboone/ticgit (fetch)
pb https://github.com/paulboone/ticgit (push)
이제 사용자는 명령줄에서 전체 URL 대신 pb 문자열을 사용할 수 있습니다. 예를 들어, Paul이 가지고 있지만 자신이 아직 가지고 있지 않은 내용을 원격 저장소에서 가져오고 싶다면 git fetch pb 명령을 실행할 수 있습니다.
$ git fetch pb
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 43 (delta 10), reused 31 (delta 5)
Unpacking objects: 100% (43/43), done.
From https://github.com/paulboone/ticgit
* [new branch] master -> pb/master
* [new branch] ticgit -> pb/ticgit
Paul의 master 브랜치는 이제 로컬에서 pb/master로 접근 가능합니다. 사용자는 이를 자신의 브랜치 중 하나로 병합하거나, 확인하고 싶을 때 로컬 브랜치로 체크아웃할 수 있습니다.
2.5.3. 원격 저장소에서 데이터 가져오기(Fetch) 및 풀(Pull)
방금 본 것처럼, 원격 프로젝트에서 데이터를 가져오려면 다음을 실행합니다.
$ git fetch <원격_이름>
이 명령은 해당 프로젝트로 이동하여 사용자가 아직 가지고 있지 않은 데이터를 가져옵니다. 이 작업을 완료한 후 사용자는 해당 원격 프로젝트의 모든 브랜치를 가지게 되며, 언제든지 병합하거나 확인할 수 있습니다.
저장소를 복제하면 명령은 자동으로 origin 아래에 해당 원격 저장소를 추가합니다. 따라서 git fetch origin은 복제 후 이 서버에 푸시된 모든 새로운 작업을 가져올 것입니다. git fetch 명령은 데이터를 로컬 저장소로만 다운로드하며, 자동으로 사용자의 작업과 병합하거나 현재 작업 내용을 조정하지는 않는다는 점에 유의해야 합니다. 사용자는 이를 수동으로 작업에 병합해야 합니다.
사용자의 현재 브랜치가 원격 브랜치를 추적하도록 설정되어 있다면(다음 섹션 참조), git pull 명령을 사용하여 원격 브랜치를 자동으로 가져와서 현재 브랜치로 병합할 수 있습니다. 이것이 더 편리하고 간단한 워크플로우일 수 있습니다. 기본적으로 git clone 명령은 사용자 로컬 master 브랜치가 원격 master 브랜치를 추적하도록 자동으로 설정합니다(master 브랜치에만 국한되지 않고 어떤 브랜치라도 가능합니다). git pull을 실행하면 복제 원본에서 데이터를 가져와 자동으로 로컬 브랜치로 병합을 시도합니다.
Git 2.27부터는 pull.rebase 변수가 설정되지 않으면 경고 메시지가 발생합니다. 이 변수가 설정될 때까지는 계속 경고가 나옵니다.
- 기본 Git 동작을 원한다면:
git config --global pull.rebase "false" - 풀(pull) 시 리베이스(rebase)를 원한다면:
git config --global pull.rebase "true"
2.5.4. 원격 저장소로 푸시하기
사용자의 프로젝트가 공유하고 싶은 단계에 도달하면, 상류로 푸시해야 합니다. 이 명령은 git push <원격_이름> <브랜치_이름>입니다. 사용자의 master 브랜치를 원래 서버(복제 시 자동으로 설정된 내용)로 푸시하고 싶다면, 다음 명령을 실행하여 자신의 작업을 서버 측으로 푸시할 수 있습니다.
$ git push origin master
이 명령은 사용자가 해당 서버에 쓰기 권한을 가지고 있고, 현재 다른 사람이 푸시 작업을 하고 있지 않을 때에만 정상적으로 작동합니다. 만약 사용자가 다른 사람과 동시에 복제했고, 그 사람이 먼저 푸시 작업을 수행한 다음 사용자가 푸시 작업을 수행한다면, 사용자의 푸시는 거부될 것입니다. 먼저 서버에서 그들의 작업을 가져와 자신의 작업 내용과 병합한 다음에만 푸시할 수 있습니다.
2.5.5. 원격 저장소 정보 확인
특정 원격 서버의 정보를 확인하고 싶다면 git remote show <원격_이름> 명령을 사용할 수 있습니다. origin과 같은 짧은 이름을 지정하여 이 명령을 실행하면 다음과 유사한 정보를 얻을 수 있습니다.
$ git remote show origin
* remote origin
Fetch URL: https://github.com/schacon/ticgit
Push URL: https://github.com/schacon/ticgit
HEAD branch: master
Remote branches:
master tracked
dev-branch tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
이것은 원격 저장소의 URL과 추적 중인 브랜치 정보를 나열합니다. 이 명령은 사용자가 master 브랜치에 있고, git pull을 실행하면 원격 master 브랜치의 내용이 자동으로 로컬 브랜치로 병합될 것임을 아는 데 도움이 됩니다. 또한 이미 가져온 모든 원격 참조도 나열합니다.
위는 간단한 예시입니다. 만약 Git을 많이 사용하는 사용자라면, git remote show 명령을 실행할 때 다음과 같이 더 복잡한 내용을 볼 수도 있습니다.
$ git remote show origin
* remote origin
URL: https://github.com/my-org/complex-project
Fetch URL: https://github.com/my-org/complex-project
Push URL: https://github.com/my-org/complex-project
HEAD branch: master
Remote branches:
master tracked
dev-branch tracked
markdown-strip tracked
issue-43 new (next fetch will store in remotes/origin)
issue-45 new (next fetch will store in remotes/origin)
refs/remotes/origin/issue-11 stale (use 'git remote prune' to remove)
Local branches configured for 'git pull':
dev-branch merges with remote dev-branch
master merges with remote master
Local refs configured for 'git push':
dev-branch pushes to dev-branch (up to date)
markdown-strip pushes to markdown-strip (up to date)
master pushes to master (up to date)
이 명령은 git push 실행 후 어떤 특정 브랜치들이 자동으로 원격으로 푸시되는지 알려줍니다. 또한 사용자가 아직 사용하지 않은 서버의 원격 브랜치, 사용자가 가진 브랜치 중 원격 서버에서 이미 삭제된 브랜치, 그리고 여러 로컬 브랜치가 git pull 실행 후 자동으로 로컬 브랜치와 병합될 수 있는지 등을 알려줍니다.
2.5.6. 원격 저장소 이름 변경 및 제거
git remote rename 명령을 실행하여 원격 저장소의 짧은 이름을 변경할 수 있습니다. 예를 들어, pb라는 이름의 원격 저장소를 paul로 변경하고 싶다면 다음과 같이 실행할 수 있습니다.
$ git remote rename pb paul
$ git remote
origin
paul
이 변경은 추적하는 모든 브랜치 이름을 변경하며, 원래의 pb/master는 이제 paul/master가 됩니다.
원격 브랜치를 제거하고 싶다면 (서버를 변경했거나 더 이상 이 미러를 사용하지 않을 때), git remote remove 또는 git remote rm 명령 중 하나를 사용할 수 있습니다.
$ git remote remove paul
$ git remote
origin
이 방식으로 원격 참조를 삭제하면 모든 원격 추적 브랜치와 관련 설정이 삭제됩니다.
2.6. 태그(Tagging) 사용
대부분의 VCS와 마찬가지로 Git은 저장소의 특정 이력 지점에 태그를 추가하는 기능을 제공합니다. 일반적으로 이 기능은 릴리스 버전을 표시하는 데 사용됩니다.
2.6.1. 태그 목록 보기
Git에서 현재 존재하는 태그를 나열하는 것은 git tag (-l 또는 --list 옵션과 함께) 명령으로 간결하고 명확하게 수행할 수 있습니다.
$ git tag
v1.0
v2.0
이 명령은 모든 태그를 문자 정렬 순서로 나열하며, 이 배열 순서는 중요하지 않습니다.
특정 형식에 따라 태그를 검색할 수 있습니다. Git 소스 저장소에 500개의 태그가 있고, v1.8.5와 같은 태그 유형만 관심이 있다면 다음 명령을 사용할 수 있습니다.
$ git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5-rc2
v1.8.5-rc3
v1.8.5.1
v1.8.5.2
v1.8.5.3
v1.8.5.4
v1.8.5.5
2.6.2. 태그 생성
Git은 두 가지 유형의 태그를 지원합니다: 경량(lightweight) 태그와 주석(annotated) 태그.
경량 태그는 더 이상 변경되지 않는 브랜치와 매우 유사합니다. 단순히 특정 커밋을 가리키는 포인터일 뿐입니다.
주석 태그는 Git 데이터베이스에 완전한 객체로 저장됩니다. 이들은 체크섬으로 검증되며, 태그 이름, 이메일, 날짜, 태그 메시지를 포함하고 GPG(GNU Privacy Guard) 서명으로 인증할 수 있습니다. 일반적으로 이러한 모든 정보를 포함하는 주석 태그를 생성하는 것이 권장됩니다. 임시 태그가 필요하거나 다른 정보를 보존하고 싶지 않다면 경량 태그를 사용할 수도 있습니다.
2.6.3. 주석 태그
Git에서 주석 태그를 생성하는 것은 매우 간단하며, 가장 쉬운 방법은 git tag 명령을 실행할 때 -a 옵션을 사용하는 것입니다.
$ git tag -a v1.4 -m "나의 버전 1.4 릴리스"
$ git tag
v0.1
v1.3
v1.4
-m은 태그와 함께 저장될 태그 메시지를 지정합니다. 주석 태그에 이 메시지를 지정하지 않으면, Git은 편집기를 실행하여 사용자가 내용을 입력하도록 합니다.
git show 명령에 태그 이름을 지정하여 커밋 정보를 확인할 수 있습니다.
$ git show v1.4
tag v1.4
Tagger: Ben Straub <ben@straub.cc>
Date: Sat May 3 20:19:12 2014 -0700
나의 버전 1.4 릴리스
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
Change version number
이 정보는 태그 정보, 태그 커밋의 로그를 표시하며, 커밋 메시지 이전에 주석 태그 메시지를 보여줍니다.
2.6.4. 경량 태그
또 다른 태그 방식은 경량 태그를 붙이는 것입니다. 이것은 기본적인 커밋 정보일 뿐이며, 다른 정보는 없습니다. 경량 태그를 생성하려면 -a, -s 또는 -m 옵션을 사용하지 않고, 사용할 태그만 제공하면 됩니다.
$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5
이때 git show 명령에 태그를 지정하면 추가 태그 정보를 볼 수 없습니다. 이 명령은 단순히 커밋 정보만 표시합니다.
$ git show v1.4-lw
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
Change version number
2.6.5. 나중에 태그 추가
커밋을 한참 나중에 해당 커밋에 태그를 붙일 수도 있습니다. 커밋 이력이 다음과 같다고 가정해 봅시다.
$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 Create write support
0d52aaab4479697da7686c15f77a3d64d9165190 One more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc Add commit function
4682c3261057305bdd616e23b64b0857d832627b Add todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a Create write support
9fceb02d0ae598e95dc970b74767f19372d61af8 Update rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc Commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a Update readme
이제 "Update rakefile" 커밋에 버전 v1.2 태그를 붙이는 것을 잊었다고 가정해 봅시다. 지금 태그를 붙일 수 있으며, git tag -a v1.2 뒤에 태그를 붙일 커밋의 체크섬을 지정합니다.
$ git tag -a v1.2 9fceb02
이제 태그가 붙은 커밋 정보를 확인할 수 있습니다.
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700
Update rakefile
...
2.6.6. 태그 공유
기본적으로 git push는 태그를 원격 서버에 전달하지 않습니다. 태그를 생성한 후에는 원격 서버에 명시적으로 태그를 푸시해야 합니다. 이 과정은 원격 서버와 태그를 공유하는 것과 같으며, git push origin <태그_이름>을 실행하여 공유할 수 있습니다.
$ git push origin v1.5
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done.
Total 14 (delta 3), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
* [new tag] v1.5 -> v1.5
한 번에 여러 태그를 원격 저장소에 푸시하고 싶다면, git push에 --tags 옵션을 사용할 수 있습니다. 이 옵션은 모든 새 태그를 원격 저장소에 푸시합니다.
$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 160 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
* [new tag] v1.4 -> v1.4
* [new tag] v1.4-lw -> v1.4-lw
이제 다른 사람이 저장소에서 코드를 복제하거나 가져오면, 사용자가 붙인 모든 태그를 얻게 될 것입니다.
git push <원격_이름> --tags는 두 가지 유형의 태그를 모두 푸시한다는 점에 유의하십시오. git push <원격_이름> --follow-tags를 사용하면 주석 태그만 원격 저장소에 푸시됩니다.
2.6.7. 태그 삭제
로컬 저장소의 태그를 삭제하려면 git tag -d <태그_이름>을 사용할 수 있습니다. 예를 들어, 이 명령을 사용하여 경량 태그를 삭제할 수 있습니다.
$ git tag -d v1.4-lw
Deleted tag 'v1.4-lw' (was e7d5add)
이 명령을 실행해도 원격 저장소의 태그는 삭제되지 않는다는 점에 유의하십시오. 원격 저장소에서 태그를 삭제하는 두 가지 방법이 있습니다.
첫 번째는 git push <원격_이름> :refs/tags/<태그_이름>을 사용하는 것입니다.
$ git push origin :refs/tags/v1.4-lw
To /git@github.com:schacon/simplegit.git
- [deleted] v1.4-lw
두 번째는 다음과 같이 더 직관적인 명령을 사용하는 것입니다.
$ git push origin --delete <태그_이름>
2.6.8. 태그 체크아웃
사용자가 태그로 지정된 버전을 확인하고 싶다면, git checkout 명령을 사용하여 해당 태그를 체크아웃할 수 있습니다. 이 명령은 저장소를 "detached HEAD" 상태로 만들며, 이는 여러 가지 부작용을 가집니다.
$ git checkout v2.0.0
Note: switching to 'v2.0.0'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 99ada87... Merge pull request #89 from schacon/appendix-final
$ git checkout v2.0-beta-0.1
Previous HEAD position was 99ada87... Merge pull request #89 from schacon/appendix-final
HEAD is now at df3f601... Add atlas.json and cover image
"detached HEAD" 상태에 있으면, 사용자가 변경 사항을 만들고 커밋하더라도 태그는 변경되지 않으며, 새로 커밋한 내용은 어떤 브랜치에도 속하지 않고 커밋으로 생성된 해시를 통해서만 접근할 수 있습니다. 따라서 이전 버전에 대한 수정이 정말 필요하다면(예: 이전 버전의 버그 수정), 다음과 같이 새 브랜치를 생성할 수 있습니다.
$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
이렇게 하고 커밋을 수행하면, version2 브랜치는 v2.0.0 태그와 크게 달라질 수 있습니다. 이 브랜치 노드를 기반으로 계속 앞으로 나아갈 것이기 때문에 신중하게 사용해야 합니다.
2.7. Git 별칭(Aliases)
다음 장으로 넘어가기 전에 명령 별칭에 대해 소개하고자 합니다. git config 명령을 사용하여 특정 명령에 대한 별칭을 지정할 수 있습니다.
$ 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 ci를 사용하는 것은 git commit을 사용하는 것과 동일합니다.
이 명령은 사용자가 직접 원하는 명령을 생성하는 데도 사용할 수 있습니다.
3. Git 브랜치 활용
거의 모든 VCS는 어떤 형태의 브랜치 기능을 지원합니다. 브랜치는 주 개발 흐름에서 갈라져 나와 주 흐름을 방해하지 않고 개발을 진행할 수 있는 공간입니다. 일부 VCS 도구에서는 이 기능을 구현하는 것이 비용이 많이 들 수 있으며, 종종 사용자가 소스 코드의 새 복사본을 만들어야 하므로 시간과 노력이 많이 소요됩니다.
일부 사람들은 Git 브랜치 모델을 Git의 '킬러 기능'으로 꼽으며, 이 기능은 Git을 다른 VCS와 차별화합니다. 왜 그렇게 특별할까요? Git 브랜치는 예상외로 가볍고, 브랜치 생성은 즉시 완료되며, 브랜치 간 전환도 매우 빠릅니다. 다른 VCS와 달리 Git은 워크플로우에서 브랜치를 반복적으로 생성하고 병합하는 것을 권장합니다. 이 기능을 이해하고 숙달하면 강력한 도구를 얻게 되며, 개발 방식을 완전히 바꿀 수 있습니다.
3.1. 브랜치 개요
Git이 브랜치를 만드는 방식을 완전히 이해하려면 한 걸음 뒤로 물러나 Git이 데이터를 저장하는 방식을 살펴보아야 합니다.
Git의 작동 방식 섹션에서 Git이 데이터를 일련의 변경 집합이 아니라 일련의 스냅샷으로 저장한다는 것을 기억할 것입니다.
사용자가 커밋을 수행하면 Git은 스테이징된 내용을 포함하는 스냅샷의 커밋 객체를 저장합니다. 이 객체에는 작성자의 이름과 이메일 주소, 사용자가 입력한 메시지, 그리고 이 커밋의 부모(초기 커밋은 0개의 부모, 일반 커밋은 1개의 부모, 여러 브랜치의 병합은 여러 개의 부모)를 가리키는 포인터가 포함됩니다.
이 과정을 시각화하기 위해, 세 개의 파일이 포함된 디렉토리가 있고, 이들을 스테이징하고 커밋했다고 가정해 봅시다. 스테이징된 파일은 각 파일에 대해 체크섬을 계산하고, 이 파일 버전을 Git 저장소에 저장하며, 체크섬을 스테이징 영역에 추가합니다.
$ git add README test.rb LICENSE
$ git commit -m '초기 커밋'
git commit을 실행하여 커밋 작업을 수행하면, Git은 각 하위 디렉토리에 대해 체크섬을 생성하고(이 예시에서는 루트 디렉토리에 대해서만 수행), 이를 Git 저장소의 트리 객체에 저장합니다. Git은 그 다음 메타데이터와 루트 프로젝트 트리를 가리키는 포인터를 포함하는 커밋 객체를 생성하여, 필요할 때 이 스냅샷을 다시 생성할 수 있도록 합니다.
사용자의 Git 저장소는 이제 5개의 객체를 포함합니다: 세 개의 블롭(각각 세 개의 파일 중 하나의 내용을 나타냄), 디렉토리 내용을 나열하고 어떤 블롭에 어떤 파일 이름이 저장되어 있는지 가리키는 하나의 트리, 그리고 루트 트리를 가리키는 포인터와 모든 커밋 메타데이터.
만약 사용자가 몇 가지 수정을 하고 다시 커밋하면, 다음 커밋은 이전 커밋을 직접 가리키는 포인터를 저장합니다.
Git에서 브랜치는 이러한 커밋을 가리키는 가볍고 이동 가능한 포인터일 뿐입니다. Git에서 기본 브랜치 이름은 master입니다. 커밋을 시작하면 master 브랜치가 최신 커밋을 가리킵니다. 사용자가 커밋할 때마다 마스터 브랜치 포인터는 자동으로 앞으로 이동합니다.
Git의 master 포인터는 특별한 브랜치가 아닙니다. 다른 브랜치와 동일합니다. 모든 저장소가 master 브랜치를 가지는 이유는 git init 명령이 기본적으로 이 브랜치를 생성하며, 사용자가 이 기본 설정을 특별히 변경하지 않기 때문입니다.
브랜치와 그 커밋 이력의 토폴로지 다이어그램은 다음과 같습니다. (이미지 대체 설명: 커밋들이 선형으로 연결되어 있고, 각 커밋을 가리키는 브랜치 포인터들이 그려져 있습니다.)
3.1.1. 새 브랜치 생성
브랜치를 생성하면 어떤 일이 발생할까요? 브랜치를 생성하면 이동할 새로운 포인터가 생성됩니다. testing이라는 이름의 새 브랜치를 만들어 봅시다. git branch 명령을 통해 생성할 수 있습니다.
$ git branch testing
이 명령은 현재 커밋 노드에 새로운 포인터를 생성합니다.
Git은 사용자가 현재 어떤 브랜치에 있는지 어떻게 알까요? HEAD라는 특별한 포인터를 유지합니다. 이 HEAD는 이전에 사용했던 SVN이나 CVS와 같은 VCS의 HEAD와는 다른 내용이라는 점에 유의하십시오. Git에서 HEAD는 사용자가 현재 있는 브랜치를 가리키는 포인터입니다. 이 경우, 사용자는 여전히 master 브랜치에 있습니다. git branch 명령은 단순히 새 브랜치를 생성할 뿐이며, 새 브랜치로 전환하지는 않습니다.
git log --oneline --decorate 명령을 통해 현재 있는 브랜치를 표시할 수 있습니다.
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit
master 및 testing 브랜치를 볼 수 있습니다.
3.1.2. 브랜치 전환
git checkout 명령을 사용하여 기존 브랜치로 전환합니다. 이제 testing 브랜치로 전환해 봅시다.
$ git checkout testing
이 명령은 HEAD 포인터를 testing 브랜치를 가리키도록 이동시킵니다.
이것이 무엇을 의미할까요? 이제 또 다른 커밋을 만들어 봅시다.
$ vim test.rb
$ git commit -a -m '변경 사항 생성'
흥미롭게도, testing 브랜치가 앞으로 이동한 반면 master 브랜치는 커밋 전 상태에서 움직이지 않았습니다. 이제 다시 master 브랜치로 전환해 봅시다.
$ git checkout master
git log는 항상 모든 브랜치를 표시하지 않는다는 점에 유의하십시오. 지금 git log 명령을 실행하면, 방금 생성한 testing 브랜치가 어디로 갔는지 궁금할 수 있지만, 출력에 나타나지 않습니다.
이 브랜치는 사라지지 않았습니다. Git은 사용자가 이 브랜치에 관심이 있다는 것을 알지 못하므로, 사용자가 관심 있는 것만 표시하려고 합니다. 즉, 사용자가 체크아웃한 현재 브랜치의 이력 정보만 표시합니다.
특정 브랜치의 커밋 이력을 보려면 git log testing과 같이 명시적으로 표시할 브랜치를 지정할 수 있으며, 모든 브랜치 정보를 보려면 git log --all 명령에 --all 옵션을 사용할 수 있습니다.
git checkout master 명령은 두 가지 작업을 수행했습니다. HEAD 포인터를 master 브랜치로 다시 이동시키고, 사용자 작업 디렉토리의 파일을 해당 버전의 스냅샷으로 되돌렸습니다. 이렇게 하면 다른 작업을 수행하기 편리하도록 원래 버전으로 돌아갑니다.
이제 다시 수정하고 커밋해 봅시다.
$ vim test.rb
$ git commit -a -m '다른 변경 사항 생성'
이제 프로젝트 이력이 변경되었습니다. 브랜치를 만들고, 해당 브랜치에서 작업을 수행한 다음, 다시 원래 버전으로 전환하여 다른 작업을 수행했습니다. 이러한 변경 사항은 서로 다른 브랜치 간에 격리됩니다. 서로 다른 브랜치 간에 전환할 수 있으며, 이들을 병합할 수도 있습니다.
지금 git log --oneline --decorate --graph --all을 실행하면, 사용자의 커밋 이력을 출력하여 브랜치 포인터가 어디를 가리키는지, 그리고 이력이 어떻게 분기되었는지 보여줍니다.
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) 다른 변경 사항 생성
| * 87ab2 (testing) 변경 사항 생성
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 프로젝트 초기 커밋
Git에서 브랜치는 실제로 커밋을 가리키는 40자리 SHA-1 체크섬을 포함하는 파일에 불과하므로, 브랜치 생성 및 삭제는 매우 저렴합니다. 새 브랜치를 생성하는 것은 단순히 파일에 41자(40자 + 한 줄 바꿈)를 쓰는 것과 같습니다.
이는 이전 버전의 VCS 도구의 브랜치와 비교할 때 매우 날카롭다고 할 수 있습니다.
브랜치를 생성하면서 동시에 해당 브랜치로 전환하려면 어떻게 해야 할까요? git checkout -b <새_브랜치_이름>과 같은 작업을 통해 수행할 수 있습니다. Git 2.23 버전부터는 git checkout 대신 git switch를 사용할 수 있습니다.
- 기존 브랜치로 전환:
git switch testing-branch - 새 브랜치를 생성하고 해당 브랜치로 전환:
git switch -c new-branch - 이전에 체크아웃한 브랜치로 돌아가기:
git switch -
3.2. 기본적인 브랜치 및 병합
실제 작업에서 사용될 수 있는 브랜치 및 병합 워크플로우를 시도해 봅시다. 다음 단계를 따라야 합니다.
- 웹 사이트에서 일부 작업을 수행합니다.
- 새 사용자 스토리 브랜치를 생성합니다.
- 이 브랜치에서 일부 작업을 수행합니다.
이 단계에서 긴급한 문제가 발생하여 핫픽스를 수행해야 한다는 연락을 받으면 다음 작업을 수행할 것입니다.
- 제품 브랜치로 다시 전환합니다.
- 핫픽스를 추가하기 위한 브랜치를 생성합니다.
- 테스트 후 핫픽스를 병합하고 제품을 배포합니다.
- 원래 사용자 스토리로 돌아가 작업을 계속합니다.
3.2.1. 기본적인 브랜치 사용
먼저, 프로젝트에서 작업 중이며 master 브랜치에 여러 번 커밋했습니다.
(이미지 대체 설명: 마스터 브랜치에 C1, C2 커밋이 선형으로 쌓여 있습니다.)
이제 문제 #53(문제의 대명사)을 해결하기로 결정했습니다. git checkout -b 명령을 통해 이 새 브랜치를 생성하고 전환합니다.
$ git checkout -b iss53
Switched to a new branch "iss53"
이는 다음 두 명령을 실행하는 것과 동일합니다.
$ git branch iss53
$ git checkout iss53
이 브랜치에서 작업을 시작하고 몇 번의 커밋을 수행합니다. 이렇게 하면 #53 브랜치가 성장하기 시작합니다.
$ vim index.html
$ git commit -a -m '새 푸터 생성 [이슈 53]'
(이미지 대체 설명: 마스터 브랜치에 C1, C2가 있고, C2에서 분기된 iss53 브랜치에 C3, C4 커밋이 선형으로 쌓여 있습니다. HEAD는 iss53을 가리킵니다.)
이제 웹 사이트에 긴급히 수정해야 할 문제가 있다는 연락을 받았습니다. Git 덕분에 #53 브랜치에 있는 이 긴급 문제에 대한 수정 사항을 배포할 필요가 없으며, 원래 버전으로 롤백하는 복잡한 작업을 할 필요도 없습니다. master 브랜치로 다시 전환하기만 하면 됩니다.
하지만 master 브랜치로 전환하기 전에, 작업 디렉토리나 스테이징 영역에 master 브랜치와 충돌하는 커밋되지 않은 내용이 있다면 Git은 브랜치 전환을 허용하지 않을 것입니다. 주 브랜치로 전환하기 전에 깨끗한 작업 영역을 유지하는 것이 가장 좋습니다. 이에 대한 해결 방법은 나중에 자세히 설명하겠습니다. 지금은 모든 수정 내용을 이미 커밋했다고 가정하므로 master 브랜치로 다시 전환할 수 있습니다.
$ git checkout master
Switched to branch 'master'
(이미지 대체 설명: 마스터 브랜치에 C1, C2가 있고, C2에서 분기된 iss53 브랜치에 C3, C4 커밋이 있습니다. HEAD는 마스터를 가리키고, 작업 디렉토리는 C2 상태로 되돌아갑니다.)
이때 프로젝트 작업 디렉토리는 #53으로 전환하기 전의 작업 디렉토리가 됩니다. 이제 이 핫픽스를 시작할 수 있습니다. 이것은 중요한 포인트입니다: 브랜치를 전환하면 Git은 작업 디렉토리를 해당 브랜치에서 마지막으로 커밋했을 때의 상태로 재설정합니다. 파일을 자동으로 추가, 삭제, 수정하여 작업 디렉토리가 이 상태가 되도록 보장합니다.
다음으로, 핫픽스를 시작합니다. 핫픽스 브랜치를 생성합니다.
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m '깨진 이메일 주소 수정'
[hotfix 1fb7853] 깨진 이메일 주소 수정
1 file changed, 2 insertions(+)
(이미지 대체 설명: 마스터 브랜치에 C1, C2가 있고, C2에서 분기된 iss53 브랜치에 C3, C4 커밋이 있습니다. 마스터 브랜치에서 다시 hotfix 브랜치가 분기되어 C5 커밋이 쌓여 있습니다. HEAD는 hotfix를 가리킵니다.)
이제 이 핫픽스에 대해 테스트를 실행하여 문제가 수정되었는지 확인합니다. 마지막으로 핫픽스 브랜치를 주 브랜치에 병합하여 제품을 배포합니다. git merge 명령을 통해 이를 수행합니다.
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
(이미지 대체 설명: iss53 브랜치와 hotfix 브랜치가 C2에서 분기된 상태에서 hotfix 브랜치 C5가 마스터 브랜치로 병합됩니다. Fast-forward 병합으로 마스터 브랜치 포인터가 C5를 가리키도록 이동합니다.)
병합 후 출력에서 "Fast-forward" 문구가 나타난 것을 주목했을 것입니다. 이번 커밋인 C4는 사용자가 있었던 C2를 기반으로 하므로, Git은 포인터를 C2를 기반으로 앞으로 이동시킵니다. 다른 방식으로 설명하자면, 사용자가 이전 버전으로 직접 포인팅하는 병합을 시도할 때, 충돌하는 내용이 없기 때문에 Git은 단순히 포인터를 앞으로 이동시키기만 하면 병합이 완료됩니다. 이를 "fast-forward"라고 합니다.
이제 사용자의 수정된 내용 스냅샷은 master 브랜치를 직접 가리키며, 이제 이 수정된 버전을 배포할 수 있습니다.
이 매우 중요한 수정 배포 후, 사용자는 이전 작업 내용으로 돌아갈 수 있습니다. 이전에 핫픽스를 위해 브랜치를 직접 생성했습니다. 이 브랜치는 이미 master 브랜치에 병합되었으므로, 이제 핫픽스를 위해 생성한 브랜치를 직접 삭제할 수 있습니다. master 브랜치가 동일한 위치를 가리키기 때문입니다. -d 옵션을 통해 이 브랜치를 삭제할 수 있습니다.
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
(이미지 대체 설명: hotfix 브랜치가 삭제되고, iss53 브랜치만 C2에서 분기된 상태로 남아 있습니다.)
이제 사용자는 이전의 #53 문제 브랜치로 돌아가 작업을 계속할 수 있습니다.
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m '새 푸터 완료 [이슈 53]'
[iss53 ad82d7a] 새 푸터 완료 [이슈 53]
1 file changed, 1 insertion(+)
(이미지 대체 설명: iss53 브랜치에 C3, C4 커밋이 있고, C4 다음에 C6 커밋이 추가됩니다. HEAD는 iss53을 가리킵니다.)
핫픽스를 수행할 때 변경된 파일은 #53 브랜치에 나타나지 않는다는 점에 유의해야 합니다. 이 핫픽스 브랜치 내용을 #53 문제 브랜치로 병합하고 싶다면 git merge master 명령을 실행하거나, #53 문제가 완료될 때까지 기다린 다음 그 변경 사항을 master 브랜치로 병합할 수 있습니다.
3.2.2. 기본적인 병합
이제 #53 문제가 완료되었고, master 브랜치로 병합할 준비가 되었다고 가정해 봅시다. 이 작업을 수행하려면, 이전에 핫픽스 브랜치를 병합했던 것처럼 #53 브랜치를 master 브랜치로 병합할 것입니다. 해야 할 일은 병합할 브랜치를 체크아웃한 다음 git merge 명령을 실행하는 것입니다.
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
(이미지 대체 설명: 마스터 브랜치 C5와 iss53 브랜치 C6가 병합되어 C7이라는 새 커밋이 생성됩니다. C7은 C5와 C6 모두를 부모로 가집니다. HEAD는 마스터를 가리킵니다.)
이는 핫픽스 브랜치를 병합했을 때와는 다소 다르게 보입니다. 이 병합의 경우, 개발 이력이 핫픽스 때문에 변경되었습니다. 커밋될 지점이 병합될 브랜치의 직접적인 조상이 아니기 때문에 Git은 약간의 작업을 수행해야 합니다. 이 경우, Git은 master 브랜치와 #53 브랜치의 공통 조상, 그리고 이 두 브랜치를 사용하여 삼자 병합을 수행합니다.
Git은 이 삼자 병합을 기반으로 스냅샷을 생성하고 자동으로 이를 가리키는 새 커밋을 생성하며, 단순히 현재 브랜치에서 성장하는 방식이 아닙니다. 이는 여러 부모를 가진 병합 커밋을 포함합니다.
이제 작업이 병합되었으므로, 사용자에게는 #53 문제 브랜치가 더 이상 필요하지 않습니다. 이 문제를 닫고 브랜치를 삭제할 수 있습니다.
$ git branch -d iss53
(이미지 대체 설명: iss53 브랜치가 삭제되고, 마스터 브랜치만 C7 커밋을 가리킵니다.)
3.2.3. 기본적인 병합 충돌
때때로 병합 과정이 항상 순조롭지는 않습니다. 사용자가 병합될 두 브랜치에서 동일한 파일의 동일한 부분을 수정했다면, Git은 두 브랜치를 깔끔하게 병합할 수 없습니다. #53 브랜치에서 핫픽스 브랜치와 동일한 내용을 수정했다면, 병합 시 병합 충돌을 보게 될 것입니다.
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git은 새 병합 커밋을 자동으로 생성하지 않았습니다. 대신 병합 프로세스를 일시 중지하고 사용자가 이 충돌을 해결하기를 기다립니다. 충돌 후 어떤 파일이 병합되지 않았는지 확인하고 싶다면 git status 명령을 사용할 수 있습니다.
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
병합 충돌이 발생했지만 해결되지 않은 내용은 unmerged 섹션에 나열됩니다. Git은 충돌 해결 표시를 추가하며, 사용자는 이들을 수동으로 열어 충돌을 해결할 수 있습니다. 사용자의 파일에는 다음과 유사한 조각이 포함됩니다.
<<<<<<< HEAD:index.html
<div id="footer">연락처 : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
위 예시에서 HEAD 버전(병합 시 master 브랜치에 있었으므로 HEAD는 master 브랜치)의 모든 내용은 해당 블록의 상단(======= 이전의 모든 내용)에 있으며, #53 브랜치의 모든 내용은 해당 블록의 하단에 있습니다. 충돌을 해결하려면, 사용자는 어느 한쪽을 선택하거나 직접 내용을 병합할 수 있습니다. 예를 들어, 전체 블록 내용을 다음과 같이 수정할 수 있습니다.
<div id="footer">
please contact us at email.support@github.com
</div>
이 내용은 각 부분에 포함되며, <<<<<<<, =======, >>>>>>> 줄은 제거됩니다. 충돌 파일의 이 부분들을 해결한 후, git add를 실행하여 이 파일들을 해결된 내용으로 표시하고, 이 파일을 스테이징 영역에 커밋하여 Git에게 이 충돌이 해결되었음을 알립니다.
이 문제를 해결하기 위해 그래픽 도구를 사용하고 싶다면 git mergetool을 실행할 수 있습니다. 이 명령은 시각화 도구를 시작하여 충돌 내용을 보여줍니다.
$ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html
Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (opendiff):
기본 도구가 아닌 사용자 자신의 병합 도구를 사용하고 싶다면, "one of the following tools" 출력 아래에 지원되는 도구를 볼 수 있습니다. 사용할 도구 이름을 입력하기만 하면 됩니다.
병합 도구를 종료한 후, Git은 병합이 성공적으로 이루어졌는지 묻습니다. 스크립트에 충돌이 해결되었다고 알리면, 파일은 스테이징 영역에 추가되고 해결된 것으로 간주됩니다. 이제 git status를 실행하면 충돌 내용이 해결되었음을 볼 수 있습니다.
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: index.html
이 결과에 만족하고 모든 충돌 내용이 스테이징 영역에 있음을 확인했다면, git commit을 사용하여 최종 병합 커밋 작업을 수행할 수 있습니다. 기본 커밋 메시지는 다음과 같습니다.
Merge branch 'iss53'
Conflicts:
index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: index.html
#
3.3. 브랜치 관리
이제 사용자는 여러 브랜치를 생성하고, 병합하고, 삭제했습니다. 이제 몇 가지 브랜치 관리 도구를 살펴봅시다.
git branch 명령은 단순히 브랜치를 생성하고 삭제하는 것 이상의 역할을 합니다. 이 명령 뒤에 아무 정보도 추가하지 않고 사용하면 현재 브랜치 목록만 얻게 됩니다.
$ git branch
iss53
* master
testing
* 기호가 master 브랜치 앞에 있다는 점에 유의하십시오. 이는 사용자가 현재 있는 브랜치(즉, HEAD가 가리키는 브랜치)를 나타냅니다. 이는 지금 사용자가 커밋하면 master 브랜치가 새로운 작업 내용으로 인해 성장할 것임을 의미합니다. 각 브랜치의 마지막 커밋을 보려면 git branch -v를 실행할 수 있습니다.
$ git branch -v
iss53 93b412c Fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 Add scott to the author list in the readme
유용한 --merged 및 --no-merged 옵션은 사용자가 현재 있는 브랜치에 병합되었거나 병합되지 않은 브랜치를 이 목록에서 필터링합니다. 예를 들면 다음과 같습니다.
$ git branch --merged
iss53
* master
iss53을 주 브랜치에 병합했으므로, 병합된 목록에 나타나는 것을 볼 수 있습니다. 이 목록에서 *가 없는 브랜치는 git branch -d를 통해 안전하게 삭제할 수 있습니다. 이 브랜치들의 내용은 이미 다른 브랜치에 포함되어 있으므로, 이 브랜치들을 삭제해도 어떤 내용도 손실되지 않습니다.
병합되지 않은 브랜치를 보려면 다음을 실행합니다.
$ git branch --no-merged
testing
이는 다른 브랜치를 표시할 것입니다. 병합되지 않은 작업을 포함하고 있으므로, git branch -d를 사용하여 이 브랜치를 삭제하려고 하면 실패할 것입니다.
$ git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
만약 사용자가 정말로 이 브랜치를 삭제하고 작업을 버리고 싶다면, -D 옵션을 사용하여 강제로 삭제할 수 있습니다.
3.3.1. 브랜치 이름 변경
다른 협업자가 사용 중인 브랜치 이름을 변경하지 마십시오. "마스터 브랜치 이름 변경" 내용을 읽기 전에는 master/main/mainline과 같은 이름을 변경하지 마십시오.
bad-branch-name이라는 브랜치가 있고 이를 corrected-branch-name으로 변경하고 모든 이력을 유지하고 싶다고 가정해 봅시다. 또한 원격 서버(GitHub, GitLab 또는 기타 서버)의 브랜치 이름도 변경하고 싶다면 어떻게 해야 할까요?
로컬 브랜치 이름을 변경하려면 git branch --move 명령을 사용할 수 있습니다.
$ git branch --move bad-branch-name corrected-branch-name
이 명령은 bad-branch-name을 corrected-branch-name으로 변경하지만, 이 변경은 현재 로컬에만 해당됩니다. 다른 사람들도 브랜치 이름이 변경되었음을 보게 하려면 원격으로 푸시해야 합니다.
$ git push --set-upstream origin corrected-branch-name
이제 현재 상태를 확인해 봅시다.
$ git branch --all
* corrected-branch-name
main
remotes/origin/bad-branch-name
remotes/origin/corrected-branch-name
remotes/origin/main
이제 corrected-branch-name 브랜치에 있으며, 이 브랜치는 원격 에이전트에서 접근 가능합니다. 그러나 bad-branch-name 브랜치는 여전히 존재하지만, 다음 명령을 실행하여 삭제할 수 있습니다.
$ git push origin --delete bad-branch-name
이제 bad-branch-name 브랜치는 corrected-branch-name 브랜치로 완전히 대체되었습니다.
마스터 브랜치 이름 변경:
이 작업을 수행하기 전에 모든 협업자와 충분히 소통해야 합니다. 또한 저장소, 참조, 이전 브랜치 및 스크립트에 대한 철저한 조사를 수행해야 합니다.
로컬 master 브랜치 이름을 main으로 변경합니다.
$ git branch --move master main
이제 로컬에는 master 브랜치가 없으며, main 브랜치로 변경되었습니다.
다른 사람들이 이 main 브랜치를 보게 하려면 원격으로 푸시해야 합니다.
$ git push --set-upstream origin main
이제 브랜치 상태를 확인해 봅시다.
$ git branch --all
* main
remotes/origin/HEAD -> origin/master
remotes/origin/main
remotes/origin/master
로컬의 master 브랜치는 사라지고 main 브랜치로 대체되었습니다. main 브랜치는 원격에 나타납니다. 그러나 이전 master 브랜치는 여전히 원격에 있습니다. 다른 협업자들은 다른 변경을 하기 전까지는 이 master 브랜치를 계속 개발 브랜치로 사용할 수 있습니다.
이제 전환을 완료하려면 다음 작업을 수행해야 합니다.
master에 의존하는 모든 프로젝트는 코드, 설정을 업데이트해야 합니다.- 모든 테스트 실행 구성 파일을 업그레이드합니다.
- 빌드 및 릴리스 스크립트를 조정합니다.
- 저장소 호스팅(예: 저장소의 기본 브랜치, 병합 규칙 및 기타 브랜치 이름과 일치하는 내용)을 리디렉션합니다.
- 오래된 브랜치에 대한 참조가 있는 문서를 업데이트합니다.
- 오래된 브랜치에 대한 모든 풀 리퀘스트를 닫거나 병합합니다.
이러한 작업을 완료하고 main 브랜치가 master 브랜치와 동일하게 작동함을 확인한 후, master 브랜치를 삭제할 수 있습니다.
$ git push origin --delete master
3.4. 브랜치 워크플로우
이제 브랜치 및 병합의 기본적인 지식을 갖추었습니다. 이들을 어떻게 활용해야 할까요? 이 섹션에서는 몇 가지 가벼운 브랜치 관리 워크플로우를 소개합니다.
3.4.1. 장기 실행 브랜치
Git은 간단한 삼자 병합을 사용하므로, 하나의 브랜치를 다른 브랜치에 자주 병합하는 것이 가능합니다. 이는 사용자가 여러 단계에서 여러 개의 열린 브랜치를 가질 수 있음을 의미합니다. 이들을 다른 브랜치와 병합할 수도 있습니다.
일부 Git 개발자는 이 과정을 받아들여, master 브랜치에는 안정적인 버전의 브랜치만 유지합니다. 다른 병렬 브랜치에서는 개발 또는 안정성 테스트를 진행하며, 나중에 이들을 master 브랜치에 병합할 수 있습니다.
실제로, 우리는 포인터가 커밋 과정에서 앞으로 이동하고, 안정적인 버전은 오래된 커밋 이력에 위치하며, 더 적극적인 브랜치는 최신 이력에 나타나는 것에 대해 이야기하고 있습니다.
(이미지 대체 설명: 안정된 버전과 테스트 버전, 개발 버전을 나타내는 여러 브랜치들이 서로 병합되면서 발전하는 흐름을 보여주는 다이어그램입니다.)
이를 마치 발사대처럼 생각할 수 있으며, 안정적인 발사대는 오래도록 검증된 것입니다.
(이미지 대체 설명: 안정성 수준에 따라 개발 단계를 분리하는 브랜치 모델을 보여주는 다이어그램입니다.)
사용자는 이를 기반으로 다양한 수준의 안정성 테스트를 수행할 수 있습니다. 한 버전의 안정성이 특정 수준에 도달하면, 이전 안정 버전으로 병합할 수 있습니다.
3.4.2. 토픽 브랜치
토픽 브랜치는 모든 규모의 프로젝트에서 사용할 수 있는 개념입니다. 토픽 브랜치는 수명이 매우 짧으며, 단일 기능 개발을 위해 생성됩니다. 이는 다른 VCS 작업에서는 상상조차 할 수 없었을 것입니다. 다른 VCS는 브랜치 생성 및 병합이 매우 비싸기 때문입니다. 하지만 Git에서는 이는 매우 흔한 일입니다.
이전 섹션에서 iss53 브랜치와 핫픽스 브랜치 사용을 이미 보았습니다. (예시는 생략됩니다. 모두 이해하실 것입니다.) 이 과정의 모든 작업은 로컬에서 수행되며, 원격 서버와 통신할 필요가 없습니다.
3.5. 원격 브랜치 (내용 없음)
3.5.1. 푸시 (내용 없음)
3.5.2. 추적 브랜치 (내용 없음)
3.5.3. 풀 (내용 없음)
3.5.4. 원격 브랜치 삭제 (내용 없음)
3.6. 리베이스(Rebasing)
여기서 리베이스(Rebase)는 '기반 변경' 또는 '기저 변경'으로 번역될 수 있습니다. Git에는 다른 브랜치의 내용을 통합하는 두 가지 주요 방법이 있습니다: 병합(merge)과 리베이스(rebase). 이 섹션에서는 리베이스가 무엇인지, 어떻게 리베이스하는지, 언제 사용할 수 있고 언제 사용해서는 안 되는지 설명합니다.
3.6.1. 기본적인 리베이스
이전의 '기본적인 병합' 섹션으로 돌아가 보면, 작업을 분기하고 두 개의 다른 브랜치에 내용을 커밋할 수 있었습니다.
(이미지 대체 설명: 마스터 브랜치에 C1, C2 커밋이 있고, C2에서 분기된 experiment 브랜치에 C3, C4 커밋이 선형으로 쌓여 있습니다.)
두 브랜치를 통합하는 가장 간단한 방법은 우리가 이미 언급한 merge 명령입니다. 이는 삼자 병합을 수행하여 새 스냅샷을 생성합니다.
(이미지 대체 설명: 마스터 브랜치 C2와 experiment 브랜치 C4가 병합되어 C5라는 새 커밋이 생성됩니다. C5는 C2와 C4 모두를 부모로 가집니다.)
이제 다른 방법이 있습니다. C4의 변경 내용을 C4에 패치할 수 있습니다. Git에서는 이를 'rebase'라고 합니다. 이 명령을 통해 사용자는 한 브랜치에 커밋된 내용을 다른 브랜치에 적용할 수 있습니다.
예를 들어, 사용자가 experiment 브랜치를 체크아웃한 다음 이를 master 브랜치로 리베이스합니다.
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
(이미지 대체 설명: experiment 브랜치의 C3, C4 커밋이 master 브랜치 C2 뒤에 C3', C4'로 다시 적용되어 선형적인 이력을 만듭니다. HEAD는 experiment 브랜치 C4'를 가리킵니다.)
이 작업은 두 브랜치의 공통 조상으로 돌아가, 두 커밋의 다른 내용을 비교하고, 이 차이점을 임시 파일에 저장하고, 현재 브랜치를 리베이스하려는 대상 브랜치로 재설정한 다음, 마지막으로 변경 사항을 대상 브랜치에 적용합니다.
이때 사용자는 master 브랜치로 돌아가 'fast-forward' 병합을 수행할 수 있습니다.
$ git checkout master
$ git merge experiment
(이미지 대체 설명: master 브랜치 C2가 experiment 브랜치 C4'로 fast-forward 병합됩니다. master 브랜치 포인터가 C4'를 가리키도록 이동합니다.)
이때, C4'를 가리키는 이 스냅샷은 이전에 merge를 사용하여 병합했을 때의 C5 스냅샷과 동일합니다. 통합 결과에는 차이가 없지만, 리베이스는 깔끔한 이력을 생성합니다. 리베이스 후 브랜치 로그를 확인하면 선형적인 이력을 보여줍니다. 즉, 모든 작업이 병렬로 진행되었음에도 불구하고 선형적으로 발생한 것처럼 보입니다.
일반적으로 사용자는 이 방법을 사용하여 원격 브랜치에 적용되는 커밋이 깔끔하도록 보장합니다. 아마도 자신이 유지 관리하지 않는 프로젝트에 커밋하려고 할 때 그렇습니다. 이 예시에서 사용자는 한 브랜치에서 작업을 완료한 다음, 작업을 완료하고 커밋하려 할 때 내용을 master 브랜치로 리베이스합니다. 이 방식에서는 유지 관리자가 통합 작업을 수행할 필요가 없습니다. 이 병합은 깔끔하게 적용되는 'fast-forward' 병합입니다.
병합 작업을 위해 merge를 사용하든 rebase를 사용하든 최종적으로 얻는 스냅샷은 동일하다는 점에 유의하십시오. 유일한 차이점은 이력 로그가 다르다는 것입니다.
3.6.2. 더 흥미로운 리베이스
더 복잡한 상황을 고려해 봅시다. master, server, client 세 개의 브랜치를 유지 관리하고 있습니다.
(이미지 대체 설명: 마스터 브랜치 C1-C2, C2에서 분기된 server 브랜치 C3-C4, C4에서 분기된 client 브랜치 C5-C6를 보여주는 다이어그램입니다.)
이제 client 브랜치의 내용을 master 브랜치에 적용하고, server 측 수정 사항을 계속 유지하면서 client와 server 측의 다른 부분만 master 브랜치에 병합하고 싶다고 가정해 봅시다(C8, C9). git rebase --onto master server client 명령을 사용하여 이를 구현할 수 있습니다.
$ git rebase --onto master server client
(이미지 대체 설명: client 브랜치의 C5-C6 커밋이 server 브랜치와 master 브랜치의 공통 조상 C4에서 분리되어 master 브랜치 C2 뒤에 C5', C6'로 다시 적용됩니다.)
이 명령은 Git에게 client 브랜치를 사용하여 server 브랜치와 다른 부분을 추출하고, C8, C9를 마치 master에서 직접 분기된 것처럼 간주하여 master 브랜치에 패치를 적용하도록 지시합니다. 다소 복잡하지만 매우 흥미로운 기능입니다.
이제 사용자는 master 브랜치를 'fast-forward' 병합할 수 있습니다.
$ git checkout master
$ git merge client
(이미지 대체 설명: master 브랜치 C2가 client 브랜치 C6'로 fast-forward 병합됩니다. master 브랜치 포인터가 C6'를 가리키도록 이동합니다.)
이제 server 브랜치의 내용을 master 브랜치에 병합해야 한다고 가정해 봅시다. 사용자는 해당 브랜치를 체크아웃할 필요 없이 git rebase <기반_브랜치> <토픽_브랜치> 명령을 사용하여 브랜치를 직접 리베이스할 수 있습니다. 이 명령은 토픽 브랜치(server)의 내용을 체크아웃하고 기반 브랜치(master)에 적용합니다.
$ git rebase master server
(이미지 대체 설명: server 브랜치의 C3-C4 커밋이 master 브랜치 C6' 뒤에 C3'', C4''로 다시 적용됩니다.)
이 리베이스 결과는 다음 그림과 유사하게 보입니다.
(이미지 대체 설명: 마스터 브랜치에 C6'가 있고, server 브랜치에 C3'', C4''가 쌓여 있습니다.)
이때, 다시 'fast-forward' 병합을 수행할 수 있습니다.
$ git checkout master
$ git merge server
(이미지 대체 설명: master 브랜치 C6'가 server 브랜치 C4''로 fast-forward 병합됩니다. master 브랜치 포인터가 C4''를 가리키도록 이동합니다.)
이제 client 및 server 브랜치는 모든 내용이 master 브랜치에 통합되었으므로 삭제할 수 있습니다.
$ git branch -d client
$ git branch -d server
이때 최종 결과는 다음 그림과 유사합니다.
(이미지 대체 설명: 모든 브랜치가 삭제되고 master 브랜치만 C4'' 커밋을 가리키며 선형적인 이력을 가집니다.)
3.6.3. 리베이스의 위험성
이 기능에는 단점이 없는 것은 아닙니다. 한 문장으로 요약하자면: 자신의 저장소 외부에서 커밋된 내용에 대해 리베이스를 수행하지 마십시오. 그리고 다른 사람들이 작업 중인 내용에 대해 리베이스를 수행하지 마십시오.
리베이스가 어떻게 문제를 일으킬 수 있는지 살펴봅시다. 사용자가 중앙 서버에서 저장소를 복제하고 이를 기반으로 일부 작업을 수행했다고 가정해 봅시다. 사용자의 커밋 이력은 다음 그림과 유사하게 보입니다.
(이미지 대체 설명: 중앙 서버의 master 브랜치에 C1-C2 커밋이 있고, 이로부터 분기된 로컬 브랜치에 C3-C4 커밋이 쌓여 있습니다.)
이제 다른 사람이 일부 작업을 수행하고, 병합 및 커밋 작업을 중앙 서버에 푸시했습니다. 사용자가 새 내용을 가져오고(fetch), 새 내용을 자신의 작업에 병합했습니다. 이제 이력은 다음 그림과 유사하게 보입니다.
(이미지 대체 설명: 중앙 서버의 master 브랜치에 C1-C2-C5 커밋이 있고, 로컬 브랜치에 C3-C4가 있으며, C5가 로컬 브랜치와 병합되어 C6 커밋이 생성됩니다.)
이어서 푸시 병합 작업을 수행한 사용자가 자신의 작업을 되돌려 리베이스하기로 결정하고, 강제로 서버 이력을 덮어쓰는 git push --force 작업을 실행했습니다. 사용자는 나중에 서버에서 이 새로운 내용을 가져오면(fetch), 로컬 저장소의 이력 토폴로지는 다음 그림과 유사하게 보입니다.
(이미지 대체 설명: 중앙 서버의 master 브랜치가 C1-C2-C5가 아닌 C1-C2-C3'-C4'-C5'로 리베이스되어 이력이 변경되었습니다. 로컬 브랜치에는 C3-C4-C6가 그대로 남아 있으며, 로컬 master는 C5를 가리키고, fetch한 origin/master는 C5'를 가리킵니다. 두 이력이 충돌하는 상황입니다.)
이때 두 사용자 모두 진퇴양난에 빠집니다. 만약 사용자가 git pull 작업을 실행하면, 이 두 이력을 포함하는 병합 커밋이 생성되고 사용자의 저장소는 다음 그림과 유사하게 보일 것입니다.
(이미지 대체 설명: 로컬 master 브랜치와 origin/master(리베이스된 이력)가 병합되어 새로운 C7 커밋이 생성됩니다. C7은 C6와 C5' 모두를 부모로 가집니다. 이력은 복잡해지고 중복 커밋이 발생합니다.)
사용자의 저장소가 이렇게 보인다면, git log 명령을 실행할 때 동일한 작성자, 날짜 및 메시지를 가진 두 개의 커밋을 보게 될 것입니다. 이는 혼란을 야기할 수 있습니다. 지금 이 이력을 서버에 커밋하고 싶다면, 중앙 서버에 이 리베이스된 커밋들을 도입하게 될 것입니다.
3.6.4. 리베이스 시 리베이스
사용자가 이 상황에 처했을 때, Git은 사용자에게 도움이 될 수 있는 몇 가지 방법을 제공합니다.
(내용 생략)
3.6.5. 리베이스 대 병합 (내용 생략)
4. 서버에서 Git 사용
이 시점에서 사용자는 Git을 사용하여 일반적인 작업을 수행할 수 있어야 합니다. 그러나 Git을 통해 다른 사용자와 협업하려면 원격 Git 저장소가 필요합니다. 기술적으로는 개인 저장소를 통해 푸시 및 풀 작업을 수행할 수 있지만, 그렇게 하면 실수로 문제가 발생할 수 있습니다. 또한 사용자는 자신의 장치가 오프라인일 때에도 다른 사용자가 저장소에 접근할 수 있기를 원할 수 있습니다. 따라서 더 신뢰할 수 있는 공용 저장소를 가지는 것이 매우 유용합니다. 가장 좋은 방법은 사용자와 협업자 모두가 접근하여 푸시 및 풀 작업을 수행할 수 있는 중간 저장소를 설정하는 것입니다.
Git 서비스를 실행하는 것은 매우 직관적입니다. 먼저, 사용자는 서버가 지원해야 할 프로토콜을 선택합니다. 첫 번째 섹션에서는 각 지원 프로토콜과 그 장단점을 소개합니다. 다음 섹션에서는 몇 가지 일반적인 설정을 설명합니다. 마지막으로 몇 가지 프록시 선택 사항을 소개합니다.
자신의 서버를 실행하는 데 관심이 없다면 마지막 섹션을 건너뛸 수 있습니다.
원격 저장소는 일반적으로 'bare' 저장소입니다. 즉, Git 저장소에는 작업 디렉토리가 없습니다.
4.1. 프로토콜
Git은 데이터를 전송하기 위해 네 가지 다른 프로토콜을 사용할 수 있습니다: 로컬(Local), HTTP, 보안 셸(SSH), 그리고 Git 프로토콜. 이들이 무엇이며 어떤 상황에서 사용해야 하는지 논의할 것입니다.
4.1.1. 로컬 프로토콜
가장 기본적인 프로토콜은 로컬 프로토콜로, 이 프로토콜에서는 원격 저장소가 동일한 호스트의 다른 파일에 있습니다. 이는 일반적으로 NFS 마운트와 같은 공유 파일 시스템에 그룹의 모든 사람이 접근하거나, 모든 사람이 동일한 컴퓨터에 로그인할 수 있는 경우에 발생합니다. 후자의 경우는 모든 코드가 동일한 장치에 있으므로 데이터 손실 위험이 있어 권장되지 않습니다.
만약 사용자가 공유 마운트 파일 시스템을 가지고 있다면, 로컬 파일 저장소를 기반으로 복제, 푸시, 풀 작업을 수행할 수 있습니다. 이러한 방식으로 저장소를 복제하거나 기존 프로젝트에 원격으로 추가하려면, 해당 저장소의 경로를 URL로 사용합니다. 예를 들어, 로컬 저장소를 복제하려면 다음 명령을 사용합니다.
$ git clone /srv/git/프로젝트.git
또는 다음과 같이 사용할 수 있습니다.
$ git clone file:///srv/git/프로젝트.git
file://을 지정하면 Git 작업이 첫 번째 예시와 약간 다릅니다. 사용자가 단순히 경로를 지정하면 Git은 하드 링크 또는 디렉토리 복사를 통해 필요한 파일을 복사하려고 시도합니다. file://을 지정하면 Git은 일반적인 네트워크 사용 시의 데이터 전송 프로세스를 시작하며, 이는 때때로 비효율적입니다. file://을 지정하는 주된 이유는 저장소의 깨끗한 복사본을 원하고 추가 참조나 객체를 제외하고 싶을 때입니다. 여기서는 빠른 방법이기 때문에 일반적인 경로를 계속 사용할 것입니다.
기존 Git 프로젝트에 로컬 저장소를 추가하려면 다음 명령을 실행할 수 있습니다.
$ git remote add 로컬_프로젝트 /srv/git/프로젝트.git
이제 사용자는 새로운 원격 이름 로컬_프로젝트를 통해 푸시 및 풀 작업을 수행할 수 있지만, 이 방법은 일반적으로 네트워크를 통해 이루어집니다.
4.1.1.1. 장점
파일 기반 저장소의 장점은 간단하며, 기존 파일 권한 및 네트워크 권한을 사용한다는 것입니다.
4.1.1.2. 단점
단점은 공유 방식이 비교적 복잡할 수 있다는 것입니다. 공유 파일 시스템은 구성하기 어렵고, 기본적인 네트워크 연결 접근 방식보다 여러 위치에서 접근하기 불편합니다. 예를 들어, 집에서 내용을 푸시하려면 먼저 원격 디스크를 마운트해야 하며, 이는 네트워크 연결 접근 방식보다 구성이 편리하지 않고 속도도 비교적 느립니다.
4.1.2. HTTP 프로토콜 (내용 생략)
4.1.2.1. Smart HTTP (내용 생략)
4.1.2.2. Dumb HTTP (내용 생략)
4.1.2.3. 장점 (내용 생략)
4.1.2.4. 단점 (내용 생략)
4.1.3. SSH 프로토콜 (내용 생략)
4.1.3.1. 장점 (내용 생략)
4.1.3.2. 단점 (내용 생략)
4.1.4. Git 프로토콜 (내용 생략)
4.1.4.1. 장점 (내용 생략)
4.1.4.2. 단점 (내용 생략)
4.2. 서버에 Git 설정하기
4.2.1. 베어(Bare) 저장소 서버에 배치
(내용 생략)
4.2.2. 소규모 설정
(내용 생략)