사전 Perforce
구현체

Perforce

gabury1고친 사람 github-actions[bot]

Perforce 는 여러 사람이 함께 고치는 파일과 그 이력을 서버 한 곳에 모아 지켜 주는 도구입니다. 파일을 고치기 전에 서버에 먼저 알립니다. 고친 파일은 묶음 하나로 서버에 올립니다. 합칠 수 없는 그림 파일은 한 사람만 고치도록 막을 수도 있습니다.

쉽고 빠른 이해

Perforce 는 팀의 파일과 변경 기록을 서버 한 대에 모아 두는 도구입니다. 게임 팀이라면 엔진 코드와 캐릭터 그림 파일을 한 서버에 둡니다. 모두가 거기서 받아 고칩니다.

이런 도구가 없으면 누가 무엇을 언제 바꿨는지 남지 않습니다. 그림 파일은 둘이 동시에 고치면 합칠 방법이 없습니다. 한 사람의 작업이 버려집니다.

  1. 서버에서 내가 맡은 폴더의 최신 파일을 받습니다
  2. 고치기 전에 서버에 「이 파일을 고치겠다」고 알립니다. 그래야 파일에 쓰기가 풀립니다
  3. 고친 파일을 묶음 하나로 올립니다. 묶음 안의 파일은 전부 들어가거나 하나도 안 들어갑니다

대가도 있습니다. 서버에 닿지 않으면 알리기도 올리기도 못 합니다. 그림 파일을 잠가 두면 앞사람이 끝낼 때까지 기다려야 합니다.

상세

Perforce 는 버전관리 도구입니다. 버전관리는 파일을 언제 누가 어떻게 바꿨는지 남겨 두는 일입니다. 필요하면 예전 모습으로 되돌립니다.

이 절은 이력이 어디에 사는지부터 봅니다. 이어서 받기·열기·제출의 흐름을 명령으로 따라갑니다. 끝으로 파일 형식, 브랜치, 셸브를 봅니다.

서버 한 대가 이력을 쥐는 구조

Perforce 는 클라이언트 프로그램과 서버로 나뉘어 돕니다. 서버 프로그램은 p4d 입니다. p4d 는 파일의 모든 판과 그 파일에 대한 기록을 관리합니다. 쓰는 사람은 명령줄 클라이언트 프로그램 p4 같은 프로그램으로 그 서버에 붙습니다.

서버는 파일 내용과 기록을 갈라서 쥡니다. 이 기록이 메타데이터입니다. 누가 어느 판을 만들었는지가 여기 들어갑니다.

각 사람의 작업 폴더가 지금 어떤 상태인지, 누가 무엇을 할 수 있는지도 메타데이터입니다. 메타데이터는 서버의 데이터베이스에 담깁니다.

이력을 서버 한 곳에 두는 이 방식을 중앙집중식이라고 부릅니다. 반대편은 사람마다 이력 전체를 한 벌씩 갖는 분산식입니다. Git 이 분산식입니다. Perforce 서버도 분산식 흐름을 받칩니다. 그래도 기본은 공유 서버와 바로 주고받는 흐름입니다.

서버 한 곳이 모두의 상태를 알고 있어서 되는 일이 둘 있습니다. 파일을 받을 때 서버는 내게 없는 판만 보냅니다. 그리고 관리자가 지정해 둔 파일은 한 번에 한 사람만 열게 막습니다.

같은 도구의 세 이름

Perforce 는 1995년에 같은 이름의 회사가 내놓은 버전관리 도구입니다. 처음부터 줄여서 P4 라고도 불렸습니다. p4 sync · p4 submit 같은 명령 이름이 그 줄임말을 씁니다.

2016년에 회사의 주인이 바뀌었습니다. 같은 때 회사는 Seapine Software 와 그 회사의 ALM(Application Lifecycle Management, 애플리케이션 생애주기 관리) 제품 Test Track 을 사들였습니다. 이 인수가 원래 Perforce 라고 불린 제품의 이름을 바꾸게 만들었습니다.

회사 경영진이 그때 고른 이름이 Helix Core 입니다. 2025년 3월에는 다시 Perforce P4 로 바뀌었습니다.

세 이름이 같은 도구를 가리킵니다. 공식 문서 주소에는 아직 helix-core 가 들어 있습니다. 페이지 머리에는 「Helix Core is now P4」 라는 띠가 붙어 있습니다.

디포와 워크스페이스

디포(depot)는 서버 안에서 파일을 담는 보관소입니다. 디포에는 지금까지 제출된 모든 파일의 모든 판이 들어 있습니다. 지운 파일의 예전 판도 되살릴 수 있습니다.

예외는 이력까지 영구히 없애는 명령으로 지운 파일입니다. 그 명령은 아래 「운영」의 p4 obliterate 입니다. 서버 하나에 디포를 여럿 둘 수 있고, 종류가 다른 디포를 섞어 둘 수도 있습니다.

디포 안의 파일은 슬래시 둘로 시작하는 경로로 가리킵니다. //depot/source/module/file.c 처럼 씁니다. 슬래시 둘 다음 첫 토막은 디포 이름이거나 워크스페이스 이름입니다. 디포를 가리키는 경로라면 첫 토막이 디포 이름이고 그 뒤가 디포 안의 경로입니다.

내 컴퓨터에서 파일을 받아 고치는 폴더 묶음을 워크스페이스라고 부릅니다. 워크스페이스에는 이름이 있고 서버는 그 이름으로 워크스페이스를 가립니다. 워크스페이스를 가리키는 경로의 첫 토막이 그 이름입니다. //myworkspace/file.c 가 그런 꼴입니다.

명령과 공식 문서는 워크스페이스를 클라이언트 워크스페이스, 줄여서 클라이언트라고도 부릅니다. 앞에 나온 클라이언트 프로그램과 글자가 같습니다. 이 문서에서는 프로그램을 「클라이언트 프로그램」, 작업 폴더를 「워크스페이스」로 갈라 부릅니다.

