Skip to content

Latest commit

 

History

History
985 lines (865 loc) · 52.7 KB

File metadata and controls

985 lines (865 loc) · 52.7 KB

Git과 Perforce

Perforce는 기업에서 많이 사용하는 버전 관리 시스템이다. 1995년 무렵부터 사용됐으며 이 장에서 다루는 시스템 중에서 가장 오래된 버전 관리 시스템이다. 처음 Perforce를 만든 당시 환경에 맞게 설계했기 때문에 몇 가지 특징이 있다. 언제나 중앙 서버에 연결할 수 있고 로컬에는 한 버전만 저장한다. Perforce가 잘 맞는 워크플로도 있겠지만 Git을 도입하면 훨씬 나은 워크플로를 적용할 수 있을 것이라 생각한다.

Perforce와 Git을 함께 사용하는 방법은 두 가지다. 첫 번째는 Perforce가 제공하는 “Git Fusion” 이다. Perforce Depot의 서브트리를 읽고 쓸 수 있는 Git 저장소로 노출 시켜 준다. 두 번째 방법은 git-p4라는 클라이언트 Bridge를 사용하여 Git을 Perforce의 클라이언트로 사용하는 것이다. 이 방법은 Perforce 서버를 건드리지 않아도 된다.

Git Fusion

Perforce는 Git Fusion(http://www.perforce.com/git-fusion 에서 다운로드 받을 수 있음)이라는 제품을 제공한다. 이 제품은 Perforce 서버와 서버에 있는 Git 저장소를 동기화한다.

Git Fusion 설치

Perforce 데몬과 Git Fusion이 포함된 가상 머신 이미지를 내려받는 것이 Git Fusion을 가장 쉽게 설치하는 방법이다. 가상머신 이미지는 http://www.perforce.com/downloads/Perforce/20-UserGit Fusion 탭에서 받을 수 있다. VirtualBox 같은 가상화 소프트웨어로 이 이미지를 동작시킬 수 있다.

가상머신을 처음 부팅시키면 root, perforce, git 세 Linux 계정의 암호를 입력하라는 화면과 가상머신 인스턴스 이름을 입력하라는 화면이 나타난다. 인스턴스 이름은 같은 네트워크 안에서 인스턴스를 구분하고 접근하기 위해 사용하는 이름이다. 이러한 과정을 마치고 나면 아래와 같은 화면을 볼 수 있다.

Git Fusion 가상머신 부팅 화면.
Figure 1. Git Fusion 가상머신 부팅 화면.

화면의 IP 주소는 계속 사용할 거라서 기억해두어야 한다. 다음은 Perforce 사용자를 생성해보자. “Login” 항목으로 이동해서 엔터키를 누르면(또는 SSH로 접속하면) root 로 로그인한다. 그리고 아래 명령으로 Perforce 사용자를 생성한다.

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

첫 번째 명령을 실행하면 VI 편집기가 뜨고 생성한 사용자의 정보를 수정할 수 있다. 기본으로 입력되어있는 정보를 그대로 사용하려면 간단히 :wq 를 키보드로 입력하고 엔터키를 누른다. 두 번째 명령을 실행하면 생성한 Perforce 사용자의 암호를 묻는데 안전하게 두 번 묻는다. 쉘에서 하는 작업은 여기까지이므로 쉘에서 나온다.

다음으로 해야 할 작업은 클라이언트 환경에서 Git이 SSL 인증서를 검증하지 않도록 설정하는 것이다. Git Fusion 이미지에 포함된 SSL 인증서는 도메인 이름으로 접속을 검증한다. 여기서는 IP 주소로 접근할 거라서 Git이 HTTPS 인증서를 검증하지 못한다. 그래서 접속할 수도 없다. 이 Git Fusion 가상머신 이미지를 실제로 사용할 거라면 Perforce Git Fusion 메뉴얼을 참고해서 SSL 인증서를 새로 설치해서 사용하는 것을 권한다. 그냥 해보는 거라면 인증서 검증을 안하면 된다.

$ export GIT_SSL_NO_VERIFY=true

제대로 작동하는지 아래 명령으로 확인해보자.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

Perforce가 제공한 가상머신 이미지는 안에 샘플 프로젝트가 하나 들어 있다. HTTPS 프로토콜로 프로젝트를 Clone 할 때 Git은 인증정보를 묻는다. 앞서 만든 john 이라는 사용자이름과 암호를 입력한다. Credential 캐시로 사용자이름과 암호를 저장해 두면 이 단계를 건너뛴다.

Git Fusion 설정

Git Fusion을 설치하고 나서 설정을 변경할 수 있다. 이미 잘 쓰고 있는 Perforce 클라이언트가 있으면 그걸로 변경할 수 있다. Perforce 서버의 //.git-fusion 디렉토리에 있는 파일을 수정하면 된다. 디렉토리 구조는 아래와 같다.

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]

