언리얼 기반 개발과 깃

돌이켜 보면 언리얼 2부터 지금의 언리얼 5까지 꽤 긴 기간에 걸쳐 언리얼 엔진을 사용하는 상업용 게임 프로젝트에 참여한 적이 있습니다. 매일 같이 언리얼에 불평하면서도 다른 한편으로는 냉정하게 언리얼이 아니면 규모가 큰 온라인 게임을 어떻게 만들어야 할 지 상상하기 쉽지 않습니다. 종종 자체 엔진을 사용한다고 알려진 프로젝트나 회사가 나타나 그 대단함을 과시하기도 하지만 하루하루 언리얼 개발환경을 사용해 일하면서 과연 저 분들의 개발환경도 언리얼 개발환경만큰 잘 갖춰져 있을지 의심해 보곤 합니다.

세월이 흐르는 사이에 언리얼 엔진은 버전이 오르기만 하고 이름이 바뀌지는 않았지만 언리얼과 함께 사용한 형상관리도구는 바뀌어 왔는데 처음 언리얼을 사용하는 프로젝트에 참여할 때는 형상관리도구가 없었고 (농담이 아님) 그 다음에는 소스세이프, CVS, SVN, Perofrce를 사용했는데 이들 중 가장 오래 사용한 도구는 맨 마지막에 언급한 퍼포스 였습니다. 언리얼과 퍼포스 조합은 여러 프로젝트에 걸쳐 성공적으로 검증되었는데 특히 한 프로젝트에 참여하는 인원이 적어도 세 자릿수이고 직접 코드를 수정하거나 직접 코드를 빌드하지 않는 다양한 직군의 사람들이 참여하며 이들이 주로 올리는 바이너리 파일로 구성된 버전을 뒷받침하는데 큰 무리가 없었습니다.

한편 최근에 언리얼 기반 프로젝트를 시작하면서 처음으로 언리얼 프로젝트에 Git을 사용하기로 합니다. 엔지니어 직군의 초기 결정이었는데 그 동안 토이 프로젝트나 할일관리에 사용하는 지라 이슈키와 파일시스템을 연동하기 위해 사용하는 프로젝트에 Git을 사용해본 적이 있었고 또 엔지니어들 사이에 Git이 널리 활용된다는 것을 알고는 있었지만 규모가 작지 않은 상용 게임 프로젝트에 사용해본 적은 없었습니다. 전통적인 큰 회사에서 일하면 큰 프로젝트의 기술 구성에 대한 의사결정이 보수적으로 일어났고 이에 따라 언리얼 프로젝트에 Git을 사용할 기회를 얻기는 거의 불가능했기 때문에 이 참에 규모가 큰 언리얼 프로젝트에 Git을 사용해볼 수 있겠다 싶어 결정에 동의했습니다.

지난 약 1년 반에 걸쳐 언리얼과 Git을 사용해 오면서 이미 기획자가 깃에 대해 불평하기 , 언리얼 기반 개발과 깃은 잘 어울리지 않는 것 같음에서 몇 달 간격으로 이들 둘이 서로 잘 어울리지 않으며 특히 게임 개발과 잘 어울리지 않는다는 생각을 해 왔는데 이번 주에 마일스톤 마감을 겪으며 당장 서버를 불태워 없애지 않으면 안 되겠다는 생각을 할 정도로 열이 받아 이 주제를 한번 더 생각해 보기로 합니다.

먼저 게임디자인 관점에서 게임 개발의 특징을 생각해 보면 리파지토리에 직접 파일을 커밋하는 사람 수가 쉽게 100명을 넘깁니다. 이 업계에서 일하며 항상 세 자릿수 인원이 한 프로젝트에서 일하며 한 리파지토리에 커밋하는 환경에서 일하다 보니 이 정도 규모가 그리 크다고 생각하지 않았었는데 다른 업계에 계신 분들과 이야기해보니 아주 흔한 것은 아닌 모양입니다.