워크스페이스의 파일은 디포 파일의 로컬 사본입니다. 지난 판은 디포에만 있습니다.

flowchart TD
    subgraph 서버["서버 · p4d"]
        D["디포 · 모든 파일의 모든 판"]
        M["메타데이터 · 누가 어느 판을 받았나"]
    end
    D --> W1["내 워크스페이스 · 파일마다 받은 판 하나"]
    D --> W2["동료 워크스페이스 · 파일마다 받은 판 하나"]

디포는 서버에 하나 있고 워크스페이스는 사람마다 따로 있습니다. 화살표는 받는 방향입니다. 누가 어느 판을 받아 갔는지는 서버의 메타데이터에 적힙니다.

워크스페이스 뷰

워크스페이스를 만들면 기본으로 디포 전체가 내 폴더에 대응됩니다. 디포와 워크스페이스 사이의 이 대응을 적은 표가 워크스페이스 뷰입니다. 한 줄마다 왼쪽에 디포 경로를 적습니다. 오른쪽에는 워크스페이스 안의 경로를 적습니다.

뷰로 할 수 있는 일은 둘입니다. 디포의 일부만 보이게 좁힐 수 있습니다. 그리고 디포 쪽 위치와 워크스페이스 쪽 위치가 어떻게 짝지어지는지를 바꿀 수 있습니다.

줄에는 와일드카드를 씁니다. ... 는 그 아래 모든 파일과 폴더에 맞습니다. * 는 한 폴더 안에서만 맞습니다.

줄 앞에 - 를 붙이면 그 경로를 뺍니다. 디포 쪽에 쓴 와일드카드는 워크스페이스 쪽에도 똑같이 써야 합니다.

View:
   //JamCode/dev/jam/...        //earl-dev-beech/jam/...
   -//JamCode/dev/jam/....html  //earl-dev-beech/jam/....html

이 뷰는 Jam 프로젝트의 dev/jam 폴더 아래를 전부 받습니다. 오른쪽의 earl-dev-beech 는 이 뷰를 쓰는 사람의 워크스페이스 이름입니다.

둘째 줄의 ....html 은 ... 뒤에 .html 이 붙은 꼴입니다. 그 아래 모든 HTML(HyperText Markup Language) 파일에 맞습니다. 줄 앞에 - 가 있어서 HTML 파일만은 받지 않습니다.

뷰는 파일의 위치만 가리킵니다. 특정 판을 뷰에 적을 수는 없습니다. 뒤에 나오는 스트림에 묶인 워크스페이스는 서버가 뷰를 알아서 만듭니다.

서버가 기억하는 내 파일

서버는 워크스페이스마다 어느 파일의 몇 번째 판을 받아 갔는지 적어 둡니다. 이 기록이 have 목록입니다.

파일을 고치거나 더하거나 지우려면 먼저 서버에 알립니다. 이 일을 「파일을 연다」고 합니다. 고치려고 여는 명령이 p4 edit, 더하려고 여는 명령이 p4 add, 지우려고 여는 명령이 p4 delete 입니다. 갈래 사이에 변경을 옮기는 p4 integrate 도 대상 파일을 엽니다.

고치려고 파일을 여는 일을 체크아웃(check out)이라고도 부릅니다. 이 문서에서 「연다」와 「체크아웃」은 같은 일입니다.

p4 sync 는 디포의 파일을 워크스페이스로 받는 명령입니다. 파일 이름을 안 주면 세 조건을 다 맞는 파일만 복사합니다.

조건 뜻
뷰에 보인다 내 워크스페이스 뷰가 그 경로를 받는다
열려 있지 않다 p4 edit · p4 delete · p4 add · p4 integrate 로 내가 연 파일이 아니다
최신 판이 없다 내 워크스페이스에 그 파일의 최신 판(head)이 아직 없다

셋째 조건 덕에 서버는 내게 없는 판만 보냅니다. 새로 만든 빈 워크스페이스라면 뷰에 보이는 파일이 전부 옵니다.

새로 받은 파일은 읽기 전용입니다. 쓰기를 풀려면 p4 edit 로 파일을 엽니다. 그러면 서버가 그 파일의 운영체제 쓰기 권한을 켭니다. 권한은 운영체제 명령이 아니라 p4 명령으로 바꾸게 되어 있습니다.

체인지리스트와 원자적 제출

p4 edit 로 연 파일은 체인지리스트에 묶입니다. 체인지리스트는 한 번에 올릴 변경의 묶음입니다. 바뀐 파일 목록, 올린 사람, 그 사람이 쓴 설명이 함께 붙습니다. 파일은 적어도 하나 들어갑니다. 많으면 수만 개가 들어갈 수도 있습니다.

아직 올리지 않은 체인지리스트를 대기 중(pending) 체인지리스트라고 부릅니다. 파일을 열면 생깁니다. 어느 체인지리스트에 넣을지 고르지 않으면 연 파일은 기본(default) 체인지리스트에 들어갑니다.

체인지리스트를 디포에 보내는 일이 제출입니다. 명령은 p4 submit 입니다. 제출은 원자적입니다. 묶음 안의 파일이 전부 저장되거나 하나도 저장되지 않습니다. 제출 도중 네트워크가 끊기면 제출 전체가 실패합니다.

이 성질 덕에 목적이 같은 변경을 한 묶음으로 올릴 수 있습니다. 버그 하나를 고친 파일 셋이 한 체인지리스트로 들어가는 식입니다. 셋 중 일부만 디포에 들어가는 일은 없습니다.

제출이 실패하면 파일은 연 채로 남습니다. 남은 체인지리스트에는 번호가 붙습니다. 문제를 고친 뒤 그 번호로 다시 제출합니다.

판 번호와 체인지리스트 번호

번호가 둘 있습니다. 판 번호는 파일마다 따로 셉니다. 연 파일을 제출하면 그 파일에 다음 판 번호가 붙습니다. 예전 판은 덮이지 않고 남습니다. 가장 큰 번호가 최신 판(head)입니다.