├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

objects 디렉토리는 Git Fusion이 Perforce 객체와 Git을 양방향으로 대응시키는 내용을 담고 있으므로 이 디렉토리 안의 내용을 임의로 수정하지 말아야 한다. p4gf_config 파일은 루트 디렉토리에, 그리고 각 저장소마다 하나씩 있으며 Git Fusion이 어떻게 동작하는지를 설정하는 파일이다. 루트 디렉토리의 이 파일 내용을 보면 아래와 같다.

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

이 책에서는 이 파일 내용 한 줄 한 줄 그 의미를 설명하지는 않는다. Git에서 사용하는 환경설정 파일과 마찬가지로 INI 형식으로 관리된다는 점을 알아두면 된다. 루트 디렉토리에 위치한 이 파일은 전역 설정이다. repos/Talkhouse/p4gf_config 처럼 각 저장소마다 설정할 수도 있는데 전역설정 위에(Override) 적용된다. 각 저장소별 설정 파일의 내용을 보면 아래와 같이 전역 설정과 다른 섹션이 있다.

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

파일 내용을 보면 Perforce와 Git의 브랜치간 매핑 정보를 볼 수 있다. 섹션 이름은 겹치지만 않으면 아무거나 사용할 수 있다. git-branch-name 항목은 길고 입력하기 어려운 Depot 경로를 Git에서 사용하기에 편한 이름으로 연결해준다. view 항목은 어떻게 Perforce 파일이 Git 저장소에 매핑되는지를 View 매핑 문법을 사용하여 설정한다. 여러 항목을 설정할 수 있다.

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

이와 같은 식으로 구성하면 디렉토리 안의 변경사항이 Git 저장소로 반영된다.

마지막으로 살펴볼 설정파일은 users/p4gf_usermap 파일로 Perforce 사용자를 Git 사용자로 매핑하는 역할을 하는데 때에 따라서는 필요하지 않을 수도 있다. Perforce Changeset을 Git의 커밋으로 변환할 때 Git Fusion은 Perforce 사용자의 이름과 이메일 주소를 가지고 Git 커밋의 저자와 커미터 정보를 입력한다. 반대로 Git 커밋을 Perforce Changeset으로 변환할 때는 Git 커밋에 저장된 이름과 이메일 정보를 가져와 Changeset에 기록하고 이 정보로 권한을 확인한다. 보통은 리모트 저장소에 동일한 정보가 등록 돼있어서 문제없겠지만 정보가 다르다면 아래와 같이 매핑 정보를 설정해야 한다.

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

매핑 설정은 한 라인에 한 유저씩 설정하며 ID 이메일 "<긴 이름>" 형식으로 구성한다. 첫 번째 라인과 두 번째 라인은 이메일 주소 두 개를 Perforce 유저 하나로 매핑한다. 이렇게 설정하면 Git 커밋에 이메일 주소를 여러 개 사용했어도 한 Perforce 유저의 Changeset으로 변환할 수 있다. 반대로 Perforce Chageset을 Git 커밋으로 변경할 때는 첫 번째 정보를 이용하여 커밋의 저자 정보를 기록한다.