프로젝트에는 직접 코드를 작성하고 빌드하는 전통적인 엔지니어 직군 외에도 초반에는 기획서를 쓰지만 중후반에는 데이터를 조립해 게임을 만들어내는데 기여하는 게임디자이너, 시각 에셋을 만들어내는 아티스트, 청각 에셋을 만들어내는 사운드디자이너 등 다양한 직군으로 구성되어 있습니다. 이들의 기술적 이해도는 서로 상당히 다른데 엔지니어들 사이에서도 현대적인 지원 도구나 에디터를 썩 선호하지 않는 분들도 있고 아티스트 사이에서도 현대적인 렌더링 파이프라인에 대한 이해가 싶은 분들이 있는가 하면 보다 창의적인 업무에 중점을 두고 이 작업과 관계가 적은 이슈에는 크게 관심을 가지지 않는 분들도 있습니다. 한때 큰 회사에서는 직군 별로 서로 다른 기술적 이해도에 의해 일어나는 문제를 완화하기 위해 팀에서 생산하는 에셋을 형상관리도구에 업데이트하는 일을 담당하는 스탭을 두기도 했습니다.

각기 다른 직군은 각자가 만들어내는 파일 형태 역시 서로 다른데 엔지니어 직군은 주로 코드, 문서, 코드를 빌드한 바이너리를 만들어내고 아티스트들은 주로 직접 사용하는 저작도구가 생성한 바이너리 에셋, 그리고 이를 언리얼 엔진으로 임포트 한 바이너리 파일을 주로 만들어내며 게임디자인은 문서, 언리얼 엔진에서 생성한 바이너리 파일, 그리고 팀에 따라 상당히 다르기도 하지만 엑셀 같은 또 다른 형태의 저작도구를 통한 바이너리 파일을 만들어 냅니다. 여러 직군이 다양한 형태의 작업 결과를 만들어내는데 인터넷에서 쉽게 볼 수 있는 엔지니어 집단의 구성원 대부분이 텍스트로 코드를 만들고 이를 빌드한 다음에야 바이너리를 만들어내는 것과는 달리 처음부터 바이너리 파일을 만들어내는 직군이 전체의 절반을 훨씬 넘습니다.

다음으로 언리얼 엔진 개발환경을 게임디자인 관점에서 납작하게 살펴보면 크게 엔진 코드, 게임 코드, 그리고 아트 에셋 및 게임디자인 에셋으로 구분할 수 있습니다. 엔지니어들은 자신들의 세부 직군에 따라 엔진 코드와 게임 코드를 작성한 다음 이를 빌드해 에디터 바이너리와 클라이언트 및 서버 바이너리를 생성합니다. 나머지 직군은 코드를 직접 빌드 하지 않으므로 엔지니어 직군이 빌드한 결과를 형상관리도구에 배포하면 이를 받아 언리얼 에디터를 실행하고 또 에디터에 기반해 게임을 실행합니다.

아티스트와 게임디자이너는 각자 자신들의 첫 번째 저작도구를 사용해 에셋을 만들고 이를 인하우스 툴을 사용해 변환하거나 직접 언리얼 엔진에 임포트 해 언리얼 에셋을 만들어내는데 이 역시 엔지니어 직군의 빌드 결과와 마찬가지로 바이너리 형태입니다. 상황을 요약하면 텍스트 에셋을 만들어내는 직군은 대부분이 엔지니어, 그리고 팀에 따라 게임디자인 직군이 아주 조금 포함되며 나머지 거의 모든 에셋은 바이너리입니다.

그러면 이제 깃(Git, 이하 깃)의 특징을 생각해봅시다. 깃은 분산 환경을 제공하는 형상관리도구입니다. 전통적인 형상관리도구는 중앙 집중 환경이었는데 작업에 참여하는 사람들은 중앙 서버로부터 특정 브랜치의 최신 버전을 받은 다음 이 위에서 작업한 결과를 다시 중앙 서버에 올리고 이런 작업을 여러 사람들이 반복해 완성된 결과를 만들어 내는 방식으로 개발합니다. 중앙 집중 환경은 중앙 서버에 문제가 생기면 여기에 의존한 모든 사람들의 작업이 중단되는 문제가 있었고 특히 중앙 서버 데이터를 유실하면 모든 개발 결과를 유실할 수도 있는 위험이 있을 뿐 아니라 대체로 브랜치 생성 비용이 높아 브랜치를 소극적으로 생성하는 경향이 있어 종종 아직 완성되지 않은 에셋이 다른 에셋과 섞여 커밋 되어 문제가 일어나기도 합니다.