체인지리스트 번호는 파일마다 세지 않습니다. 제출된 체인지리스트의 번호는 늘어나기만 합니다. 다만 103, 105, 108 처럼 건너뛸 수 있습니다. 대기 중일 때 받은 번호가 제출하면서 바뀌기도 합니다.

두 번호는 파일 이름 뒤에 붙여 씁니다. 판 번호는 # 뒤에 둡니다. 체인지리스트 번호는 @ 뒤에 둡니다.

쓰는 꼴 가리키는 판
file#n 그 파일의 n 번째 판
file#head 최신 판. 예외라고 밝힌 곳이 아니면, 판을 안 적고 파일만 쓴 것과 같다
file#have 내 워크스페이스에 마지막으로 받은 판
file#none 없는 판. p4 sync file#none 은 디포는 두고 내 워크스페이스에서만 파일을 뺀다
file@n 체인지리스트 n 이 제출된 직후의 판
file@날짜 그 날짜와 시각의 판

판 지정은 여러 파일에 한꺼번에 걸 수 있습니다. p4 sync //myclient/...#4 는 myclient 워크스페이스에서 열지 않은 모든 파일의 4번째 판을 받습니다. #4 대신 @n 을 붙이면 그 아래 모든 파일이 n 번 체인지리스트 제출 직후 모습으로 맞춰집니다.

받기 · 열기 · 제출

하루 작업은 받기, 열기, 제출 세 단계로 돕니다. 아래는 Bruno 라는 사용자가 command.c 를 고치려고 받고 여는 두 명령입니다. 받기의 출력 줄은 뺐습니다. 열기의 출력 줄만 남겼습니다.

C:\bruno_ws\dev> p4 sync //Acme/dev/command.c
C:\bruno_ws\dev> p4 edit //Acme/dev/command.c
//Acme/dev/command.c#8 - opened for edit

마지막 줄은 8번째 판을 고치려고 열었다는 뜻입니다. 이제 이 파일은 쓸 수 있습니다. 파일은 대기 중 체인지리스트에 들어 있습니다. 고친 뒤 p4 submit 으로 올리면 이 파일에 다음 판 번호가 붙습니다.

sequenceDiagram
    participant 나 as 내 워크스페이스
    participant 서버
    나->>서버: p4 sync
    서버-->>나: 내게 없는 판만 보낸다 · 읽기 전용
    나->>서버: p4 edit command.c
    서버-->>나: 쓰기 권한을 켠다 · 체인지리스트에 묶는다
    Note over 나: 파일을 고친다
    나->>서버: p4 submit
    서버-->>나: 전부 저장하거나 하나도 저장하지 않는다

화살표는 전부 서버를 오갑니다. 받기, 열기, 제출이 모두 서버와 주고받는 일입니다.

지울 파일은 p4 delete 로 엽니다. 이 명령은 내 워크스페이스에서 파일을 바로 지웁니다. 제출하면 디포에는 「지워짐」을 표시한 새 최신 판이 붙습니다. 다른 사람이 그 판을 받으면 그 사람 워크스페이스에서도 파일이 사라집니다.

새 파일은 p4 add 로 엽니다. 디포에 아직 없는 파일이거나, 최신 판이 위의 「지워짐」인 파일만 이렇게 열 수 있습니다.

열기를 취소하는 명령은 p4 revert 입니다. 고친 내용은 버려집니다. 파일은 마지막으로 받은 판으로 돌아갑니다. 그 파일은 대기 중 체인지리스트에서도 빠집니다.

stateDiagram-v2
    받음 : 받음 · 읽기 전용
    열림 : 열림 · 쓰기 가능 · 체인지리스트에 묶임
    [*] --> 받음 : p4 sync
    받음 --> 열림 : p4 edit
    열림 --> 받음 : p4 submit 성공 · 다음 판이 붙는다
    열림 --> 받음 : p4 revert · 고친 내용을 버린다
    열림 --> 열림 : 제출 실패 · 번호 붙은 체인지리스트로 남는다

워크스페이스의 파일 하나는 이 두 상태를 오갑니다. 받은 상태에서는 읽기만 됩니다. 연 상태에서는 쓸 수 있습니다. 서버는 그 파일을 체인지리스트에 묶어 둡니다.

제출 거절과 resolve

Bruno 와 Gale 이 같은 파일 command.c#8 을 각자 열어 고친 상황을 봅니다. Bruno 가 먼저 제출해 성공합니다. 이어서 Gale 이 제출하면 거절됩니다.

Gale 의 판을 받아들이면 Bruno 의 변경이 덮어써집니다. 그래서 서버는 Gale 의 체인지리스트를 거절합니다. 그 파일은 resolve 대상으로 잡아 둡니다.

resolve 는 두 판의 차이를 다루는 과정입니다. p4 resolve 는 두 파일이나 두 판의 내용을 내 워크스페이스에서 한 판으로 합칩니다. 합친 뒤 다시 제출하면 들어갑니다. resolve 를 안 마친 파일은 제출할 수 없습니다.

sequenceDiagram
    participant B as Bruno
    participant 서버
    participant G as Gale
    B->>서버: p4 edit command.c#8
    G->>서버: p4 edit command.c#8
    B->>서버: p4 submit
    서버-->>B: 성공 · 다음 판이 생긴다
    G->>서버: p4 submit
    서버-->>G: 거절 · resolve 대상으로 잡는다
    Note over G: p4 resolve · 두 판을 한 판으로 합친다
    G->>서버: p4 submit
    서버-->>G: 성공 · 다음 판이 생긴다

거절로 끝나는 것이 아닙니다. Gale 이 합치고 다시 제출하면 그 변경도 디포에 들어갑니다.

p4 resolve 가 필요한 경우는 둘입니다. 하나는 위처럼 내가 마지막으로 받은 판이 제출 때의 최신 판이 아닌 경우입니다. 다른 하나는 갈래 사이에 변경을 옮기려고 p4 integrate 를 친 경우입니다.