마지막 두 라인은 Perforce 사용자 bob도 joe도 Git 커밋으로 변환할 때는 같은 이름을 쓰도록 설정한 것이다. 이는 내부 프로젝트를 오픈 소스로 공개할 때, 내부 개발자 이름을 드러내지 않고 외부로 오픈할 때 유용하다. Git 커밋을 한 사람이 작성한 것으로 하려는게 아니라면 사람 이름과 이메일 주소는 중복되지 않아야 한다.

워크플로

Perforce의 Git Fusion은 Git과 Perforce사이에서 양방향의 데이터 변환을 지원하는 Bridge이다. Git을 Perforce의 클라이언트로 사용할 때 어떤식으로 사용하면 되는지 예제를 통해 살펴보자. 위에서 살펴본 설정파일로 “Jam” 이라는 Perforce 프로젝트를 아래와 같이 Clone 할 수 있다.

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

먼저 처음 저장소를 Clone 할 때는 시간이 매우 많이 걸릴 수 있다. Git Fusion이 Perforce 저장소에서 가져온 모든 Changeset을 Git 커밋으로 변환하기 때문이다. 변환하는 과정이야 빠르더라도 히스토리 자체 크기가 크다면 전체 Clone 하는 시간은 오래 걸리기 마련이다. 이렇게 한 번 전체를 Clone 한 후에 추가된 내용만을 받아오는 시간은 Git과 마찬가지로 오래걸리지 않는다.

Clone 한 저장소는 지금까지 살펴본 일반적인 Git 저장소와 똑같다. 확인해보면 브랜치가 3개 있다. 먼저 Git은 로컬 master 브랜치가 서버의 origin/master 브랜치를 추적하도록 미리 만들어 둔다. 내키는대로 파일을 좀 수정하고 커밋을 두어번 하면 아래와 같이 히스토리가 쌓인 모습을 볼 수 있다.

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

새 커밋 두 개가 로컬 히스토리에 쌓였다. 다른 사람이 Push 한 일이 있는지 확인해보자.

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

그새 누군가 부지런히 일을 했나보다. 정확히 누가 어떤 일을 했는지는 커밋을 까봐야 알겠지만 어쨋든 Git Fusion은 서버로부터 새로 가져온 Changeset을 변환해서 6afeb15 커밋을 만들어놨다. 여태 Git에서 본 여타 커밋이랑 다르지 않다. 이제 Perforce 서버가 Merge 커밋을 어떻게 다루는지 살펴보자.

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Git은 이렇게 Merge 하고 Push 하면 잘 되었겠거니 한다. Perforce의 관점에서 README 파일의 히스토리를 생각해보자. Perforce 히스토리는 p4v 그래프 기능으로 볼 수 있다.

Git이 Push 한 Perforce 리비전 결과 그래프.
Figure 2. Git이 Push 한 Perforce 리비전 결과 그래프.

Perforce의 이런 히스토리 뷰어를 본 적이 없다면 다소 혼란스럽겠지만 Git 히스토리를 보는 것과 크게 다르지 않다. 그림은 README 파일의 히스토리를 보는 상황이다. 왼쪽 위 창에서 README 파일과 관련된 브랜치와 디렉토리가 나타난다. 오른쪽 위 창에서는 파일의 리비전 히스토리 그래프를 볼 수 있다. 오른쪽 아래 창에서는 이 그래프의 큰 그림을 확인할 수 있다. 왼쪽 아래 창에는 선택한 리비전을 자세히 보여준다(이 그림에서는 리비전 2 다)