반면 깃은 완성된 빌드를 만들어 내기 위한 기준이 되는 브랜치를 서빙하는 서버가 있기는 하지만 근본적으로 이 서버가 여러 개가 될 수 있습니다. 이 개념을 지탱하기 위해 개발에 참여하는 모든 사람들은 리파지토리의 모든 히스토리를 받아 로컬에 가지고 있고 꼭 중앙 서버가 아니더라도 원하는 곳의 코드를 가져오거나 내 코드를 보낼 수 있습니다. 이 환경에서는 브랜치 비용이 상대적으로 낮아 서로 다른 작업들의 코드가 섞여 커밋 될 가능성이 적고 서로 다른 브랜치 사이에 에셋을 주고 받는 머지, 리베이스 같은 기능이 잘 갖춰져 있어 적극적으로 브랜치를 만들어 작업하기를 유도하는 환경으로 알려져 있습니다.

이제 지금까지 대략 살펴본 언리얼 엔진 기반의 게임 개발을 깃 기반으로 할 때 일어나는 일을 생각해봅시다. 맨 먼저 깃 자체만으로는 게임 개발을 시작조차 할 수 없는데 이유는 개발에 사용되는 파일은 쉽게 수 메가에서 수 십 메가에 달하기 때문입니다. 본격적으로 깃을 사용하기 시작하면서 LFS라는 소프트웨어가 별도로 존재한다는 사실이 상당히 불편했는데 이는 애초에 깃이 큰 파일이 올라오는 개발 환경을 고려하지 않고 개발되기 시작했기 때문일 것 같습니다.

LFS는 일단 큰 파일을 리파지토리에 올릴 수 있게 해 주고 모든 원격 브랜치에 걸친 락을 사용할 수도 있게 해 주지만 파일을 메타데이터와 파일 본체로 구분해서 동작하는 개념 덕분에 가장 기본적인 원격 리파지토리에서 파일을 받아오는 작업 자체부터 어렵게 만듭니다. 가장 흔히 경험하는 문제는 리모트 리파지토리를 받아 오려고 Pull(이하 풀)을 수행하면 여러 가지 이유로 메타데이터는 받아 왔지만 실제 파일 다운로드가 늦어져 로컬에서 메타데이터와 실제 파일의 해시가 달라지는 상황이 자주 생깁니다.

나는 그냥 파일을 풀 했을 뿐인데 갑자기 메타데이터만 업데이트 된 채로 풀에 실패하고 메타데이터와 아직 업데이트 되지 않은 실제 파일 사이에 해시가 달라지자 깃은 이 파일들을 내가 편집했다고 생각하고 다운로드에 실패한 파일을 모두 Unstage(이하 언스테이지) 상태로 만들어 버립니다. 이 상황을 벗어나는 방법은 언스테이지 상태인 파일의 변경을 취소(Discard)한 다음 다시 풀 하는 것입니다. 아마 이 형상관리도구를 개발하며 가정한 환경은 네트워크 속도가 아주 빠르고 로컬 기계와 리모트 기계 역시 아주 빨리 응답하며 파일 크기가 작아 메타데이터와 파일 바이너리를 분리할 필요 조차 없는 아주 가벼운 환경이었을 거라고 예상합니다.

형상관리도구의 가장 가장 가장 가장 기초적인 두 가지 기능(파일 받기, 파일 보내기) 조차 견고하게 동작하지 않는다는 점에서 이미 깃은 뭔가 잘못됐습니다. 특히 기술적인 이해도가 서로 다른 여러 직군이 일하는 환경에서 단지 파일을 받으려고 했을 뿐인데 바로 이어 재시도가 불가능한 상태로 작업을 종료해 버리는 동작은 파일을 받는 동작 조차 시도할 때 심호흡을 하게 만듭니다.