충돌이 있는 것을 미리 알고 그 파일을 resolve 대상으로 잡아 두려면 p4 sync 로 받으면 됩니다. 서버가 충돌을 찾아 그 파일을 resolve 대상으로 잡습니다.

파일 형식과 typemap

파일 형식은 서버가 그 파일을 어떻게 저장하고 어떻게 비교할지 정하는 성질입니다. 기본 형식에는 text 와 binary 가 있습니다. text 파일은 받을 때 줄 끝 문자가 저절로 바뀝니다. binary 파일은 그런 변환 없이 받습니다.

이진 파일은 그림이나 실행 파일처럼 글자 줄로 이루어지지 않은 파일입니다. 줄 단위로 비교할 수 없어서 두 사람의 변경을 합칠 수 없습니다.

형식을 따로 안 정하면 서버가 파일 앞 65,536 바이트를 보고 text 인지 binary 인지 고릅니다.

관리자는 p4 typemap 으로 파일 이름 꼴마다 형식을 정해 둘 수 있습니다. //depot/.../*.gif 를 한 번에 한 사람만 열 수 있는 형식으로 묶는 식입니다. typemap 은 표를 만든 뒤에 추가한 파일에만 걸립니다. 이미 있던 파일은 바뀌지 않습니다.

기본 형식 뒤에는 + 로 수식자를 붙입니다. +w 를 붙인 파일은 워크스페이스에서 늘 쓸 수 있습니다. +l 을 붙인 파일은 한 번에 한 사람만 열 수 있습니다. binary+l 은 「한 사람만 여는 이진 파일」이라는 뜻입니다.

브랜치와 스트림

브랜치는 같은 파일들을 갈래로 나눠 따로 바뀌게 하는 것입니다. 출시 판을 고치는 동안 다음 판 개발을 이어 갈 때 씁니다. 함께 바뀌어 가는 파일 묶음은 코드라인입니다.

Perforce 에서 브랜치는 디포 안의 다른 경로입니다. 한 코드라인을 다른 경로로 복사하면 브랜치가 생깁니다.

갈래 사이에 변경을 옮기는 일을 통합(integrate)이라고 부릅니다. 명령은 셋입니다.

명령 하는 일
p4 integrate 옮길 변경을 정하고 대상 파일을 연다. 대상이 없으면 복사해서 브랜치로 연다
p4 merge p4 integrate 를 줄인 꼴이다. 변경을 합칠 대상으로 잡는다. 뒤이어 resolve 를 거친다
p4 copy 원본을 대상에 똑같이 복사한다. resolve 가 필요 없다. 대상에만 있던 변경은 덮어쓴다

복사로 브랜치를 만들 때 서버는 대개 파일 내용을 새로 쓰지 않습니다. 대신 원본을 가리키는 통합 기록을 남깁니다. 이 기록만으로 파일 내용을 되살릴 수 있어서 사본을 하나 더 둘 필요가 없습니다. 이것을 게으른 복사(lazy copy)라고 부릅니다.

스트림은 브랜치에 규칙을 붙인 것입니다. 그 규칙이 변경을 어느 쪽으로 흘려보낼지 정합니다. 스트림에 묶인 워크스페이스는 서버가 뷰를 만들어 주므로 뷰를 손으로 고칠 일이 없습니다.

스트림은 스트림 디포 안에 삽니다. 앞에서 디포에 종류가 있다고 했는데, 스트림을 담는 종류가 스트림 디포입니다. 스트림 디포에는 메인라인(mainline)이 적어도 하나 있습니다.

스트림은 기본으로 부모 스트림에서 구조를 물려받습니다. 여기서 구조는 스트림 명세에 적는 경로 설정입니다. 부모가 없는 스트림이 메인라인이고, 메인라인은 이 물려받기의 예외입니다.

스트림은 부모와 견준 안정성으로 종류가 갈립니다.

종류 부모와 견준 안정성 쓰는 때
mainline 부모가 없다 대개 development · release 스트림의 부모
development 부모보다 덜 안정하다 오래 걸리는 일 · 큰 새 기능
release 부모보다 더 안정하다 출시 안정화 · 버그 수정 · 출시 판 유지

변경이 흐르는 방향도 정해져 있습니다. 덜 안정한 쪽으로는 합쳐서 내려보냅니다(merge down). 더 안정한 쪽으로는 복사해서 올립니다(copy up).

종류마다 변경이 어느 쪽에서 들어오기를 기대하는지가 다릅니다. mainline 은 두 쪽 다 기대합니다. 더 안정한 자식에서 merge down 으로 받습니다. 덜 안정한 자식에서 copy up 으로 받습니다.

release 는 부모에서 올라오는 copy up 을 기대하지 않습니다. 더 안정한 자식 스트림에서 merge down 으로 받기를 기대합니다. development 는 부모에서 merge down 으로 받기를 기대합니다.

flowchart TD
    R["release · 더 안정하다"]
    M["mainline · 부모가 없다"]
    D["development · 덜 안정하다"]
    R -->|"merge down · 버그 수정을 내려보낸다"| M
    M -->|"merge down · 최신 변경을 받는다"| D
    D -->|"copy up · 끝낸 기능을 올린다"| M

위아래는 부모 자식이 아니라 안정성입니다. development 와 release 는 흔히 둘 다 mainline 의 자식입니다. release 로 들어오는 선이 없는 것은 위에 적은 기대 때문입니다.

development 스트림은 흔히 이런 차례로 돕니다. 먼저 mainline 의 변경을 merge down 으로 받습니다. 거기서 새 기능을 만들고 시험합니다. 기능이 안정해지면 mainline 으로 copy up 합니다.

copy 는 대상에만 있던 변경을 덮어씁니다. 그래서 올리기 전에 부모의 변경을 먼저 받아 둡니다.

셸브

