이 시리즈의 원 저자인 Tom Ryder의 허락을 받고 올리는 번역글입니다. IDE가 할 수 있는 기능을 UNIX 계열의 shell 안에서도 원활하게 할 수 있는 비결을 초보자도 알기 쉽게 잘 설명한 글일 뿐만 아니라 UNIX 자체의 철학이나 기본 사용법을 따라잡기에도 굉장히 좋은 글이라 생각되어 우리말로 옮기고자 합니다. 프로그래밍 용어는 웬만하면 원래 영단어로 쓰겠습니다. 언제든지 더 좋은 표현에 대한 의견은 감사합니다.



UNIX as IDE: Revisions

2012년 2월 15일 Tom Ryder가 작성


버전 관리 도구는 전문적인 소프트웨어 개발에 있어서 이제는 떼어 놓을 수 없는 부분이 된 것 같다. Eclipse나 MS Visual Studio와 같은 GUI IDE도 대중적인 버전 관리 도구와의 연동을 지원하고 있다. 하지만 최신 버전 관리 도구는 오히려 자기 자신의 혈통을 diffpatch와 같은 UNIX 개념의 계보로 되돌리고 있으며, 그래서 많은 사람들이 버전 관라 도구는 shell에서 가장 효과적으로 사용할 수 있다고 주장하곤 한다.


이번 글이 UNIX as IDE 시리즈의 마지막 글이다. 여기서는 버전 관리의 시초인 diffpatch의 기본 개념에서 일반 오픈 소스 버전 관리 시스템이 어떻게 발전해왔는지 따라가 보겠다.



diff, patch, RCS

사람과 기계가 모두 이해할 수 있도록 쓰여진 (여러) 파일의 변경 내역인 Unified diff는 버전 관리 시스템의 근간이다. Unified의 핵심 개념이 곧 버전 관리 시스템의 핵심 개념이 되어왔다고 볼 수 있다. diff 명령어는 1974년에 나온 UNIX 5판에 Douglas McIlroy에 의해 처음 수록되었으므로 현대 시스템에서도 쓰이고 있는 가장 오래된 명령 중 하나일 것이다.


가장 널리 쓰이고 호환되는 형식인 unified diff는 아래와 같은 syntax로 파일의 두 가지 버전을 비교하여 생성된다.

$ diff -u example.{1,2}.c
--- example.c.1    2012-02-15 20:15:37.000000000 +1300
+++ example.c.2    2012-02-15 20:15:57.000000000 +1300
@@ -1,8 +1,9 @@
 #include 
+#include 

 int main (int argc, char* argv[]) { printf("Hello, world!\n");
-    return 0;
+    return EXIT_SUCCESS; }


간단히 위 예제를 해석해 보자면, 두 번째 파일에서 header 하나를 더 추가했으며 main() 함수의 return 명령이 0 대신 EXIT_SUCCESS를 사용하도록 변경됐음을 알 수 있다. 또한 unified diff는 파일명과 최근 수정 날짜와 같은 메타 자료도 포함하는 것도 확인할 수 있다.


diff의 출력물을 patch라고 부른다. 큰 코드 베이스의 버전 관리를 위한 가장 기본적인 형태는 개발자들로 하여금 patch를 보내면 동료의 코드 베이스에서는 patch 도구로 patch 파일의 내역을 적용시키는 것이다. diff의 출력물을 patch 파일로 저장하려면 다음과 같이 하면 된다.

$ diff -u example.{1,2}.c > example.patch


그럼 우리 동료에게 이 patch 파일을 보내면, 받은 사람은 이를 적용시켜서 이전 버전의 파일을 업데이트시킬 수 있다.

$ patch example.1.c < example.patch


Patch 파일은 하위 폴더 구조를 포함해서 여러 파일의 diff 결과를 담을 수 있어서 효율적인 방법으로 프로젝트 소스를 업데이트할 수 있게 해준다.