처음에 설명했듯 게임 개발에는 엔지니어 직군이 아닌 나머지 직군이 생산하는 거의 모든 파일 형태가 바이너리임입니다. 그래서 파일에 락을 거는 개념이 굉장히 중요한데 먼저 바이너리 형태는 텍스트 파일에 그렇게 하듯 쉽게 머지 할 수 있지 않습니다. 종종 저작도구에 따라 서로 다른 두 바이너리 파일을 읽어 변경사항을 머지한 새 바이너리 파일을 만들어 주는 기능을 지원하는 프로그램도 있긴 하지만 이런 기능들 역시 훌륭하지 않은 편이고 또 개념을 이해 시키기도 쉽지 않아 바이너리 파일은 애초에 머지를 시도하지 않도록 파이프라인을 구축하고 있습니다.

가령 어떤 게임에 열이 300개, 행이 20만개(과장한 것이 아님.) 정도 되는 ‘Item.xlsx’라는 엑셀 파일이 있다고 합시다. 이 파일에는 서로 다른 부서에서 인게임에 아이템 모양으로 나타나야 할 온갖 데이터를 부어 넣는데 사용하는데 비록 압축을 풀어 xml을 열어 보면 텍스트 형태로 저장되기는 하지만 xlsx 파일 자체는 바이너리 포멧입니다. 때문에 여러 사람의 동시 작업이 사실상 불가능합니다. 종종 엑셀 파일을 수정한 다음 이를 익스포트 해서 머지 가능한 형식으로 바꾸기도 하지만 익스포트 한 파일을 머지할 수 있다고 해서 각자가 수정한 엑셀 파일을 머지할 수 있는 것은 아니며 각자가 서로 다른 엑셀 파일을 가지고 있어 더 이상한 문제에 쉽게 빠지곤 합니다.

이런 상황을 회피하기 위해 주로 부서나 작업자 단위로 파일을 쪼개 ‘Item_Battle.xlsx’, ‘Item_Usable.xlsx', ‘Item_resource.xlsx’와 같이 만들어 이들 각각을 커밋하게 해서 바이너리가 충돌하는 상황을 완화하곤 합니다. 이렇게 해도 다시 부서 안에서 동시작업이 일어나거나 서로 다른 부서가 같은 바이너리 파일에 동시작업을 해야 할 일이 일어나는데 이럴 때를 대비해 파일에 Lock(이하 락)을 걸어야만 합니다. 락은 리파지토리 전체에 걸쳐 누군가 한 명이 파일을 수정하겠다고 선언하고 이 선언을 같은 리파지토리를 사용하는 모든 사용자들에게 전파하는 것입니다. 위 아이템 데이터 사례에서 누군가 한 명이 ‘Item.xlsx’를 편집하겠다고 선언하면 같은 리파지토리를 사용하는 모든 사람들의 자리에서 이 파일을 체크아웃 할 수 없고 또 파일을 읽기전용으로 바꿔 저장할 수 없게 해 형상관리도구 상태를 확인하지 않아도 파일이 잠겨 있다는 사실을 쉽게 파악할 수 있습니다.

깃은 LFS를 통해 락을 제공하기는 하지만 분산환경이라는 특성 상 혹은 소프트웨어의 결함으로 락이 예상한 대로 깔끔하게 동작하지 않습니다. 누군가가 로컬에서 파일에 락을 걸면 이 동작은 커밋, 풀, 푸시에 관계 없이 리모트 리파지토리를 통해 이 파일이 체크아웃 되어 있는 다른 모든 리모트 브랜치에 전파 되어야 합니다. 그래야 서로 다른 브랜치 사이에 바이너리 파일이 충돌하지 않을 수 있습니다. 하지만 락은 거의 항상 이 파일의 모든 체크아웃에 전파되지 않아 락을 했지만 서로 다른 둘 이상의 사람들이 작업이 충돌해 둘 중 한 명이 작업을 잃어야 하는 상황이 일어납니다. 상황에 따라 언리얼 레벨이나 애님몽타주 에셋은 아트와 게임디자인 사이에 동시작업이 일어나기 쉬운데 락을 걸어도 충돌이 상시 일어나는 상황을 참다 못한 사람들은 슬랙 채널에 내가 지금부터 이 파일을 쓸 거라고 선언하는 지경에 이르렀습니다.