셸브는 작업 중인 파일을 제출하지 않고 서버에 잠시 맡겨 두는 일입니다. 명령은 p4 shelve 입니다. 맡긴 파일은 디포에 남습니다. p4 shelve -d 로 버리거나 다시 셸브해 바꿀 때까지 남습니다.

제출과는 다릅니다. 제출은 체인지리스트를 디포에 들여놓습니다. 셸브는 체인지리스트를 들여놓지 않고 파일 사본만 맡깁니다. 그래서 셸브한 뒤에도 그 체인지리스트는 대기 중입니다.

맡긴 뒤에는 내 워크스페이스의 파일을 되돌려도 됩니다. 계속 고쳐도 됩니다. p4 unshelve 로 맡긴 판을 워크스페이스에 다시 꺼냅니다.

맡긴 파일은 다른 사람도 볼 수 있습니다. 코드 리뷰나 작업 넘기기에 쓰는 것이 이 덕입니다. 여러 플랫폼에서 같은 변경을 시험할 때도 씁니다.

포기한 것

Perforce 는 서버 한 곳이 모두의 상태를 안다는 데 기대어 만들어졌습니다. 그 대가로 안 하기로 한 것이 셋입니다. 셋 다 얻은 것과 짝을 이룹니다.

사람마다 이력을 갖지 않는다

기본 모델은 중앙집중식입니다. 사용자는 공유 서버와 바로 주고받습니다. 열기와 제출이 모두 그 서버를 거칩니다.

워크스페이스에는 파일마다 받은 판 하나만 있습니다. 지난 판과 다른 사람의 변경은 디포에 있습니다. 그래서 서버에 닿지 않으면 p4 명령으로 파일을 열 수도 제출할 수도 없습니다.

대신 서버가 워크스페이스마다 받은 판을 압니다. 받을 때 내게 없는 판만 보내는 것이 그 덕입니다. 누가 어느 파일을 열었는지도 서버 한 곳이 압니다. 아래 「이진 파일을 함께 고치지 않는다」가 이 덕에 성립합니다.

서버 없이 일하는 길이 아예 없지는 않습니다. 권하는 길은 DVCS(Distributed Version Control System, 분산 버전관리) 기능입니다.

p4 clone 으로 공유 서버의 내용을 내 컴퓨터의 개인 서버로 복제합니다. 개인 서버에 먼저 제출합니다. 나중에 p4 push 로 공유 서버에 보냅니다. p4 fetch 로는 공유 서버의 변경을 가져옵니다.

sequenceDiagram
    participant 나
    participant 개인서버
    participant 공유서버
    나->>공유서버: p4 clone
    공유서버-->>개인서버: 내용의 일부를 복제한다
    나->>개인서버: p4 submit
    나->>공유서버: p4 push
    개인서버-->>공유서버: 개인 서버에 제출한 변경이 올라간다
    나->>공유서버: p4 fetch
    공유서버-->>개인서버: 공유 서버의 변경이 내려온다

Git 과 다른 점은 공유 서버의 내용 중 일부만 복제할 수 있다는 것입니다. p4 init 으로 빈 개인 서버를 새로 만들 수도 있습니다. p4 init 을 쓰려면 최신이고 서로 맞는 p4 와 p4d 실행 파일이 운영체제 경로에 있어야 합니다.

그 밖에는 손으로 맞추는 길이 있습니다. 서버 없이 일할 때는 p4 명령을 안 칩니다. 운영체제 명령으로 파일 권한을 바꿔 고칩니다. 네트워크가 돌아오면 p4 status 나 p4 reconcile 로 바뀐 파일을 찾아 제출합니다.

p4 reconcile 은 워크스페이스와 디포를 견줘 어긋난 파일을 엽니다.

워크스페이스에서 찾은 것 p4 reconcile 이 하는 일
디포와 have 목록에는 있는데 워크스페이스에서 사라진 파일 지울 파일로 연다
워크스페이스에만 있고 디포에 없는 파일 추가할 파일로 연다
열지 않고 고친 파일 고칠 파일로 연다
지울 파일과 추가할 파일의 크기·내용이 비슷하다 둘을 이동 한 쌍으로 바꾼다

받은 파일을 마음대로 고치지 않는다

받은 파일은 읽기 전용입니다. 고칠 때마다 먼저 p4 edit 를 쳐야 합니다. 쓰기 권한도 운영체제 명령이 아니라 p4 명령으로 켜게 되어 있습니다.

대신 서버의 기록이 맞습니다. p4 edit 가 쓰기 권한을 켜는 순간 그 파일이 체인지리스트에 묶입니다. 그래서 서버는 내가 어느 파일을 고치는 중인지 압니다.

열지 않고 고친 파일은 서버가 모릅니다. 그런 파일은 나중에 p4 reconcile 로 알려야 합니다.

이 포기를 비켜 가는 형식도 있습니다. +w 를 붙인 파일은 워크스페이스에서 늘 쓸 수 있습니다. 컴파일하면 다시 생기는 실행 파일에 이 형식을 붙인 예가 아래 「예시」에 있습니다.

이진 파일을 함께 고치지 않는다

+l 을 붙인 파일은 한 번에 한 사람만 엽니다. 동시 개발을 막는 설정입니다. 그림 같은 이진 파일은 여러 사람의 변경을 합치는 일이 의미가 없습니다. 그래서 애초에 둘이 동시에 못 열게 막아 충돌을 없앱니다.

sequenceDiagram
    participant 나
    participant 서버
    participant 동료
    나->>서버: p4 edit 그림 파일
    서버-->>나: 연다 · 이제 나만 고친다
    동료->>서버: p4 edit 그림 파일
    서버-->>동료: 못 연다 · 이미 한 사람이 열었다
    나->>서버: p4 submit
    Note over 서버: 이제 다른 사람이 열 수 있다

대가는 기다림입니다. 앞사람이 제출하거나 p4 revert 로 열기를 취소할 때까지 다음 사람은 그 파일을 못 엽니다. 얻은 것은 합칠 수 없는 파일에서 한 사람의 작업이 버려지지 않는다는 것입니다.