diff 출력물을 이용해서 변경 내역을 따라가는 작업은 충분히 정규적인 작업이었기 때문에, 파일의 역사를 제 자리에 간직하기 위해서 Source Code Control System이 만들어졌고, 또한 이 시스템을 거의 대체해버린 RCS(Revision Control System)이 나중에 개발됐다. RCS는 파일을 "잠가서" 사용자가 시스템에 "check out"하지 않으면 파일을 편집할 수 없도록 했는데, 이런 방법은 추후에 개발되는 버전 관리 도구들에게 다른 방향에서 생각할 수 있게 해주었다.


RCS는 굉장히 간단한 사용법을 유지하고 있다. 기존 파일을 RCS의 관리 하에 두고 싶다면, 그저 ci <filename> 명령을 실행하고 그 파일에 대한 적당한 설명을 입력하면 된다.

$ ci example.c
example.c,v  <--  example.c
enter description, terminated with single '.' or end of file:
NOTE: This is NOT the log message!
>> example file
>> .
initial revision: 1.1
done


example.c,v라는 파일을 생성해주는 것을 볼 수 있는데, 이 파일에 example.c의 변경 내역이 계속 기록될 것이다. 이제 파일을 편집하고 싶다면 check out을 해야 하며, 편집이 끝나면 다시 check in 해야 한다.

$ co -l example.c
example.c,v  -->  example.c
revision 1.1 (locked)
done
$ vim example.c
$ ci -u example.c
example.c,v  <--  example.c
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> added a line
>> .
done


이렇게 작업을 하다가, rlog를 이용해서 그간 프로젝트의 역사를 볼 수 있다.

$ rlog example.c

RCS file: example.c,v
Working file: example.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 2; selected revisions: 2
description:
example file
----------------------------
revision 1.2
date: 2012/02/15 07:39:16;  author: tom;  state: Exp;  lines: +1 -0
added a line
----------------------------
revision 1.1
date: 2012/02/15 07:36:23;  author: tom;  state: Exp;
Initial revision
=============================================================================


rcsdiff -u를 통해서 두 버전 간 unified diff 형식의 patch를 받아볼 수 있다.

$ rcsdiff -u -r1.1 -r1.2 ./example.c
===================================================================
RCS file: ./example.c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ./example.c 2012/02/15 07:36:23 1.1
+++ ./example.c 2012/02/15 07:39:16 1.2
@@ -4,6 +4,7 @@
 int main (int argc, char* argv[])
 {
     printf("Hello, world!\n");
+    printf("Extra line!\n");
     return EXIT_SUCCESS;
 }


여기서 RCS의 명령어를 이용하게 되면서 간단한 patch를 다루지 않게 된다고 오해할 수도 있다. 하지만 diffpatch를 통한 가장 기본적인 작업은 여전히 흔히 쓰이고 있으며, centralized/decentralized 두 부류의 버전 관리 시스템에서 핵심적인 기능을 담당한다.



CVS, Subversion

개발자 여러 명이 하나의 코드 베이스를 다룰 때의 충돌 문제를 해결하기 위해 centralized 버전 관리 시스템이 탄생했는데, 그 첫 프로그램이 CVS(Concurrent Versions System)이었고 후에 조금 더 진보된 Subversion이 만들어졌다. 소스 코드 repository를 관리하는 하나의 중앙 서버를 두고, 거기서 검증된 버전의 코드 베이스를 내려받는 개념이다. 중앙 서버에서 어느 시점, 어느 버전이라도 해당하는 코드 베이스를 받아 볼 수 있다. 이런 코드 베이스를 working copy라고 부른다.


Centralized 시스템에서의 기본 작업 단위는 changeset으로, 사용자에게는 예전 시스템에서 쓰이던 전형적인 diff 형식을 통해 이를 보여준다. 변경 내역을 직접 파일에 저장하는 것이 아니라 changeset들의 기록들을 보관하는 것으로 버전 관리가 이루어진다.