기술적인 차이고 뭐고 간에 서로 다른 브랜치 사이에 파일이 락 된 상태를 전파하기가 어려운가 싶었습니다. 여기서 좀 더 파고들면 여느 중앙 집중 식 형상관리도구에서는 파일을 서버에 커밋 하고 나면 락이 알아서 풀리지만 깃은 커밋하고 푸시 한 다음에도 락이 알아서 풀리지 않아 관리자가 없을 경우 휴가 중인 사람을 찾아 락을 풀어 달라고 말해야 하는 상황이 일어나기 합니다.

바이너리 파일을 다루는 관점에서 깃의 리베이스와 머지의 구분은 설명하기 쉽지 않습니다. 깃에서 리베이스는 이전 브랜치와 여기서 갈려 나온 새 브랜치에서 작업할 때 브랜칭 이후 발생한 이전 브랜치의 변경사항을 브랜칭 한 리비전을 변경해 적용하는 방식인데 이 개념을 중앙 집중 식 형상관리도구인 퍼포스 기준으로 설명하면 그냥 ‘업데이트’ 하는 것입니다. 이 과정에서 충돌한 파일이 있으면 내 버전을 유지하거나 새로 받은 버전을 유지하거나를 결정하기만 하면 됩니다. 또한 머지 역시 서로 다른 두 브랜치 사이에 변경사항을 한 브랜치에 통합하는 과정으로 이를 퍼포스 기준으로 설명하면 그냥 커밋입니다.

그런데 깃은 이를 리베이스와 머지로 구분하고 있어 일단 설명하기 어렵고 리베이스는 초반에 설명한 LFS의 메타데이터와 실제 파일을 구분하는데서 오는 잦은 오류로 잘 수행되지 않을 뿐 아니라 바이너리 파일 관점에서 리베이스와 머지는 사실상 같은 동작일 뿐인데도 머지는 머지 커밋을 남기며 시간이 오래 걸리는 작업일 뿐입니다.

윈도우에서만 그런지 모르겠지만 파일을 사용하려고 할 때 파일이 읽고 쓸 수 없는 상태인 것은 꽤 흔한 일이라고 생각합니다. 처음 코드를 통해 파일 입출력을 배울 때 파일을 읽고 쓸 수 없는 예외상황을 처리하는 방법을 배웁니다. 처음에는 그냥 에러를 내고 끝내지만 좀 더 단단하게 만들다 보면 작업에 실패할 때 조용히 재시도해 결국 작업에 성공하게 만드는데 이릅니다. 퍼포스에서도 실행 중이거나 열려 있는 파일을 업데이트 하려고 할 때 시간 간격을 두고 여러 번 시도하고 그 사이에 파일을 사용할 수 있게 되면 작업에 성공합니다.

그런데 깃은 단 한번이라도 파일에 쓸 수 없는 상황을 마주치면 즉시 작업을 중단합니다. 가장 흔히 겪는 상황은 인덱스에 쓸 수 없다며 작업을 중단하는 건데 어쩌면 그 순간에 파일을 안티바이러스 소프트웨어가 읽고 있었을 수도 있고 드랍박스가 읽거나 또 다른 나도 모를 프로세스가 읽고 있었을 수도 있습니다. 이런 순간은 대체로 일시적이어서 좀 있다가 다시 시도해볼 법도 하지만 깃은 항상 단 한 번의 시도에 실패하면 작업을 중단하고 에러 메시지를 띄운 다음 내가 다시 시도할 것을 요구합니다. 퍼포스가 실행 중인 dll을 시간차를 두고 열 번 재시도한 다음에야 작업에 실패하는 것과는 굉장히 다른 동작입니다.