p4 lock 과는 다릅니다. p4 lock 은 누구나 파일을 열게 둡니다. 제출만 잠근 사람이 할 수 있습니다. +l 은 여는 것부터 한 사람으로 막습니다.

예시

Unreal Engine 의 typemap

Unreal Engine 편집기가 다루는 파일은 대부분 에셋입니다. 에셋은 확장자가 .uasset · .umap 인 파일입니다.

에셋은 이진 파일입니다. 텍스트로 열 수 없습니다. 텍스트 병합 도구로 합칠 수도 없습니다.

이 때문에 편집기는 에셋을 고치기 시작할 때 그 파일을 엽니다. Perforce 용어로 체크아웃입니다. 열려 있는 동안 다른 사람은 그 파일을 동시에 못 고칩니다.

고친 사람이 제출하면 변경이 서버에 올라갑니다. 그때 그 파일의 열기가 풀립니다.

typemap 은 디포에 파일을 넣기 전에 정해 둡니다. 앞에서 본 대로 typemap 은 뒤에 추가한 파일에만 걸리기 때문입니다. 아래는 Epic Games 가 Unreal Engine 5.3 안내에 예시로 실었던 typemap 입니다. 머리 주석 열 줄은 뺐습니다.

TypeMap:
                binary+w //depot/....exe
                binary+w //depot/....dll
                binary+w //depot/....lib
                binary+w //depot/....app
                binary+w //depot/....dylib
                binary+w //depot/....stub
                binary+w //depot/....ipa
                binary //depot/....bmp
                text //depot/....ini
                text //depot/....config
                text //depot/....cpp
                text //depot/....h
                text //depot/....c
                text //depot/....cs
                text //depot/....m
                text //depot/....mm
                text //depot/....py
                binary+l //depot/....uasset
                binary+l //depot/....umap
                binary+l //depot/....upk
                binary+l //depot/....udk
                binary+l //depot/....ubulk

....exe 는 앞의 뷰에서 본 ....html 과 같은 꼴입니다. 디포 아래 모든 폴더의 .exe 파일에 맞습니다. 줄들은 형식에 따라 네 무리로 갈립니다.

확장자 형식 이 형식을 붙인 까닭
.exe · .dll · .lib · .app · .dylib · .stub · .ipa binary+w 프로그래머가 컴파일한 결과물이 읽기 전용에 막히지 않게 하려는 줄이다
.bmp binary 이진 그림 파일이다
.ini · .config · .cpp · .h · .c · .cs · .m · .mm · .py text 설정과 소스 코드를 텍스트로 다룬다
.uasset · .umap · .upk · .udk · .ubulk binary+l 이진 파일이라 한 사람만 연다

지금 판 안내가 싣고 있는 typemap 은 25줄입니다. 위 블록과 줄 수가 다릅니다.

같은 안내는 서버를 대소문자 무시 모드로 띄우라고 권합니다. 대소문자 무시 모드는 파일 이름의 대소문자 차이를 같은 것으로 보는 서버 설정입니다.

권하는 까닭은 UnrealGameSync 와 어긋나지 않게 하려는 것입니다. UnrealGameSync 는 Epic 의 내부 도구입니다. Unreal Engine 프로젝트를 Perforce 에서 받아 오는 그래픽 화면입니다.

편집기에서는 툴바의 소스 컨트롤 아이콘을 누릅니다. 거기서 공급자로 Perforce 를 고르고 로그인합니다.

Unity 편집기의 Perforce 설정

Unity 편집기는 Perforce 와 Unity Version Control 두 도구에 바로 붙습니다. 설정은 Edit > Project Settings 의 Version Control 항목에 있습니다.

설정 값과 뜻
Mode Perforce 를 고른다. 기본값은 Visible meta files 다. 그건 편집기가 지원하지 않는 도구를 쓸 때의 모드다
Server 프로젝트가 있는 서버. 예 localhost:1666
Workspace 쓸 워크스페이스 이름
Work Offline 켜면 서버 없이 일한다. 돌아온 뒤 reconcile 로 디포를 맞춰야 한다
Show Failed Checkouts 체크아웃이 실패하면 대화상자를 띄운다. 연결이 끊겼거나 다른 사람이 그 에셋을 독점으로 열었을 때 실패한다
Overwrite Failed Checkout Assets 기본으로 켜져 있다. 체크아웃을 못 해도 Unity 가 파일 저장을 밀어붙인다

마지막 두 줄은 「포기한 것」을 편집기 쪽에서 본 모습입니다. 서버에 못 닿거나 남이 독점으로 열었으면 체크아웃이 실패합니다. 기본값이 켜져 있어서 그때도 파일은 저장됩니다.

워크스페이스는 한 사람만 써야 합니다. 다른 사람과 워크스페이스를 같이 쓰면 제출할 수 없습니다. 서버가 내려갔거나 라이선스 문제로 제출을 못 하면 변경은 따로 묶인 체인지셋에 담깁니다.

사용처

Google

Google 은 1999년에 코드 전체를 저장소 하나에 두기로 정했습니다. 그때 기존 코드를 CVS(Concurrent Versions System)에서 Perforce 로 옮겼습니다. 그 뒤 10년 넘게 Perforce 주 서버 하나를 기계 한 대에 두고 썼습니다. 여기에 자체 캐시 설비를 곁들였습니다.

떠난 이유는 규모입니다. 저장소가 계속 커진 것이 자체 시스템 Piper 를 만든 주된 동기였습니다. Piper 의 워크스페이스는 Perforce 의 클라이언트 워크스페이스에 견줄 만한 물건입니다.

Epic Games

Epic Games 는 Unreal Engine 편집기에 소스 컨트롤 연결을 넣어 두었습니다. 기본으로 지원하는 도구는 Perforce 와 SVN(Subversion) 둘입니다. 편집기에서 에셋을 다루는 흐름은 대체로 Perforce 의 흐름을 따릅니다. 주로 역사적인 이유에서입니다.