Perforce의 히스토리 그래프상으로는 Git의 히스토리와 똑 같아 보인다. 하지만, Perforce는 12 커밋을 저장할 만한 브랜치가 없다. 그래서 .git-fusion 디렉토리 안에 “익명” 브랜치를 만든다. Git 브랜치가 Perforce의 브랜치와 매치되지 않은 경우에도 이와 같은 모양이 된다(브랜치간 매핑은 나중에 설정할 수도 있다).

이런 작업들은 Git Fusion 내부에서 보이지 않게 처리된다. 물론 이 결과로 Git 클라이언트로 Perforce 서버에 접근하는 사람이 있다는 것을 누군가는 알게 된다.

Git-Fusion 요약

Perforce 서버에 권한이 있다면 Git Fusion은 Git과 Perforce 서버간에 데이터를 주고받는 도구로 매우 유용하다. 물론 좀 설정해야 하는 부분도 있지만 익히는게 그리 어렵지는 않다. 이 절에서는 Git을 조심해서 사용하라고 말하지 않는다. 이 절은 그런 절이다. 그렇다고 Perforce 서버가 아무거나 다 받아 주지 않는다. 이미 Push 한 히스토리를 재작성하고 Push 하면 Git Fusion이 거절한다. 이런 경우에도 Git Fusion은 열심히 노력해서 Perforce를 마치 Git 처럼 다룰 수 있게 도와준다. (Perforce 사용자에게는 생소하겠지만) Git 서브모듈도 사용할 수 있고 브랜치(Perforce 쪽에는 Integration으로 기록된다)를 Merge 할 수도 있다.

서버 관리 권한이 없으면 Git Fusion을 쓸 수 없지만 아직 다른 방법이 남아 있다.

Git-p4

Git-p4도 Git과 Perforce간의 양방향 Bridge이다. Git-p4는 모든 작업이 클라이언트인 Git 저장소 쪽에서 이루어지기 때문에 Perforce 서버에 대한 권한이 없어도 된다. 물론, 인증 정보 정도는 Perforce 서버가 필요하다. Git-p4는 Git Fusion만큼 완성도 높고 유연하지 않지만 Perforce 서버를 건드리지 않고서도 대부분은 다 할 수 있게 해준다.

Note

git-p4가 잘 동작하려면 p4 명령을 어디에서나 사용할 수 있게 PATH 에 등록해두어야 한다. p4 는 무료로 http://www.perforce.com/downloads/Perforce/20-User 에서 다운로드 받을 수 있다.

설정

예제로 사용할 Perforce 프로젝트를 가져오기 위해 앞에서 살펴본 Git Fusion OVA 이미지의 Perforce 서버를 사용한다. Git Fusion 서버 설정은 건너뛰고 Perforce 서버와 저장소 설정 부분만 설정하면 된다.

git-p4이 의존하는 p4 클라이언트를 커맨드라인에서 사용하기 위해 몇 가지 환경변수를 먼저 설정해야 한다.

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
시작하기

Git에서 모든 시작은 Clone 이다. Clone을 먼저 한다.

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Git의 언어로 표현하자면 위의 명령은 “shallow” Clone을 한다. 모든 저장소의 히스토리를 가져오지 않고 마지막 리비전의 히스토리만 가져온다. 이 점을 기억해야 한다. Perforce는 저장소의 모든 히스토리를 모든 사용자에게 허용하지 않도록 설계됐다. 마지막 리비전만을 가져와도 Git은 충분히 Perforce 클라이언트로 사용할 수 있다. 물론 전체 히스토리를 봐야하는 의도라면 충분하지 않다.

이렇게 Clone 하고 나면 Git 기능을 활용할 수 있는 Git 저장소 하나가 만들어진다.

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

(역주 - 코드 틀린듯)

Perforce 서버를 가리키는 “p4” 리모트가 어떻게 동작하는지 모르지만 Clone은 잘된다. 사실 리모트도 실제하지 않는다.

$ git remote -v

확인해보면 리모트가 전혀 없다. git-p4는 리모트 서버의 상태를 보여주기 위해 몇 가지 Ref를 만든다. 이 Ref는 git log 에서는 리모트인 것처럼 보이지만 사실 Git이 관리하는 리모트가 아니라서 Push 할 수 없다.