파일시스템에 의존하면서도 파일시스템을 깃이 완전히 독립적으로 사용할 수 있는 상황만을 가정했을 것 같은 동작은 단지 인덱스에 쓸 수 없어서 멈추는 것 이외에도 아주 많습니다. 가령 락 파일을 생성해 놓고 이를 제대로 처리하지 못하고 그 다음 작업을 시도하면 락 파일이 있다며 아무 것도 안 하고 멈추기도 합니다. 그러면 직접 .git 디렉토리를 찾아 가서 락 파일을 직접 삭제하면 또 모든 작업이 거짓말처럼 잘 수행됩니다. 이 때마다 생각하곤 합니다. 락 파일을 직접 지우면 푸틴이 핵미사일을 발사하기라도 하는 건가? 라고요.

또 머지하다가 실패하면 머지 디렉토리를 남겨 놓고 이를 내가 삭제하기 전까지 또 다시 모든 작업을 거부하기도 하는 등 오로지 깃 자신만이 파일시스템을 온전히 통제할 수 있으리라는 믿음 하에 만들어졌을 것 같은 약해 빠진 동작은 단지 내 작업에 집중하고 그 작업을 모두가 공유하는 중앙 브랜치에 올리고 싶을 뿐인 자신을 하루에 몇 십 분 씩 여러 사람에게 일어나는 깃 트러블슈팅에 낭비하게 만듭니다.

여러 사람이 함게 개발하는 소프트웨어는 근본적으로 이 모든 사람들의 작업이 한데 모여 빌드 된 다음에야 의미가 있습니다. 아티스트나 게임디자이너의 바이너리 파일은 이들이 어느 로컬 기계의 어느 브랜치에서 작업 되었든 간에 중앙 서버의 메인 브랜치에 올라가 빌드에 포함되어야만 작업이 완료되었다고 말할 수 있습니다. 깃의 로컬과 리모트의 구분, 로컬과 리모트 각각에서 서로 다른 브랜치 구분을 통해 각 작업자의 작업이 완료되었는지, 또 배포해야 할 빌드에 포함되었는지를 판단하기 쉽지 않게 만듭니다.

게임에 업적 시스템을 개발해 넣는 상황을 생각해 봅시다. 업적은 크게 업적 조건을 판단하는 기반과 업적 인터페이스, 고객이 보게 될 업적 조건과 이 업적을 반영한 퀘스트 및 다양한 컨텐츠로 구성될 수 있습니다. 무식하게 생각될 수도 있지만 이들은 게임 전체에 걸쳐 퍼져 있는 광범위한 시스템들이고 이들이 서로 연동되어 동작해야만 업적 시스템이 완성되었다고 평가할 수 있습니다.

만약 업적 시스템을 개발하기 위해 브랜치를 만들었다면 이 작업에 관련된 나머지 모든 직군들도 이 브랜치에서 작업 해야만 합니다. 그런데 방금 설명한 대로 업적 시스템은 게임에 전반적인 영향을 끼치며 게임의 나머지와 온전히 분리해 브랜치에서 개발한 다음 한 번에 머지해서 개발을 마칠 수 있지 않습니다. 업적 시스템 브랜치의 개발이 진행 됨에 따라 이 기능에 의존성이 있는 서로 다른 기능들이 서로 다른 브랜치에서 개발되며 코드를 직접 쓰는 엔지니어가 서로 다른 여러 브랜치를 계속해서 리베이스 해 가며 작업해야 할 뿐 아니라 이 코드에 연동되는 파티클 시스템 에셋, 게임디자인이 만든 엑셀 데이터, 위젯블루프린트 에셋 따위가 서로 다른 브랜치에 걸쳐 커밋 되어야 하는 상황이 일어납니다.

엔지니어 관점에서 서로 다른 브랜치를 옮겨 다니며 작업하고 또 서로 다른 브랜치 사이에 리베이스와 머지를 거듭해 가며 작업을 이어 나가는 것이 크게 이상하지 않다고 생각할 수 있습니다. 하지만 오직 바이너리 파일만 다루는 사람들 입장에서는 브랜치 사이에 머지가 불가능하므로 여러 브랜치에 걸쳐 서로 다른 상태인 위젯블루프린트에 같은 변경사항을 반복해서 작업해야 하는 상황에 처하기 쉬우며 실제로 이런 일이 일어납니다. 이쯤 되고 보면 중앙 집중식 형상관리 환경에서 가끔 누군가의 부주의한 변경사항으로 서버 기동에 실패하거나 빌드에 실패하는 정도는 그냥 웃고 넘어갈 수 있는 수준입니다.

