1. 저장소 조직하기
1.1. 태그를 이용하여 마일스톤 표시하기
프로젝트가 진행되면 마일스톤을 달성하게 된다.
마일스톤을 달성하면 릴리스를 배포하고, 새로운 버전을 생성하는 등의 작업을 한다.
태그를 사용하면 마일스톤을 쉽게 표시할 수 있고, 나중에 원하는 마일스톤으로 돌아갈 수 있다.
태그는 저장소의 책갈피처럼 동작한다.
태그를 붙여두면 나중에 그 태그의 위치로 곧장 되돌아갈 수 있다.
필요하다면 커밋에도 태그를 붙일 수도 있다.
보통 태그는 프로젝트에서 코드를 릴리스 할 때 가장 많이 사용한다.
태그를 사용하면, 릴리스 한 후에도, 버그 수정이나 변경이 필요한 경우, 릴리스 된 코드로 돌아갈 수 있다.
서브버전 사용자에게 익숙한 태그와 달리 Git의 태그는 읽기 전용이다.
읽기만 가능하다는 말은 브랜치와는 다르게 태그의 내용을 변경할 수 없음을 의미한다.
이를 통해 태그를 붙였을 당시의 정확한 내용을 알 수 있고, 그 내용이 변경되지 않았음을 확신할 수 있다. 이는 읽기만 가능한 태그의 장점이다.
Git에서 태그를 붙이는 명령은 git tag이다.
현재 어떠한 태그들이 있는지 살펴보려면 git branch 처럼 매개변수를 지정하지 않고, 명령어를 실행하면 된다.
태그 1.1을 붙이는 명령
Git에서는 태그가 성공적으로 붙여졌다고 알려주지 않는다.
매개변수 없이 git tag를 실행하면, 새로 붙인 태그를 확인할 수 있다.
Git의 태그를 붙이는 명령어는 성공할 때와는 달리, 문제가 발생하면 바로 알려준다.
예를 들어, 태그명에 공백이 포함된 경우처럼 적합하지 않은 태그명으로 태그를 붙이려 한다면, 오류 메시지를 보여준다.
유효한 이름에 대해서는 8.3절 ‘브랜치와 태그에 유효한 이름 사용하기’참고
$ git tag “version 1.1” fatal: ‘version 1.1’is not a valid tag name. |
git tag에 태그명만 지정하고 실행하면, Git은 현재 작업 트리의 커밋에 태그를 붙인다.
원하는 커밋에 태그를 붙이려면 매개변수를 추가하면 된다.
추가할 매개변수로 유효한 커밋명이나 브랜치명을 사용할 수 있다.
예를 들면, 다음과 같이 git tag를 실행하면, contacts 브랜치의 가장 최신 커밋에 태그를 붙일 수 있다.
$ git tag contacts/1.1 contacts $ git tag 1.0 1.1 contacts/1.1 |
태그를 이용해서 태그를 붙여둔 저장소의 상태로 돌아갈 수 있다.
비록 태그의 내용을 변경할 수는 없지만, 브랜치처럼 체크아웃 할 수 있다.
이 상태는 마치 ‘주인 없는 땅’에 와있는 것과 같다.
지금은 브랜치에 있지 않아서 변경사항을 추적할 수 없다.
지역 브랜치를 보려고 git branch를 실행하면, 현재 브랜치가 아님을 알 수 있다.
$ git branch * (no branch) about alternate contacts master new |
다음과 같이 git checkout 에 –b 매개변수를 지정하고, 체크아웃하면 새로운 브랜치를 생성할 수 있다.
$ git checkout –b from-1.0 Switched to a new branch “from-1.0” |
이제는 다시 변경 사항을 추적할 수 있다.
물론 Git의 다른 기능과 마찬가지로, 다른 방법으로 태그에서 브랜치를 만들 수도 있다.
git branch나 git checkout –b 에 2번째 매개변수로 태그명을 사용하면, 새로운 브랜치를 생성한다.
$ git checkout –b another-from-1.0 1.0 Switched to a new branch “another-from-1.0” |
git log를 실행해보면 1.0 태그를 만들 때의 커밋만 있음을 확인할 수 있다.
버그를 수정하거나 릴리스된 코드를 약간 변경하려고 할 때,
태그에서 릴리스 브랜치를 생성하면 유용하다.
1.2. 릴리스 브랜치 다루기
릴리스 브랜치는 릴리스할 코드를 준비하는 장소다.
개발팀에서는 일반적으로 릴리스할 코드를 분리할 목적으로 릴리스 브랜치를 사용한다.
분리하려는 대상이 무엇인지는 개발팀에 따라 다르다.
릴리스 브랜치를 생성할 시점도 개발팀과 개발 스타일에 따라 다르다.
여기서는 편의상 릴리스 브랜치의 생성 시점을 다음과 같이 정의한다.
릴리스 브랜치는, 프로젝트에서 이번 릴리스에 포함하기로 한 기능 구현이 끝나면 생성한다.
즉, 아직 완전히 검토되지 않은 상태인 브랜치이다.
릴리스 브랜치에서는 최소한의 변경만 발생하며,
버그나 로직의 수정에만 집중할 뿐, 새로운 기능을 추가하지 않는다.
따라서 릴리스 브랜치를 이용하면 master 브랜치에 새로운 기능을 쉽게 추가할 수 있으며,
이때, 개발팀은 master 브랜치에 추가되는 코드에 영향을 받지 않고, 릴리스 준비를 할 수 있다.
일반적으로 릴리스 브랜치에는 RB_ 라는 접두어를 붙이며,
그 뒤에 릴리스 번호를 붙인다.
따라서 1.2 버전 릴리스는 RB_1.2가 되고, 1.3버전은 RB_1.3이 된다.
릴리스 브랜치는 해당 릴리스가 요구하는 마지막 테스트까지 통과하는 짧은 기간 동안만 존재한다.
일단 릴리스 준비가 완료되면, 해당 릴리스를 표시하는 태그를 붙이고 브랜치를 삭제한다.
브랜치를 삭제한다고 걱정할 필요는 없다.
다시 말하자면, 이력을 유지하려고 브랜치를 유지할 필요가 없다.
태그가 해당 위치를 표시하고 있다.
브랜치 목록이 난잡해지도록 브랜치를 유지할 필요는 없다.
릴리스된 코드에서 갑자기 발생한 어쩔 수 없는 버그 수정은 어떻게 할까?
이전 절에서 했던 방식대로, 태그에서 새로운 릴리스 브랜치를 생성하면 된다.
RB_ 접두어 형식의 브랜치명과 분기해 나올 태그명을 지정한다.
$ git branch RB_1.0.1 1.0 $ git checkout RB_1.0.1 Switched to branch “RB_1.0.1” |
여기서는 git branch의 두번째 매개변수로 태그명을 사용했다.
git branch 는 태그가 참조하는 커밋을 기반으로 브랜치를 생성한다.
새로운 브랜치는, 태그를 붙였을 때의 릴리스 브랜치 상태와 같다.
이전에 태그를 붙이고 삭제했던 릴리스 브랜치와 이력도 동일하다.
git log로 이러한 내용을 확인해 보자.
$ git log –pretty=format:”%h %s” … |
이제 문제를 수정하고, 수정이 완료됐다면 새로운 태그를 붙이자.
버그를 수정하고 새로운 태그를 붙였다면, 이전 릴리스 브랜치와 마찬가지로, 새로운 브랜치를 삭제한다.
현재 위치에서 브랜치를 삭제할 수 없으므로, master 브랜치로 다시 전환하자.
$ git checkout master Switched to branch “master” $ git branch –D RB_1.0.1 Deleted branch RB_1.0.1 |
여기서는 git branch –D로 브랜치를 삭제했는데, master 브랜치가 RB_1.0.1과는 연관이 없다고 취급했다.
릴리스 브랜치는 릴리스에 가까워질 때 개발팀의 작업흐름을 조직하는데 도움을 준다.
릴리스 브랜치를 이용하면, 버그 수정이나 고객의 급작스런 변경요청과 같이 반드시 필요한 코드만 릴리스에 반영함으로써, 새로운 기능 추가나 버그로부터 릴리스에 영향을 미치지 않도록 한다.
1.3. 브랜치와 태그에 유효한 이름 사용하기
Git에서 사용하는 이름 대부분은 원하는 형태로 사용할 수 있지만, 피해야 할 몇 가지가 있다.
먼저, 태그와 브랜치의 이름에서 슬래시(/)를 사용할 수 있지만, 슬래시로 끝날 수는 없다.
슬래시를 이용하면 태그와 브랜치를 디렉터리와 비슷한 구조로 조직할 수 있다.
이름에도 마침표(.)도 사용할 수 있지만, 각 경로명은 마침표로 시작할 수 없다.
예를 들어, releases/1.0은 유효한 태그명이지만, releases/.1.0이나 .releases/1.0은 유효하지 않다.
리눅스나 BSD, 맥OS X 파일시스템과 같이, 마침표와 관련된 제약사항을 갖는다.
해당 운영체제에서는 마침표로 시작하는 파일이나 디렉터리를 숨겨진 자원으로 다룬다.
태그와 브랜치의 이름에는 몇 가지 특수문자를 사용할 수 없다.
사용할 수 없는 특수문자에는 공백, 물결표(~), 캐럿(^), 콜론(:), 물음표(?), 별표(*), 왼쪽 대괄호([)가 있다.
ASCII 제어문자(\040보다 작은 문자)나 삭제 키(ASCII \177)도 허용되지 않는다.
여기서 언급한 ASCII 문자는 실수로라도 입력할 수 없으므로, ASCII 문자가 뭔지 모른다거나 어떻게 입력해야 할지 모른다고 걱정할 필요는 없다.
마지막으로 이름에 마침표를 두 번 연속해서 사용할 수 없다.
6.2절 ‘리비전 범위 지정하기’에서 다룬 리비전의 범위를 지정하는 구문을 떠올려 보자.
범위는 <첫 번째 커밋>..<두 번째 커밋> 구문을 사용한다.
모호해질 수 있으므로, 태그와 브랜치 이름에서 연속된 마침표는 허용되지 않는다.
브랜치와 태그는 유효한 파일이나 디렉터리 형태라면 어떠한 이름도 사용할 수 있다.
브랜치와 태그를 보통의 파일 시스템을 사용하듯이, 정렬하기 쉽도록 디렉터리와 비슷한 구조로 조직할 수 있다.
1.4. 프로젝트 여러 개를 추적하기
회사들은 대부분 다수의 프로젝트를 진행한다.
하나의 제품도 다른 구성요소라면, 모두 분리해야 한다.
그렇게 해야 개발팀의 구성원이 전체 코드를 뒤질 필요 없이 필요한 코드를 찾을 수 있다.
Git에서는 프로젝트 여러 개를 다룰 수 있는 몇 가지 방법을 제공한다.
프로젝틀 모두를 저장소 하나에 저장할 수도 있고, 프로젝트마다 개별 저장소를 가질 수도 있다. 두 가지 방식 모두 장점과 단점이 있다.
1.4.1. 프로젝트 여러 개를 저장소 하나에 저장하기
가장 직관적인 방법은, 저장소 하나를 생성하고 프로젝트 여러 개를 저장하는 방식이다.
저장소 안에서 각 프로젝트마다 각기 다른 최상위 디렉터리를 생성하여, 이 방식을 사용할 수 있다.
서브버전에서 프로젝트 여러 개를 저장소 하나에 저장할 때 이렇게 사용하므로 서브버전 사용자라면 익숙할 것이다.
이는 모든 프로젝트를 함께 저장하기에 편리한 방식이며, 저장소 하나만 복제하면 모든 프로젝트를 이용할 수 있다.
이 방식은 함께 릴리스되는 컴포넌트가 여러 개로 구성된 프로젝트처럼, 공통된 이력이 필요한 프로젝트에 적합하다.
CMS나 주문 입력 시스템과 같이, 여러 개의 분리된 컴포넌트로 구성되어 있지만, 패키지 형태로 한 번에 릴리스되는 모든 종류의 프로그램에 공통의 이력이 필요한 프로젝트에 해당된다.
작은 프로젝트나 컴포넌트가 큰 프로젝트의 일부로만 릴리스된다면 이력을 함께 공유하는 게 좋은 생각이다. 저장소의 모든 이력이 큰 프로젝트 하나를 중심으로 관리된다.
작은 프로젝트나 컴포넌트가 따로 릴리스된다면, 아마도 해당 프로젝트만의 고유한 이력이 필요할 것이다.
Git에서는 태그와 브랜치를 일부 영역에만 적용할 수 없고, 저장소 전체에 적용하므로, 프로젝트의 일부분을 별도로 릴리스해야 한다면 저장소를 분리하는 게 좋다.
여러 프로젝트의 릴리스 일정이 서로 다르다면, 생성해야 할 브랜치와 태그의 수가 기하급수적으로 늘어난다.
대신에 Git에서는 저장소를 생성하는 비용이 적으므로 프로젝트마다 저장소를 생성하는 일이 그리 어려운 일이 아니다.
1.4.2. 프로젝트 하나를 저장소 하나에 저장하기
저장소 하나에 프로젝트 여러 개를 저장하는 방식의 대안은,
프로젝트마다 저장소를 생성하는 것이다.
이 방식은 약간의 설정이 필요하지만 프로젝트마다 고유한 이력을 갖는다.
1.5. Git 하위 모듈을 사용하여 외부 저장소 추적하기
때로는 저장소 여러 개를 마치 저장소 하나에 있는 것처럼 추적해야 한다.
이런 상황에는 외부 라이브러리에 종속되거나, 자체 개발 프로젝트가 관리하기 용이하도록 프로젝트 여러 개로 나뉘어질 경우가 있다.
Git에서는 하위모듈(submodule)을 사용하여 외부 저장소를 추적할 수 있다.
하위모듈(submodule)을 사용하면, 두 저장소 이력을 별도로 관리하면서, 저장소 하나를 다른 저장소 안에 저장할 수 있다.
서브버전의 svn:externals와 같은 기능이다.
1.5.1. 새로운 하위 모듈 추가하기
하위 모듈 저장소를 둘 새로운 저장소를 생성한다.
magic이라는 새로운 저장소를 생성한다.
$ mkdir /work/magic $ cd /work/magic $ git init … |
git init으로 초기화된 빈 저장소에서 연관된 하위 모듈을 보려면 git submodule을 실행한다.
아직 하위 모듈을 정의하지 않았으므로, 아무런 결과도 표시되지 않는다.
새로운 하위 모듈 추가는 간단하다. git submodule add 명령어를 사용한다.
git submodule add 에는 2개의 매개변수가 필요하다.
첫번째는 외부 저장소이고, 두번째는 저장소를 저장할 경로이다.
ex) GitHub에 있는 저장소를 hocus 디렉토리에 저장소를 저장하자.
$ git submodule add git://github.com/tswicegood/hocus.git hocus … |
다시 git submodule을 실행하면 hocus 저장소가 표시된다.
$ git submodule -20asdflajsldfa09sd8fa7sdfa6df8 hocus |
Git의 하위 모듈은 원격 저장소에 있는 특정 리비전을 추적한다.
해당 리비전은 해시로 표시되며, 이어서 하위 모듈명으로 hocus가 나온다.
해시 앞의 –기호는 hocus 하위 모듈이 아직 초기화되지 않았음을 나타낸다.
간단하게 초기화 할 수 있다.
$ git submodule init hocus |
git submodule init hocus를 실행하면,
.git/config 파일에 하위 모듈에 대한 정보를 추가해서, Git이 hocus 디렉터리에 하위 모듈을 포함하고 있음을 인식하도록 한다.
이제 추가하고 초기화하는 작업이 모두 끝났으니, 저장소에 일어난 변화를 확인해 보자.
.gitmodules는 하위 모듈에 대한 모든 정보를 저장하는 일반 텍스트 파일이다.
Git에서는 저장소 내부에서 이 파일을 추적한다.
이 파일을 저장소에서 관리함으로써 자신의 저장소를 다른 사람과 공유했을 때,
저장소를 복제한 사람이 하위 모듈을 설정하는데 필요한 정보를 얻을 수 있다.
1.5.2. 하위 모듈과 함께 저장소 복제하기.
처음 복제한 저장소에 하위 모듈을 설정하려면, 몇 가지 절차가 필요하다.
우선 magic 저장소를 복제한다.
$ cd /work $ git clone magic new-magic Initialized empty Git repository in /work/new-magic/.git/ $ cd new-magic $ ls hocus |
hocus 디렉터리가 존재하지만 비어 있다.
git submodule을 실행하면, 하위 모듈이 아직 초기화되지 않았음을 알 수 있다.
$ git submodule -20234k2lrjfljaldfjasdjflafa hocus |
첫 번째 문자로 –기호가 나타나면, 하위 모듈이 초기화되지 않았음을 의미한다.
초기화되도록 git submodule inti 명령을 실행.
$ git submodule init hocus Submodule ‘hocus’(git://github.com/twsicegoold/hocus.git) registered for path ‘hocus’ |
hocus 디렉토리는 아직도 비어 있는데,
하위 모듈의 변경 사항을 가져오려면 git submodule update 명령을 실행한다.
다른 git submodule 명령어와 마찬가지로 git submodule update도 현재 작업 중인 하위 모듈명을 매개변수로 지정한다.
$ git submodule update hocus Initialized empty Git repository in /work/new-magic/hocus/.git/ remote:… remote:… remote:… Receiving objects: 100% (7/7), done. Submodule path ‘hocus’: checked out ‘20234k2lrjfljaldfjasdjflafa’ |
이제 hocus 디렉터리에 해시명이 2023인 커밋의 모든 파일이 저장되었다.
현재는 하위 모듈이 커밋 하나만 추적한다.
1.5.3. 하위 모듈이 추적하는 커밋 변경하기.
Git 하위 모듈은 저장소의 최신 커밋을 추적하지 않는다.
즉, 독립된 커밋 하나만을 추적한다.
처음에는 하위 모듈을 추가할 당시의 마지막 커밋인 HEAD를 추적한다.
하위 모듈이 추적하는 커밋을 변경하려면 다른 작업이 필요하다.
이러한 과정은 서브버전을 사용해 본 사람이라면 의아할 것이다.
서브버전은 저장소를 추적하며 갱신하면, 알아서 최신의 커밋을 가져온다.
개별 리비전을 추적하려면 명시적으로 설정해야 한다.
언뜻 보기에 이러한 방식이 더 편리해 보이지만 문제가 발생할 수 있다.
추적하는 저장소에서 버그가 발생하거나, 자신의 원격 저장소 리비전이 다른 개발자의 버전과 다를 경우 어떻게 될까? 명시적으로 하나의 커밋만 추적하면 이런 문제는 발생하지 않는다.
Git에서는 하위 모듈을 생성할 때, 추적할 커밋이 결정된다.
즉, 저장소에서 해당 커밋만 가져온다.
하위 모듈은 사실 특정 커밋을 체크아웃 하여 완전히 복제된 저장소다.
hocus 디렉토리로 이동한 다음, 브랜치 목록을 보면 이것을 확인할 수 있다.
$ cd hocus/ $ git branch * (no branch) master |
이 저장소는 2개의 커밋만 가진다.
둘째 커밋 대신에 첫째 커밋으로 변경해보자
HEAD 바로 전으로 체크아웃하자.
$ git checkout HEAD^ Previous HEAD position was 20cc9dd…initial commit HEAD is now at 7901f67…initial commit with README |
다음은 Git에게 변경된 하위 모듈을 사용한다는 사실을 알려야 한다.
매개변수를 지정하지 않고 git subodule을 실행하면 Git은 변경이 있음을 감지할 수 있다.
$ cd .. $ git submodule +7901f67…. hocus (7901f67) |
+ 기호는 Git이 예상하던 커밋이 아님을 의미한다.
git status를 실행해 보면 hocus 디렉터리가 변경됐음을 볼 수 있다.
$ git status # On branch master # Changed but not updated: # (use “git add <file>…“to update what will be committed) # # modified: hocus # no changes added to commit (use “git add”and/or “git commit –a”) |
이제 hocust 디렉터리를 추가하고 변경 사항을 커밋해야 한다.
이렇게 하면 Git은 새로운 커밋을 사용하여 하위 모듈을 추적한다.
$ git add hocus $ git commit –m ‘update commit to track in submodule’ Creaated commit fedf2bc: update commit to track in submodule 1 files changed, 1 insertions(+), 1 deletions(-) |
1.5.4. 하위 모듈을 이용하여 작업할 때의 주의 사항.
git add를 실행할 때 명령어가 슬래시(/)로 끝나서는 안 된다.
Git은 슬래시(/)를 추가하면, 하위 모듈이 추적하는 커밋을 업데이트하는 대신,
참조하고 있는 저장소의 모든 파일을 현재 지역 저장소에 추가하라는 요청으로 해석한다.
커밋하기 전에 git status를 실행해 보면 혹시 실수했는지 확인할 수 있다.
또 다른 주의 사항으로는,
git submodule update는 충돌을 고려하지 않고 덮어쓴다는 점이다.
갱신 명령을 실행하면, 하위 모듈에 있는 아직 커밋되지 않은 변경 사항까지 덮어쓴다.
git submodule update를 실행해서 변경사항을 수정하기 전에, 바꾼 내용은 없는지 하위 모듈의 작업 트리를 다시 한번 확인하자.
마지막 주의사항은 하위 모듈의 저장소에 직접 새로운 내용을 추가하는 것과 관련한 사항이다.
먼저 변경하기에 앞서, 변경한 내용을 반영하려는 브랜치를 체크아웃하자.
하위 모듈이 일반 브랜치에서 가져왔음을 기억하자.
간단히 git checkout으로 수정한다.
그 다음은 일단 변경 사항을 커밋했다면, 변경 사항을 하위 모듈 체크아웃에 공유하기 전에 원격 저장소에 변경 사항이 존재하는지 확인해야 한다.
git submodule update를 실행하려고 시도할 때, 하위 모듈을 참조하는 커밋이 원격 저장소에 존재하지 않는다면, Git은 예상과 다르게 동작할 것이다.
출처 : http://youmin3.egloos.com/1990629