워크플로

준비를 마쳤으니 또 수정하고 커밋하고 Push 해보자. 어떤 중요한 작업을 마치고 팀 동료들에게 공유하려는 상황을 살펴보자.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

커밋을 두 개 생성했고 Perforce 서버로 전송할 준비가 됐다. Push 하기 전에 다른 동료가 수정한 사항이 있는지 확인한다.

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

팀 동료가 수정한 내용이 추가되어 master 브랜치와 p4/master 브랜치가 갈라지게 되었다. Perforce의 브랜치 관리 방식은 Git과 달라서 Merge 커밋을 서버로 전송하면 안된다. 대신 git-p4는 아래와 같은 명령으로 커밋을 Rebase 하기를 권장한다.

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

실행 결과를 보면 단순히 git p4 rebasegit rebase p4/master 하고 git p4 sync 명령을 실행한 것 처럼 보일 수 있다. 브랜치가 여러개인 상황에서 훨씬 효과를 보이지만 이렇게 생각해도 괜찮다.

이제 커밋 히스토리가 일직선이 됐고 Perforce 서버로 공유할 준비를 마쳤다. git p4 submit 명령은 p4/mastermaster 사이에 있는 모든 커밋에 대해 새 Perforce 리비전을 생성한다. 명령을 실행하면 주로 쓰는 편집기가 뜨고 아래와 같은 내용으로 채워진다.

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

이 내용은 p4 submit 을 실행했을 때 보이는 내용과 같다. 다만 git-p4는 아래쪽에 도움이 될 만한 내용을 덧 붙여 준다. git-p4는 커밋이나 Changeset을 생성할 때 최대한 Git과 Perforce에 있는 정보를 이용한다. 하지만 경우에 따라 변환할 때 직접 입력해줘야 할 수도 있다. 보내려고 하는 커밋의 저자가 Perforce에 계정이 없을 때도 그 저자가 작성한 Changeset으로 기록되길 바랄 것이다.

git-p4가 Git 커밋의 내용을 바탕으로 Perforce Changeset의 메시지를 생성하기 때문에 보통 그냥 내용을 저장하고 편집기를 종료하면 된다. 커밋이 두 개 있으므로 저장하고 종료하기를 두 번 한다. 어쨌든간에 git p4 submit 의 실행한 결과는 아래와 같다.

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

git p4 submit 에 가까운 Git의 명령은 push 이며 위의 결과를 보면 git push 명령을 실행한 것과 비슷하다.

Git 커밋이 Perforce의 Changeset으로 변환되는 과정을 자세히 살펴보자. Git 커밋 여러개를 하나의 Perforce Changeset으로 만들려면 git p4 submit 명령을 실행하기 전에 Rebase로 커밋을 하나로 합치면 된다. 서버로 보낸 커밋의 SHA-1 해시를 보면 그 값이 바뀐다. git-p4이 Changeset으로 변환할 때 각 커밋 메시지의 마지막에 아래와 같이 한 라인을 추가해서 달라진다.

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

Merge 커밋을 서버로 전송하면 어떤 일이 일어나는지 살펴보자. 아래와 같은 커밋 히스토리가 현재 처한 상황이라고 생각해보자.

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

775a46f 커밋 이후에 Git과 Perforce 히스토리가 갈라졌다. Git으로 작업한 쪽에는 커밋이 두 개, Perforce 쪽에는 커밋 하나가 추가됐고 Merge 하고 서도 커밋이 추가됐다. 여기서 서버로 보내면 Perforce 쪽 Changeset 위에 쌓인다. 바로 Perforce 서버로 히스토리를 보내 보자.

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