요약하면 깃은 그 자체만으로는 게임 개발에 사용할 수 없고 바이너리 파일 관점에서 LFS의 핵심 기능인 락은 여러 브랜치에 걸쳐 신뢰할 수 있게 동작하지 않으며 깃 은 파일시스템을 마치 자기 혼자 사용하는 것처럼 행동하고 에러 상황을 재시도 할 수 없는 상태로 만들어 놓고 그냥 작업을 중단해 트러블슈팅에 익숙하지 않은 사람들을 겁먹게 만듭니다.

언리얼 엔진 기반 개발은 전체 인원의 반 이상이 항상 바이너리 파일을 생산해 브랜치 사이에 머지가 불가능하고 바이너리 파일 관점에서 리베이스와 머지는 구분할 수 없어 개념을 복잡하게 만들 뿐 아니라 로컬과 리모트, 또한 각각의 여러 브랜치를 옮겨 다니는 작업 스타일은 머지가 불가능한 바이너리 파일을 만드는 관점에서는 결코 간단하지 않고 단일 기능이 시스템의 넓은 범위에 걸쳐 영향을 끼치는 요구사항의 특성 상 메인에서 분리된 브랜치에서 개발을 완수하기가 어렵습니다. 이런 생각 끝에 약 1년 반에 걸친 언리얼 엔진과 깃 사용 경험 끝에 이들 둘을 앞으로 몇 년에 걸쳐 지속적으로 사용하는 것은 프로젝트 전체에 걸친 항우울제 사용량을 늘리는데 기여할 것이라는 결론을 내렸습니다.

마지막으로 한동안 너무 짜증나고 답답해서 타임라인에 깃 욕설을 쏟아냈는데 리눅스나 맥에서는 그런 현상을 겪어본 적 없다든지 지금 사용하는 깃 비주얼 클라이언트가 이상할 거라는 의견을 여러 번 들었습니다. 맥이나 리눅스에서는 멀쩡하지만 윈도우에서는 이상하게 동작한다면 그건 윈도우를 지원하는 것이 아니라 윈도우 사용자를 엿 먹이기 위해 동작하는 결함 소프트웨어일 뿐입니다. 윈도우에는 윈도우 OS의 규칙이 있고 그 규칙 하에서 잘 동작하지 않는다면 이를 윈도우 잘못이라고 쉽게 말할 수 있을까요.

또 깃 비주얼 클라이언트를 의심하는 분들 대부분은 아마도 깃을 CLI 환경에서 사용할 것 같은데 엔지니어가 아닌 사람이 전체의 절반이 훨씬 넘는 환경에서 모든 사람들에게 깃 CLI 환경을 교육할 수 있을지 모르겠습니다. 또한 지금까지 접해본 깃 비주얼 클라이언트에는 Sourcetree, Sublime Merge, Fork 등이 있는데 이 셋 모두 임베디드 깃 기반으로 동작하기도 하지만 동시에 시스템 깃을 제어하는 껍데기로써 동작하기도 하며 지금까지 이야기한 온갖 문제는 시스템 깃을 사용하는 환경에서 일어난 것입니다. 비주얼 클라이언트에 기반해 문제를 겪고 있지만 실은 CLI를 제어하는 껍데기일 뿐이어서 비주얼 클라이언트의 문제라고 가정하기는 아주 어렵다고 생각합니다.

지금 이 순간에도 과연 깃이 지금처럼 세계를 지배하며 모든 개발에 적용할 만한 형상관리도구일지 잘 모르겠습니다. 종종 사람들과 이야기하며 퍼포스야말로 미개한 쓰레기 같은 느낌을 받았지만 암만 생각해봐도 지금까지 경험한 언리얼 개발 환경 중 이를 잘 지탱한 형상관리도구는 퍼포스였던 것도 사실입니다.