Epic 의 내부 도구 UnrealGameSync 도 Perforce 위에 서 있습니다. 개발자는 체인지리스트가 제출되자마자 그 체인지리스트로 받아 로컬에서 컴파일할 수 있습니다.

운영

메타데이터와 판 파일의 백업

서버 루트 P4ROOT 에는 두 가지가 나란히 삽니다. 하나는 메타데이터를 담은 데이터베이스 파일 db.* 입니다. 다른 하나는 디포마다 하위 폴더에 든 판 파일입니다. 판 파일은 사용자가 제출한 파일 내용입니다.

텍스트 파일은 대개 파일이름,v 꼴의 파일 하나에 판을 쌓습니다. 이진 파일은 파일이름,d 폴더에 판마다 온전한 파일로 저장됩니다.

flowchart TD
    subgraph 루트["서버 루트 · P4ROOT"]
        DB["db.* · 메타데이터"]
        V["디포 폴더 · 판 파일"]
    end
    DB -->|"p4d -jc"| C["체크포인트 · 그 시각의 데이터베이스 사본"]
    DB -->|"바뀔 때마다 적는다"| J["저널 · 마지막 체크포인트 뒤의 변경"]
    V -->|"운영체제 백업 명령"| B["판 파일 백업"]

db.* 에서 나가는 두 갈래가 메타데이터를 지킵니다. 체크포인트는 어느 시각의 데이터베이스 사본입니다. 저널은 마지막 체크포인트 뒤로 데이터베이스에 생긴 변경의 기록입니다. 체크포인트 하나와 그 뒤의 저널이 있으면 데이터베이스를 되살릴 수 있습니다.

체크포인트는 저절로 안 생깁니다. 사람이나 자동화가 p4d -r 서버루트 -jc 나 p4 admin checkpoint 를 돌려야 합니다. 서버 루트가 기본값이면 -r 없이 p4d -jc 로도 됩니다.

체크포인트를 만들면 저널이 잘립니다. 옛 저널은 이름이 바뀌어 남습니다. 체크포인트 파일은 데이터베이스와 다른 곳에 두는 편이 안전합니다.

체크포인트와 저널은 db.* 만 담습니다. 판 파일은 따로 백업해야 합니다.

순서도 정해져 있습니다. 체크포인트를 먼저 만듭니다. 그다음 판 파일을 백업합니다. 되살린 데이터베이스의 기록을 판 파일이 모두 담고 있어야 하기 때문입니다.

db.* 백업에서 하지 말 것이 둘입니다.

하지 말 것 까닭
디스크 스냅샷으로 백업 데이터베이스의 어긋남을 못 잡는다. 트랜잭션이 맞지 않는 표를 담을 수 있다
운영체제 백업 도구로 db.* 를 직접 백업 그런 도구는 파일을 잠가 서버 동작과 성능을 방해하는 일이 잦다

체크포인트와 저널은 한 달치를 남겨 두기를 권합니다.

원격지 서버

서버 한 대로 모자랄 때 서버를 더 두는 구성이 있습니다. 여기서는 셋을 봅니다. 프록시 p4p, 엣지 서버, 복제 서버입니다.

앞 둘은 멀리 떨어진 사용자를 위한 것입니다. 사용자가 WAN(Wide Area Network, 광역망)을 건너 공유 서버에 붙을 때 씁니다. 사용자 쪽 네트워크에 서버를 하나 더 두어 거리를 줄입니다.

복제 서버는 쓰임이 다릅니다. 대기 서버를 두는 데, 주 서버의 부하와 멈춤을 줄이는 데, 재해 복구에 씁니다. 대기 서버는 원래 서버가 멈추면 대신 서는 서버입니다.

서버를 여럿 두면 그중 하나가 판 파일과 메타데이터의 유일한 정본을 쥡니다. 앞에서 공유 서버라고 부른 그 서버입니다. 부르는 이름은 구성에 따라 갈립니다.

엣지 서버를 둔 구성에서 가장 안쪽 서버는 커밋 서버라고 부릅니다. 복제 서버가 메타데이터를 복사해 오는 쪽은 중앙 서버라고 부릅니다. 프록시를 둔 구성에서 프록시가 붙는 쪽은 상류 서버라고 부릅니다.

방식 하는 일 걸리는 점
프록시 p4p 사용자 가까이 둔다. 자주 오가는 판 파일을 캐시해 대신 내준다. 큰 파일에서 특히 이득이 크다. 캐시는 백업할 필요가 없다 작은 파일을 많이 받는 데는 최적이 아니다. 캐시는 지울 때까지 쌓인다. 프록시가 디스크 사용량을 관리하지 않는다
엣지 서버 커밋 서버와 떨어진 곳에 둔다. 제출 전의 작업을 가까이서 처리해 커밋 서버의 부하를 던다 커밋 서버의 대기 서버가 될 수 없다. 엣지마다 장비 관리가 든다. 빌드용으로만 두는 엣지(build edge server)가 아니면 백업도 든다
복제 서버 중앙 서버의 메타데이터를 자동으로 복사한다. 판 파일도 담을 수 있다 대기 서버로 쓰려면 메타데이터와 판 파일을 둘 다 복제해야 한다
flowchart TD
    subgraph 프록시["프록시를 둔 구성"]
        U1["원격 사용자"] --> P["프록시 p4p · 사용자 쪽"]
        P --> WA["WAN"]
        WA --> C1["상류 서버 · 정본"]
    end
    subgraph 엣지["엣지 서버를 둔 구성"]
        U2["원격 사용자"] --> E["엣지 서버 · 사용자 쪽"]
        E --> WB["WAN"]
        WB --> C2["커밋 서버 · 정본"]
    end
    subgraph 복제["복제 서버를 둔 구성"]
        C3["중앙 서버 · 정본"] --> RP["복제 서버 · 메타데이터 사본"]
    end