-n 옵션은 --dry-run 의 단축 옵션으로 명령일 실제로 실행하기 전에 어떻게 동작하는 지 미리 확인해 볼 수 있다. 결과를 보면 로컬에만 있는 커밋 3개가 Perforce Changeset으로 잘 만들어지는 것으로 보인다. 확실히 이 결과는 우리가 원하던 바이다. 실제로 실행하자.

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Rebase 하고 나서 전송한 것처럼 히스토리가 일직선이 됐다. 이 결과는 Git으로 자유롭게 브랜치를 만들고 버리고 Merge 해도 된다는 것을 말해준다. 히스토리가 Perforce에 맞지 않더라도 걱정할 필요 없다. 물론 직접 Rebase 해서 Perforce 서버로 전송해도 된다.

브랜치

Perforce 프로젝트에 브랜치가 많아도 괜찮다. git-p4은 Perforce 브랜치를 Git 브랜치로 생각하게 끔 만들어 준다. Perforce Depot이 아래와 같다고 하자.

//depot
  └── project
      ├── main
      └── dev

dev 브랜치가 아래와 같은 View Spec을 갖고 있다면,

//depot/project/main/... //depot/project/dev/...

아래와 같이 git-p4는 자동으로 브랜치 정보를 찾아서 잘 처리한다.

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Depot 경로에 “@all” 이라고 지정해주면 git-p4는 마지막 Changeset만을 가져오는 것이 아니라 지정한 경로의 모든 Changeset을 가져온다. Git의 Clone과 비슷하다. 프로젝트 히스토리가 길면 Clone 하는데 오래 걸린다.

--detect-branches 옵션을 주면 git-p4는 Perforce의 브랜치를 Git의 브랜치로 매핑해 준다. 매핑 정보를 Perforce 서버에 두는 것이 Perforce 다운 방식이지만 git-p4에 직접 알려줄 수도 있다. 브랜치 매핑 정보를 git-p4에 전달해서 위의 결과와 똑 같이 매핑시킬 수 있다.

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

git-p4.branchList 설정에 main:dev 값을 저장해두면 git-p4는 “main” 과 “dev” 가 브랜치 이름이고 후자는 전자에서 갈라져나온 것이라 파악한다.

이제 git checkout -b dev p4/project/dev 하고 커밋을 쌓으면, git p4 submit 명령을 실행할 때 git-p4가 똘똘하게 알아서 브랜치를 잘 찾아 준다. 안타깝게도 마지막 리비전만 받아 오는 Shallow Clone을 해야 하는 상황에서는 동시에 브랜치를 여러개 쓸 수 없다. 엄청나게 큰 Perforce이고 여러 브랜치를 오가며 작업해야 한다면 브랜치 별로 git p4 clone 을 따로 하는 수 밖에 없다.

Perforce의 브랜치를 생성하거나 브랜치끼리 합치려면 Perforce 클라이언트가 반드시 필요하다. git-p4는 이미 존재하는 브랜치로부터 Changeset을 가져오거나 커밋을 보내는 일만 할 수 있다. 일직선 형태의 Changeset 히스토리만을 유지할 수 있다. 브랜치를 Git에서 Merge 하고 Perforce 서버로 보내면 단순히 파일 변화만 기록된다. 어떤 브랜치를 Merge 했는 지와 같은 메터데이터는 기록되지 않는다.

Git-Perforce 함께쓰기 요약

git-p4 Perforce 서버를 쓰는 환경에서도 Git으로 일할 수 있게 해준다. 하지만 프로젝트를 관리하는 주체는 Perforce이고 Git은 로컬에서만 사용한다는 점을 기억해야 한다. 따라서 Git 커밋을 Perforce 서버로 보내서 공유할 때는 항상 주의깊게 작업해야 한다. 한 번 Perforce 서버로 보낸 커밋은 다시 보내서는 안된다.

Perforce와 Git 클라이언트를 제약없이 사용하고 싶다면 서버 관리 권한이 필요하다. Git Fusion은 Git을 매우 우아한 Perforce 클라이언트로 만들어 준다.