프로젝트를 branching해서 동시에 다른 작업을 진행하다가 메인 가지에 합친다든지, 테스트 및 회고를 통해 가지를 버린다든지 하는 개념이 여기서 처음 도입됐다. 특정 버전에 소프트웨어 배포 버전 등의 표시를 해주는 tagging 개념, merge하거나 충돌점을 수동으로 해결하는 개념도 마찬가지로 이 때 만들어진 개념이다.



Git, Mercurial

버전 관리의 다음 세대인 distributed 또는 decentralized 시스템은 working copy가 프로젝트의 전체 역사를 담도록 해서 중앙 서버에 메달리지 않도록 한다. 오픈 소스 환경 및 UNIX에 친숙한 환경에서 선봉에 서고 있는 버전 관리 시스템은 Git과 Mercurial이다. 각각 githg라는 클라이언트 프로그램을 사용할 수 있다.


이 두 시스템은 push, pull, merge 작업을 통해서 changeset을 가지고 소통하는 개념을 구현했다. push, pull, merge는 한 repository에서의 변경 사항을 다른 repository에서 받아들이는 흐름이다. 이렇게 분산화된 시스템은 매우 복잡하지만 개발 생태계를 엄격하게 통제할 수 있게 한다. Git은 Linus Torvalds에 의해서 처음 개발되기 시작했는데, 그 이유가 버전 관리 시스템이 리눅스 커널 개발을 관리할 수 있도록 하기 위해서였다.


CVS와 Subversion과 달리, Git과 Mercurial 모두 기본 작업 단위가 changeset이 아니고 온전한 파일(blob)을 압축해서 저장한다. 따라서 우리가 위에서 다뤘던 기본적인 diff, patch 작업을 위해서 더 많은 연산을 해야 한다만, 여전히 git log --patch의 출력 내용은 40년 전 diff가 처음 쓰일 때의 나오던 것과 다르지 않다.

commit c1e5559ddb09f8d02b989596b0f4100ad1aab422
Author: Tom Ryder 
Date:   Thu Feb 2 01:14:21 2012

Changed my mind about this one.

diff --git a/vim/vimrc b/vim/vimrc index cfbe8e0..65a3143 100644
--- a/vim/vimrc
+++ b/vim/vimrc
@@ -47,10 +47,6 @@
 set shiftwidth=4
 set softtabstop=4
 set tabstop=4

-" Heresy
-inoremap  
-inoremap  
-
 " History
 set history=1000


이 두 시스템은 그 기능과 명령어 기준 꽤나 겹치는 면이 많이 있어서 둘 중 무엇을 쓸 것이냐에 대한 상당한 토론을 찾아볼 수 있다. 필자가 본 최고의 설명서는 Scott Chacon의 Pro Git, Joel Spolsky의 Hg Init이다.



시리즈를 마치며

이렇게 UNIX as IDE 시리즈를 마친다. GNU/Linux의 shell 안에서 쓸 수 있는 기본 도구만을 가지고 전문적인 IDE가 제공하는 필수 기능들을 따라 잡기 위한 빠른 요점 정리를 하고자 노력했다. 간혹 필자가 철저하고 완전한 설명을 하지 못한 부분도 있겠지만, 그래도 GNU/Linux에서 개발하는 것이 익숙하지 않은 분들에게 어떻게 the humble shell이 이렇게 자유롭고 매우 성숙한 소프트웨어 표준 도구가 될 수 있는 지에 대해 아이디어를 주었기를 바라는 마음이다.



UNIX as IDE




'GNU-Linux' 카테고리의 다른 글

Arch Linux에서 systemd-networkd로 WiFi 자동 연결 세팅  (3) 2018.01.08
UNIX as IDE  (0) 2017.11.09
UNIX as IDE: 6. Debugging  (0) 2017.11.08
UNIX as IDE: 5. Building  (0) 2017.11.07
UNIX as IDE: 4. Compiling  (0) 2017.11.06