셋이 갈리는 곳은 쓰임입니다. 프록시와 엣지 서버는 사용자 쪽에 두어 거리를 줄입니다. 복제 서버가 맡는 것은 거리가 아니라 대기 서버와 부하 줄이기와 재해 복구입니다.

처음 띄운 서버의 권한

새로 설치한 서버는 누구나 붙을 수 있습니다. 붙은 사용자는 모두 슈퍼유저로 취급됩니다. 슈퍼유저는 서버의 권한 설정까지 바꿀 수 있는 사용자입니다. 그래서 설치 직후 바로 p4 protect 를 돌려야 합니다.

p4 protect 를 처음 친 사용자가 슈퍼유저가 됩니다. 나머지 사용자는 모든 파일에 쓰기 권한을 받습니다. 더 좁힌 권한은 이 명령이 여는 보호 표를 고쳐서 만듭니다.

이력을 지우는 obliterate

p4 delete 는 최신 판에 「지워짐」 표시만 합니다. 예전 판은 디포에 남습니다. 디스크 공간도 돌아오지 않습니다. 파일과 그 이력을 서버에서 영구히 없애는 명령은 p4 obliterate 입니다.

p4 obliterate 는 -y 를 붙여야 지웁니다. -y 가 없으면 무엇을 지울지 보고만 합니다. 지우고 나면 서버에는 그 파일이 있었다는 기록조차 남지 않습니다. 되돌릴 수 없어서 보고 모드로 먼저 돌려 보기를 권합니다.

예외도 있습니다. p4 integrate 가 브랜치를 만들 때 남긴 게으른 복사는 아직 쓰이고 있으면 지워지지 않습니다. 그 게으른 복사가 가리키는 판 파일도 함께 남습니다.

사용자 워크스페이스의 사본도 남습니다. 서버는 그 사본을 모릅니다. 이력은 두고 파일 내용만 지우려면 p4 archive -p 를 씁니다.

처음 띄울 때 정하는 값

서버를 처음 띄울 때 정하고 나중에 바꾸기 어려운 값이 둘 있습니다. 파일 이름의 대소문자를 어떻게 다루나, 그리고 유니코드 모드입니다.

리눅스의 서버는 기본으로 파일 이름의 대소문자를 구분합니다. 윈도의 서버는 대소문자 차이를 무시합니다. 리눅스 서버를 대소문자 무시 모드로 돌리려면 p4d -C1 을 씁니다.

둘이 어긋나면 받은 파일끼리 서로 덮어씁니다. 리눅스 사용자가 대소문자를 구분하는 서버에 readme.html · Readme.html · README.html 셋을 제출했다고 합시다. 윈도 클라이언트가 셋을 차례로 받으면 뒤 파일이 앞 파일을 덮어씁니다. 디스크에는 파일 하나만 남습니다.

유니코드 모드도 처음에 정합니다. 서버를 p4d -xi 로 띄우면 유니코드 모드가 됩니다. 이 명령은 먼저 서버에 이미 들어 있는 메타데이터가 전부 올바른 UTF-8(Unicode Transformation Format 8-bit, 8비트 유니코드 변환 형식)인지 검사합니다. 검사를 지나면 서버에 유니코드 모드 표시를 켭니다.

껐다 켜도 유니코드 모드는 유지됩니다. 한번 유니코드 모드가 된 서버는 다시 비유니코드 모드로 돌릴 수 없습니다.

클라우드판 P4 Cloud 는 가입할 때 대소문자 무시 모드를 고를 수 있습니다. 배포한 뒤에는 이 모드를 바꿀 수 없습니다. UnrealGameSync 처럼 대소문자 무시가 필요한 클라이언트 프로그램이 있을 때만 고르는 값입니다.

라이선스가 드는 서버

Perforce 는 상용 제품입니다. 사용자 5명, 워크스페이스 20개까지는 무료로 씁니다.

이 한도 안에 들지 않으면 서버 종류마다 라이선스를 신청합니다. 정본을 쥔 서버가 그 하나입니다. 구성에 따라 커밋 서버나 중앙 서버로 부르는 그 서버입니다. 대기 서버와 복제 서버도 장애 때 대신 서게 하려면 라이선스가 필요합니다.

라이선스가 드는 종류에는 Distribution Server 도 있습니다. 앞에서 본 프록시·엣지·복제 셋과는 다른 종류입니다.

관련 항목

Perforce 가 속하는 상위 분류

버전관리 · 중앙집중식 버전관리 · 형상 관리 · 소스 관리

Perforce 와 같은 역할을 하는 버전관리 도구

Git · Subversion · Mercurial · CVS · Plastic SCM · Unity Version Control · Piper · 분산 버전관리 · Git LFS

Perforce 서버를 이루는 구성 요소

p4d · 디포 · 워크스페이스 · 뷰 매핑 · 메타데이터 · have 목록 · 체인지리스트 · 리비전 · 파일 형식 · typemap

Perforce 로 하는 작업과 명령

체크아웃 · 커밋 · 셸브 · 브랜치 · 코드라인 · Perforce 스트림 · 병합 · 충돌 해소 · 코드 리뷰 · p4 reconcile · p4 obliterate

Perforce 가 동시 수정을 다루는 방식

비관적 잠금 · 낙관적 잠금 · 이진 파일 · 원자성 · 게으른 복사

Perforce 서버를 굴리는 데 쓰는 장치

체크포인트 · 저널 · 프록시 서버 · 엣지 서버 · 레플리카 · 접근 제어 · 대소문자 구분 · 유니코드

Perforce 와 함께 나오는 같은 회사의 제품

P4V · P4Merge · P4 Code Review · P4 Cloud

Perforce 를 쓰는 회사 · 엔진 · 개발 분야

Google · Epic Games · Unreal Engine · UnrealGameSync · Unity · 게임 개발 · 모노레포

다른 이름: 퍼포스 · P4 · p4 · Helix Core · Perforce Helix · Perforce P4