'분류 전체보기'에 해당되는 글 2491건

  1. 2010.12.16 잘못된 웹표준, 웹접근성 상식
  2. 2010.12.16 웹 접근성을 위한 자바스크립트 적용 방법 (Graceful Degradation vs. Progressive Enhancement) 1
  3. 2010.12.16 웹 접근성 고려한 “관련사이트 바로가기” 구현 방법
  4. 2010.12.16 웹 표준, 웹 접근성 고려한 플래시 사용
  5. 2010.12.10 oracle 세로 출력값을 가로로 출력하기
  6. 2010.12.09 jeus Linux 설치
  7. 2010.12.06 [펌] dataoutputstream.writeUTF 에러
  8. 2010.12.01 알집 암호깨기
  9. 2010.11.30 USB메모리에 위키가 쏘옥~, Dokuwiki on a stick
  10. 2010.11.30 호스팅서버에서 DokuWiki 사용
  11. 2010.11.30 DokuWiki 설명서
  12. 2010.11.30 JSPWiki 한글 Language 추가
  13. 2010.11.30 JSPWiki 설치하기
  14. 2010.11.30 Trac] Trac 설치
  15. 2010.11.30 Hudson을 이용한 빌드 배포 테스트 자동화
  16. 2010.11.30 Hudson
  17. 2010.11.30 [펌] hudson 설치
  18. 2010.11.30 Hudson
  19. 2010.11.30 ms-sql 백업과 복원 (펌)
  20. 2010.11.30 [펌] 프로젝트의 지속적인 통합서버, 허드슨 (CI Server Hudson) #1
  21. 2010.11.29 Eclipse plugin 추천세트
  22. 2010.11.26 Confluence 3.4.x
  23. 2010.11.26 Trac 소개
  24. 2010.11.26 Android 애플리케이션을 위한 Java 기술
  25. 2010.11.26 아파치 메이븐 2 시작하기 (한글)
  26. 2010.11.26 [펌] Subversion 사용자를 위한 Git, Part 1: 시작
  27. 2010.11.26 [펌] Git 사용기 (windows 환경에서 GIT-GUI 와 github.com 을 중점으로)
  28. 2010.11.26 [펌] Git 사용자 설명서
  29. 2010.11.26 Apache JMeter를 활용한 테스트
  30. 2010.11.25 how to use maven on your project
반응형

잘못된 웹표준, 웹접근성 상식

웹표준을 준수하기 위해서는 XHTML을 써야 한다?

HTML 4.01을 잘 준수해도 웹표준을 준수한 것이다. 웹표준에서 중요한 것은 어떤 종류의 마크업 언어를 사용했는냐 보다 요소와 속성의 의미를 얼마나 잘 이해하고 적절히 문법에 맞게 사용했는지이다.

Strict DTD에서는 CSS 핵을 사용할 수 없고 Transitional DTD에서는 사용할 수 있다?

DTD와 CSS 핵 사용 가능 여부는 전혀 관련이 없다. CSS 핵을 쓰는 것은 표준에 위배되는 것을 감수하고 사용하는 것이고 특정 DTD라고 해서 핵의 사용이 허용되는 것은 아니다. Transitional DTD를 썼다고 해서 CSS 핵을 마구 사용해도 되는 것은 아니다.

CSS 핵은 특정 브라우저를 겨냥한 방법이기 때문에 되도록 사용을 피해야 하고 사용할 때에도 제한적으로, 다른 대안이 없을 경우에만 사용해야 한다.

Transitional DTD나 Frameset DTD를 사용하면 쿽스모드(quirks mode)로 랜더링 된다?

쿽스 모드로 랜더링 될 것인가 표준 모드로 랜더링 될 것인가는 DTD의 종류보다는 DTD의 선언 방법에 의해서 결정된다. Transitional DTD를 선언하더라도 표준모드로 랜더링되는 경우가 있다.

DIV 요소의 사용을 되도록 피해야 한다?

DIV 요소가 너무 남용되는 것은 문제이지만 그렇다고 DIV 요소의 사용을 너무 배척할 필요는 없다. DIV 요소는 내용을 그루핑하는 목적도 있기 때문에 표현하는 콘텐츠에 따라서 디자인적인 용도와 무관하게 사용할 수 있다. DIV를 사용하지 않은 코드보다는 DIV 요소와 적절한 id, class를 사용한 코드가 가독성도 훨씬 높다.

물론 <div class="round-button"><div><div><a href="list.html">목록</a></div></div></div> 이런 경우는 DIV 과용으로 피해야 하는 경우이다.

FRAME, IFRAME은 접근성을 해치므로 사용해서는 안된다?

과도하게 많은 프레임을 사용하는 것은 페이지 구성상도 좋지 않고 접근성을 해치게 된다. 하지만 프레임을 사용해야 하는 이유가 충분하고 접근성있게 프레임을 사용했다면 어떤 경우에는 프레임을 사용하는 것이 더 접근성이 좋을 수도 있다.

접근성을 높인다고 표준에 정의되어 있는 특정 요소나 기능을 배제할 필요는 없다. 접근성을 높이는 방법은 다양한 선택권을 사용자에게 주는 것이지 특정 형태만 강요하는 것이 아니다.

title 속성은 툴팁이나 스크린리더에서 읽혀질 내용을 제공할 때 사용된다?

title 속성은 추가적인 정보를 제공하기 위해서 사용된다. 대부분의 브라우저는 이를 툴팁으로 보여주고 있지만 브라우저에 따라서는 이를 이렇게 처리하지 않을 수도 있다. 스크린리더 역시 이 정보를 활용하지만 스크린리더를 위해서 이 정보를 제공하는 것은 아니다.

HTML 태그와 속성의 의미를 잘 이해하고 사용하는 것이 중요하다. 브라우저나 특정 사용자 기기에서 구현된 모습을 보고 원래의 의미를 혼동해서는 안된다.

summary, CAPTION을 사용하여 스크린리더에 정보를 제공한다?

summary, CAPTION, scope, header 등을 사용할 경우 이를 스크린리더가 참조할 수는 있다. 하지만 이러한 속성과 요소가 스크린리더만을 위한 것은 아니다. HTML의 의미를 이해하고 이에 맞게 사용해야지 특정 브라우저나 사용자 기기만을 염두에 두고 HTML을 구성해서는 안된다.

내부(internal) 스타일 시트가 외부(external) 스타일 시트보다 우선순위가 높다?

내부 스타일 시트 선언은 문서 안에서 STYLE 요소를 이용해서 선언하는 것을 말하고 외부 스타일 시트 선언은 LINK 요소를 이용해 외부의 .css 파일을 링크하는 것을 말한다. 이 두가지 방법의 우선순위는 같고 전적으로 선언한 순서에 따라서 적용 순위가 달라진다. 내부 스타일 시트를 먼저 선언하고 그 아래에 외부 스타일 시트를 불러온다면 내부가 아닌 외부 선언이 적용되게 된다.

반면에 style 속성을 이용한 인라인(inline) 스타일 선언은 위의 두가지 보다 우선순위가 높다.

시각 장애인을 위해서는 TTS를 사용한 음성 서비스와 글자 확대, 축소를 제공해야 한다?

특정 장애 유형을 위한 서비스를 제공할 때에는 사용자 환경을 잘 고려해서 제공해야 한다. 전맹인 시각 장애인은 스크린리더기 운영체제에 설치하거나 운영체제에서 제공하는 스크린리더를 사용하고 있기 때문에 웹사이트에서 제공하는 음성 서비스는 쓸 수 없다. 오히려 웹사이트에서 나오는 소리로 인해 스크린리더 사용을 방해 받을 수 있다.

글자 확대, 축소기능은 운영체제나 브라우저와 같은 소프트웨어에서 이미 제공이 되고 있는 기능이다. 웹사이트에서 별도의 서비스로 이러한 기능들을 제공하기 보다는 사용자가 이러한 기능들을 사용했을 때 글자 크기가 자유롭게 변경이 되도록 제작하는 것이 중요하다. 또한 텍스트를 이미지로 제공하지 않는 것도 이러한 기능들이 잘 작동할 수 있게 해주는 방법이다.

운영체제 수준에서 제공되는 접근성 기능을 잘 파악하여 이러한 기능을 사용자가 사용할 때 불편함이 없도록하는 것이 더욱 중요하다.

청각 장애인을 위해서 음성이나 소리로 내용을 전달하지 않아야 한다?

음성이나 소리'만'으로 내용을 전달하게 되면 문제가 되지만 음성, 음악 등을 텍스트와 같은 다른 수단과 함께 내용 전달 수단으로 활용하는 것은 사용자에게 다양한 선택권을 보장해 주는 것이기 때문에 오히려 정보 전달력을 높이게 된다.

색각 장애인을 위해서 이미지에 대체 텍스트를 제공하고 되도록 이미지로 내용을 전달하지 않아야 한다?

색각 장애인은 색상을 잘 구별하지 못하지만 이미지를 충분히 판독하고 이해할 수 있다. 이미지에 대한 대체 텍스트는 이미지를 활용하지 못하는 경우에 제공되는 정보이기 때문에 색각 장애인은 이 대체 텍스트에 접근을 하지 않는다. 색상'만'으로 정보를 제공하는 것은 문제가 되지만 대체 텍스트와 같은 텍스트 정보와 이미지를 같이 활용하는 것은 오히려 도움이 된다. 색각 장애인을 위해서는 색상외에 대체 텍스트가 아닌 이미지와 함께 판독이 가능한 텍스트 정보, 색상 외에도 구분할 수 있는 패턴이나 아이콘을 같이 제공하면 된다. 이렇게 색상에 비의존적으로 정보를 제공하게되면 색각 장애인 뿐만 아니라 흑백 인쇄와 같이 색상을 사용할 수 없는 환경에서도 도움이 된다.


출처 : http://hyeonseok.com/soojung/webstandards/2010/06/19/586.html

Posted by 1010
반응형

두서에서 밝힙니다. 이글은 웹 페이지에서 javascript 를 사용함에 있어 “Graceful Degradation” 과 “Progressive Enhancement” 에 대해서 말하고 있습니다. 이렇게 운을 떼는건 이 두가지가 오래전부터 회자되어 온 새로운게 아니란겁니다. 관련해서 잘 아시는 분은 읽지 않으셔도 됩니다 :)

Graceful Degradation

Graceful Degradation 은 “우아한 낮춤”, “적절한 퇴보”, “성능 저하” 등 여러가지로 번역되고 있습니다.

의미는, 먼저  최신의 기술 기반 혹은 최신의 기기에서 동작하는 기능을 만들고 나서 오래된 기술 기반 혹은 오래된 기기에서도 유사하게 (성능을 낮춰서라도) 동작하도록 조치하는것을 말합니다. 이런 의미에서 위의 번역들은 모두 틀리지 않다고 할 수 있겠습니다. (광의적으로 “성능 저하”도 틀리지는 않지만 웹 개발의 관점에서 보면 사용자 경험을 낮춰서라도 기능을 지원하는 것이기 때문에 “성능 저하”는 맞지 않을 것 같네요.)

※ 컴퓨터 시스템에서의 허용 오차 시스템(Fault tolerant system) 과 유사하다고 할 수 있습니다. 허용 오차 시스템은 프로그램 구동 환경에 문제가 생겨도 프로그램이 정지하지 않고 성능을 낮추면서 프로그램 구동을 지속하도록 하는 시스템을 말합니다.

우리가 잘 알고 있는 예를 들자면 IMG 태그에 alt 속성을 지정하는걸 들 수 있겠네요. 이미지를 표시하는 브라우저에서는 이쁘게 이미지를 표시하지만 텍스트 브라우저라든지 이미지를 렌더링하지 못하는 브라우저에서는 alt 속성에 부여한 대체 텍스트를 표시하도록 구성 합니다. (이 예에서는 성능을 낮춘다기보다 사용자 경험을 낮춰서라도 … 의 표현이 어울리겠네요. )

javascript 관점에서 Graceful Degradation 을 보자면 NOSCRIPT 라는 태그를 들 수 있습니다. 보통 NOSCRIPT 는 두가지 방식으로 사용됩니다.

NOSCRIPT (대체 콘텐츠 제공)

1.<script type="text/javascript" src="slide.js"></script>
2.<noscript>
3.<ul>
4.<li><a href="some url"><img src="1.jpg" alt="image 1"/></a></li>
5.<li><a href="some url"><img src="2.jpg" alt="image 2"/></a></li>
6.<li><a href="some url"><img src="3.jpg" alt="image 3"/></a></li>
7.</ul>
8.</noscript>

위의 예제에서 slide.js 는 서버로부터 이미지 3개를 받아 사용자 경험이 풍부한 스라이드 쇼를 만드는 스크립트입니다. (있다고 칩시다 –;) 바로뒤에 NOSCRIPT 태그를 사용해 스크립트가 동작하지 않는 브라우저에게 대신할 수 있는 콘텐츠를 제공하고 있습니다.

꽤 괜찮은 방법이라고 생각합니다. 자바스크립트가 구동되지 않는 환경에서도 콘텐츠에 접근할 수 있으며 IMG에 alt 를 제공하고 링크까지 걸려 있어 검색엔진도 좋아하는 형태입니다.

이 예에서의 문제는 NOSCRIPT 태그의 근본적인 문제입니다. 브라우저는 자바스크립트를 지원하지만 자바스크립트 파일을 통제하는 네트웍 환경 (보안상의 이유로?)에 있거나 자바스크립트를 다운로드 하지 못한 경우 사용자는 아무것도 볼 수 없게 됩니다.

NOSCRIPT (자바스크립트를 켜세요 ?)

1.<script type="text/javascript"  src="slide.js"></script>
2.<noscript>
3.<p>자바스크립트를 지원하는 브라우저를 사용하거나, 자바스크립트를 활성화 시켜 주세요</p>
4.</noscript>

우리는 사용자가 왜 자바스크립트가 동작하지 않는 브라우저를 사용하는지, 자바스크립트를 비활성화 시켰는지 알지 못합니다. 그런 상황에 사용자에게 “이래라, 저래라” 하는것은 좋은 방법이 아닙니다. 그리고 위 지시 문은 사용자가 자바스크립트가 뭔지 알고 있고, 자바스크립트를 활성화 시킬 수 있다고 가정하고 있습니다. 하지만, 태반의 사용자가 이런 사항 잘 알지 못합니다. 순전히 개발자의 관점에서 작성된 겁니다.

반성합니다 !! 저도 두번째 예제 코드를 무수히 만들었습니다. 변명을 하자면 KADO-WAH 초기 버전에서 (지금은 안 그렇죠?) SCRIPT 태그 이후에 NOSCRIPT 가 없는 경우 오류 (경고?)로 잡았었습니다. 그래서 기계적으로 SCRIPT 태그가 있는 곳에 NOSCRIPT (자바스크립트를 켜세요) 를 넣었고, 이게 또 HEAD 안에서는 P 태그 등이 올 수 없기 때문에 모든 SCRIPT/NOSCRIPT 태그를 BODY 밑으로 내리는 작업을 했습니다.  반성합니다.

Graceful Degradation 접근 방법

위의 두가지 예제를 보면 구현 모양은 다르지만 동일한  Graceful Degradation 접근 방법을 사용하고 있습니다.

자바스크립트로 slide.js 라는 사용자 경험 풍부한 기능을 먼저 만들고 자바스크립가 지원되지 않는 환경을 위해 NOSCRIPT를 이용해 추가(개선) 작업을 하고 있습니다. 이것이 뒤에서 말하려는 Progressive Enhancement 과 대조되는 부분입니다.

Progressive Enhancement

“점진적 향상” 이라고 번역할 수 있겠습니다.

말 그대로 기능을 점진적으로 향상시키는것을 말합니다. 바로 웹과 결부해서 말하자면

  1. HTML 로만 콘텐츠를 구성합니다. (CSS 를 지원하지 않는 브라우저에서도 접근 가능합니다.)
  2. CSS 로 시각적인 향상을 꾀합니다. (HTML은 이미 구성되었으므로 외부 CSS파일이 적당하겠네요.)
  3. Javascript 를 적용해 사용자 경험을 향상 시킵니다. (역시 외부 js 파일을 두고 이벤트를 가로채는 방법으로 사용해야겠네요)

1.2.3 의 순서로 점차적으로 향상 시키고 있습니다. 이렇게 구현했을 때 나중에 나온 기술(css, javascript)들이 지원되지 않더라도 콘텐츠에 접근하는데 무리가 없습니다. 추가로 얻을 수 있는 장점은 이 방법을 사용하면 밑에 예제를 봐도 알겠지만 구조, 표현, 동작이 자연스럽게 분리된다는 점입니다. 단계적으로 html, css, js 가 추가되기 때문입니다. (솔깃하네요.)

직접 Progressive Enhancement 방법을 사용해 구현을 해 보겟습니다. 예제로는 어느 사이트에나 있는 FAQ를 사용합니다.

1. HTML 구성

01.<h1>자주답변하는 질문들</h1>
02.<dl id="faq-list">
03.<dt>백업파일(*.bak)을 생성하지 않는 방법은?</dt>
04.<dd>'기본설정'->'파일' 에서 '저장시 백업 파일 생성' 옵션을 제거하시면 됩니다.</dd>
05.<dt>업그레이드 후 이전 설정이 없어진 경우.</dt>
06.<dd>이전 버전을 올바르게 설치하지 않은 경우 일어나는 현상입니다. 이런 경우에는 '도구'->'INI 파일 디렉토리' 메뉴 옵션을 실행해서 이전 설정 (*.ini 파일)이 있는 디렉토리를 지정해 주십시오.</dd>
07.<dt>사용자 정의 stx, acp, ctl 파일을 추가하는 방법은?</dt>
08.<dd>
09.<p>*.STX 파일과 *.ACP 파일을 설정하는 방법</p>
10.<ol>
11.<li>'기본설정' 대화상자에서 '설정 및 구문강조' 페이지를 선택합니다.</li>
12.<li>원하는 파일 종류를 선택하거나 원하는 파일 종류가 없는 경우 '추가' 버튼을 누른 후 '파일 확장자' 란에 파일 확장자를 지정합니다.</li>
13.<li>STX 와 ACP 파일의 완전한 경로를 '구문 파일' 과 '자동 완성' 란에 지정합니다.</li>
14.</ol>
15.</dd>
16.</dl>

위의 FAQ는 에디트플러스 사이트에서 가져왔습니다. :) FAQ 목록에 DL 을 사용하는게 적절한지는 논외로 하겠습니다. HTML 만 사용했을 때 질문과 답변을 구분할 수 있도록 하는 목록 태그로는 적절하다고 생각해서 사용했습니다.

HTML 적용된 화면

HTML만 적용된 화면

1단계에서 중용한 건 의미 있는 마크업을 논리적인 순서에 따라 구축하는게 중요하겠습니다. 물론, 이를 만족하기 위해서는 웹 표준을 준수하는게 우선되어야 할거고요.

하지만, 기획팀 으로부터 넘어온 스토리보드, 화면설계와 디자인팀으로 부터 잘려 넘어온 이미지들을 보고 있으면 당최 논리적으로 짜보기가 두려워 집니다. 뭐, 이런걸 잘 하는것도 우리의 능력중의 하나라고 봐야겠습니다. (전 멀었습니다.;;)

2. CSS 적용

1.body,dl,dt,dd {margin:0; padding:0;}
2.html {font-size:78%;}
3.html body {font-size: 1em;}
4.h1 {border:1px dotted #ccc; background:#eee; margin: 0 0 5px 0; padding:5px; font-size:1.4em;}
5.dt,dd {border-bottom:1px solid #ccc;line-height:1.5em;}
6.dt {background:black url(images/q.png) no-repeat left center; color:white; padding:7px 0 7px 25px; cursor:pointer;}
7.dd {background: #eee; padding:10px;}
CSS 까지 적용된 화면

CSS 까지 적용된 화면

네, 이쁘지 않은거 압니다. 제 미적감각이 이정도입니다. (사실, 최선을 다하지 않았다고 변명을 … ㅠㅠ)

3. javascript 적용

01.window.onload = function() {
02.var selectedQ = 0;
03.var dts = document.getElementsByTagName('dt');
04.var dds = document.getElementById('faq-list').getElementsByTagName('dd');
05.for(var i = 0, n = dts.length; i < n; i++) {
06.dds[i].style.display = (i == 0) ? 'block' : 'none';
07.dts[i].idx = i;
08.dts[i].onclick = function() {
09.if (selectedQ != this.idx) {
10.dds[selectedQ].style.display = 'none';
11.dds[this.idx].style.display = 'block';
12.selectedQ = this.idx;
13.}
14.};
15.}
16.};
javascript 까지 적용된 화면

javascript 까지 적용된 화면

네, 썩 좋지 않은거 압니다. (사실, 돌아가게 만드는게 우선이엇다는 변명을 … ㅠㅠ 정말 오랜만에 mootools 없이 javascript 만들어봤네요;;)

좋지 않은 예이긴 하지만, HTML → CSS → javascript 수순으로 점진적으로 향상시키고 있음을 주목해주세요.

위의 예는 faq.htm 에서 보실 수 있습니다. (ie8, firefox 에서만 테스트 되었습니다.)

정리

구현할 때 “누가 요새 javascript 안되는 브라우저를 쓰겠어?” 라는 생각이 들 정도로 자바스크립트는 대부분의 브라우저에서 지원하고 있는게 사실입니다. 사실, 그냥 막 써도 이로인한 불평(자바스크립트가 안되는 환경에서의 불평)도 들어본적이 없습니다. 아마, 사용자들 스스로가 감내하고 있겠지요. 하지만, 웹 접근성을 생각하고 이를 보장하고자 한다면 자바스크립트에 의존하는 코드를 사용해서는 안되겠습니다. href=”javascript:…” , href=”#” onclick=”" 이러 코드 때문에 얼마나 많은 시간을 웹 접근성 개선을 위해 허비했는지 … Progressive Enhancement  방법을 수행하는게 쉬운것만은 아닙니다. 아마 Graceful Degradation 과 비교해서 시간, 노력 등의 자원을 더 필요로 할겁니다. 기 구축된 사이트를 개선하는 작업에 있어서는 Progressive Enhancement 보다 Graceful Degradation 을 선택하는게 더 현명한 방법일지도 모르겠습니다. 하지만 잊지말아야 할 원칙 하나는 “Web for All”. 팀 버너스 리의

웹의 힘은 그것의 보편성에 있다. 장애에 구애없이 모든 사람이 접근할 수 있는 것이 필수적인 요소이다.
(The power of the Web is in its universality, Access by everyone regardless of disability is an essential aspect.)

라는 것이겠지요.


출처 : http://www.yangkun.pe.kr/post/874

Posted by 1010
반응형

제목에서는 “관련사이트 바로가기”라고 했지만 정확히 이를 뭐라고 부르고 있는지 모르겠습니다. “백문이불여일견” !! 바로 보시는게 낫겠습니다.

관련 사이트 바로가기

이런겁니다. 콤보박스가 나오고 목록에서 항목을 선택하면 새창으로 해당 사이트가 열립니다. 코드는 아래와 같습니다.

01.<!-- 코드 #1 -->
02.<p>
03.관련 사이트 바로가기
04.<select onchange="if(this.value != '') window.open(this.value);">
05.<option value="">관련사이트 목록</option>
06.<option value="http://twitter.com/yangkun7">양군의 트위터</option>
07.<option value="http://picasaweb.google.com/yangkun7">건이네 웹앨범</option>
08.<option value="http://www.greenfarm.pe.kr/">푸른농장 (준혁,서영이네)</option>
09.</select>
10.</p>

#1. 문제점

#1. 개선

  1. SELECT 양식에 대한 LABEL 추가
  2. onchange 이벤트 제거
  3. [이동] 버튼 추가
  4. [이동] 버튼 클릭 시 해당 사이트로 이동하도록 변경.

개선된 코드는 다음과 같습니다.

01.<!-- 코드 #2 -->
02.<p>
03.<label for="relSites">관련사이트 바로가기<label>
04.<select id="relSites">
05.<option value="">관련사이트목록</option>
06.<option value="http://twitter.com/yangkun7">양군의 트위터</option>
07.<option value="http://picasaweb.google.com/yangkun7">건이네 웹앨범</option>
08.<option value="http://www.greenfarm.pe.kr/">푸른농장 (준혁,서영이네)</option>
09.</select>
10.<input type="button" value="이동" onclick="window.open(document.getElementById('relSites').value);"/>
11.</p>

#2. 문제점

여전히 문제가 남아 있습니다.

#2. 개선

  1. SELECT 양식 값을 전달할 수 있도록 FORM 삽입
  2. SELECT 양식에 name 설정
  3. SELECT 양식 값을 전달할 수 있도록 일반 버튼을 submi 버튼으로 변경
  4. 전달받은 URL로 이동할 수 있는 서버측 로직 구성

개선된 코드

01.<!-- 코드 #3 -->
02.<p>
03.<form action="redirect.php" method="post" target="_blank">
04.<label for="relSites">관련사이트 바로가기<label>
05.<select id="relSites" name="url">
06.<option value="http://twitter.com/yangkun7">양군의 트위터</option>
07.<option value="http://picasaweb.google.com/yangkun7">건이네 웹앨범</option>
08.<option value="http://www.greenfarm.pe.kr/">푸른농장 (준혁,서영이네)</option>
09.</select>
10.<input type="submit" value="이동"/>
11.</form>
12.</p>

서버측 코드

1.<?php
2.// redirect.php
3.// 필요하면 카운팅 로직 추가
4.header('Location: ' . $_POST['url']);
5.?>

개선점

#3의 코드는 그대로 완벽한 것 같지만 개선의 여지가 남아 있습니다.

  • 코드대로 실행하면 선택한 사이트가 새창으로 열리게 되는데 이에 대해 알리는 부분이 없습니다. (KWCAG 1.0 항목 2.6 반응시간의 조절기능)
  • 자바스크립트가 사용 가능한 환경에서는 굳이 서버 로직을 거치지 않고 선택한 사이트로 이동하게 하고 싶을 수도 있습니다.

최종 개선 코드

01.<!-- 코드 #4 -->
02.<script type="text/javascript">
03.function gotoUrl(id) {
04.window.open(document.getElementById(id).value);
05.return false;
06.}
07.</script>
08.<p>
09.<form action="redirect.php" method="post" target="_blank" onsubmit="return gotoUrl('relSites');">
10.<label for="relSites">관련사이트 바로가기<label>
11.<select id="relSites" name="url">
12.<option value="http://twitter.com/yangkun7">양군의 트위터</option>
13.<option value="http://picasaweb.google.com/yangkun7">건이네 웹앨범</option>
14.<option value="http://www.greenfarm.pe.kr/">푸른농장 (준혁,서영이네)</option>
15.</select>
16.<input type="image" src="btn_move.gif" alt="이동 (새창)"/>
17.</form>
18.</p>

관련 지침

Posted by 1010
반응형

바로 이전 포스트(원숭이 엉덩이는…)에서 유트브 동영상을 삽입했는데.

HTML Validator 를 통과하지 못했습니다. (본 블로그는 xhtml, css 2.1 을 준수하고 있습니다. -_-a)

워드프레스에 유트브 영상 삽입시 HTML Validator 에러

유트브영상 삽입 하나로 9개의 HTML Validator 오류가 ...

유트브에서 제공한 코드는 다음과 같습니다.

1.<object width="425" height="344">
2.<param name="movie" value="http://www.youtube.com/v/39a1jzuqGGQ&;amp;amp;hl=ko_KR&amp;amp;fs=1&amp;amp;rel=0"></param>
3.<param name="allowFullScreen" value="true"></param>
4.<param name="allowscriptaccess" value="always"></param>
5.<embed src="http://www.youtube.com/v/39a1jzuqGGQ&;amp;amp;hl=ko_KR&amp;amp;fs=1&amp;amp;rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed>
6.</object>

웹 표준

위의 코드는 HTML4에서 폐기된 EMBED 태그를 사용하기 때문에 오류로 인식되며, 더불어 태그에 포함된 type, width, height 등의 모든 속성이 오류로 인식됩니다.

그렇다고 EMBED 태그를 제거하면 OBJECT 태그의 classid 와 몇몇 이유 때문에 파이어폭스에서는 재생되지 않는 문제가 생깁니다.

아래와 같은 태그를 사용하면 모든 브라우저에서 동일한 플래시 영상을 표시할 수 있습니다.

1.<object type="application/x-shockwave-flash" width="425" data="플래시 url" height="344">
2.<param name="movie" value="플래시 url" />
3.</object>

ie6,ie7,ie8,chrominum 4,firefox 3.5,opera 10,safai 4 에서 테스트했습니다.

웹 접근성

플래시는 웹 접근성 관련해서 아래와 같은 제약이 존재합니다.

  • 플래시 요소에 대체 텍스트 제공이 녹녹치 않습니다. accessibility 패널을 이용해 대체 텍스트와 탭 순서 제공이 가능하지만 이는 스테이지에 보이는 요소에 한정되며, 대부분의 네비게이션이 액션스크립트에의해 생성 되는걸 고려하면 좀 … 복잡해집니다.
  • IE외의 브라우저에서 키보드로 접근이 힘듭니다. (tabindex=”0″ 을 주는 편법이 있다고 하지만, 꽁수는 꽁수.)
  • 키보드 접근을 허용하려면 플래시의 윈도우 모드를 window로 설정해햐 합니다.
  • 플래시 플러그인이 없는 환경을 고려해 플래시 외부에 대체 텍스트를 제공해야 합니다.
  • 미래에는 어떨지 모르지만, 현재는 검색엔진이 플래시의 텍스트를 인덱싱 하지 못합니다. (혹시 있나요 ?)
  • 페이지가 확~ 무거워져요.

반드시, 플래시 사용을 해야 한다면 위의 사항들을 고려해야 합니다. (쉽지 않아요. ㅠㅠ)

그래서, 웹 접근성 까지 고려 한다면 플래시 자체의 접근성을 확보한 후 아래의 태그를 사용합니다.

1.<object type="application/x-shockwave-flash" width="425" data="플래시 url" height="344">
2.<param name="movie" value="플래시 url" />
3.<p>대체 텍스트... <a href="플래시 플레이어 얻기 링크"><img src="플래시 플레이어 얻기" width="" height="" alt=""/></a></p>
4.</object>

(워드 프레스에서는 위와 같이 작성한 경우 P 태그를 임의로 조정해 <p></object></p> 와 같은 코드를 만들어버리네요 ㅠㅠ)

하지만,  위의 경우 IE에서 플래시 무비가 모두 다운로드 되기 전까지 플래시 플레이어가 보이지 않습니다. 이러한 문제를 피하기 위해서는 힉시 메소드를 사용하거나 A List Apart 에서 제안하는 Flash Satay 방법을 사용합니다.

힉시 메소드 (IE 의 condition comment 를 이용합니다.)

01.<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="160" height="75" id="a" align="middle">
02.<param name="movie" value="플래시 url" />
03.<!-- Hixie method -->
04.<!--[if !IE]> < -->
05.</object><object type="application/x-shockwave-flash" data="플래시 url" width="160" height="75">
06.<param name="movie" value="플래시 url" />
07.<p>대체 텍스트</p>
08.</object>
09.<!--> < ![endif]-->

이 방법은 정보화진흥원에 접근성 교육 받으면서 성민장군님에게서 들었습니다. (꾸벅 ^^)

Flash Satay (container.swf 에서 원래의 플래시 무비를 loadMovie를 이용해 로드 합니다.)

1.<object type="application/x-shockwave-flash data="container.swf?path=플래시 url"  width="400" height="300">
2.<param name="movie" value="container.swf?path=플래시 url" />
3.<p>대체 텍스트</p>
4.</object>

Flash Satay 방법의 경우 URL이 꼬이는 수가 있습니다. 결국, 가장 쉬운 방법은 코드를 더 적어야 하긴 하지만 힉시 메소드가 아직은 제일 낫네요.

생각해보세요 위의 플래시가 사이트의 주요 네비게이션 용도로 사용된다면 플래시 자체 접근성도 확보해야 하고 “대체 텍스트”부분에 ul, li 등을 이용해 동일한 네비게이션을 제공해야 합니다. 것 보세요, 쉽지 않죠. 플래시는 자제, 자제…


출처 : http://www.yangkun.pe.kr/post/773

Posted by 1010
02.Oracle/DataBase2010. 12. 10. 13:30
반응형

SELECT 컬럼,
        LTRIM(sys_connect_by_path(컬럼,','),',') AS 컬럼명
FROM  (
        SELECT 컬럼,
        menurole_id,
        row_number() OVER (partition by 그룹컬럼 order by 정렬할컬럼) rn,
        COUNT (*) OVER (partition by 그룹컬럼 ) cnt
        FROM 테이블명
)
WHERE level = cnt
start with rn = 1
connect by prior 그룹컬럼 = 그룹컬럼 and prior rn = rn-1


====================================================================

 세로로 출력되는 값들이 가로로 출력되는걸 확인할수 있다.


컬럼1  
컬럼2

1                  나

2                  너

1                  우리

1                  개똥이


컬럼1       컬럼2

1                 나,우리,개똥이

2                 너


출처 : http://kinjsp.pe.kr/board/board_view.kin?boardId=721.0&boardTypeCode=1072&fastFlag=true&numberPerPage=10&pageNumber=0

출처 : http://kinjsp.pe.kr/board/board_view.kin?boardId=721.0&boardTypeCode=1072&fastFlag=true&numberPerPage=10&pageNumber=0

Posted by 1010
61.Linux2010. 12. 9. 13:29
반응형

[tmax@test ~]$ ./jeus50-unix-vm.bin

Preparing to install...

Extracting the JRE from the installer archive...

Unpacking the JRE...

Extracting the installation resources from the installer archive...

Configuring the installer for this system's environment...


Launching installer...


Preparing CONSOLE Mode Installation...


===============================================================================

JEUS5.0                           (created with InstallAnywhere by Macrovision)

-------------------------------------------------------------------------------





===============================================================================

Introduction

------------


InstallAnywhere will guide you through the installation of JEUS5.0.


It is strongly recommended that you quit all programs before continuing with

this installation.


Respond to each prompt to proceed to the next step in the installation.  If you

want to change something on a previous step, type 'back'.


You may cancel this installation at any time by typing 'quit'.


PRESS <ENTER> TO CONTINUE:




===============================================================================

License Agreement

-----------------


Installation and use of JEUS5.0 requires acceptance of the following License

Agreement:


JEUS (Java Enterprise User Solution) Release JEUS5.x

TmaxSoft Co., Ltd. (hereafter, TmaxSoft) End-User License Agreement


Product : JEUS


This is a legal agreement between you (either an individual or an company) and

TmaxSoft, Incorporated.  By opening the sealed software package and/or by using

the software, you agree to be bound by the terms of this agreement.


TmaxSoft License

1.  Grant of License:  This TmaxSoft License Agreement ("License") permits you

to use one copy of the TmaxSoft product JEUS, on any single computer, provided

the software is in use on only one computer at any one time.  If this package

is a license pack, you may make and use additional copies of the software up to

the number of licensed copies authorized.  If you have multiple licenses for

the software, then at any time you may have as many copies of the software in

use as you have licenses.

  The software is "in use" on a computer when it is loaded into the temporary

memory (i.e., RAM) or installed into the permanent memory (e.g., hard disk,

CD-ROM, or other storage devices) of that computer, except that a copy

installed on a network server for the sole purpose of distribution to other

computers is not "in use".  If the anticipated number of users of the software

will exceed the number of applicable licenses, then you must have a reasonable


PRESS <ENTER> TO CONTINUE:


mechanism or process in place to ensure that the number of persons using the

software concurrently does not exceed the number of licenses.


2.  Copyright:  The software (including any images, "applets," photographs,

animations, video, audio, music and text incorporated into the software) is

owned by TmaxSoft or its suppliers and international treaty provisions.

Therefore, you must treat the software like any other copyrighted materials

(e.g., a book or musical recording) except that you may either (a) make one

copy of the software solely for backup or archival purposes, or (b) transfer

the software to a single hard disk provided you keep the original solely for

backup or archival purposes.  You may not copy the printed materials

accompanying the software, nor print copies of any user documentation provided

in "online" or electronic form.


3.  Other restrictions: This license is your proof of license to exercise the

rights granted herein and must be retained by you. You may not rent, lease, or

transfer your rights under this license on a permanent basis provided you

transfer this license, the software, and all accompanying printed materials,

retain no copies, and the recipient agrees to the terms of this license. You

may not reverse engine, decompile, or disassemble the software, except to the

extent that the foregoing restriction is expressly prohibited by applicable

law.


PRESS <ENTER> TO CONTINUE:




DO YOU ACCEPT THE TERMS OF THIS LICENSE AGREEMENT? (Y/N): Y




===============================================================================

Choose Platform

---------------


Choose current system ( OS - architectures  )

1)HP-UX PA-RISC

2)Solaris Ultra-Sparc

3)AIX Power PC

4)Linux i386

Quit) Quit Installer


Choose Current System (DEFAULT: 4): 4





===============================================================================

Choose Install Folder

---------------------


Where would you like to install?


  Default Install Folder: /home/tmax/jeus5


ENTER AN ABSOLUTE PATH, OR PRESS <ENTER> TO ACCEPT THE DEFAULT

      :




===============================================================================

Choose Install Set

------------------


Please choose the Install Set to be installed by this installer.


  ->1- Full Install

    2- Typical


ENTER THE NUMBER FOR THE INSTALL SET, OR PRESS <ENTER> TO ACCEPT THE DEFAULT

   : 1




===============================================================================

Choose JDK Folder

-----------------


Please Choose a Folder:


Input User JDK Folder (DEFAULT: /usr):





===============================================================================

Password Input

--------------


Enter the Password for the administrator account.

This password will be registered in JEUS as the first user.


Input Password::


--------------------------

 This is a Wrong Password

--------------------------

    Above 7 characters


    a ~ z  A ~ Z  0 ~ 9


Input Password::


Corfirm Password::




===============================================================================

Pre-Installation Summary

------------------------


Please Review the Following Before Continuing:


Product Name:

    JEUS5.0


Install Folder:

    /home/tmax/jeus5


Install Set

    Full Install


Disk Space Information (for Installation Target):

    Required:  253,712,462 bytes

    Available: 94,800,117,760 bytes


PRESS <ENTER> TO CONTINUE:




===============================================================================

Installing...

-------------


 [==================|==================|==================|==================]

 [------------------|------------------|------------------|------------------]




===============================================================================

Installation Complete

---------------------


Congratulations! JEUS5.0 has been successfully installed to:


/home/tmax/jeus5


Press Done to quit the installer.


PRESS <ENTER> TO EXIT THE INSTALLER:

[tmax@test ~]$

Posted by 1010
01.JAVA/Java2010. 12. 6. 17:19
반응형
 
낮에 파일에서 읽은 인코딩과 데이터베이스로 보내는 스트림의 인코딩이 달라서 고생을 좀 했습니다. 편법을 쓰면 금방 해결하는 일이었지만, 쩝... 오기가 생긴다거나 제대로 해야겠다는 것도 아니지만...^^; (어차피 모든 것을 다 알수는 없으니까요)
아무튼, 그동안 뒷전으로 미루었던 것들에 대해 처음부터 다시 한다는 기분으로 살펴보는 것도 좋겠다 싶더군요. 그래서 일단 코드를 짜봅니다.
먼저 파일 네 개를 인코딩이 전부 다르게 해서 만들었습니다. 데이터는 별 뜻 없이 넣었구요.
one, two, three and to the four
1234%^&*(||+)
거추장스러운 허식을 벗고 나면 무엇이 남을까?
난로는 그냥 쓰시고, 동아일보는 봄부터
영어, 숫자, 특수문자와 한글을 혼용해서 넣었는데, 생각해보니 ISO8859-1에 한글을 넣은 것은 좀 어처구니 없는 것 같기도 하지만, 일단 똑같이 데이터를 넣고 아래 코드를 실행해 봤습니다.
// 1. ms949, ISO-8859-1, UTF-8, UTF-16 포맷의 파일에서 읽은 문자열의 길이 비교

// 1.1 각 파일 데이터를 String으로 읽기
String files[] = {"ms949.txt", "iso8859-1.txt", "utf-8.txt", "utf-16.txt"};
String data[] = new String[4];

for(int i = 0; i < files.length; i++){
  BufferedReader reader = new BufferedReader(
    new FileReader(files[i]));
 
  data[i] = ""; // 초기화 하지 않으면, 반복문에서 null 이라는 글자가 추가됨
  String line;
  while((line = reader.readLine()) != null)
   data[i] += line + "/n";
 
  reader.close();
}

// 1.2 String 데이터 길이 비교
for(int i = 0; i < data.length; i++){
  System.out.println(files[i] + " " + data[i].length());
  System.out.println(data[i]);
}
일단 콘솔 출력은 다음과 같이 나옵니다. 유니코드는 엽기로 출력됩니다. 브라우저에서 많이들 보셨겠지만 ^^;
유심히 살펴볼 것은 String으로 읽은 값의 길이가 다르다는 점입니다.
ms949.txt 98
one, two, three and to the four/n1234%^&*(||+)/n거추장스러운 허식을 벗고 나면 무엇이 남을까?/n난로는 그냥 쓰시고, 동아일보는 봄부터/n
iso8859-1.txt 98
one, two, three and to the four/n1234%^&*(||+)/n?????? ??? ?? ?? ??? ????/n??? ?? ???, ????? ???/n
utf-8.txt 127
one, two, three and to the four/n1234%^&*(||+)/n嫄곗텛?옣?뒪?윭?슫 ?뿀?떇?쓣 踰쀪퀬 ?굹硫? 臾댁뾿?씠 ?궓?쓣源??/n?궃濡쒕뒗 洹몃깷 ?벐?떆怨?, ?룞?븘?씪蹂대뒗 遊꾨???꽣/n
utf-16.txt 178
??
인코딩 String 길이 파일 크기(bytes)
MS949 98 131
ISO-8859-1 98 96
UTF-8 127 166
UTF-16 178 194

글쎄요.. 인코딩에 대해서 본격적으로 다뤄보기 전에 일단 문제의 발단부터 해결까지의 과정을 먼저 말씀드려야겠네요. 우선, 4메가(4351960) 정도 되는 우편번호 데이터를 넣는 sql 파일이 있습니다. MS949로 인코딩된 sql 파일을 그대로 실행시켜서, 데이터베이스에 넣었더니, 자바 클래스에서 비교를 하니까 다 깨지더군요. 그래서 아예 파일 자체를 UTF-8로 갖고 있으면 편하겠다 싶었죠. ^^;
그래서, MS949 파일의 데이터(insert 문장을 길게 나열한 것이죠.)를 BufferedReader.readLine()올 한 줄씩 읽어서 DataOutputStream.writeUTF() 로 쐈더니만 모든 행의 첫 줄에 입력하지 않는 글자들이 하나씩 붙더군요. ㅡㅡ;  API를 다시 보니, 일반, UTF-8이 아니라 modified UTF-8를 썼습니다. 그냥 간단히 살펴보면
'u0001'과 'u007F' 사이의 문자는 다음과 같이 한 바이트로 나타내고,
Bit Values
Byte 1
0
bits 6-0
널 문자인 'u0000'와 'u0080'과 'u07FF' 사이의 문자는 두 바이트로
Bit Values
Byte 1
1
1
0
bits 10-6
Byte 2
1
0
bits 5-0
'u0800'과 'uFFFF' 사이의 문자는 세 바이트로 나타낸다고 합니다.
Bit Values
Byte 1
1
1
1
0
bits 15-12
Byte 2
1
0
bits 11-6
Byte 3
1
0
bits 5-0
여하튼, UTF-8의 결함을 보완하기 위해서 수정한 것이라지만, 앞에 붙는 문자들로 인해서 ANT의 sql 태스크가 실행되지 않았습니다. DBMS에서 SQL 실행이 안되는 것이죠. insert 앞에 이상한 것들이 붙어 있으니까.. 문법 오류가 나죠. ^^;
문제를 어떻게 해결하느냐?
먼저 떠오르는 것은 이클립스를 이용한 꽁수였습니다. MS949 파일을 열어서 CTRL+A 하고 나서 CTRL+C 하고, 다시 UTF-8 포맷의 파일을 열어서 CTRL+V 하는 방법이죠. ^^;
두번째는 일일이 바이트를 인코딩해주는 것입니다. 호기심 좀 채우자고, RFC 같은 것을 읽을 수는 없으니까(너무 길더군요) URLEncoder의 encode() 메소드 소스를 보면 할 수 있을 것 같더군요. 뭐, 시간이 되면... 톰캣에 있는 encode() 메소드 구현이나 java.net.URLEncoder의 구현을 보고, 글을 올리겠습니다. 장담은 못하구요. ^^;
마무리는 꽁수가 아니라, JDK 5.0으로 해결한 이야기를 해드리죠.

꽁수를 써서 처음엔 해결하려고 해도 자꾸 이클립스에서 Heap이 넘쳐나는 에러가 나더군요. ^^;
그래서 다른 방법을 찾았는데 나중에 이 문제는 다시 부딪히게 되었습니다. 그건 나중에 이야기하구요. JDK5.0에서는 PrintWriter 클래스의 생성자 중에서 File과 인코딩 방식을 인자로 넣어주는 것이 있더군요. 그것을 사용하니까 잘 되었습니다. 너무 쉽게...^^; (테스트 코드를 첨부합니다.)
public void testReadByUTF8() throws Exception{
      
       BufferedReader reader = new BufferedReader(
               new FileReader("ref/zipcode_mysql6.sql"));
      
       PrintWriter writer = new PrintWriter(
               new File("setup/zipcode-mysql-6-data.sql"), "UTF-8");

      
       String line;
       while ((line = reader.readLine()) != null) {
           System.out.println(line);
           writer.println(line);
       }      
      
       reader.close();
       writer.close();
  }
프로그램이 잘 실행되었습니다. sysout(System.out.print) 한 내용도 문제 없이 출력되었구요. 파일에 데이터도 잘 복사되었습니다. Ant에서 sql 태스크를 수행하는데 잘 되다가 돌연 에러가 났습니다. 에러가 난 지점을 확인해보니.. 출력이 되다가 말았더군요. ㅡㅡ;
하필, 47,706건 중에서 100개도 안남긴 충북 청원군 부용면 부강8리의 데이터 삽입 구문이 출력되다 말았습니다. 정확히, 4,874,058  bytes 에서 문제가 발생한거죠. TaskMgr(작업 관리자)의 메모리 확인해보고, VM 옵션으로 힙(Heap) 크기를 크게 줘봐도 변화가 없었습니다. ㅡㅡ;
에러가 나는데, .log는 도대체 어디 있는건지. 탐색기에서 검색으로 찾아봤더니 이클립스의 로그 파일인 .log는 프로젝트 디렉토리 아래의 .metadata 디렉토리에 있었습니다.
java.lang.OutOfMemoryError: Java heap space
이걸로 어쩌란 것인지..ㅡㅡ;
결국은 이클립스 뉴스리스트를 보고 알았습니다. 이클립스에서 프로그램을 실행할 때 VM 옵션을 수정해줘봐야 소용이 없고, eclipse를 실행할 때 VM 옵션을 수정해야 합니다. 이클립스가 실행될 때의 디폴트 힙 사이즈는 128MB인 모양입니다.(정확하지는 않습니다. ^^;)
그런데, 플러긴까지 설치하면 금방 넘어가겠죠. 그랬던 것입니다. 고작 5MB도 안되는 파일을 읽다가 멈췄다기 보다, 딱 4MB 정도 남았는데 제가 파일을 로딩하면서 힙 사이즈를 초과한 것이라고 추측이 됩니다. 처음에 말했던 꽁수를 부릴 때도 에러가 난 것도 마찬가지 이유죠.
eclipse.exe -vm <자바 설치 디렉토리>jrebinjavaw.exe -vmargs -Xmx512M
위와 같이 실행했더니 무사히 앞서 했던 작업을 마칠 수 있었습니다. 이클립스 역시 VM 상에서 돌아간다는 것을 깜빡했던거죠. 이클립스가 수행되는 VM의 힙 사이즈를 늘려서 문제는 해결되었습니다.
저처럼 평소에 더블클릭으로만 이클립스를 띄웠던 분들은 참조하세요.
eclipse [platform options] [-vmargs [Java VM arguments]]
-vmargs args VM에 옵션을 전달하고자 할 때

닫기
퍼온 글

출처: http://blog.naver.com/inking007/120001550360
문자 코드 변환 과정

1. 컴파일
컴파일시 원시파일의 문자열을 현재 환경(한글환경 KSC5601)의 locale로 인코딩하여 읽어 들인후
유니코드에서의 대응하는 코드값으로 변환한다.
원시파일에서  '한글' 이라는 문자열은 다음과 같은 hex code를 가지고 있다. (KSC5601)
c7 d1 b1 db
2.실제 .class 파일로 저장시 UTF-8로 변환하여 저장한다.
예를 들어 다음과 같은 코드 작성후
컴파일하면
public class Test {

   public static void main(String args[]) {
        String str = "한글";
       System.out.println(str);
  }

}
클래스파일 Test.class에 '한글' 이라는 문자열은 다음과 같이 인코딩되어 저장된다.
ED 95 9C EA B8 80
디컴파일 하면 다음과 같이 변환된다. UTF-8 --> UTF-16
        String s = "uD55CuAE00";
uD55C  '한'
uAE00  '글'
아래 URL을 클릭하면 해당 Unicode가 나타내는 문자및 UTF-8코드를 볼수 있다.
3. 실행시 문자열을 UTF-16으로 변환하여 로드하고 화면에 출력시 디폴트 인코딩으로 출력한다.





닫기
인코딩에 따라 글자수 인식이 달라진다.
인코딩이 되지 않고 한글이 입력될 때는(ISO8859-1로 추정)
'이호'라고 입력하면 6자로 인식된다.
한글 한 글자를 3자로 인식하는 것이다.
'육봉달'이라고 입력하면 9.. 받침하고 상관없다.
'튕뽃뾳'이라고 해도.. 9
이번엔 인코딩을 해본다.
먼저 UTF-8
위의 세 개의 문자를 대상으로 테스트하면
2, 3, 3 글자.. 역시 받침에 상관없고
한글 한 글자를 영문 알파벳 하나와 동일하게 인식한다.
퉧뾳뾳, 111, aaa, %%%
위의 입력 모두 동일하게 3글자로 인식한다.
EUC-KR은 다를까?
위의 네개 문자 조합을 입력하면 나머지 셋은 3 글자로 인식하지만
한글인 '퉧뾳뾳'은 2글자로 인식하여 모두 6글자가 된다.
이것만 보고 EUC-KR은 한글을 두 자로 인식하구나 일반화하는 것은 위험한 일이었다.
다음과 같은 기이한 현상을 보인다.
퉧뾳뾳 : 제대로 인코딩을 못하며 24글자로 인식한다.(&#53863;&#49075;&#49075;)
이쁋: 역시 두 번째 글자는 인식을 제대로 못해 9자가된다.(이&#49227;)
제대로 인식하지 못하는 글자에 대해서는 8글자가 배당됨을 알 수 있다.
EUC-KR을 관행처럼 쓰고 있는 가운데
UTF-8을 쓰자할 때 뭐가 장점이냐 물으면.. 사실 떠오르는 것이 별로 없었는데
한가지 예로 들 수 있는 현상이다.
UTF-8에선.. 뾳뛕쁋쿅푝 같은 외계어같은 문자마저 올바로 5글자로 인식한다는 면에서 EUC-KR보다 실용성이 높다고 할 수는 없지만 신뢰성이 높은 것은 분명한 것이다.


출처 : http://blog.iampro.net/2368
Posted by 1010
반응형
Posted by 1010
98..Etc/Doku wiki2010. 11. 30. 17:59
반응형

USB메모리에 위키가 쏘옥~, Dokuwiki on a stick

일정관리나 할일 목록을 관리하기 위해서 사람들마다 참 다양한 자신만의 방법이 있죠. 종이재질의 전통적인 다이어리를 고집하는 분들도 있고 스마트폰, 웹서비스, 각종 프로그램 등…

저도 이것 저것 시도는 많이 해보는데요, 회의나 약속은 아웃룩, 자료관리는 My Notes Keeper(디렉토리 구조로 문서를 엮을 수 있고 하이퍼링크 할수 있는 일종의 강화된 메모장프로그램), 할일은 tadalist.com, 간단한 메모는 stickypad…

이렇게 나눠놓으니 정신이 하나도 없더군요. 약속이야 메일로 받으면 바로 일정에 써 넣으니 아웃룩이 제격인것 같지만 다양한 문서들, 아이디어, 자료등은 일관된 문서로 보관해야 할 필요가 있겠더라구요. 두말할 필요없이 제가 선택한건 위키인데요. 보안상 외부서버에 설치할 수는 없고 개인PC에 설치해야 하는데 그러기 위해서는 웹서버, 디비, PHP등을 미리 설치하고 구동해야 하는 번거로움이 있지요. 또 다른 PC에서는 접근이 불가능하다는 단점도 있구요.

예전 팀에서 팀내 일정,자료 공유를 위해 위키를 사용했습니다. 그때 DokuWiki를 썼는데 꽤 편했던 기억이 있습니다. 다시 한번 시도해볼까, 하고 도쿠위키 사이트에 가서 이리저리 찾아보니 DokuWiki on a Stick이란게 있더군요. USB메모리안에 쓸어담기만 하면 바로 위키를 사용할 수 있게 만들어 놓은 것입니다. 압축된 파일을 받아서 USB메모리안에 풀어놓으니 10메가바이트가 채 안되었습니다. exe파일로 된 데몬을 실행하고 나서 웹브라우저로 내 PC로 접속하니 아주 손쉽게 위키가 떴습니다. ^_^


출처 : http://hof.pe.kr/wp/archives/2913/

Posted by 1010
98..Etc/Doku wiki2010. 11. 30. 17:58
반응형

호스팅서버에서 DokuWiki 사용

DokuWiki를 임대한 웹 공간에 설치하고 싶다면 이 문서는 약간 도움이 될 지도 모릅니다.

이 형태의 설치 과정스크린샷을 보도록 합니다.

설치

먼저 기본 설치과정을 실행합니다:

  • 하드디스크에 타르볼(tar.gz)파일을 압축 해제합니다.
  • dokuwiki-YYYY-MM-DD디렉토리의 모든 파일을 웹 공간에 업로드합니다.
  • 브라우저로 http://www.yourserver.com/path/to/dokuwiki/install.php을 열도록 합니다.
  • 접근 권한 에러를 보게 되면, 권한 문제를 먼저 해결해야 합니다.

문제 해결

만일 chmod를 통해 접근 권한(permission)문제들을 해결했다면, 대부분 웹서버가 파일을 업로드하는데 사용되는 계정이 아닌 다른 계정으로 웹서버가 실행되기 때문입니다. 만일 그룹 역시 다르다면 DokuWiki가 실행되는 웹서버는 일반(FTP)사용자로 지울 수 없는 파일들을 생성하게 됩니다. 이 문제를 피하기 위해서 fmode옵션과 dmode옵션의 설정을 변경하여 접근권한을 느슨하게 할 필요가 있습니다. 이미 잘못된 권한으로 파일들을 설정한 경우 호스팅 제공자에게 도움을 부탁하거나 fixperms.php 스크립트를 실행하여 이 문제를 해결합니다.
하위디렉토리를 포함해 전체 디렉토리를 변경하기 위해서, FileZilla 버전3 (2006년 11월에도 아직 베타 상태지만 이 작업은 잘해냅니다.)은 파일과 디렉토리에 대해 '재귀적인(recursive)' 권한 설정을 지원합니다.

E-mail 문제

어떤 호스팅 제공자들은 서버에서 sendmail을 지원하지 않습니다. 대신 인증이 필요한 SMTP가 동작하는 별개의 기계를 제공합니다. 이 시나리오에서는 새로운 사용자 등록시 패스워드를 받도록 하는 것을 포함해서 여러 DokuWiki 이메일 기능들은 동작되지 않습니다.

2006-11-06 버전에서 인증에 상관없이 외부 SMTP 호스트를 사용하도록 설정하는 비공식적인, 지원되지 않는 패치가 있습니다. 자세한 정보를 보려면 여기를 참고하시기 바랍니다..

Strato 호스팅 문제 해결

'Powerweb A' Strato 서버에 일반적인 과정으로 설치를 한다면 스크릿샷에서 보여준 것처럼 설정하더라도 보안에 관련하여 ‘500 Internal Server Error’ 에러 페이지를 보게 됩니다. 발생하는 인증 문제를 users.auth.php파일에서 사용자를 수동으로 추가해서 우회하는 방법은 개선되지 않는 것처럼 보입니다.(사용자가 존재하지 않는다는 경고를 금지합니다.) 심지어 md5 해쉬를 사용한 인증(ACL)을 추가하는 경우에도 이 에러가 발생합니다. 이 문제는 다른 사용자가 있다면 local.php 파일의 passscrpyt 옵션을 “smd5”에서 “md5”로 변경하면 해결됩니다.

번역

english version: dokuwiki-2006-11-06.

Add your email here if you created translated or modified whole or part of this page.

Posted by 1010
98..Etc/Doku wiki2010. 11. 30. 17:57
반응형

DokuWiki 설명서

이 설명서는 DokuWiki를 사용하면서 발생하는 모든 일반적인 질문들에 대한 보충자료가 되는 것을 목적으로 하고 있습니다. 일반적인 사용자부터 위키 설치와 환경 설정을 원하는 관리자에 이르기까지 유용한 정보를 포함하고 있습니다. 페이지들은 각 항목에 대한 일반적인 설명부터 시작해서 사용하는 방법, 그 후 설정하는 방법으로 진행됩니다.

  1. DokuWiki 사용
    1. DokuWiki페이지들을 통해 살펴보기
    2. 미디어 파일
    3. 비익명 위키
  2. DokuWiki 설치
  3. DokuWiki 관리
  4. 필요성에 따른 변경
  5. 좀 더? — 더이상의 자세한 설명은 생략한다..끝

    출처 : http://www.dokuwiki.org/ko:manual
Posted by 1010
98..Etc/jspwiki2010. 11. 30. 17:39
반응형
JSPWiki를 처음 설치하면 주요 페이지의 메뉴들이 영어로 되어 있으며, Use Preference에서 language를 변경하면 그에 따른 언어로 메뉴들의 문자가 바낀다.

한글은 기본적으로 제공하지 않으므로 추가해야한다. 과정은 다음과 같다.

1. http://www.jspwiki.org/wiki/JSPWikiDownload 에서  JSPWiki 2.8.1 의 소스파일을 다운로드 한다.

2. 압축을 풀면 /doc/Translating.txt 파일이 있는데 이 과정을 따라하면 된다.

3.  % ant i18n-create-template   를 수행하면 추가할 언어코드를 입력하라고 한다.
ko라고 입력하면 i18n_templates/JSPWiki_ko 라는 디렉토리가 생성되며 그 안에는 아래의 파일이 만들어 진다.

etc/i18n/CoreResources_ko.properties
etc/i18n/plugin/PluginResources_ko.properties
etc/i18n/templates/default_ko.properties

4. 프러퍼티 파일을 펴보면 여러가지 항목에 해당하는 메시지들을 정의할 수 있는데, 그것을 한글로 변경하고 저장한다.
attach.tab=첨부
attach.list=첨부파일 목록

5. 위의 저장된 파일을 그대로 사용하는 것이 아니라 ASCII파일로 변환을 해야한다.
$JAVA_HOME\bin\native2ascii 를 사용한다.

6. 변환된 파일을  JSPWiki 2.8.1 의 소스 디렉토리\etc\i18n\이하에 위치 시킨다.

7. % ant war 를 수행한다.
이때 소스데릭토리 아래 build 디렉토리가 없으면 빌드가 실패하므로 미리 만들어준다.
빌드가 성공하면 build/JSPWiki.jar 파일이 생성된다.

8. 생성된 JSPWiki.jar를 웬컨테이너에 위치한 우리의 wiki\WEB-INF\lib에 넣는다. 기존에 있던 JSPWiki.jar는 지운다.

9. 서버를 내렸다 올리면 User Preference의 language 목록에 한국어가 추가되어 있으며, 이를 선택하여 저장하면
첨부 탭의 탬이름이 한글로 보인다. *.* 이와 같은 방법으로 하나씩 수정해나간다~


User Preference 페이지에서 language목록은 프러퍼티파일을 읽어 _ko _es 를 읽어 Locale 객체를 생성하여 가져오더라...


출처 : http://burningjade.tistory.com/entry/JSPWiki-%ED%95%9C%EA%B8%80-Language-%EC%B6%94%EA%B0%80
Posted by 1010
98..Etc/jspwiki2010. 11. 30. 16:08
반응형
프로젝트에서 의사소통 및 문서정리를 위해서 wiki를 하나 설치하기로 했다. Trac같이 이슈트랙킹과 wiki가 같이 제공하는 것도 좋지만 이슈 트랙킹은 다른 것으로 하기에 간단한 걸 하나 구하기로 했다.
내가 쓰는 언어가 Java이니 문제 발생할 경우 그래도 복구가 가능한 java 기반의 JSPWiki를 선택했다.

1. Requirements
다음과 같은 시스템 요구사항을 가진다. 일반적인 것이기 때문에 다음 소프트웨어의 설치는 생략한다.

  • Java 1.4 이상
  • tomcat 5.5 이상


2. 다운로드
다음 URL에서 JSPWiki를 다운받는다.
http://www.jspwiki.org/wiki/JSPWikiDownload
여기서 설치할 버전은 2.6.4를 기준으로 한다.

3. 설치
Zip 파일을 풀면 JSPWiki.war 파일이 있다. tomcat을 구동하고 webapps 밑에 복사한다.
여기 경우는 context root를 단순히 wiki로 하기 위해 wiki.war로 이름을 바꾸어서 webapps 밑에 넣었다.
tomcat 이 war를 풀면서 다음과 같은 에러가 발생한다. 일단 무시하고 tomcat을 내린다.

java.lang.NullPointerException
        at com.ecyrd.jspwiki.util.WatchDog.enterState(WatchDog.java:231)
        at com.ecyrd.jspwiki.search.LuceneSearchProvider$LuceneUpdater.backgroundTask(LuceneSearchProvider.java:711)
        at com.ecyrd.jspwiki.util.WikiBackgroundThread.run(WikiBackgroundThread.java:135)

이제 war가 풀린 디렉토리에서 작업할 것이다. webapps/wiki.war는 삭제하고 풀린 webapps/wiki 만 남긴다. 주의할 점은 tomcat이 기동된 상태에서 wiki.war를 삭제하면 wiki 디렉토리도 삭제된다는 점이다.

4. WEB-INF/jspwiki.properties 수정
각종 wiki 데이터가 생성될 디렉토리가 필요하다. 여기서는 /home/wikidata 디렉토리 생성했다.

그리고 tomcat의 webapps/wiki 디렉토리 밑의 WEB-INF/jspwiki.properties 중 다음 내용을 수정한다.

...
jspwiki.fileSystemProvider.pageDir = /home/wikidata/
...
jspwiki.basicAttachmentProvider.storageDir = /home/wikidata/
...
log4j.appender.FileLog.File = /home/wikidata/jspwiki.log
...

그리고 다시 tomcat 시작...

5. 설치확인
다음 URL로 접속하여 설치사항을 확인한다. 물론 ip와 port는 자신의 환경에 맞게...
URL: http://localhost:8080/wiki/Install.jsp
처음에는 관리계정이 없다. 각각 설정을 확인하고 "Configure" 버튼을 클릭한다. 그러면 설치가 되고 admin 계정이 생성되고 임의적으로 생성된 Password를 알려준다.
생성된 admin 암호는 "Configure" 클릭한 다음 화면에 나타나니 화면이동 말고 잘 적어둔다.

6. 접속
다음 URL에 접속하고 admin으로 로그인해서 이것 저것 살펴본다. ^^
http://localhost:8080/wiki/

출처 : http://greatkim91.tistory.com/entry/JSPWiki-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0

Posted by 1010
98..Etc/Trac2010. 11. 30. 16:05
반응형
Trac을 설치했다. 포스팅 안 하고 그냥 넘어가려 했는데.. 이거.. 너무 어렵고 복잡해서.. 다시 설치하려면 고생할 것 같아 대충이라도 적어놔야겠다. 게다가 윈도우에서 설치하는 방법은 그다지 없고, 설치 방법 마다 설치하는 내용이 왜이리도 다른지.. 머리 터지는 줄 알았음 TㅅT 막판에 apache 설정을 삽질하는 바람에 생고생 까지.. TㅅT

우선.. Trac을 설치하면서 함께 필요한 것들.. 이 많은 것들을 모두 설치해야 한다.

- 일반적인 방법으로 설치하는 것들
Apache (웹서버. 없는 경우 tracd 명령으로 동작시킬 수 있음. 14번 단계 참고)
Mod-python (apache에서 python을 사용하기 위한 apache module)
Python
Subversion
Subversion Python Binding (python을 통해서 subversion을 사용할 수 있게 하는 도구)
setuptools (Python Package Index에 있는 패키지를 손쉽게 다운로드, 설치하는 도구)

- setuptools(easy_install)를 통해 간단히 설치하는 것들(따로 다운로드하지 않아도 됨)
SQLite (다른 database를 사용하는 방법은 Trac 설치 문서 참조)
Genshi (HTML 랜더링을 위해 사용한다고 한다.)
Pygments (구문 lighlighting을 위해 사용)
Trac

setuptools로 설치하는 것들은 있어도 되고, 없어도 되는 것들이 보통이다. 옵션으로 있으면 더 편하고 쓰기 좋은 기능을 하는 것 같다.


1. Apache와 Subversion 설치
나는 apache와 subversion은 이미 설치해서 사용하고 있었기 때문에 이 설치 내용은 패스~~

2. Python 설치
http://www.python.org/에 접속해서 python을 받아 설치한다. python만 사용하면 아무 버전을 사용하면 될텐데.. 현재 윈도버전용 mod는 python 2.5와 apache 2.2까지만 지원해서 최신 버전을 설치했다 다시 지우고 2.5 버전을 설치했다. TㅅT

3. Mod-python
Trac은 python으로 되어 있기 때문에 python 코드를 동작시키기 위해 mod를 설치한다. 이걸로 apache와 python 연결은 끝..

4. Subversion Python Binding
Trac의 python 코드가 subversion을 사용할 수 있게 하기 위해 설치한다.

5. setuptools
다음의 설치를 쉽게 하기 위한 도구를 설치한다. 이 setuptools을 사용하면 Python Package Index에 등록된 package를 자동으로 다운받아 설치한다. easy_install이라는 명령으로 package를 설치하게 된다.

6. SQLite
Trac은 database도 사용한다. 기본은 SQLite이고, 다른 database를 사용하고자 하면 Trac 설치 문서를 참고하자. 다음 명령을 통해 setuptools를 사용해서 Pysqlite를 간단하게 설치할 수 있다.
easy_install pysqlite


7. Genshi
HTML, XML 랜더링을 해주는 도구이다.
easy_install Genshi


8. Pygments
소스 코드의 syntax highlighting을 지원하는 도구이다.
easy_install Pygments


9. Trac
Trac도 setuptools를 통해서 설치할 수 있다.
easy_install Trac



자.. 이제 설치는 끝났다. 설정을 해볼끄나?? =ㅅ= apache, subversion 설치를 뛰어 넘었는데도 설치한게 벌써 몇 개다냐.. setuptools로 설치한 Genshi와 Pygments 외에도 enscript 등 여러 도구가 있는데 설치해도 되고 그렇지 않아도 되는 듯 싶다. Trac 설치는 너무 복잡하다. TㅅT

10. Subversion 저장도 만들기
Trac 이 녀석.. subversion도 사용한단다. 아아~~ 그럼 만들어 줘야지.. 나는 Trac용 subversion repository를 따로 모으기 위해 D:\SubversionRepository\tracRepository 경로를 만들었다. 그리고 test라는 trac 프로젝트를 위해 test라는 경로로 subversion repository를 생성한다.
svnadmin create [SVN_REPOSITORY]\[PROJECT_NAME]
svnadmin create D:\SubversionRepository\tracRepository\test


11. Trac 프로젝트 만들기
이제 정말 Trac 프로젝트를 만든다. test 프로젝트를 위해 trac repository 경로(D:\tracRepository) 하위에 test 경로에 생성했다.
trac-admin [TRAC_REPOSITORY]\[PROJECT_NAME] initenv
trac-admin D:\tracRepository\test initenv

위의 명령을 입력하면 커맨드라인으로 사용자의 입력을 받는다. 아래는 그 내용 중 입력하는 부분만 골랐다. 모두 입력하면 프로젝트를 생성하며, 마지막에 "Congratulations!"가 출력되면 모두 생성된 것이다.
...
Project Name [My Project]> test
...
Database connection string [sqlite:db/trac.db] 그냥 엔터 (다른 database를 사용하는 경우 설치 문서 참조)
...
Repository type [svn]> 그냥 엔터
...
Path to repository [/path/to/repos]> D:\SubversionRepository\tracRepository\test (앞에서 만든 subversion repository 경로)
...


12. Apache mod 설정하기
앞에서 설치한 mod_python을 사용하도록 [APACHE_HOME]/conf/httpd.conf 파일에 설정한다. "LoadModule"로 찾아보면 기존에 설정된 module 설정이 있으니 이 부분에 함께 끼워 넣으면 되겠다. 이 부분을 빼먹어서 한참 삽질했었다. TㅅT
LoadModule (기존 module 설정들..)
LoadModule python_module modules/mod_python.so


13. Apache virtual host 설정하기
Trac에 접속할 때 apache를 사용하기 위해 apache의 virtual host를 설정한다. 만일 apache를 사용하지 않는다면 14번 단계의 tracd를 사용하면 된다. apache 2.1 이하 버전에서는 [APACHE_HOME]/conf/httpd.conf에 다음 설정을 추가하면 되고, 2.2 이상 버전에서는 [APACHE_HOME]/conf/extra/httpd-vhosts.conf에 추가하면 된다.
NameVirtualHost *:80

# Trac
<VirtualHost *:80>
  DocumentRoot "C:/Program Files/Apache Software Foundation/Apache2.2/htdocs/"
  ServerName trac.TuxedoCat
  ErrorLog "logs/trac.TuxedoCat-error.log"
  CustomLog "logs/trac.TuxedoCat-access.log" common
 
  <Location /trac>
      SetHandler mod_python
      PythonInterpreter main_interpreter
      PythonHandler trac.web.modpython_frontend
      PythonOption TracEnvParentDir D:\tracRepository # trac 프로젝트 경로가 아닌 그 상위 경로이다.
      PythonOption TracUriRoot /trac
  </Location>
 
  <LocationMatch "/trac/[^/]+/login"> # 로그인을 하기 위한 URL을 적어준다.
      AuthType Basic
      AuthName "Trac"
      AuthUserFile conf/.htpasswd # 15 단계에서 생성할 로그인 계정 설정 파일 경로
      Require valid-user
  </LocationMatch>
</VirtualHost>

동작 원리는 /trac URL로 접근하면 mod_python을 통해서 python handler를 동작시킨다. handler parameter로 몇 가지 경로 등을 설정하고, 그 경로 하위로 존재하는 trac 프로젝트를 메인 페이지에서 보여준다.

14. apache 대신 tracd 사용하기
이 단계는 apache를 사용하는 경우 진행하지 않아도 된다. 13단계의 설정과는 달리 trac 프로젝트 경로를 적어준다.
tracd --port 8000 D:\tracRepository\test


15. 사용자 암호 설정하기
13 단계에서 설정한 virtual host의 접속 계정 파일을 생성한다. [APACHE_HOME]/bin/htpasswd.exe를 통해서 로그인 계정 설정 파일을 생성한다. 생성된 파일은 13 단계에서 설정한 위치로 옮긴다. 해당 경로는 [APACHE_HOME]에서부터의 상대경로이다. 13 단계의 설정 예는 [APACHE_HOME]/conf/.htpasswd 이다. 추가할 사용자가 있으면 "-c" 옵션 없이 계속 추가하면 된다. "-c' 옵션은 파일 생성 시에만..
cd [APACHE_HOME]/bin/
htpasswd -c .htpasswd admin
htpasswd .htpasswd userName


16. 사용자에 관리자 권한 추가하기
15 단계에서 추가한 사용자 중 원하는 프로젝트에 관리자 권한을 설정할 수 있다. 관리자 권한을 설정할 프로젝트 경로(D:\tracRepository\test)와 사용자명(admin)을 설정해준다.
trac-admin D:\tracRepository\test permission add admin TRAC_ADMIN

17. 접속 테스트
자.. 이 엄청난 설치와 설정이 모두 끝났다. 휴우~~ apache virtual host로 설정한 URL로 접속해보자. 설치된 프로젝트 목록이 보이면 성공!!
http://127.0.0.1/trac/




+ 참조 사이트
Trac 설치하기
Subversion과 Trac 설치(Windows)
Apache+Python+Subversion+Trac 설치(Windows시스템)

+ 어떤 참조는 중간에 한 스텝을 빼먹기도 해서 삽질 많이 했다. TㅅT

출처 :  http://entireboy.egloos.com/4310366
Posted by 1010
98..Etc/Hudson2010. 11. 30. 15:38
반응형
Hudson을 이용한 빌드와 테스트의 자동화


 

2007-04-04
BEA Systems Korea
Sr consultant Byungwook Cho (bcho@bea.com)

Continuous Integration(점진적 통합,이하 CI)이란, 개발자가 각각 개발한 소스코드를 모아서 한꺼번에 빌드하는 통합 빌드의 과정을 특정 시점이 아니라 매일이나 매주와 같이 아주 잦은 주기로 수행함으로써 통합에서 발생하는 오류와 시간을 줄이기 위한 기법이다.
Extreme Programming Community (XP)에서 애자일 방법론의 일부로 Kent Beck에 의해서 고안된 방법으로 다음과 같은 특징을 가지고 있다.

1. CI의 특징
(1) 소스코드 일관성 유지
CI툴을 설정하기 위해서는 기본적으로 소스 관리 시스템이 필요하다.
대표적인 소스 관리 시스템은 Subversion,CVS,Perforce등이 있다.
CI툴은 이 소스 관리 시스템으로부터 프로젝트 소스의 메인 브랜치(trunk 라고도 한다.) 코드를 Check out 받아서 빌드를 수행한다.

(2) 자동 빌드
소스 코드에 대한 빌드는 CI툴에 의해서 자동적으로 이루어 져야 한다.
빌드가 이루어지는 시점을 정하는 방법이 두가지가 있는데 다음과 같다.

1) 커밋에 따른 자동 빌드
다른 방법으로는 소스코드가 소스 관리 시스템에 커밋이 되었을 때 마다 CI툴이 이를 감지 하고 자동으로 빌드를 수행하도록 설정할 수 있다.
이렇게 설정할 경우 소스 코드의 변경이 있을 때 마다 빌드 작업을 수행하기 때문에 소스 관리 시스템에 저장된 소스 코드에 대한 무결성을 보장하기는 매우 좋지만, 빌드 시간이 길 경우 빌드가 적체 되는 현상이 발생할 수 있다.
(일반적으로 대규모 애플리케이션의 FULL 빌드는 길게는 2~3시간 까지 소요될 수 있다.) 그래서 이 방법은 빌드 시간이 오래 걸리는 경우나 커밋이 자주 발생하는 경우에는 적절하지 않다.

2) 시간 간격에 의한 빌드
일정 시간 간격을 정해서 빌드를 하는 방법이다. 매일 5시에 빌드를 한다. 또는 매주 금요일 저녁 5시에 빌드를 한다는 것과 같이 주기를 정할 수 있다. 빌드 스케쥴이 미리 정해져있기 때문에 개발자들이 커밋에 대한 스케쥴을 관리할 수 있고 빌드 시간이 오래걸리는 대규모 빌드에도 적정하다.
 빌드 시간을 정할 때 중요한 점은 가급적이면 퇴근 시간 1~2시간 전으로 개발자들이 퇴근하기 전 시간으로 여유를 두는 것이 좋다.
이후 빌드가 깨진 경우는 컴파일이 실패하였거나 테스트가 통과하지 못하였을 경우인데 이때 소스 관리 시스템에 저장된 코드는 문제가 있는 코드이다. (빌드가 깨졌기 때문에) 이 코드들을 다른 개발자가 체크아웃 받아서 개발을 했을 때 잘못된 코드로 인해서 잘못된 개발 방향으로 갈 수 가 있기 때문에 빌드가 깨졌을 때는 가급적 빨리 문제를 수정하여 빌드를 정상화 시키고 소스 관리 시스템에 저장되 코드의 무결성을 회복하거나 빌드가 성공한 전버전으로 BACK(Revert) 해서 무결성이 보장된 코드내에서 작업하도록 한다. Revert를 위해서는 소스 관리 시스템에 빌드때마다 Tagging을 해서 무결성이 보장된 버전에 대한 History를 관리해야 한다.

Silent period에 대해서
CI툴에서는 소스 관리 시스템으로부터 소스를 체크아웃 또는 업데이트 받을 때 Silent Period라는 옵션을 제공한다.
개발자가 소스를 커밋하고 있을 때 커밋이 완료되지 않은 상태에서 CI툴이 소스를 체크아웃하게 되면 불완전한 코드가 내려와서 빌드가 망가질 수 있다. 이를 방지하는 옵션이 Silent Period인데, 커밋이 있은후 일정 시간동안 소스 관리 시스템에 변화가 없을 때 CI툴이 체크아웃을 받아서 불완전한 코드를 내려 받을 수 있는 가능성을 최소화 하는 것이다.

이렇게 자동 빌드를 하면서 얻을 수 있는 이점은, 주기적인 빌드를 통해서 소스코드의 무결성 관리와 빅뱅방식의 통합에서 올 수 있는 리스크를 개발과정 전반으로 분산할 수 있다.

(3) 자동 테스팅
빌드 과정에서 테스트를 포함할 수 있는데, 주기적인 빌드 과정에 테스트를 포함해서 자동 빌드를 통한 Syntax에 대한 검증과 더불어 테스팅을 통한 기능과 비기능적(성능등)에 대한 요건을 매번 검증함으로써 코드의 품질에 대한 무결성을 함께 유지한다.

(4) 일일 체크아웃과 빌드
개발자가 출근후 소스 관리 시스템에서 최신 코드를 내려받고, 출근전에 현재 코드를 소스 관리 시스템에 저장함으로써 소스 코드에 대한 무결성을 유지한다.

예를 들어 개발자 A와 개발자 B가 같이 개발을 할 때, 개발자 A가 작성한 모듈을 개발자 B가 참고해서 사용한다고 하자, 이런 경우 개발자 A가 임의로 컴포넌트에 대한 작동 방식이나 인터페이스를 변경했을 때 일일 체크아웃과 빌드를 하면 개발자 A의 모듈을 사용하는 개발자 B의 모듈의 컴파일 오류나 또는 테스트 오류가 발생할 것이고 코드 변경으로 인한 임팩트를 빠르게 발견하여 수정할 수 있다.
그러나 통합 빌드의 과정이 일일이 아니거나 일일 체크아웃 빌드를 하지 않고 일주일이나 한달 단위로 할 경우 일주일동안 개발자 B는 잘못된 코드를 양산할것이고, 그 만큼의 시간 낭비가 발생한다.

일일 체크아웃과 빌드는 이를 방지해주는 역할을 한다.

2.CI 프로세스

CI에 대한 프로세스를 정리해보면 다음과 같다.
 

사용자 삽입 이미지

<그림. Continous Integration Process >


(1) 개발자
1) 개발자는 아침에 출근해서 소스 관리 시스템으로부터 최신 코드를 Checkout 또는 update받는다.
2) 코드를 가지고 개발을 수행하고 테스트를 작성한다.
3) 로컬에서 빌드 및 테스트를 진행한다.
4) 테스트과정에서 커버러지분석과 Code Inspection을 수행한다. (Optional)
 커버리지 분석
커버러지 분석은 테스트의 대상중에 테스트에 해당하는 부분중에 어느 부분이 테스트가 수행이 되었는지를 체크하는 과정이다. 개발 과정에서의 테스트 커버러지 분석은 일반적으로 코드 커버러지로 분석한다.
코드 커버러지란 테스트가 전체 소스 코드중 어느부분을 수행했는지를 검토하는 것이다.
코드 커버러지를 측정할 때 가장 중요한 것은 목표 커버러지율을 결정하는 것이다. 코드 100%를 테스트하는 것이 좋겠지만, Exception,If 문에 대해서 100% 테스트가 불가능하다. 또한 Setter와 Getter만 있는 ValueObject의 경우 테스트를 작성하는 것도 쉽고 테스트 자체가 의미가 없나 Coverate rate는 많이 올릴 수 있다. 만약 커버러지율을 강제적으로 높이고자 하면 개발팀에서 VO등 테스트가 필요하지 않고 테스트가 쉬운곳에만 테스트를 집중할 수 있기 때문에 컴포넌트의 우선순위를 정해서 중요한 컴포넌트에 대해서 커버러지를 관리하는 것이 필요하다.
커버러지율은 잘 만든 테스트라도 50~70% 내외이고, 고 가용성 시스템도 80%를 넘기 힘들기 때문에, 컴포넌트의 중요도별로 목표 커버러지율을 융통성 있게 결정하는 것이 필요하다.
대표적인 오픈소스 도구로는 EMMA와 Cobertura등이 있고, 상용 도구로는 Atlassian社의 Clover등이 있다.
 Code Inspection
Code Inspection이란, 완성된 코드에 대한 검토를 통해서 코드상에 존재하고 있는 잠재적인 문제를 발견하는 과정이다. 흔히 “정적 분석” 이라는 이름으로도 불리는데, 이 과정에서 Deadlock에 대한 검출 Lock contention과 같은 병목 구간에 대한 검출 Memory Leak이나 Connection Leak과 같은 자원 누수에 대한 검출과 코딩 스타일 (변수명이나 메서드명 규칙등)에 대한 가이드를 수행한다.
보통 이런 도구들은 룰 셋을 추가하여 Inspection을 각 팀의 스타일에 맞춰서 Customizing할 수 있으며 대표적인 오픈 소스 도구로는 PMD, FindBugs등이 있다.

5) 완료된 코드를 소스 관리 시스템에 저장한다.
완료된 코드와 테스트를 소스 관리 시스템에 커밋한다.

(2) CI Tools

1) 체크아웃
CI Tools는 정해진 시간이나 소스가 커밋이 되었을 때 등 정책에 따라서 빌드를 시작한다. 먼저 소스 코드를 체크아웃 받는다.
2) 컴파일
체크아웃 받은 코드에 내장되어 있는 빌드 스크립트를 기동하여 컴파일을 수행한다.
3) 배포
컴파일이 완료된 코드를 테스트 서버에 배포한다.
4) 테스트 수행
체크아웃 받은 코드내에 있는 테스트 코드들을 수행하고 리포팅을 한다.
5) 커버러지 분석
테스트 과정중에 코드 커버러지를 수행한다.
커버러지의 기본 원리는 커버러지 분석 대상이 되는 코드내에 커버러지 분석 코드를 삽입하여 테스트가 완료된 후에 그 결과를 수집하여 분석을 하는데 분석 코드를 삽입하는 과정을 Code Instrument라고 한다. Instrumented된 코드는 커버러지 분석 기능으로 인해서 성능 저하를 유발할 수 있기 때문에 만약에 테스트 과정에 성능이나 응답시간에 관련된 테스트가 있을때는 커버러지 분석을 위해서 테스트를 마친후에 Instrument를 다시하여 3),4) 과정을 다시 거쳐서 커버러지 분석을 해야 테스트 과정에서 성능에 대한 요소를 올바르게 추출할 수 있다.
6) Code Inspection
다음으로 Code Inspection을 수행하고 리포트를 생성한다.
7) 소스 태깅
1)~6) 과정이 정상적으로 수행되었을 때, 현재 소스 관리 시스템에 저장된 버전을 안정적인 버전으로 판단하고 소스 관리 시스템에 현재 버전에 대한 이미지를 저장하기 위해서 Tagging을 수행하여 현재 버전을 저장해놓는다.
8) Reverse (Optional)
만약에 빌드나 테스트가 실패하였을때는 이전에 성공한 빌드 버전으로 소스를 롤백하고, 실패의 원인을 파악한후에 다시 커밋한다.
이것은 소스 관리 시스템에 저장된 소스는 문제가 없는 소스를 항상 유지하여 개발자들이 문제가 없는 소스로 작업을 할 수 있게 보장해주며, 릴리즈가 필요한 시기에 언제든지 릴리즈가 가능하도록 지원해준다.
9) 결과 분석
빌드와 테스트가 완료된 후에 테스트 결과서를 통해서 문제가 있는 테스트를 개발자가 수정하도록 하고, Code Inspection결과를 PM이 검토하여 담당 개발자가 수정하도록 한다.
다음으로 Coverage 분석 결과를 통해서 테스트가 부족한 부분은 PM이 담당 개발자에게 지시항 테스트를 보강하도록 한다.

3.Hudson 설치

(1) Hudson의 설치 및 기동


1) https://hudson.dev.java.net/ 에서 hudson을 다운로드 받는다.
2) 다운로드 받은 Hudson.war를 Apache Tomcat에 deploy해서 구동 하거나 도는 java –jar hudson.war로 hudon으르 기동한다. 디폴트로 설치된 hudson은 8080포트를 통해서 접근이 가능하다. (Tomcat을 통해서 설치 하지 않은 경우)

WAS에 인스톨 정보
http://hudson.gotdns.com/wiki/display/HUDSON/Containers

(2) Hudson과 소스 관리 시스템 연동
좌측 메뉴에서 “New Job”을 선택하여 새로운 작업을 등록한다.
Job name을 선택하고 “Build a free-style software project”를 선택한다. 아직까지 다른 빌드 선택은 안정화 되어있지 않기 때문에 이 메뉴를 중심으로 설명한다.
 

사용자 삽입 이미지

<그림 1. 새로운 프로젝트의 생성>

(3) 소스 관리 시스템과 연동
Job이 생성되고 나면 초기화면에서 Job을 선택할 수 있다 Job을 선택하면 좌측에 Configure라는 메뉴가 생기는데, 그 안으로 들어가면 Job에 대한 설정을 할 수 있다.

먼저 소스 관리 시스템으로부터 코드를 내려받도록 설정해야 한다.
“Source Code Management”에서 사용하는 소스 관리 시스템을 선택한다.
이 예제에서는 Subversion을 선택한다.
Subversion을 선택한후 Repository URL에 SVN접근 주소를 입력한다.
svn://source.example.com/trunk 그 아래에 “Local module directory”에 SVN 레파지토리의 하위 디렉토리를 선택한다. “/myproject” 식으로
이렇게 하면 svn://source.example.com/trunk/myproject 에서 소스 코드를 매번 내려받게 된다. Repository URL과 Location을 지정하면 Hudson이 자동으로 SVN에 접속을 시도해본다. 만약에 id와 passwd가 필요한 경우에는 아래 그림과 같이 “enter credential”이라는 링크가 Repository URL 아래 나타나서 id와 passwd를 입력할 수 있게 한다.
 

사용자 삽입 이미지

<그림 2. SVN 접속 정보 입력>
여기에 소스 관리 시스템 연결에 관련해서 몇가지 옵션을 추가할 수 있다.

 Use Update
소스 관리 시스템에서 소스를 내려받을 때 디폴트가 모든 소스를 매번 다운로드 받는것인데 이런 경우에는 소스양이 많을 경우 다운로드에 많은 시간이 소요되서 전체 빌드 시간이 늘어날 수 있다. 이때 “Use Update”라는 옵션을 사용하면 처음에만 소스 코드를 전체 다운로드 받고, 두번째 부터는 변경된 소스 코드만 다운로드 받기 때문에 소스 코드를 다운 받는 시간을 많이 줄일 수 있다. (svn update와 같은 명령)

Repository Browser
Repository Browser란 소스 관리 시스템에 저장된 소스의 내용 웹에서 브라우징할 수 있는 도구이다. 도구에 따라서 이전 버전과 변경된 부분에 대한 Diff비교 또는 처음 소스 코드가 생성되었을 때부터 매번 커밋되었을 때 변경 내용에 대한 Revision등에 대한 모든 히스토리를 출력해준다. 이런 기능은 빌드가 깨졌을 때, 바로 빌드 버전에 대한 소스 변경 내용을 추적하여 누가 어떤 코드를 변경하였는지를 추적하여 가능한한 빠른 시간내에 문제를 해결하게 해준다.
이 옵션을 체크해놓으면 빌드가 완료된후 Job의 Changes를 보면 소스 코드가 변경된 부분에 대해서 Repository Browser로 링크가 걸려서 소스를 웹에서 바로 확인하거나 전버전에서 바뀐 부분을 확인할 수 있다.
대표적인 Repository Browser로는 오픈소스 제품인 Sventon과 상용제품인 Fisheye등이 있다.

(4) 빌드 트리거링
다음 설정해야 하는 부분이 Build Triggers 설정이다.
이 설정은 언제 빌드가 돌아야 하는가를 설정하는 부분으로 3가지 옵션을 제공한다.

1) Build after other projects are built
이 옵션에는 다른 Job(Project)의 이름을 인자로 넣는다.
이렇게 하면 지정된 프로젝트의 빌드가 정상적으로 끝나면 자동으로 이 프로젝트가 Invoke된다. 만약에 빌드만 하는 Job과 테스트만 하는 Job이 있고 테스트는 자주 사용하고 빌드후에 반드시 테스트를 해야할 때, 테스트 Job에서 이 옵션으로 선행작업을 빌드로 해놓으면 빌드를 수행할 때 마다 빌드가 성공하면 테스트를 수행하게 된다. 테스트만 수행하면 빌드와 상관없이 테스트만 수행된다.
이 옵션은 프로젝트 실행의 전후 관계(Chainning)을 하는데 매우 유용하게 사용할 수 있다.

2) Poll SCM
이 옵션을 사용하면 여기에 지정한 주기별로 소스 관리 시스템을 폴링(체크)하여 변경이 있을 경우에만 빌드를 수행한다.
 

사용자 삽입 이미지

<그림 Build Trigger 등록 >

시간 설정 방법은 unix의 crontab 명령과 같은 형식으로 아래와 같은 형식을 사용한다.
분 시간 날짜 월 요일

사용 예는 다음과 같다.
# 매일 12시에 실행
00 12 * * *
# 매주 일요일 1시에 실행
00 01 * * 7
# 매일 12시와 5시에 실행
00 05 * * *
00 12 * * *

3) Build periodically
마지막으로 Build periodically는 정해진 시간 주기별로 소스가 변경과 상관없이 무조건 주기적으로 빌드를 수행하며 Poll SCM과 마찬가지로 crontab과 같은 형식으로 스케쥴을 등록한다.

(5) Build
정해진 스케쥴 정책에 따라 빌드 프로세스가 기동되면 실제 빌드를 수행할 빌드 스크립트가 구동되어야 하는데, Hudson에서는 ANT,MAVEN,그리고 Unix/Windows shell을 수행할 수 있도록 되어 있다. 여기서는 ANT 기반으로 설명을 한다.
 

사용자 삽입 이미지

<그림. Invoke ANT 설정 >

Invoke ANT를 선택 하면 다음과 같은 옵션을 선택할 수 있다.

 ANT Version
Hudson 초기화면에서 “Manage Hudson”메뉴로 들어가면 “System configuration” 메뉴에서 여러 개의 ANT_HOME을 등록할 수 있다.
ANT 버전에 따라서 플러그인이 차이가 날 수 도 있고, 프로젝트에 따라서 사용해야 하는 ANT 버전이 틀릴 수 있기 때문에 여러 개의 ANT를 등록할 수 있게 해준다.
예를 들어 프로젝트가 JUnit 3.8대와 JUnit 4.X대로 각각 구현되어 있다면 ANT에 설치되어야 하는 JUnit 라이브러리 버전이 틀리기 때문에 두개의 ANT를 설정해야 할 수 있다. 이런 경우에 “System configuration”에서 ANT를 여러 개 등록해놓고, 이 ANT Version 메뉴에서 필요한 ANT 버전을 선택하면 된다.

 Target
ANT 스크립트의 Target을 설정한다.

 Build File
ANT 스크립트를 지정한다. 일반적으로 build.xml을 지정한다.

 Properties
ANT 스크립트에 전달해야 하는 Property를 지정한다.
예를 들어 스크립트내에 $workspace라는 변수를 지정하였을 경우 –Dworkspace=값 이런식으로 텍스트 상자에 정의하면 빌드 스크립트로 인자를 전달할 수 있다.

 Java Options
ANT 를 기동하는데 필요한 자바 옵션을 지정한다. ANT도 자바 프로그램이기 때문에 여러가지 JVM 옵션이 지정되는데 Heap,Perm size등을 여기서 –Xmx256m 식으로 지정하여 이 옵션으로 ANT 프로세스를 실행할 수 있다.

여기 까지 설정하면 주기적으로 빌드를 자동화 하는 설정이  완료 되었다.

(6) Hudson의 사용
초기화면에 들어가면 등록된 프로젝트 리스트들이 나온다.
 

사용자 삽입 이미지

초기화면에서는 현재 빌드 상태와 프로젝트별 빌드 성공 실패 여부가 나타난다. 빌드가 성공하면 맑은 태양이 실패율이 높으면 천둥 모양으로 아이콘이 변경이 된다.

초기화면에서 프로젝트를 클릭하면 아래와 같은 화면이 나오는데
 

사용자 삽입 이미지

< 그림, Hudson 프로젝트별 초기화면 >
Changes는 빌드 버전별로 소스 관리 시스템에서 지난 버전에 비해서 변경된 내용 누가 변경했는지 그리고 커밋시에 개발자가 추가한 Comment를 확인할 수 있다.Workspace는 이 프로젝트의 빌드 디렉토리를 보여준다. 브라우져를 통해서 빌드에 사용된 파일등을 확인할 수 있다.
그 아래 메뉴가 “Build Now”인데 이 메뉴는 스케쥴에 상관없이 지금 강제적으로 빌드를 하게 한다.
좌측 맨 아래 메뉴는 BuildHistory로 언제 빌드가 수행되었고 현재 빌드 상태와 빌드 성공 실패 여부를 나타낸다.

(7) Hudson과 Email 연동
Hudson 초기화면에서 Manage Hudson > System Configuration 메뉴에 들어가면 Email-Notification 설정부분이 있는데, 여기 SMTP 서버를 설정해주면 빌드가 실패하였을 때 등에 담당자들에게 메일로 통보를 할 수 있다. SMTP 설정을 한후 프로젝트의 configuration 메뉴에서 Post-build Actions에서 Email Notification에 Receipients에 이메일 주소를 적어놓으면 빌드가 실패했을때와 실패한 빌드가 복구 되었을 때 이메일이 발송된다.
 

사용자 삽입 이미지

<그림. Email Notification 설정 >


(8) JUnit 테스트 연동
CI에 대해서 설명할 때 Test에 대해서 설명했는데, Hudson에서는 JUnit Test Report를 출력해주는 기능을 지원한다.
프로젝트 configuration에서 Post-build actions의 “Publish JUnit test result report” 메뉴에서 JUnit 리포트 파일명을 지정해주면 아래와 같이 테스트가 끝나고 테스트 리포트가 생성되면 테스트 성공 실패 여부를 그래프로 나타내주고, 테스트의 Progress도 누적 그래프로 프로젝트 초기화면에서 출력해준다.

사용자 삽입 이미지

<그림. 프로젝트 초기화면에서 테스트 히스토리에 대한 그래프 > 
사용자 삽입 이미지

<그림. 프로젝트별 테스트 성공 실패 요약 >

이때 주의할점은 JUnit의 테스트 리포트의 파일 경로는 절대 경로가 아니라 프로젝트 Workspace에 대한 상대 경로이기 때문에 상대 경로로 지정해야 한다.

(9) Hudson과 Japex를 이용한 부하 테스트 연동
Japex는 단위 테스트에 대한 부하 테스트를 할 수 있는 단위 부하 테스트 자동화 프레임웍이다. Japex에 대한 사용 방법은 단위 테스트의 “단위 부하 테스트” 부분을 참고하기 바란다.
Japex 역시 JUnit과 마찬가지로 성능에 대한 결과 (테스트 소요 시간)를 그래프로 출력할 수 있다. JUnit과 설정 방법이 같으며 프로젝트 > configuration > Post-build Actions > Record Japex test report에 Japex 테스트 리포트 경로를 추가하면 된다.
 
설정이 제대로 됐으면 테스트 수행후에 왼쪽에 Japex Trends Report라는 메뉴가 생겨서 성능 테스트에 대한 결과를 누적 그래프로 출력해준다.

Japex 테스트 플러그인은 Hudson에 Default로 포함된 것이 아니기 때문에 http://hudson.dev.java.net에서 다운받아서 Hudson에 추가로 설치해줘야 한다.


(10) Hudson과 Cobertura를 이용한 Coverage분석
Coverage 분석에 대해서는 EMMA와 Cobertura, Clover에 대한 확장 플러그인을 제공하는데, 플러그인을 설치한후 JUnit과 Japex와 동일한 방법으로 리포트에 대한 위치를 등록해주면 아래와 같이 커버러지의 누적 그래프를 클래스별,라인별,브랜치별로 출력해준다.
 

사용자 삽입 이미지

(11) 그외의 기능들
본 문서에서는 Hudson에 대한 대략적인 기능을 살펴보았다.
Hudson은 이 이외에도 여러 개의 Hudson 인스턴스를 구성하여 클러스터된 빌드환경을 구축할 수 있다. 여기서 클러스터란 로드분산이나 장애 대응등의 의미가 아니라,
하나의 소스 코드를 가지고 Windows,AIX,HP 버전으로 각각 빌드가 필요할 때, 각 서버에 Hudson을 따로 설치하고 각각 관리하는 것이 아니라 설치는 각각 하더라도 하나의 콘솔화면에서 컨트롤을 하도록 설정이 가능하다.
또한 여러 확장 플러그인을 통해서 기능 확장이 가능하다.

(12) Hudson 사용시 주의할점
Hudson은 이미 JBoss 프로젝트나 기타 상용 프로젝트에서 사용될정도로 매우 널리 쓰이고 안정적인 버전이다. 그럼에도 불구하고 오픈 소스의 한계점과 잦은 업그레이드로 인해서 잠재적인 버그가 있고 특히 플러그인들의 버전이 Hudson의 버전 업그레이드를 쫓아가지 못해서 일부 플러그인들은 하위 버전에서만 작동하고 최신 버전에서 작동하지 않는 경우가 있기 때문에 자신이 사용하고자 하는 플러그인들에 맞는 Hudson 버전을 사용하는 것이 중요하다.
여기서 소개된 플러그인들은 Hudson 1.7 버전을 기준으로 사용 및 검증이 되었다.

4. 그외의 CI Tools
예전에는 CI 툴이 Cruise Control이나 Ant hill이 주류를 이루고 있었으나,
Hudson이 등장하면서 많은 프로젝트들이 쉽고 직관적인 인터페이스와 확장성으로 인해서 많이 사용되고 있다.
TeamCity의 경우 일반 버전은 무료로 제공되며 TeamCity Pro는 상용이다. 무료 버전도 상용 코드를 기반으로 하는 만큼 Hudson에 비해서 높은 안정성을 가지고 있으다.
AntHill Pro 역시 꾸준히 Java 진영의 CI도구로 사용되고 Atlassian의 Bamboo도 근래에 들어 많이 프로젝트에 사용되고 있다.

Hudson을 이용한 빌드와 테스트의 자동화.doc

출처 : http://bcho.tistory.com/entry/Hudson%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%B9%8C%EB%93%9C-%EB%B0%B0%ED%8F%AC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94
Posted by 1010
98..Etc/Hudson2010. 11. 30. 15:35
반응형
Posted by 1010
98..Etc/Hudson2010. 11. 30. 15:32
반응형

CI 스터디에서 참관멤버인 kenny 군이 소개한 CI TOOL인 hudson 이 무료이면서 기능 또한 상당히 좋은것 같아 바로 설치해 보았다.
(설치한 날은 꽤 지났는데, 이제야 포스팅 하네요;;)

OS : solaris 10
CI TOOL : hudson
버젼관리 : CVS
빌드 : ANT

JDK 1.6 설치
http://java.sun.com
Java SE Development Kit 6u7
jdk-6u7-solaris-sparc.sh  79.45 MB 
Java SE Development Kit 6u7
jdk-6u7-solaris-sparcv9.sh  10.79 MB 

64bit 시스템이라서 solaris-sparcv9 도 설치

[hostname:/usr/jdk] sh ./jdk-6u7-solaris-sparc.sh
라이센스 읽고 나서 yes 입력후 ENTER
[hostname:/usr/jdk] sh ./jdk-6u7-solaris-sparc.sh
라이센스 읽고 나서 yes 입력후 ENTER

/usr/jdk/jdk1.6.0_07 에 설치완료

ciuser 사용자계정생성
useradd -g other -d /export/ciuser -s /bin/ksh ciuser

tomcat 설치
http://tomcat.apache.org/download-60.cgi
/export/ciuser 에 압축 해제.
/export/ciuser/apache-tomcat-6.0.16/conf/server.xml 에서 port 8080 에서 8090 으로 변경

ant 설치
http://ant.apache.org/bindownload.cgi
/export/ant 에 압축 해제.

hudson 설치
https://hudson.dev.java.net/servlets/ProjectDocumentList?folderID=2761&expandFolder=2761&folderID=0
에서 최신버젼을 다운로드
/export/ciuser/apache-tomcat-6.0.16/webapps 에 hudson.war 를 복사한다.
ROOT direcotry로접근 (http://xx.xxx.xxx.xxx:8090/)을 위해 tomcat 의 $CATALINA_HOME/webapps 밑에 모든파일 삭제후  hudson.war 를 webapps 에 복사후 ROOT.war 로 이름변경
자세한 사항은 다음을 참고한다.
http://hudson.gotdns.com/wiki/display/HUDSON/Tomcat

To install Hudson on Tomcat, simply copy hudson.war to $TOMCAT_HOME/webapps, then access http://yourhost/hudson.
If you are running Tomcat just to host Hudson, then remove everything from $TOMCAT_HOME/webapps, and place hudson.war as ROOT.war. Tomcat should expand this and create the ROOT directory, and you should see Hudson in http://yourhost/ without any additional path. This also works nicely when you set up a virtual host, as it allows a single Tomcat instance to run multiple applications, yet users can still access your hudson with URLs like http://hudson.acme.org/ without any additional path. See the Tomcat documentation for more about how to set up a virtual host.

환경변수및 PATH 설정
다음과 같이 환경변수 및 PATH 를 설정한다.(.profile)
vi /export/ciuser/.profile

export JAVA_HOME=/usr/jdk/jdk1.6.0_07
export PATH=$JAVA_HOME/bin:$PATH:.
export CVSROOT=:pserver:dev@localhost:/public
export CVS_PASSFILE=/export/ciuser/.cvspass
export ANT_HOME=/export/ant
PATH=$PATH:$ANT_HOME/bin:$ANT_HOME/lib
export HUDSON_HOME=/export/ciuser/hudson_home
export CATALINA_HOME=/export/ciuser/apache-tomcat-6.0.16

hudson 사용을 위한 모든 준비작업이 완료됐다.
이제 접속해 보자.
서버의 아이피를 12.12.12.12 로 가정하자.

http://12.12.12.12:8090/
다음과 같이 처음화면이 나온다.

사용자 삽입 이미지

create new jobs 를 클릭해서 새로운 프로젝트를 생성하자.
(이미 CVS 에 zcitest 라는 프로젝트가 추가되어 있다고 가정한다.)
사용자 삽입 이미지
job name 을 프로젝트명과 동일하게 (물론 틀려도 된다.) 입력하고 Build a free-style software project 를 선택한다.
사용자 삽입 이미지

새로운 job 에 대한 환경설정(configure)이다.
Source code Management
CVS 로 지정할 때는 주의해야 할 점이 하나 있다. 바로 패스워드지정이다.
해당 configure 화면에서는 패스워드를 입력하는 텍스트박스가 없다. 그렇다고 anonymous 로 해 놓을수도 없는 노릇이다. hudson 에서 cvs 패스워드를 지정하는 방식은 2가지가 있다.
1. 환경변수
위의 환경변수및 PATH 설정 에서  export CVS_PASSFILE=/export/ciuser/.cvspass
를 추가했던 것이 바로 hudson 에서 사용하는 cvspass 파일을 지정해주는 환경변수이다. .cvspass 는 cvs 에서 자동으로 만들어 주는 것이다. 해당계정(솔라리스계정)에서 cvs server 로 로그인 할 경우 계정홈디렉토리에 .cvspass 파일이 생성된다.
2. 직접지정
Manage Hudson -> Configure System 를 보면
CVS 부분에 .cvspass file 을 입력하는 텍스박스가 있다. 여기에 직접경로(/export/ciuser/.cvspass)를 지정하면 된다.

Build
Add build step 에서 Invoke Ant 선택후 실행할 Target 을 지정하면 된다.

환경설정을 맞추면 다음과 같은 완료화면이 나타난다.
사용자 삽입 이미지

이제 실행해 보자 Build Now 를 클릭하면 실행된다.
사용자 삽입 이미지

Build 시 에러가 발생되면 해당 로그에 Output 을 보고 해결하면 된다.


출처 : http://bestbang.tistory.com/30
Posted by 1010
98..Etc/Hudson2010. 11. 30. 15:00
반응형
Posted by 1010
02.Oracle/DataBase2010. 11. 30. 13:44
반응형

출처:http://www.sqlworld.pe.kr/index.asp

저작권 문제시 즉시 삭제가능~!!!

===========================================================================

백업장치

===========================================================================

1. 백업 장치? 돈주고 사나요?

백업 장치를 만든다는 것은 어떤 하드웨어적인 장치를 준비하는 것을 이야기 하는 것이 아닙니다. 여기서 이야기 하는 백업 장치는 물리적인 파일 이름이 길고 복잡하기 때문에 이를 쉽게 사용하기 위해서 하나의 별칭(Alias)를 만들어 두는 것을 이야기 합니다. 기억하실지 모르겠지만 데이터베이스를 만들 때 물리적인 파일의 경로와 이름을 지정하는 것 이외에 논리적인 데이터베이스 파일명을 지정하게 됩니다. (기억이 안난다면 데이터베이스 관련 강좌를 한번 보고 오시기 바랍니다).

2. 백업 장치의 필요성

예를 들어 "C:\Program FIles\Microsoft SQL Server\MSSQL\Backup\sqlworld.bak" 이라는 이름의 물리적인 파일로 백업을 받는다고 했을 경우 너무나 긴 경로명 때문에 왠지 짜증이 납니다. 이를 해결해 주는 것이 바로 백업장치명, 즉 논리적인 파일명이 됩니다.

나중에 보겠지만 다음과 같은 백업을 받은 T-SQL 문이 있가도 가정하겠습니다.

BACKUP DATABASE sqlworld
TO DISK = 'D:\Program Files\Microsoft SQL Server\MSSQL\Backup\sqlworld.bak'

위에서 빨간색 부분의 파일 경로는 너무나 길고 짜증납니다. 하지만 앞으로 배우게 될 방법으로 백업장치인 sqlworld를 만들어 놓게 되면 위 백업 문장은 다음과 같이 바뀌게 됩니다.

BACKUP DATABASE sqlworld TOsqlworld

매우 간단한 백업 문장이 되었습니다. 이것이 백업 장치의 위력(?) 입니다.

3. 백업 장치 만들기

백업 장치를 만드는 방법은 두가지가 있습니다. 추측하시다 시피 EM(Enterprise Manager)를 이용한 방법이 있고 T-SQL문을 이용하는 방법이 있습니다. 이 두가지 방법에 대하여 자세히 살펴보도록 하겠습니다.

[참고]

대부분의 작업들이 EM을 이용해서도 가능하고 T-SQL문을 이용해서도 가능합니다. 이중에 어느것을 사용하는게 좋은지 묻는 경우가 있습니다. EM의 경우는 대화 화면을 보면서 작업을 하기 때문에 더 쉽게 작업을 할 수 있습니다. 하지만 했던 작업을 다시 하려고 하면 처음부터 다시 해주어야 하는 경우가 많습니다. 하지만 T-SQL 문을 작업을 하고 저장해 둔 후 다음에 다시 이용 할 수 있는 장점이 있습니다. 그리고 예기치 않게 EM을 사용하지 못하게 되는 경우 T-SQL 문에 익숙한 관리자는 작업을 별 문제 없이 해 낼 수 있지만 EM에만 의존했던 관리자는 아무 작업도 못하고 발만 동동 구르게 됩니다. T-SQL 문을 통한 관리 방법을 꼭 익히시기 바랍니다.

1) EM을 이용한 백업 장치 만들기

① 아래 [그림 1]과 같이 백업 장치를 만드는 부분을 EM에서 볼 수 있습니다.


[그림 1]

② 아래 [그림 2] 처럼 "백업" 위에서 마우스 오른쪽 버튼을 눌러 표시되는 단축메뉴에서 "새 백업 장치(N)"를 선택하면 [그림 3] 과 같은 대화창이 뜹니다.


[그림 2]

③ 아래 [그림 3] 처럼 "이름" 부분에 원하는 장치명을 입력합니다. 되도록 의미가 있는 이름으로 하시는게 좋습니다. 그리고 "파일 이름" 부분에는 실제로 물리적인 경로가 어떻게 되는지 정해 주시면 됩니다. 결로을 정해주시면 기본적으로 입력한 "이름" + ".bak" 이 물리적인 파일 이름으로 지정됩니다.


[그림 3]

④ 아래 [그림 4] 는 sqlworld 라는 이름으로 백업장치가 만들어진 결과를 보여 줍니다.


[그림 4]

[참고]

위와 같은 과정으로 백업장치를 만들었다고 해서 지정했던 물리적인 경로의 파일(위의 경우는 E:\Data\sqlworld.bak")이 즉시 생기지 않습니다. 실제로 물리적인 파일이 생기는 시점은 첫번째 백업이 이루어지는 때입니다.

2) T-SQL 문을 이용한 백업 장치 만들기

T-SQL 문을 이용한 백업장치 만들기는 아주 간단합니다. 위에서 EM을 통해서 했던 작업을 T-SQL 로 한다면 다음과 같습니다.

USE master
GO

EXEC sp_addumpdevice 'disk', 'SQLWORLD', 'E:\Data\sqlworld.bak'

o disk : 하드 디스크로 덤프 받음을 의미합니다.(이외에 pipe, tape 등이 있습니다)
o SQLWORLD : 논리적인 백업 장치 명입니다.
o E:\Data\sqlworld.bak : 물리적인 파일의 경로와 파일명입니다.

sp_addumpdevice 저장프로시져에 대한 자세한 설명을 원하시면 온라인 설명서(Books Online)을 참조하여 주시기 바랍니다.

이제 우리가 만든 백업장치에 백업을 받으면 됩니다. 이어지는 전체 백업(Full Backup) 관련 강좌에서는 백업장치를 이용하여 백업을 받는 방법과 백업장치를 이용하지 않고 백업을 받는 방법에 대하여 살펴보도록 하겠습니다.

========================================================================================

Full Backup

========================================================================================

1. 전체 백업(Full Backup)의 특징

여러가지 백업 방법을 설명하면서 전체 백업에 대해서도 간단히 언급을 했었습니다. 전체 백업은 말 그대로 데이터베이스 전체를 백업 받는 것을 의미합니다. 데이터베이스 사이즈가 작은 경우라면 전체 백업을 받는데 별 무리가 없겠지만 데이터베이스 사이즈가 큰 경우라면 전체 백업은 그리 좋은 방법이 아닙니다. 전체 백업을 사용할 수 있는 경우를 생각해 본다면 다음과 같지 않을까 생각합니다.

o 개발용 데이터베이스를 현재 상태로 보관하고 싶은 경우 전체 백업
o 데이터베이스를 다른 서버로 옮기고 싶은 경우 전체 백업해서 다른 서버에 전체 복원
o 데이터베이스에 어쩌다 가끔 변경이 생기는 경우 이를 보관하기 위해 전체 백업
o 현재의 Master 데이터 베이스 보관을 위해 전체 백업

몇가지 생각나는 경우를 적어 보았는데 이게 정답이 아닐 수 도 있습니다.

2. 전체 백업 수행

그럼 전체백업을 받는 과정을 보도록 하겠습니다. 여기서는 다음과 같이 3가지 경우를 살펴 보도록 하겠습니다.

o EM에서 백업 장치를 이용한 전체 백업
o EM에서 물리적인 파일을 이용한 전체 백업
o T-SQL 문을 이용한 전체 백업

1) EM에서 백업 장치를 이용한 전체 백업

바로 이전의 강좌에서 만든 SQLWORLD라는 이름의 백업장치가 있었습니다. 이 백업 장치에 sqlworld 데이터베이스를 전체 백업받는 과정을 보도록 하겠습니다. 강좌를 보시는 분들은 연습용 데이터베이스가 없으면 pubs 데이터베이스를 백업받아 보시기 바랍니다.

① sqlworld데이터베이스 위에서 마우스 오른쪽 버튼을 눌러 [모든작업] - [데이터베이스 백업] 을 선택하시면 아래 [그림 1] 과 같은 대화 창이 표시됩니다.


[그림 1]

② "이름(N)" 부분과 "설명(R)" 부분에는 참고할 만한 내용을 입력합니다. 위 [그림 1] 에서 처럼 빨간색 라인 부분을 보면 백업 방법이 "데이터베이스-전체(D)" 로 되어 있습니다. 즉 전체 백업을 받음을 의미합니다. 백업 대상 부분의 현재 내용이 있으면 [제거(M)] 버튼을 눌러 제거 하시고 [추가(A)] 버튼을 눌러 아래 [그림 2] 처럼 장치 선택화면이 나오도록 합니다. 그리고 "백업장치(B)" 부분에 이미 만들어져 있는 SQLWORLD 라는 백업장치를 선택하고 [확인] 버튼을 누릅니다.


[그림 2]

③ 아래 [그림 3]을 보시면 "덮어쓰기" 부분이 보입니다. 이 부분은 만일에 백업 장치에 기존에 백업 받은 내용이 있으면 여기에 추가 할 것인지(미디어에 추가) 아니면 기존의 내용을 지우고 쓸 것인지(기존 미디어 덮어쓰기)를 선택하는 것입니다. 처음 백업 받는 것이므로 현재는 무엇을 선택하든 아무 의미가 없습니다. "예약" 부분은 스케쥴을 설정하여 백업받는 것인데 다음에 자세히 다루도록 하겠습니다.


[그림 3]

④ 위 화면에서 [확인] 버튼을 누르면 백업 진행 상황이 보이면서 실제 백업이 이루어 집니다. 백업이 완료 되었으면 백업 장치를 만들 때 지정했던 물리적인 폴더에 실제 백업 파일이 생성되었나 확인해 보시기 바랍니다.

⑤ SQLWORLD 백업 장치의 [등록정보]를 열어 [내용보기] 버튼을 누르면 아래 [그림 4]와 같이 백업 받은 히스토리가 표시됩니다.


[그림 4]

[실습 1] 다음을 실습해 보시기 바랍니다.

위 과정을 이용하여 같은 데이터베이스(예제의 경우 sqlworld)를 동일한 백업장치(예제의 경우 SQLWORLD)에 여러번 백업을 받아 보시기 바랍니다. 백업을 받는 과정에서 [그림 3]의 "덮어쓰기" 부분을 바꾸어 가면서 백업을 받아 보시기 바랍니다. 그리고 [그림 4]와 같이 백업 히스토리를 확인해 보시기 바랍니다. "덮어쓰기"를 어떻게 선택하냐에 따라 변화된 히스토리를 볼 수 있을 것입니다.

2) EM에서 물리적인 파일을 이용한 전체 백업

위 백업 방법과 대부분 동일 합니다. 단 [그림 2]에서 "백업 장치" 대신 "파일 이름" 부분에 원하는 물리적인 파일의 경로와 파일의 이름을 설정해 주면 됩니다. 자세한 설명은 생략하도록 하겠습니다. 직접 해보시기 바랍니다.

3) T-SQL 문을 이용한 전체 백업

BACKUP DATABASE 문을 이용해서 백업 장치 또는 물리적인 파일에 직접 백업을 받을 수 있습니다. 몇가지 다양한 예를 들어 보도록 하겠습니다.

[예제 1]

USE Master
GO

BACKUP DATABASE sqlworld TO SQLWORLD WITH INIT

o sqlworld 데이터베이스를 SQLWORLD 라는 백업 장치로 백업을 받습니다.
o WITH INIT 옵션은 기존의 백업 내용이 있으면 덮어 쓰라는 설정입니다.

[예제 2]

USE Master
GO

BACKUP DATABASE sqlworld TO SQLWORLD WITH NOINIT

o sqlworld 데이터베이스를 SQLWORLD 라는 백업 장치로 백업을 받습니다.
o WITH NOINIT 옵션은 기존의 백업 내용을 보존하고 거기에 추가하여 백업 받으라는 설정입니다. 결국 백업 파일의 사이즈는 증가하게 됩니다. INIT 이나 NOINIT을 설정하지 않으면 NOINIT이 기본으로 적용됩니다.

[예제 3]

USE Master
GO

BACKUP DATABASE sqlworld TO DISK = 'E:\Data\sqlworld.bak' WITH NOINIT

o sqlworld 데이터베이스를 E:\Data\sqlworld.bak 라는 물리적인 파일로 백업을 받습니다.
o 앞의 [예제 1]과 [예제 2]와는 다르게 TO 대신 TO DISK 로 바뀌었음을 숙지하시기 바랍니다.

BACKUP DATABASE 에 대한 자세한 설명은 온라인 설명서(Books Online)을 통해 필히 확인하시기 바랍니다. 다양한 옵션들이 제공됩니다. 물론 이들이 자주 사용되는 옵션은 아니지만 이런것도 있구나..라고 확인은 해보시기 바랍니다. 다음 강좌에서는 전체 백업 받은 것을 복구하는 과정을 살펴보도록 하겠습니다.

========================================================================================

Full Backup Restore

========================================================================================

1. 데이터베이스 복원(Restore)이란?

데이터베이스 복원이란 백업받은 것을 사용가능한 데이터베이스로 원위치 시키는 작업을 이야기 합니다. 데이터베이스를 운영하는 동안에 한번도 복원을 할 일이 없다면 좋겠지만 분명 언젠가는 복원을 해야하는 경우가 발생하므로 그 방법을 미리 알고 있어야 합니다. 특히 백업받은 내용이 정확한지 혹은 백업받은 내용이 물리적으로 손상되지는 않았는지 모의 복원을 통해 확인해보는 것도 중요하다고 생각합니다. 복원 방법을 전혀 모르는 상태에서 데이터베이스에 심각한 문제가 생겼을 때 백업 파일을 가지고 발만 동동 구르는 일이 없어야 하겠습니다. 데이터베이스 복원을 하게 되는 경우를 찾아본다면 다음과 같지 않을까 생각합니다.

o 데이터베이스에 심각한 문제가 발생하여 복원을 통한 복구가 불가피한 경우
o 다른 서버에서 백업받은 내용을 새로운 서버에 복원해야 하는 경우
o 백업받은 내용을 가지고 실제 데이터베이스와 비슷한 테스트용 데이터베이스를 만들고자 하는 경우

2. 전체 백업을 이용한 데이터베이스 복원 작업

바로 이전의 강좌에서 우리는 sqlworld 데이터베이스를 전체 백업 받았습니다. 이 전체 백업을 이용해서 데이터베이스 복원하는 방법을 살펴보도록 하겠습니다. 나중에 살펴보도록 하겠지만 차등 백업이나 트랜잭션 백업을 받은 경우의 데이터베이스 복원은 더 복잡합니다.

이 강좌에서는 다음의 두가지 방법을 이용한 데이터베이스 복원 방법을 살펴보도록 하겠습니다.

o 데이터베이스가 연결되어 있는 경우 EM에서 데이터베이스 복원
o 데이터베이스가 연결되어 있지 않는 경우 EM에서 데이터베이스 복원
o T-SQL 문을 이용한 데이터베이스 복원

1) 데이터베이스가 연결되어 있는 경우 EM에서 데이터베이스 복원

① 아래 [그림 1]와 같이 sqlworld 데이터베이스를 선택한 상태에서 [도구] 메뉴에서 "데이터베이스 복원(R)" 을 선택하시면 데이터베이스 복원을 위한 대화창이 표시됩니다.


[그림 1]

② [그림 2]와 같은 대화창이 표시되면 [도움말] 버튼을 눌러 각 항목에 대한 의미를 살펴보시기 바랍니다. 빨간색 부분에서 복원할 백업내역을 선택하고 [확인] 버튼을 누르면 복원이 진행됩니다.


[그림 2]

2) 데이터베이스가 연결되어 있지 않는 경우 EM에서 데이터베이스 복원

① 아래 [그림 3]을 보시면 sqlworld 데이터베이스가 제거된 상태입니다. 이 상태에서 복구를 하는 방법은 약간 다릅니다. 위의 방법과 동일하게 [도구] 메뉴에서 "데이터베이스 복원(R)" 을 선택하시면 데이터베이스 복원을 위한 대화창이 표시되나 [그림 2]와 같이 sqlworld 데이터베이스에 대한 백업 내역이 표시되지 않습니다.


[그림 3]

② [그림 4]와 같이 대화창이 표시되면 "데이터베이스로 복원" 부분에 sqlworld라고 입력하고 [장치내용]을 선택하고 [장치선택] 버튼을 누르면 [그림 5]와 같이 백업 장치나 백업 파일을 선택하는 대화창이 표시됩니다.


[그림 4]

③ [그림 5]와 같이 대화창이 표시되면 [추가] 버튼을 눌러 표시되는 "복원할 위치 선택" 창에서 SQLWORLD 백업장치를 선택하면 됩니다. 백업 장치가 아니고 물리적인 파일인 경우는 "파일이름" 부분을 선택하고 물리적인 파일을 지정하시면 됩니다.


[그림 5]

④ [그림 6]은 백업 장치 선택이 끝난 화면입니다. 이 화면에서 [내용보기] 버튼을 누르면 백업 내역이 표시되며 이 중에서 복원하고자 하는 시점의 백업 내역을 선택하시면 됩니다.


[그림 6]

3) T-SQL 문을 이용한 데이터베이스 복원

RESTORE DATABASE 문을 이용해서 백업 장치 또는 물리적인 파일로부터 데이터베이스를 복원 할 수 있습니다.

[예제 1]

USE Master
GO

RESTORE DATABASE sqlworld FROM SQLWORLD

o sqlworld 데이터베이스를 SQLWORLD 라는 백업 장치로부터 복원합니다.

[예제 2]

USE Master
GO

RESTORE DATABASE sqlworld FROM DISK = 'E:\Data\sqlworld.bak'

o sqlworld 데이터베이스를 E:\Data\sqlworld.bak 라는 물리적인 파일에서 직접 복원 합니다.

RESTORE DATABASE 에 대한 자세한 설명은 온라인 설명서(Books Online)을 통해 필히 확인하시기 바랍니다. 다양한 옵션들이 제공됩니다. 물론 이들이 자주 사용되는 옵션은 아니지만 이런것도 있구나..라고 확인은 해보시기 바랍니다. 다음 강좌에서는 차등 백업과 트랜잭션 백업에 대하여 살펴보도록 하겠습니다.

========================================================================================

Differential Backup/Restore

========================================================================================

1. 차등 백업(Differential Backup)의 특징

데이터베이스 차등 백업이란 전체 백업(Full Backup) 이후에 변경된 데이터만 백업받는 방법입니다. 예를 들어서 현재 100GB 정도의 데이터베이스가 있고 어제 저녁에 전체 백업을 받았다고 가정 하겠습니다. 오늘 하루 동안 입력된 데이터가 1,000건 정도 있다고 했을 때 가장 효과적인 백업(트랜잭션 백업을 제외하고)은 오늘 변경된 1,000 건의 데이터에 대한 백업만 받는 것입니다. 이후에 문제가 발생하게 되면 어제 받은 전체 백업을 복원하고 오늘 추가로 백업 받은 변경분 백업만 이어서 복원하게 되면 전체 데이터가 복원됩니다.

그래서 차 등백업이 효과적인 경우는 전체 데이터베이스 사이즈가 너무 커서 전체 백업을 받기에 너무 부담이 되는 경우입니다. 단지 변경 내용만 백업 받게 되면 짧은 시간에 백업을 받을 수 있어 효과적입니다. 중요한 것은 차등 백업만 있으면 아무 의미가 없다는 것입니다. 전체 백업 받은 것이 있어야 차등 백업이 의미가 있습니다. 다시 말하면 문제가 발생하여 데이터베이스 복원을 해야 하는데 차등 백업 데이터는 있는데 전체 백업 데이터가 없다면 복원이 불가능하게 됩니다.

혼동하기 쉬운 차등 백업의 특징이 있습니다. 차등 백업은 전체 백업이 이루어지고 난 이후에 변경된 내용을 백업 받는다고 했습니다. 이 사실을 정확히 이해 하셔야 합니다. 다음의 가정을 세우도록 하겠습니다.

고객 테이블에 홍길동 고객정보추가
전체 백업
고객 테이블에 안경태 고객정보 추가
차등 백업
고객 테이블에 김치국 고객정보 추가
차등백업
문제 발생

이렇게 문제가 발생 한 경우 데이터베이스 복원 작업을 하려고 합니다. 과연 이때 필요한 백업 데이터는 어떤것들 일까요?

위 경우 ④ 번의 차등 백업은 안경테 고객의 데이터를 가지고 있습니다. 그리고 ⑥ 번의 차등 백업은 김치국 고객의 정보 뿐만 아니라 안경태 고객의 정보도 가지고 있습니다. 왜냐하면 전체 백업 이후의 변경 데이터를 가지고 있기 때문입니다. 그래서 복원을 원한다면 전체 백업을 복원 한 후 ⑥ 번의 차등 백업을 복원 하면 데이터베이스 복원이 이루어지게 되는 것입니다.

2. 차등 백업 수행

1) EM에서의차등 백업

차등 백업을 받는 방법은 전체 백업을 받는 과정과 동일 합니다. 단지 아래 [그림 1]과 같이 백업 방법을 '데이터베이스 - 차등'으로 선택만 하시면 됩니다. 각 항목들의 의미는 전체 백업과 동일합니다.


[그림 1]

2) T-SQL 문을 이용한 전체 백업

BACKUP DATABASE 문을 이용해서 전체 백업은 물론 차등 백업을 수행 할 수 있습니다. 테이블을 하나 만들어 전체 백업과 차등 백업을 수행하고 그 결과를 보도록 하겠습니다.

[예제 1] Test1 테이블 만들기

CREATE TABLE Test1
(
col1 char(05),
col2 int
)
GO

o 간단한 구조의 테이블 Test1 을 만들었습니다.

[예제 2] 샘플 데이터 추가

INSERT INTO Test1 VALUES('AAAAA',10)
INSERT INTO Test1 VALUES('BBBBB',20)
INSERT INTO Test1 VALUES('CCCCC',30)

o 3개의 레코드를 추가 하였습니다.

[예제 3] 전체 백업 수행

USE Master
GO

BACKUP DATABASE sqlworld TO SQLWORLD WITH INIT

o sqlworld 데이터베이스를 예전에 만든 백업 장치인 SQLWORLD에 전체 백업을 받았습니다.

[예제 4] 샘플 데이터 추가

INSERT INTO Test1 VALUES('DDDDD',40)
INSERT INTO Test1 VALUES('EEEEE',50)
INSERT INTO Test1 VALUES('FFFFF',60)

o 다시 3개의 레코드를 추가 하였습니다.

[예제 5] 첫번째 차등 백업 수행

USE Master
GO

BACKUP DATABASE sqlworld TO SQLWORLD WITH DIFFERENTIAL, NOINIT

o 첫번째 차등 백업을 수행했습니다. NOINIT을 준 이유는 앞에서 백업받은 전체 백업을 보존하기 위해서 입니다. INIT으로 하게 되면 전체 백업 내용이 사라집니다.

[예제 6] 샘플 데이터 추가

INSERT INTO Test1 VALUES('GGGGG',70)
INSERT INTO Test1 VALUES('HHHHH',80)
INSERT INTO Test1 VALUES('IIIII',90)

o 다시 3개의 레코드를 추가 하였습니다.

[예제 7] 첫번째 차등 백업 수행

USE Master
GO

BACKUP DATABASE sqlworld TO SQLWORLD WITH DIFFERENTIAL, NOINIT

o 두번째 차등 백업을 수행했습니다.

3. 차등 백업으로 부터의 데이터베이스 복원

[예제 1] 에서 부터 [예제 7]까지의 과정에서 몇가지 데이터의 변화가 있었고 우리는 전체 백업 한번과 두번의 차등 백업의 과정으로 데이터베이스를 백업 받은 상태입니다. 이 시점에서 문제가 발생하여 sqlworld 데이터베이스를 복원해야 한다고 가정을 하고 복원 작업을 하도록 하겠습니다.

전체 백업으로 부터 복원하는 방법을 이전의 강좌에서 배웠습니다. 동일한 방법으로 sqlworld 데이터 베이스 복원을 시도하게 되면 아래 [그림 2]와같은 화면이 표시됩니다. 빨간색 부분을 보면 위에서 우리가 시도한 3번의 백업 히스토리가 보입니다. 그 중에서 파란색 부분은 두번의 차등 백업 히스토리입니다. [그림 2]에서는 맨 처음 전체 백업과 마지막 차등 백업이 선택되어 있습니다. 이때 전체 백업을 선택하지 않으려고 해도 할 수가 없습니다. 왜냐하먼 차등 백업은 전체 백업 내용이 복원 되어야 의미가 있기 때문입니다.


[그림 2]

위 [그림 2]의 상태에서 [확인] 버튼을 눌러 복원을 하면 전체 데이터가 복원 됩니다. 만일 전체 백업 내용과 첫번째 차등 백업 내용을 선택하고 복원을 하게 되면 Test1 테이블에는 6개의 레코드만 존재하는 상태가 됩니다. 마지막 입력된 3개의 레코드는 복원이 안되기 때문입니다.

========================================================================================

데이터베이스 옵션을 이용한 트렌젝션 로그제어

========================================================================================

1. 트랜잭션(Transaction) 이란?

트랜잭션(Transaction)이란 무언인가에 대하여 다음과 같이 온라인설명서에서는 이야기 하고 있습니다.

트랜잭션은 하나의 논리적 작업 단위로 수행되는 일련의 작업입니다. 작업의 논리적 단위는 ACID(원자성, 일관성, 격리성 및 영속성) 속성이라고 하는 네 가지 속성을 통해 트랜잭션으로서의 자격을 부여합니다.

원자성

트랜잭션은 더 이상 분류할 수 없는 작업 단위여야 하며 모든 데이터 수정 작업이 수행되거나 하나도 수행되지 말아야 합니다.

일관성

완료된 트랜잭션의 모든 데이터는 일관적이어야 합니다. 관계형 데이터베이스에서는 트랜잭션 수정에 모든 규칙을 적용하여 모든 데이터 무결성을 유지해야 합니다. 트랜잭션 마지막에는 B-tree 인덱스 또는 이중 연결 목록 등 모든 내부적 데이터 구조를 반드시 수정해야 합니다.

격리성

동시 트랜잭션에 의한 수정은 다른 동시 트랜잭션에 의한 수정과 격리되어야 합니다. 트랜잭션에서 다른 동시 트랜잭션이 수정하기 전 상태의 데이터를 보거나, 두 번째 트랜잭션이 완료된 후의 데이터를 볼 수는 있지만 중간 상태는 볼 수 없습니다. 결과적으로 시작 데이터를 다시 로드하고 일련의 트랜잭션을 재생하여 원래 트랜잭션이 수행된 후의 상태로 데이터를 되돌릴 수 있는데 이를 순차성이라고 합니다.

영속성

트랜잭션이 완료되고 나면 그 영향이 영구적으로 시스템에 적용됩니다. 수정은 시스템에 오류가 발생한 경우에도 지속됩니다.

이해하기가 상당히 어렵습니다.

로그 백업을 설명하기 위한 것이므로 트랜잭션에 대한 자세한 설명은 하지 않도록 하겠습니다. 이 강좌에서는 트랜잭션이란 데이터를 변경시키는 일련의 행위라고 생각해주시면 되겠습니다. 즉 테이블의 데이터를 수정(Update)하거나 삭제(Delete)하거나 추가(Insert)시키는 행위를 말합니다.

2. 트랜잭션 로그 백업 (Transaction Log Backup) 이란?

트랜잭션 로그 백업(일반적으로 로그 백업이라고 합니다)은 실제 데이터를 백업 받는게 아니고 데이터를 변경시킨 행위 자체를 백업받는것을 이야기 합니다. 예를 들어 고객 테이블에 '홍길동' 고객의 정보가 있는데 이 '홍길동' 고객이 이름이 '홍기동'으로 변경되었다고 한다면 데이터백업은 '홍기동' 이라는 데이터를 백업 받는 것이고 로그 백업은 '홍길동'의 이름이 '홍기동'으로 바뀌었다는 사실을 백업받은 것입니다. 만일 데이터에 문제가 생기는 이름을 바꾼 행위를 재수행 함으로써 데이터를 복원하게 됩니다.

SQL 서버는 관리자가 로그를 지우라는 설정을 하지 않는 한 문제 발생시 트랜잭션을 취소하거나 데이터베이스에 반영되지 않은 트랜잭션을 반영하기 위한 목적으로 로그를 남기게 됩니다. 이렇게 남는 로그는 관리자 입장에서 볼 때는 불의의 사태로부터 데이터를 복구 시킬 수 있는 중요한 수단이 됩니다.

또한 로그 백업은 데이터베이스의 사이즈가 너무 커서 데이터베이스 전체를 백업받기가 어려울 때 데이터의 손실을 막기 위한 또다른 백업 방법으로서 사용됩니다.

3. 로그에 대한 데이터베이스 설정

다음과 같은 내용을 데이터베이스 설정을 통하여 구현할 수 있습니다.

o트랜잭션 로그가 쌓이지 않게 하기
o 트랜잭션 로그가 쌓이게 하기
o 로그 파일의 사이즈가 무작정 커지지 않게 하기

1) 트랜잭션 로그가 쌓이지 않게 하기

트랜잭션 로그가 쌓이지 않게 하는 것은 실제 업무에서는 사용하지 않는게 좋습니다. 로그가 쌓이지 않게 하면 로그 파일이 무작정 증가하는 사태를 막을 수는 있으나 만일의 경우에 중요한 복구가 불가능 할 수 있기 때문입니다. 제 개인적인 생각으로는 시스템을 개발하는 동안에 테스트용 데이터가 기록되는 과정에서는 로그가 쌓이지 않게 설정을 해서 작업을 하고 실제 업무에 반영이 되는 시점에서는 로그가 쌓이게 하는게 좋습니다. 로그가 쌓이게 되면 관리자는 로그 백업을 이용해서 로그 사이즈가 계속 증가하는 사태를 막아야 합니다. (이에 대해서는 뒤에서 살펴보도록 하겠습니다)

로그가 쌓이지 않게 하기 위해서는 데이터베이스 복구 모델을 '단순(Simple)'로 하면 됩니다. SQL 서버 7.0의 경우는 데이터베이스 옵션 중에서 'trunc. log on chkpt' 옵션이 있는데 SQL 서버 2000에서는 복구모델을 이용해서 이 기능을 대신하게 됩니다. 물론 이전 버젼과의 호환을 위해서 'trunc. log on chkpt' 을 지정해주어도 되나 결과는 같습니다.

① 해당 데이터베이스 위해서 마우스 오른쪽 버튼을 눌러 표시되는 단축메뉴에서 [등록정보]를 선택하여 데이터베이스 등록 정보를 표시합니다.

② 다음 [그림 1]은 데이터베이스 옵션에서 복구모델을 '단순(Simple)'로 한 예입니다.


[그림 1]

복구 모델은 '단순' 으로 하게되면 커밋(Commit) 된 트랜잭션의 로그는 자동으로 제거되어 로그가 쌓이지 않게 됩니다.

만일 데이터베이스 복구 모델을 QA에서 변경하고자 하는 경우는 다음과 같이 ALTER DATABASE 를 이용하시면 됩니다.

USE Master
GO

ALTER DATABASE Sqlworld SET RECOVERY SIMPLE

o SIMPLE 위치에는 FULL 또는 BULK_LOGGED 가 지정될수 있습니다.

※ 복구 모델에 대한 자세한 설명을 원하시면 온라인설명서(Books Online)에서 '복구 모델'을 찾아보시기 바랍니다.

2) 트랜잭션 로그가 쌓이게 하기

로그가 쌓이게 하려면 위 [그림 1]에서 복구 모델을 '단순'이 아닌 '최대' 또는 '대량로그' 로 하시면 됩니다. 로그가 쌓이게 되면 로그가 증가함에 따라 로그 파일이 자동으로 증가하게 됩니다. (물론 데이터베이스 생성시 자동증가로 설정한 경우) 그러므로 이를 방치하게 되면 로그 파일이 무작정 증가하여 전체 하드디스크 공간을 차지하게 되는 경우가 있습니다. 실제 데이터베이스의 사이즈는 500MB인데 로그 사이즈는 50GB가 되는 경우도 있습니다.

로그 파일의 사이즈가 무작정 증가하지 않도록 하기 위해서는 정기적으로 로그 파일을 백업받아 주어야 합니다. 로그 백업은 만일의 경우에 대비해서 로그를 별도로 기록해주는 목적과 로그 사이즈를 줄여주는 목적 두가지를 가지고 있습니다.

로그 백업에 대해서는 다음 강좌에서 곧바로 다룰 것입니다. 우선 로그 사이즈 증가를 방지하기 위한 데이터베이스 옵션 설정에 대하여 살펴보도록 하겠습니다.

3) 로그 파일의 사이즈가 무작정 커지지 않게 하기

로그가 무작정 쌓이는 것은 백업을 통해서 방지 할 수 있다고 했습니다. 하지만 엄청난(?) 트랜잭션에 의해 발생된 로그로 인하여 로그 파일의 사이즈가 커진 상태에서 로그를 백업 받게 되면 로그는 줄어드나 커져버린 로그 파일의 사이즈는 별도의 설정이 없으면 자동으로 줄어들지 않게 됩니다.

로그 파일의 사이즈를 SQL 서버가 수시로 확인하여 불필요하게 커진 경우 그 안에 로그가 없으면(백업으로 인해) 로그 파일의 사이즈를 자동으로 줄어들게 할 수 있습니다. 이러한 설정은 데이터베이스의 '자동 축소' 옵션을 이용함으로써 가능합니다. 아래 [그림 2]은 '자동축소' 옵션을 선택한 화면의 예입니다.


[그림 2]

4. 정리

다음의 내용을 필히 기억해 주시기 바랍니다.

o 트랜잭션 로그는 만일의 사태에서 데이터 복구를 가능하게 하는 중요한 의미를 가지고 있어 함부로 지워서는 안됩니다.
o 실제 업무에서 데이터베이스를 사용 하는 경우는 데이터베이스 복구 모델을 '최대' 또는 '대량로그'로 설정하여 로그가 지워지지 않고 쌓이게 합니다.
o 쌓이는 로그를 정기적으로 백업 받아 로그가 꽉 차는 사태를 방지해야 합니다.
o 백업 받아 빈공간이 많아진 로그 파일을 자동으로 축소하기 위해서는 데이터베이스 옵션 중에서 '자동축소'를 선택하시면 됩니다.
o 자동 축소가 아닌 경우는 DBCC SHRINKDATABASE 또는 DBCC SHRINKFILE을 이용해서 수작업으로 축소시켜 주어야 합니다.

현재 자신이 사용하고 있는 데이터베이스의 옵션을 살펴보고 복구모델은 어떻게 되어 있는지, 로그 백업은 어떻게 이루어지고 있는지 확인을 해보시기 바랍니다.

다음 강좌에서는 로그 백업을 받는 방법을 살펴보고 증가된 로그를 백업받고 축소시켜주는 내용을 실습해 보고자 합니다.

========================================================================================

트렌젝션로그 줄이기 테스트

========================================================================================

1. 테스트를 위한 임시 테이블 만들기와 데이터 추가하기

예전의 테스트 처럼 sqlworld 데이터베이스를 이용하여 실제 상황을 재현해 보도록 하겠습니다. 현재 sqlworld 데이터베이스의 옵션은 다음과 같습니다.

o 데이터베이스 복구모델 : 최대
o 자동 축소기능 : 사용안함

다음 [그림 1]은 현재 sqlworld의 데이터베이스와 로그 사이즈를 보여줍니다.


[그림 1]

우선 현재의 sqlworld 데이터베이스를 백업 받도록 하겠습니다.(모든 백업의 기본은 전체 백업입니다)

USE Master
GO

BACKUP DATABASE sqlworld TO DISK = 'E:\Data\sqlworld.bak'

1) 테이블 만들기

다음과 같이 테스트 테이블 Test_Log를 만들도록 하겠습니다.

USE sqlworld
GO

CREATE TABLE Test_Log
(
col1 int PRIMARY KEY,
col2 char(1000)
)

다음과 같이 스크립트를 이용해서 레코드를 추가하도록 하겠습니다.

SET NOCOUNT ON
GO

DECLARE @num int
SET @num = 1
WHILE @num < 5001
BEGIN
INSERT INTO Test_Log VALUES(@num, REPLICATE('A',1000))
SET @num = @num + 1
END

위 작업을 통해서 오천개 정도의 레코드가 Test_Log 테이블에 추가되었습니다. 이로 인하여 sqlworld 데이터 사이즈와 로그 사이즈가 증가했습니다. 아래 [그림 2]가 그 내용을 확인해주고 있습니다.


[그림2]

만일 sqlworld 데이터베이스 복구 모델이 '단순' 이었다면 위와 같은 작업이 완료가 되면 로그가 자동으로 제거되어 [그림 2] 처럼 로그 사이즈가 증가하지 않을 것입니다.(이 내용이 잘 이해되지 않으면 바로 이전의 강좌를 다시한번 읽어보시기 바랍니다)

2) 데이터를 변경하여 데이터와 로그 사이즈 증가시키기

다음과 같이 데이터를 변경하고 삭제하여 로그 사이즈를 증가시켜 보겠습니다.

UPDATE Test_Log Set col2 = REPLICATE('B',1000) WHERE col1 < 1000
GO
DELETE FROM Test_Log WHERE col1 < 2000
GO
DELETE FROM Test_Log WHERE col1 < 4000

위 작업을 수행하니 Test_Log 테이블의 데이터 변경에 대한 로그와삭제에 대한 로그가 증가함에 따라 sqlworld 데이터베이스의 사이즈는 다음 [그림 3]과 같이 변하였습니다.


[그림3]

로그 파일은 0.99 MB에서 6.74 MB 로 사이즈가 증가함을 알 수 있습니다.

이처럼 트랜잭션이 발생함에 따라 로그가 계속 쌓이면서 로그 파일의 사이즈가 계속 증가함을 알 수 있습니다. 이제 확인할 내용은 로그 백업을 하면 로그가 줄어드는지, 그리고 로그 파일의 사이즈는 어떻게 되는지 하는 것입니다.

2. 트랜잭션 로그 백업을 통한 로그 줄이기

로그 백업은 예전에 배운 데이터베이스 백업과 비슷한 방법으로 이루어 집니다. EM을 통해서 할 수도 있고 QA에서 직접 BACKUP LOG 문을 이용하여 백업 받을 수도 있습니다. 이 테스트에서는 QA를 이용하여 현재 sqlworld 데이터베이스의 로그를 백업받도록 하겠습니다.

USE Master
GO

BACKUP LOG sqlworld TO DISK = 'E:\Data\sqlworld_log.bak'

수행하는 방법은 BACKUP DATABASE 문과 비슷합니다.

만일 로그를 백업 받지는 않고 단지 현재의 로그만 지우고 싶으면 다름과 같이 TRUNCATE_ONLY 옵션을 사용하면 됩니다.

USE Master
GO

BACKUP LOG WITH TRUNCATE_ONLY

이렇게 로그를 백업 받은 후의 sqlworld 데이터베이스의 사이즈를 보니 다음 [그림 4]와 같이 변한 것을 볼 수 있습니다.


[그림4]

로그 사이즈가 3.64 MB 에서 1.84 MB로 줄어 들었습니다. 하지만 로그 파일의 사이즈는 아직도 6.74 MB로서 원래 사이즈를 그대로 유지하고 있습니다. 결국 4.9 MB의 빈 공간이 놀고(?) 있게 됩니다.

3. DBCC SHRINKDATABASE을 이용한 로그파일 사이즈 줄이기

DBCC SHRINKDATABASE 을 이용해서 수작업으로 비어있는 로그 사이즈를 없애서 로그 파일의 사이즈를 축소해 보도록 하겠습니다.

USE Master
GO

DBCC SHRINKDATABASE(Sqlworld)

결과는 다음 [그림 5]와 같이 변한것을 알 수 있습니다.


[그림5]

로그 파일의 사이즈가 [그림 4]와 비교 할 때 6.74 MB에서 2.49 MB로 줄어든 것을 알 수 있습니다. DBCC SHRINKDATABASE에 의해 줄어드는 로그 파일의 사이즈는 실제 데이터 파일의 빈 공간이 많을 수록 효과가 좋습니다. DBCC SHRINKDATABASE 또는 DBCC SHRINKFILE에 대한 내용은 이전의 데이터베이스에 대한 강좌를 참고해 주시기 바랍니다.

4. 정리

로그 파일 사이즈 문제를 가지고 질문을 하시는 경우가 상당히 많습니다. 지금까지 설명드린 내용이 도움이 되었으면 합니다. 데이터베이스 관리자는 항상 로그 파일에 대한 관심을 가지고 모니터링을 해야 합니다. 하드디스크의 여유 공간은 충분한지도 수시로 확인하셔야 합니다. 그렇지 않으면 TempDB가 꽉 찼다는 등의 오류가 발생하게 됩니다.

========================================================================================

백업과 복원 정리

========================================================================================

이번 강좌에서는 복원에 대한 전체적인 내용을 살펴 보도록 하겠습니다. 즉, 전체 백업과 차등 백업 및 로그 백업을 받은 상태에서 복원을 하는 순서와 방법을 살펴보게 됩니다.

1. 테스트를 위한 임시 테이블 만들기와 데이터 추가하기

우선 현재의 sqlworld 데이터베이스에 테스트 테이블을 만들고 백업을 받도록 하겠습니다.

1) 테이블 만들기

다음과 같이 테스트 테이블 Test_Log를 만들도록 하겠습니다.

USE sqlworld
GO

CREATE TABLE Test1
(
col1 int,
col2 char(05)
)
GO

2) 데이터 추가하기

다음과 같이 스크립트를 이용해서 레코드를 추가하도록 하겠습니다.

INSERT INTO Test1 VALUES(1,'AAAAA')
INSERT INTO Test1 VALUES(2,'BBBBB')
INSERT INTO Test1 VALUES(3,'CCCCC')
INSERT INTO Test1 VALUES(4,'DDDDD')
INSERT INTO Test1 VALUES(5,'EEEEE')
GO

위 작업을 통해서 다섯개의 레코드가 Test1 테이블에 추가되었습니다.

2. 데이터베이스 백업

1) 데이터베이스 백업을 위한 백업 장치 만들기

다음과 같이 데이터베이스를 백업 받기 위한 백업 장치를 만들도록 하겠습니다.

EXEC sp_addumpdevice 'disk', 'sqlworld_dump', 'E:\Data\sqlworld_dump.bak'
GO

※ 백업 장치 만드는 방법에 대해서 이해하고 있지 못하신 분들은 예전에 진행된 백업 장치 만들기 강좌를 참고하여 주시기 바랍니다.

2) 데이터베이스 전체 백업

앞에서 만든 sqlworld_dump 백업 장치에 현재까지의 sqlworld 데이터베이스를 전체 백업 받도록 하겠습니다.

BACKUP DATABASE sqlworld TO sqlworld_dump WITH INIT
GO

이 전체 백업 작업으로 인해서 다섯개의 레코드가 추가된 Test1 테이블이 백업된 것을 알 수 있습니다.

※ 전체 백업에 대해 이해하고 있지 못하신 분들은 예전에 진행된 전체 백업 받기강좌를 참고하여 주시기 바랍니다.

3) 데이터 추가하기

다음과 같이 스크립트를 이용해서 다섯개의 레코드를 더 추가하도록 하겠습니다.

INSERT INTO Test1 VALUES(6,'FFFFF')
INSERT INTO Test1 VALUES(7,'GGGGG')
INSERT INTO Test1 VALUES(8,'HHHHH')
INSERT INTO Test1 VALUES(9,'IIIII')
INSERT INTO Test1 VALUES(10,'JJJJJ')
GO

위 작업을 통해서 다섯개의 레코드가 Test1 테이블에 추가되었습니다. 이제 Test1 테이블에는 col1 컬럼이 1 에서 10까지의 값을 갖는 열개의 레코드가 기록되어 있습니다.

4) 데이터베이스 차등 백업

앞에서 만든 sqlworld_dump 백업 장치에 현재까지의 sqlworld 데이터베이스를 차등 백업 받도록 하겠습니다.

BACKUP DATABASE sqlworld TO sqlworld_dump WITH DIFFERENTIAL
GO

이 차등 백업에 의해 앞의 3) 번에서 이루어진 다섯개의 레코드를 추가한 내용이 백업되었습니다.

※ 전체 백업에 대해 이해하고 있지 못하신 분들은 예전에 진행된 차등백업 받기 강좌를 참고하여 주시기 바랍니다.

5) 데이터 또 추가하기

다음과 같이 스크립트를 이용해서 다섯개의 레코드를 더 추가하도록 하겠습니다.

INSERT INTO Test1 VALUES(11,'KKKKK')
INSERT INTO Test1 VALUES(12,'LLLLL')
INSERT INTO Test1 VALUES(13,'MMMM')
INSERT INTO Test1 VALUES(14,'NNNNN')
INSERT INTO Test1 VALUES(15,'OOOOO')
GO

위 작업을 통해서 다섯개의 레코드가 Test1 테이블에 추가되었습니다. 이제 Test1 테이블에는 col1 컬럼이 1 에서 10까지의 값을 갖는 열개의 레코드가 기록되어 있습니다.

6) 데이터베이스 차등 백업

앞에서 만든 sqlworld_dump 백업 장치에 현재까지의 sqlworld 데이터베이스를 차등 백업 받도록 하겠습니다.

BACKUP DATABASE sqlworld TO sqlworld_dump WITH DIFFERENTIAL
GO

이 차등 백업에 의해 앞의 3)번과 5) 번에서 이루어진 열개의 레코드를 추가한 내용이 백업되었습니다.

7) 데이터 변경

다음과 같이 스크립트를 이용해서 col1 컬럼이 6보다 작은 다섯개의 레코드에 대하여 col2 컬럼을 전부 'XXXXX' 로 변경하였습니다.

UPDATE Test1 SET col2 = 'XXXXX' WHERE col1 < 6
GO

8) 트랜잭션 로그 백업

7) 번에서 이루어진 변경 처리를 로그 백업을 받도록 하겠습니다. 로그 백업을 위해 새로운 백업장치 sqlworld_log_dump 를 만들어 이 장치에 로그를 백업 받도록 하겠습니다.

EXEC sp_addumpdevice 'disk', 'sqlworld_log_dump', 'E:\Data\sqlworld_log_dump.bak'
GO

BACKUP LOG sqlworld TO sqlworld_log_dump WITH INIT
GO

9) 데이터 추가 변경 (문제 발생)

다음과 같이 스크립트를 이용해서 col1 컬럼이 5보다 큰 코드에 대하여 col2 컬럼을 전부 'YYYYY' 로 변경하였습니다.

UPDATE Test1 SET col2 = 'YYYYY'
GO

앗! WHERE 절을 적지 않아서 모든 레코드의 col2 컬럼이 'YYYYY'로 바뀌어 버렸습니다.

3. 현재까지 백업된 내용 정리

지금까지 이루어진 4번의 백업 작업에 의해 Test1 테이블에 대해서는 다음과 같은 내용이 백업되었습니다.

1) 전체 백업 : col1 컬럼에 1 부터 5 까지 다섯개의 레코드
2) 첫번째 차등 백업 : col1 컬럼에 6 부터 10 까지 다섯개의 레코드
3) 두번째 차등 백업 : col1 컬럼에 6 부터 15 까지 열개의 레코드
4) 로그 백업 : col1 컬럼이 6 보다 작은 다섯개의 레코드에 대해 col2 컬럼을 'XXXXX' 로 변경 한 행위

4. T-SQL을 이용한 데이터베이스 복원

현재의 상황에서 앞에서 수행되었던 모든 백업을 복원하여 col2 컬럼이 'YYYYY'로 바뀐 사태를 수습하도록 하겠습니다.

1) 전체 백업 복원

USE master
GO

RESTORE DATABASE sqlworld FROM sqlworld_dump WITH FILE =1, NORECOVERY
GO

o NORECOVERY 옵션으로 전체 백업 받은 내용을 복원 했습니다.
o FILE = 1은 sqlworld_dump 백업 장치에 여러 가지 백업(한번의 전체 백업과 두번의 차등 백업)이 있으므로 이 중에서 첫번째, 즉 전체 백업을 선택한다는 의미입니다.

2) 차등 백업 복원

RESTORE DATABASE sqlworld FROM sqlworld_dump WITH FILE = 3, NORECOVERY
GO

o NORECOVERY 옵션으로 차등백업 받은 내용을 복원 했습니다.
o FILE = 3 은 sqlworld_dump 백업 장치에 여러 가지 백업(한번의 전체 백업과 두번의 차등 백업)이 있으므로 이 중에서 세번째, 즉 두번째의 차등 백업을 선택한다는 의미입니다.
o 결국 첫번째의 차등 백업은 사용될 필요가 없습니다.

3) 로그 백업 복원

RESTORE LOG sqlworld FROM sqlworld_log_dump WITH RECOVERY
GO

o 로그 복원이 복원의 마지막 단계 이므로 RECOVERY 옵션을 사용하였습니다.

5. 과제

다음의 내용을 구현해 보시기 바랍니다.

1) 위에서 T-SQL로 구현한 복원을 EM(Enterprise Manager)를 이용해서 구현해 보세요.
2) 첫번째 차등 백업, 즉 col1 컬럼이 1부터 10까지만 가지고 있는 내용을 복원해 보세요.

부족한 부분은 관련된 강좌를 다시한번 확인하신 후 처리해 보시기 바랍니다.

Posted by 1010
98..Etc/Hudson2010. 11. 30. 12:53
반응형

현재 프로젝트 내에서 지속적인 통합서버, 통치 CI 서버를 운영하고 있습니다.

사용하는 제품(Product)은 허드슨(Hudson) 입니다.

허드슨 서버는 구 펜티엄 2.8 XP 데스크탑 PC 입니다.

개발용 파일서버로도 이용하고 있습니다만 무리없이 잘 사용하고 있습니다.



메인 화면입니다.

빌드용 JOB과 Deploy 용 JOB을 따로 나누어 놓았습니다.

 


기본 Build JOB 입니다.


Cobertura Coverage Report와 CPD (Copy & Paste Detector), 그리고 Findbugs, PMD 메뉴가 보입니다.

 

JDK 1.4 프로젝트라 Findbugs 는 1.2 대를 사용하고 있습니다.



 

  




 











 

이건 실제 기본 빌드 JOB의 메인 화면 입니다.



Cobertura 커버리지 보고서 입니다.

오늘 오후는 밀린 테스트 케이스도 작성하고, 기능테스트도 수행하는 테스트 DAY 였는데, 네트워크 문제로 알로 반나절을 날렸습니다. (정말 가지가지로 괴롭힙니다.... 라고 쓰고, 핑계김에 쉬었습니다라고 읽습니다.)

최근 빌드 이력입니다. 아쉽게도 실패하는 테스트들이 있어서 UNStable 상태로 표시되고 있네요.

 


최근에 진행한 빌드 정보 입니다.


소스를 커밋하면 SCM 트리거가 알아서 돕니다. (두번째 아이콘 참조)

이번 빌드로 CI 게임 점수도 10점이 올랐네요!!

 


네! 그렇습니다. 제 점수가 올랐던 거네요. ㅎ~~

PMD 3건 수정, 그리고 실패하는 테스트를 하나 복구했습니다.



이번주의 팀원들의 점수 입니다. (Leader Board)

0점인 사람들은 현재 화면부분을 작성중이라 점수 변동이 없네요. (0점에서 시작했습니다)

별건 아니지만, 나름 개발자들에게 동기 부여가 됩니다.


Hudson 에 대해서는 할 얘기가 조금 더 있습니다만, 추후에 다시 적어 보겠습니다.


기타 허드슨 CI 서버의 설치및 일반 사용 가이드는 http://blog.doortts.com/80 를 참고하시면 조금더 도움 되시지 않을까 생각합니다.


그럼, 즐거운 개발 되세요.


출처 : http://doortts.textcube.com/17

Posted by 1010
56. Eclipse Etc.../Eclipse2010. 11. 29. 10:36
반응형

필수 플러그인

설치하면 좋은 플러그인


cn출처 : http://benelog.springnote.com/pages/4214931


Posted by 1010
98..Etc/Confluence2010. 11. 26. 17:03
반응형

Confluence 3.4.x

User's Guide

The Confluence User's Guide is for project managers, developers, testers – anyone who uses Confluence. New to Confluence? Start by exploring the Confluence dashboard and learning about spaces, pages and blog posts. Try creating a new space, then add a page to that space, add some content and a comment to that page and then export the page to PDF. Using the SharePoint Connector? Visit the SharePoint Connector User's Guide. Want to build up your skills from white belt (beginner) to Confluence master? Try our wiki ninja tutorial. Interested in what other people are doing with Confluence or want to share your own tips? See our tips via Twitter and tips of the trade pages.

Administrator's Guide

The Confluence Administrator's Guide is for people with Confluence administration rights. It will help you set up users and groups, security for users, groups and spaces, and keep track of any changes and updates made within your Confluence site. You may want to customise the look and feel of Confluence, by applying a theme to a space or modifying colour schemes and layouts. Admin tasks such as backup are also covered. You may also find the Knowledge Base, FAQ and the Confluence Announcements and User forums useful. If you are using the Confluence SharePoint Connector, see the SharePoint Connector Administrator's Guide.

Installation Guide

The Confluence Installation Guide is for people who are installing Confluence for the first time. Check the requirements and supported platforms, then download and install Confluence. Where to next? The Confluence 101 will help you get started. When setting up Confluence, load the Example Site ('Demonstration Space'), which contains a tutorial and additional content to help you get familiar with using Confluence. For help with installing and configuring the Confluence SharePoint Connector, see the SharePoint Connector Installation and Upgrade Guide. If you are using other Atlassian products, take a look at the Integration Guide.

Upgrade Guide

The Confluence Upgrade Guide is for people who are upgrading their instance of Confluence. Start by reading the latest Release Notes, the Upgrade Notes Overview and version-specific Upgrade Notes for the version to which you are upgrading. Then, download Confluence and follow the main Upgrade Guide.

Developer Resources

These resources are for software developers who want to create their own plugins for Confluence. Take a look at the Confluence developer documentation and the API documentation. You may also find the Confluence developers forum useful. (Click here to subscribe.)

Labels

documentation documentation Delete
installation installation Delete
example example Delete
admin admin Delete
 
  
 
Looking for a label? Just start typing.
Posted by 1010
98..Etc/Trac2010. 11. 26. 16:58
반응형
Posted by 1010
04.Anddoid2010. 11. 26. 16:41
반응형

시작

이 기사에서는 까다로운 상황을 처리하는 데 필요한 Android SDK 도구 중 일부에 대해 다룬다. Android 애플리케이션을 개발하려면 JDK(Java Development Kit)가 필요한 최신 Android SDK가 필요하다. 필자는 Android 2.2 및 JDK 1.6.0_17을 사용했다(이러한 도구에 대한 링크는 참고자료 참조). 실제 장치는 없어도 된다. 이 기사에 있는 모든 코드는 SDK와 함께 제공되는 Android 에뮬레이터에서 정상적으로 실행된다. 이 기사에서는 기본적인 Android 개발에 대해서는 설명하지 않기 때문에 사용자는 Android 프로그래밍에 익숙해야 한다. 하지만 Java 프로그래밍 언어에 대한 지식이 있으면 이 기사의 내용을 이해할 수 있을 것이다.


동시성 및 네트워킹

자주 사용하는 약어

  • API: Application Programming Interface
  • SQL: Structured Query Language
  • SDK: Software Developer Kit
  • UI: User Interface
  • XML: Extensible Markup Language

Android 애플리케이션의 가장 일반적인 태스크 중 하나는 네트워크를 통해 데이터를 검색하거나 원격 서버로 전송하는 것이다. 이 조작을 수행하면 사용자에게 표시하기 원하는 일부 새로운 데이터가 생성되는 경우가 자주 있다. 이는 사용자 인터페이스를 수정해야 한다는 것을 의미한다. 대부분의 개발자는 사용자가 기본 UI 스레드에서 네트워크(특히 네트워크 연결 속도가 매우 느린 휴대전화)를 통해 데이터에 액세스하는 것과 같은 잠재적인 장기 실행 태스크를 수행해서는 안 된다는 것을 알고 있다. 이러한 장기 실행 태스크를 수행하면 해당 태스크가 완료될 때까지 애플리케이션이 멈춘다. 실제로 이 태스크를 수행하는 데 5초 넘게 걸리는 경우 Android 운영 체제는 그림 1에 있는 악명 높은 Application Not Responding 대화 상자를 표시한다.


그림 1. Android의 악명 높은 Application Not Responding 대화 상자
Android의 악명 높은 Application Not Responding 대화 상자 화면 캡처

사용자의 네트워크 연결 속도가 얼마나 느릴지는 알 수 없다. 모험을 피하기 위해서는 이러한 태스크를 다른 스레드에서 수행해야 하거나 최소한 기본 UI 스레드에서는 수행해서는 안 된다. 대부분은 아니더라도 다수의 Android 애플리케이션은 복수의 스레드를 처리해야 하기 때문에 동시성을 처리해야 한다. 애플리케이션에는 데이터를 로컬로 유지해야 할 경우가 있는데 이 경우 Android의 로컬 데이터베이스가 매력적인 옵션이다. 이러한 세 가지 시나리오(다른 스레드, 동시성 및 로컬로 데이터 유지) 모두에는 Java 환경에서 이러한 사항을 수행하는 일부 표준 방식이 있다. 하지만 이 기사에서 알 수 있듯이 Android는 일부 다른 옵션을 제공한다. 이러한 각각의 옵션과 이러한 옵션의 장단점에 대해 살펴본다.


Android 네트워크

Java 프로그래밍에서 네트워크를 통해 호출을 작성하는 것은 간단하다. 익숙한 java.net 패키지에는 이를 수행하는 데 필요한 몇 가지 클래스가 포함되어 있다. 이러한 클래스는 대부분 Android에도 있으며 다른 Java 애플리케이션에서와 같이 실제로 java.net.URLjava.net.URLConnection과 같은 클래스를 사용할 수 있다. 하지만 Android에는 Apache HttpClient 라이브러리가 포함되어 있다. 이 방식은 Android에서 네트워킹을 수행하기 위해 선호되는 방법이다. 일반적인 Java 클래스를 사용하는 경우라도 Android의 구현에서는 계속 HttpClient를 사용한다. Listing 1에서는 이 필수 라이브러리의 사용 예를 보여 준다. (모든 소스 코드는 다운로드를 참조한다.)


Listing 1. Android에서 Http Client 라이브러리 사용하기
private ArrayList<Stock> fetchStockData(Stock[] oldStocks) 
    throws ClientProtocolException, IOException{
    StringBuilder sb = new StringBuilder();
    for (Stock stock : oldStocks){
        sb.append(stock.getSymbol());
        sb.append('+');
    }
    sb.deleteCharAt(sb.length() - 1);
    String urlStr = 
        "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" + 
                sb.toString();
    HttpClient client = new DefaultHttpClient();
    HttpGet request = new HttpGet(urlStr.toString());
    HttpResponse response = client.execute(request);
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(response.getEntity().getContent()));
    String line = reader.readLine();
    int i = 0;
    ArrayList<Stock> newStocks = new ArrayList<Stock>(oldStocks.length);
    while (line != null){
        String[] values = line.split(",");
        Stock stock = new Stock(oldStocks[i], oldStocks[i].getId());
        stock.setCurrentPrice(Double.parseDouble(values[1]));
        stock.setName(values[2]);
        newStocks.add(stock);
        line = reader.readLine();
        i++;
    }
    return newStocks;
}

이 코드는 Stock 오브젝트의 배열을 포함한다. 이러한 오브젝트는 사용자가 소유하는 주식에 대한 정보(예: 종목 기호, 주가 등)와 이 주식에 대해 사용자가 지불한 금액과 같은 보다 개인적인 정보를 함께 보유하는 기본적인 데이터 구조 오브젝트이다. HttpClient 클래스를 사용하여 Yahoo Finance에서 동적 데이터(예: 주식의 현재 가격)를 검색한다. HttpClientHttpUriRequest를 사용하며 이 경우에서 사용자는 HttpUriRequest의 서브클래스인 HttpGet을 사용한다. 이와 비슷하게 데이터를 원격 서버에 게시해야 하는 경우를 위한 HttpPost 클래스가 있다. 클라이언트로부터 HttpResponse를 수신하면 응답의 기본 InputStream에 액세스하여 버퍼링한 후 구문 분석하여 주식 데이터를 얻을 수 있다.

네트워크를 통해 데이터를 검색하는 방법을 살펴봤으니 이제는 이 데이터를 사용하여 멀티스레딩을 통해 Android UI를 효율적으로 업데이트하는 방법을 살펴본다.


Android 동시성의 실제

Listing 1에 있는 코드를 애플리케이션의 기본 UI 스레드에서 실행하는 경우 사용자 네트워크의 속도에 따라 Application Not Responding 대화 상자가 나타날 수 있다. 따라서 스레드를 파생(spawn)시켜 이 데이터를 페치해야 한다. Listing 2에서는 이를 수행하는 한 가지 방법을 보여 준다.


Listing 2. 기본 멀티스레딩(작동하지 않으므로 수행하지 않음)
private void refreshStockData(){
    Runnable task = new Runnable(){
        public void run() {
            try {
                ArrayList<Stock> newStocks = 
                    fetchStockData(stocks.toArray(
                                  new Stock[stocks.size()]));
                for (int i=0;i<stocks.size();i++){
                    Stock s = stocks.get(i);
                    s.setCurrentPrice(
                                  newStocks.get(i).getCurrentPrice());
                    s.setName(newStocks.get(i).getName());
                    refresh();
                }
            } catch (Exception e) {
                Log.e("StockPortfolioViewStocks", 
                            "Exception getting stock data", e);
            }
        }
    };
    Thread t = new Thread(task);
    t.start();
}

Listing 2의 캡션은 기본 코드임을 나타내고 있으며 실제로도 그렇다. 이 간단한 예제에서는 Listing 1fetchStockData 메소드를 Runnable 오브젝트에서 랩핑한 후 새 스레드에서 실행하여 호출한다. 이 새 스레드에서는 둘러싸는 Activity(UI를 작성하는 클래스)의 멤버 변수인 stocks에 액세스한다. 이름이 나타내듯 이는 Stock 오브젝트의 데이터 구조이다(이 예에서는 java.util.ArrayList). 달리 말하면 두 개의 스레드(기본 UI 스레드와 파생(spawn)된 스레드(Listing 2에 있는 코드에서 호출됨) 사이에서 데이터를 공유한다. 파생(spawn)된 스레드에서 공유 데이터를 수정하면 Activity 오브젝트에 대한 refresh 메소드를 호출하여 UI를 업데이트한다.

Java Swing 애플리케이션을 프로그래밍한 경우에는 이와 같은 패턴을 따랐을 것이다. 하지만 Android에서는 이 패턴이 작동하지 않는다. 파생(spawn)된 스레드는 UI를 전혀 수정할 수 없다. 그렇다면 UI를 멈추지 않고 데이터가 수신되면 UI를 수정할 수 있는 방식으로 데이터를 검색하려면 어떻게 해야 하는가? android.os.Handler 클래스를 사용하면 스레드 사이에서 조정하고 통신할 수 있다. Listing 3에서는 Handler를 사용하는 업데이트된 refreshStockData 메소드를 보여 준다.


Listing 3. Handler를 사용하여 실제로 작동하는 멀티스레딩
private void refreshStockData(){
    final ArrayList<Stock> localStocks = 
          new ArrayList<Stock>(stocks.size());
    for (Stock stock : stocks){
        localStocks.add(new Stock(stock, stock.getId()));
    }
    final Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            for (int i=0;i<stocks.size();i++){
                stocks.set(i, localStocks.get(i));
            }
            refresh();
        }
    };
    Runnable task = new Runnable(){
        public void run() {
            try {
                ArrayList<Stock> newStocks = 
                    fetchStockData(localStocks.toArray(
                                  new Stock[localStocks.size()]));
                for (int i=0;i<localStocks.size();i++){
                    Stock ns = newStocks.get(i);
                    Stock ls = localStocks.get(i);
                    ls.setName(ns.getName());
                    ls.setCurrentPrice(ns.getCurrentPrice());
                }
                handler.sendEmptyMessage(RESULT_OK);
            } catch (Exception e) {
                Log.e("StockPortfolioViewStocks", 
                            "Exception getting stock data", e);
            } 
        }
    };
    Thread dataThread = new Thread(task);
    dataThread.start();
}

Listing 2에 있는 코드와 Listing 3에 있는 코드 사이에는 두 가지 주요 차이점이 있다. 두드러진 차이점은 Handler의 존재 여부이다. 두 번째 차이점은 파생(spawn)된 스레드에서 UI를 수정하지 않는다는 것이다. 대신 메시지를 Handler에 전송하면 Handler가 UI를 수정한다. 또한 이전과 같이 스레드에서 stocks 멤버 변수를 수정하지 않는다는 것에 유의한다. 대신 데이터의 로컬 사본을 수정한다. 이는 반드시 필요한 것은 아니지만 이 방법이 더 안전하다.

Listing 3에서는 동시 프로그래밍에서 매우 일반적인 패턴으로 판명된 방식인 데이터를 복사한 후 일부 장기 태스크를 수행하는 새 스레드에 전달하고 결과 데이터를 다시 기본 UI 스레드에 전달한 후 해당 데이터로 기본 UI 스레드를 업데이트하는 방식을 보여 준다. Handlers는 Android에서 이를 위한 기본 통신 메커니즘이며 이 패턴의 구현을 더 용이하게 만든다. 하지만 Listing 3에는 여전히 상용구 코드가 상당히 포함되어 있다. 다행히도 Android는 이 상용구 코드 대부분을 캡슐화하여 제거할 수 있는 방법을 제공한다. Listing 4에서 이 방법을 보여 준다.


Listing 4. AsyncTask를 사용한 편리한 멀티스레딩
private void refreshStockData() {
    new AsyncTask<Stock, Void, ArrayList<Stock>>(){
        @Override
        protected void onPostExecute(ArrayList<Stock> result) {
            ViewStocks.this.stocks = result;
            refresh();
        }

        @Override
        protected ArrayList<Stock> doInBackground(Stock... stocks){
            try {
                return fetchStockData(stocks);
            } catch (Exception e) {
                Log.e("StockPortfolioViewStocks", "Exception getting stock data", e);
            }
            return null;
        }
    }.execute(stocks.toArray(new Stock[stocks.size()]));
}

여기서 알 수 있듯이 Listing 4에는 Listing 3보다 훨씬 적은 상용구가 포함되어 있다. 사용자는 스레드 또는 Handlers를 작성하지 않는다. AsyncTask를 사용하여 모두를 캡슐화한다. AsyncTask를 작성하려면 doInBackground 메소드를 구현해야 한다. 이 메소드는 항상 별도의 스레드에서 실행되므로 자유롭게 장기 실행 태스크를 호출할 수 있다. 입력 유형은 작성되는 AsyncTask의 유형 매개변수에서 제공된다. 이 경우에는 첫 번째 유형 매개변수가 Stock이었기 때문에 doInBackground에는 Stock 오브젝트의 배열이 전달된다. 마찬가지로 ArrayList<Stock>AsyncTask의 세 번째 유형 매개변수이기 때문에 리턴된다. 이 예제에서 필자는 onPostExecute 메소드도 대체하도록 선택했다. 이 메소드는 doInBackground에서 다시 제공되는 데이터에 대해 수행해야 할 사항이 있는 경우 구현할 선택적 메소드이다. 이 메소드는 항상 기본 UI 스레드에서 실행되므로 UI를 수정하는 데 아주 적합하다.

AsyncTask를 사용하면 멀티스레드 코드를 매우 단순화할 수 있다. AsyncTask는 개발 경로에서 다수의 동시성 위험을 제거한다. 하지만 AsyncTask 오브젝트에 있는 doInBackground 메소드가 실행되는 동안 장치에서 방향이 변경되면 발생하는 문제와 같은 AsyncTask의 일부 잠재적 문제점을 여전히 찾을 수 있다. 이와 같은 경우를 처리하는 방법에 대한 일부 기술은 참고자료에 있는 링크를 참조한다.

이제 Android가 데이터베이스에 대한 일반적인 Java 작업 방식과 상당한 차이를 보이는 또다른 일반적인 태스크에 대해 살펴본다.


Android 데이터베이스 연결

Android의 한 가지 매우 유용한 기능은 로컬 관계형 데이터베이스가 있다는 것이다. 물론 데이터를 로컬 파일에 저장할 수 있지만 RDBMS(Relational Database Management System)를 사용하여 데이터를 저장하는 것이 더 유용한 경우가 자주 발생한다. Android는 Android와 같은 임베디드 시스템에 최적화된 유명한 SQLite 데이터베이스를 제공한다. 이 데이터베이스는 Android의 많은 핵심 애플리케이션에서 사용한다. 예를 들어, 사용자 주소록이 SQLite 데이터베이스에 저장된다. Android의 Java 구현을 고려하면 JDBC를 사용하여 이러한 데이터베이스에 액세스할 수 있을 것으로 예상할 수 있다. 놀랍게도 Android에는 JDBC API의 대부분을 구성하는 java.sqljavax.sql 패키지도 포함되어 있다. 하지만 로컬 Android 데이터베이스에 대해 작업하는 경우에는 이것이 아무 쓸모가 없는 것으로 판명되었다. 대신 android.databaseandroid.database.sqlite 패키지를 사용한다. Listing 5에서는 이러한 클래스를 사용하여 데이터를 저장하고 검색하는 예제를 보여 준다.


Listing 5. Android를 사용한 데이터베이스 액세스
public class StocksDb {
    private static final String DB_NAME = "stocks.db";
    private static final int DB_VERSION = 1;
    private static final String TABLE_NAME = "stock";
    private static final String CREATE_TABLE = "CREATE TABLE " + 
        TABLE_NAME + " (id INTEGER PRIMARY KEY, symbol TEXT, max_price DECIMAL(8,2), " +
            "min_price DECIMAL(8,2), price_paid DECIMAL(8,2), " +
            "quantity INTEGER)";
    private static final String INSERT_SQL = "INSERT INTO " + TABLE_NAME +
            " (symbol, max_price, min_price, price_paid, quantity) " +
            "VALUES (?,?,?,?,?)";
    private static final String READ_SQL = "SELECT id, symbol, max_price, " +
            "min_price, price_paid, quantity FROM " + TABLE_NAME;
    private final Context context;
    private final SQLiteOpenHelper helper;
    private final SQLiteStatement stmt;
    private final SQLiteDatabase db;
    public StocksDb(Context context){
        this.context = context;
        helper = new SQLiteOpenHelper(context, DB_NAME, null, 
                DB_VERSION){
            @Override
            public void onCreate(SQLiteDatabase db) {
                db.execSQL(CREATE_TABLE);
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, 
                    int newVersion) {
                throw new UnsupportedOperationException();
            }
        };
        db = helper.getWritableDatabase();
        stmt = db.compileStatement(INSERT_SQL);
    }
    public Stock addStock(Stock stock){
        stmt.bindString(1, stock.getSymbol());
        stmt.bindDouble(2, stock.getMaxPrice());
        stmt.bindDouble(3, stock.getMinPrice());
        stmt.bindDouble(4, stock.getPricePaid());
        stmt.bindLong(5, stock.getQuantity());
        int id = (int) stmt.executeInsert();
        return new Stock (stock, id);
    }
    public ArrayList<Stock> getStocks() {
        Cursor results = db.rawQuery(READ_SQL, null);
        ArrayList<Stock> stocks = 
                 new ArrayList<Stock>(results.getCount());
        if (results.moveToFirst()){
            int idCol = results.getColumnIndex("id");
            int symbolCol = results.getColumnIndex("symbol");
            int maxCol = results.getColumnIndex("max_price");
            int minCol = results.getColumnIndex("min_price");
            int priceCol = results.getColumnIndex("price_paid");
            int quanitytCol = results.getColumnIndex("quantity");
            do {
                Stock stock = new Stock(results.getString(symbolCol), 
                        results.getDouble(priceCol), 
                        results.getInt(quanitytCol), 
                                    results.getInt(idCol));
                stock.setMaxPrice(results.getDouble(maxCol));
                stock.setMinPrice(results.getDouble(minCol));
                stocks.add(stock);
            } while (results.moveToNext());
        }
        if (!results.isClosed()){
            results.close();
        }
        return stocks;
    }
    public void close(){
        helper.close();
    }    
}

Listing 5에 있는 클래스는 주식 정보를 저장하는 데 사용된 SQLite 데이터베이스를 완전히 캡슐화한다. 애플리케이션에서 사용될 뿐만 아니라 애플리케이션에서 작성하기도 하는 임베디드 데이터베이스에 대해 작업하기 때문에 데이터베이스를 작성하는 데 필요한 코드를 제공해야 한다. Android는 이를 위해 SQLiteOpenHelper라는 유용한 추상 헬퍼 클래스를 제공한다. 이를 구현하려면 이 추상 클래스를 확장한 후 코드를 제공하여 onCreate 메소드에서 데이터베이스를 작성한다. 이 헬퍼의 인스턴스가 확보되면 임의의 SQL문을 실행하는 데 사용할 수 있는 SQLiteDatabase의 인스턴스를 얻을 수 있다.

데이터베이스 클래스에는 몇 가지 편의 메소드가 포함되어 있다. 첫 번째는 새 주식을 데이터베이스에 저장하는 데 사용되는 addStock이다. SQLiteStatement 인스턴스를 사용한다는 것에 주목한다. 이 인스턴스는 java.sql.PreparedStatement와 비슷하다. addStock이 호출될 때마다 재사용할 수 있도록 클래스의 생성자에서 이 인스턴스가 컴파일되는 방식에 유의한다. 각각의 addStock 호출 시 SQLiteStatement의 변수(INSERT_SQL 문자열에 있는 물음표)는 addStock에 전달된 데이터에 바인드된다. 이것 역시 JDBC에서 익숙한 PreparedStatement와 매우 비슷하다.

다른 편의 메소드는 getStocks이다. 이름이 나타내듯이 이 메소드는 데이터베이스에서 모든 주식을 검색한다. JDBC에서와 마찬가지로 여기서도 SQL 문자열을 사용한다는 것에 다시 한번 주목한다. SQLiteDatabase 클래스에서 rawQuery 메소드를 사용하여 이를 수행한다. 이 클래스에는 SQL을 직접 사용하지 않고 데이터베이스를 쿼리할 수 있는 몇 가지 쿼리 메소드도 포함되어 있다. 이러한 다양한 메소드는 모두 java.sql.ResultSet와 매우 비슷한 Cursor 오브젝트를 리턴한다. 쿼리에서 리턴되는 데이터 행 위로 Cursor를 이동할 수 있다. 각각의 행에서 getInt, getString 및 기타 메소드를 사용하여 쿼리하는 데이터베이스 테이블의 다양한 열과 연관된 값을 검색할 수 있다. 역시 이것도 ResultSet와 매우 비슷하다. ResultSet와 비슷하므로 작업을 완료한 경우에는 Cursor를 닫는 것이 중요하다. Cursors를 닫지 않으면 빠르게 메모리 부족이 발생하여 애플리케이션에 오류가 발생할 수 있다.

로컬 데이터베이스를 쿼리하면 프로세스의 속도가 저하될 수 있으며 데이터 행의 수가 많거나 여러 테이블을 결합하는 복합 쿼리를 실행해야 하는 경우에는 특히 더 그렇다. 데이터베이스 쿼리 또는 삽입에 5초 이상 소요되어 Application Not Responding 대화 상자가 표시될 가능성은 없지만 코드가 데이터를 읽고 쓰는 중에 UI를 잠재적으로 멈출 수 있기 때문에 권장되지 않는다. 따라서 이러한 상황을 예방하는 가장 쉬운 방법은 AsyncTask를 사용하는 것이다. Listing 6에서는 이러한 예제를 보여 준다.


Listing 6. 별도의 스레드에 있는 데이터베이스에 삽입하기
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener(){
    public void onClick(View v) {
        String symbol = symbolIn.getText().toString();
        symbolIn.setText("");
        double max = Double.parseDouble(maxIn.getText().toString());
        maxIn.setText("");
        double min = Double.parseDouble(minIn.getText().toString());
        minIn.setText("");
        double pricePaid = 
                Double.parseDouble(priceIn.getText().toString());
        priceIn.setText("");
        int quantity = Integer.parseInt(quantIn.getText().toString());
        quantIn.setText("");
        Stock stock = new Stock(symbol, pricePaid, quantity);
        stock.setMaxPrice(max);
        stock.setMinPrice(min);
        new AsyncTask<Stock,Void,Stock>(){
            @Override
            protected Stock doInBackground(Stock... newStocks) {
                // There can be only one!
                return db.addStock(newStocks[0]);
            }
            @Override
            protected void onPostExecute(Stock s){
                addStockAndRefresh(s);
            }
        }.execute(stock);
    }
});

단추의 이벤트 리스너를 작성하는 것으로 시작한다. 사용자가 단추를 클릭하면 다양한 위젯(정확하게는 EditText 위젯)에서 주식 데이터를 읽고 새 Stock 오브젝트를 채운다. AsyncTask를 작성하고 doInBackground 메소드를 통해 Listing 5addStock 메소드를 호출한다. 따라서 기본 UI 스레드가 아니라 백그라운드 스레드에서 addStock이 실행된다. 완료되면 새 Stock 오브젝트를 데이터베이스에서 기본 UI 스레드에서 실행되는 addStockAndRefresh 메소드로 전달한다.


결론

이 기사에서는 Android가 Java 환경에 있는 많은 API의 서브세트만 지원하면서도 기능 면에서 전혀 부족하지 않다는 것을 보여줬다. 네트워킹과 같은 일부 경우에는 익숙한 API를 완전히 구현하지만 더 편리한 방법도 제공한다. 동시성의 경우에서 Android는 따라야 하는 API 및 규칙을 추가한다. 마지막으로 데이터베이스 액세스의 경우 Android는 데이터베이스에 액세스하는 완전히 다른 방법을 제공한다(익숙한 개념이 다수 포함되어 있음). 이들은 표준 Java 기술과 Android의 Java 기술 사이의 임의의 차이점에 그치지 않고 Android 개발의 기본적인 빌딩 블록을 형성한다.


Posted by 1010
98..Etc/Maven2010. 11. 26. 16:38
반응형
  • 메이븐 2 개요
  • 메이븐 2 종속성 관리 모델 이해하기
  • 메이븐 2 저장소(repository)와 메이븐 2 코디네이트(coordinates)
  • 메이븐 2 생명주기, 각 단계들, 플러그인, 모조(mojo)
  • 메이븐 2 다운로드와 설치
  • 메이븐 2 다루기 - 첫 번째 메이븐 2 프로젝트
  • POM(Project object model) 커스터마이징
  • 여러 프로젝트로 작업하기
  • 메이븐 2 다루기 - 여러 프로젝트 빌드하기
  • 이클립스 3.2에 메이븐 2.X 플러그인 설치하기
  • 이클립스 3.2에서 메이븐 2.X 플러그인 사용하기


출처 : http://www.ibm.com/developerworks/kr/library/tutorial/j-mavenv2/index.html

Posted by 1010
98..Etc/Git2010. 11. 26. 16:34
반응형

무료 오픈 소스 VCS(Version Control System)에 익숙하지 않은 개발자의 경우 과거에 유명했던 CVS(Concurrent Versions System) 대신 Subversion을 비상업용 표준 VCS로 사용하고 있다. CVS는 제한적이기는 하지만 지금도 유용하게 사용되고 있다. 하지만 Subversion에는 웹 서버에서 간단한 설치 작업만 수행하면 된다는 매력적인 장점이 있다. Subversion에도 약간의 문제가 있기는 하지만 대부분의 경우 정상적으로 작동한다. 이 기사에서는 이러한 문제에 대해서도 설명한다.

그렇다면 또 다른 도구가 필요한 이유가 무엇일까? Git(대문자 "G", git는 명령행 도구임)는 많은 면에서 Subversion보다 좋게 설계되었으며 많은 분산 VCS 중 하나이다. 분산 VCS 중 필자가 처음으로 경험한 제품은 Arch/tla였으며 Mercurial, Bazaar, darcs 등도 사용해 보았다. 여러 가지 이유로(이에 대해서는 관련된 본문에서 설명한다.) Git는 많은 인기를 얻고 있으며 Subversion과 함께 개인 또는 기업 VCS로서 가장 많이 사용되고 있는 제품으로 인정 받고 있다.

Subversion 사용자가 Git에 관심을 보이는 두 가지 중요한 이유는 다음과 같다.

  • Subversion의 일부 기능이 제한적이기 때문에 Git로 이동하려고 한다.
  • Git에 대한 호기심이 있어서 Subversion과 비교해 보고 싶다.

아마도 Git가 비교적 새롭게 떠오르는 기술이기 때문에 이력서에 넣고 싶다는 세 번째 이유도 있을 것이다. 이 이유가 주 목적이 아니길 바란다. Git를 배우는 것은 개발자가 가장 큰 보람을 느낄 수 있는 일 중 하나이다. 지금 당장 Git를 사용하지 않는다고 하더라도 이 분산 VCS에 담겨 있는 개념과 워크플로우는 향후 10년 이내에 IT 산업의 대부분의 분야에서 겪게 될 지리적인 대규모 분산 환경에서의 거대한 변화에 대응하기 위한 필수 지식이 될 것이다.

중요한 이유가 아닐 수도 있겠지만 마지막으로 들 수 있는 이유는 다음과 같다. Linux 커널 개발자가 아닐 경우 Git를 사용하여 커널 및 기타 수많은 주요 프로젝트를 관리하고 있기 때문에 기여도를 높이기 위해 Git에 익숙해지고 싶다는 것이다.

이 기사는 입문 수준에서 중급 수준 사이의 Subversion 사용자를 위한 것이다. 따라서 Subversion에 대한 입문자 수준의 지식과 버전 제어 시스템에 대한 일반적인 지식이 있어야 한다. 이 기사의 정보는 주로 UNIX® 계열(Linux® 및 Mac OS X) 시스템의 사용자를 대상으로 하며 Windows® 사용자를 고려한 부분도 조금 있다.

이 시리즈의 Part 2에서는 Git의 고급 사용 방법 즉, 분기 병합, 차이 생성 등을 비롯한 일반적인 작업에 대해 설명한다.

Subversion 및 Git 기초

앞으로는 편의를 위해 "Subversion"을 "SVN"으로만 쓰겠다.

git-svn

git-svn을 들어본 적이 있을 것이다. 이 도구를 사용하면 Git에서 Subversion 저장소를 사용할 수 있다. 유용한 경우도 있기는 하지만 절반만 분산된 중앙 집중식 VCS를 사용하는 것은 분산 VCS로 전환하는 것과는 다른 방법이다.

그렇다면 SVN의 장점은 무엇일까? 이미 알고 있을지도 모르지만 VCS는 파일에 대한 것이 아니라 바로 변경 사항에 대한 것이다. 중앙 서버에서 실행되는 SVN은 변경 사항을 데이터 저장소에 추가한 후 변경 작업이 발생할 때마다 사용자에게 스냅샷을 제공할 수 있으며 이 스냅샷에는 개정 번호가 있다. 그리고 이 개정 번호는 SVN과 사용자 모두에게 매우 중요한 요소이다. 변경 사항이 차례로 적용되기 때문에 개정 번호를 통해 최신 변경 사항을 확인할 수 있다.

Git에도 변경 사항 추적이라는 비슷한 목표가 있다. 그러나 중앙 집중식 서버를 사용하지는 않는다. 이 점이 매우 중요한 차이점이다. 즉, SVN은 중앙 집중식인 반면 Git는 분산식이다. 따라서 Git에는 "최신 개정"이 없기 때문에 증가하는 개정 번호를 제공하는 방법도 없다. 물론 고유 개정 ID가 있기는 하지만 SVN 개정 번호처럼 유용하지는 않다.

Git에서 중요한 작업은 더 이상 커미트가 아니다. 여기에서 중요한 작업은 바로 병합이다. 누구나 저장소를 복제한 후 복제본에 대해 커미트를 수행할 수 있다. 그리고 저장소의 소유자는 병합된 변경 사항을 되돌릴 수 있다. 또는 개발자가 변경 사항을 저장소에 다시 push할 수 있다. 여기에서는 마지막 방법인 authorized-push 모델에 대해서만 설명한다.


SVN에서 디렉토리 관리하기

먼저 SVN을 사용하여 디렉토리의 컨텐츠를 추적하는 일반적인 간단한 예제부터 살펴보자. SVN 서버, 파일이 있는 디렉토리 그리고 해당 서버에서 적어도 하나의 경로에 대한 커미트 권한이 있는 계정이 있어야 한다. 먼저 다음과 같이 디렉토리를 추가하고 커미트하자.


Listing 1. SVN 아래에 디렉토리 설정하기
% svn co http://svnserver/...some path here.../top
% cd top
% cp -r ~/my_directory .
% svn add my_directory
% svn commit -m 'added directory'

이렇게 디렉토리를 설정한 후에는 이 디렉토리에서 커미트된 파일의 최신 버전을 확인하고, 파일을 삭제하고, 파일의 이름을 바꾸고, 새 파일 또는 디렉토리를 작성하고, 기존 파일에 변경 사항을 커미트하는 등의 작업을 수행할 수 있다.


Listing 2. SVN에서의 기본적인 파일 작업
# get latest
% svn up
# what's the status?
% svn st
# delete files
% svn delete
# rename files (really a delete + add that keeps history)
% svn rename
# make directory
% svn mkdir
# add file
% svn add
# commit changes (everything above, plus any content changes)
% svn commit

이러한 명령에 대해서 자세히 설명하지는 않겠지만 잘 알고 있어야 한다. svn help COMMAND를 입력하면 이들 명령에 대한 설명을 볼 수 있으며 자세한 정보를 보려면 설명서를 참조해야 한다.


Git에서 디렉토리 관리하기

이 섹션도 SVN 예제와 같은 방식으로 설명한다. 앞에서와 마찬가지로 디렉토리에 이미 데이터가 있다고 가정한다.

여기에서는 무료 github.com 서비스를 원격 서버로 사용하지만 원한다면 사용자의 고유 서버를 설정할 수도 있다. GitHub를 사용하면 원격 Git 저장소를 쉽게 관리할 수 있다. 이 기사의 집필 당시를 기준으로 무료 계정의 경우 최대 300MB의 데이터를 저장할 수 있으며 저장소가 공용이어야 한다. 필자는 "tzz"라는 사용자로 등록한 후 "datatest"라는 공용 저장소를 작성하여 편하게 사용하고 있다. 그리고 공용 SSH 키를 제공했다. 이 키가 아직 없다면 새롭게 생성해야 한다. Gitorious 서버나 repo.or.cz를 사용할 수도 있으며 git.or.cz Wiki에서 Git 호스팅 서비스 목록을 볼 수 있다(참고자료의 링크 참조).

GitHub의 장점은 친숙하다는 것이다. Git를 설정하고 저장소를 초기화하는 데 필요한 명령을 정확하게 알려 준다. 이제 이 과정을 자세히 살펴보자.

먼저 각 플랫폼에 해당하는 Git를 설치하고 초기화해야 한다. Git 다운로드 페이지(참고자료 참조)에서 플랫폼에 따른 다양한 옵션을 볼 수 있다. (Mac OS X의 경우 필자는 port install git-core 명령을 사용했다. 하지만 MacPorts를 먼저 설정해야 한다. 또한 Git 다운로드 페이지에 연결된 독립 실행형 MacOS X Git 설치 프로그램을 사용하면 대부분의 경우 쉽게 설치할 수 있다.)

설치한 후에는 다음과 같은 명령을 사용하여 기본 설정을 수행해야 한다(고유 사용자 이름 및 이메일 주소 선택).


Listing 3. 기본적인 Git 설정
% git config --global user.name "Ted Zlatanov"
% git config --global user.email "tzz@bu.edu"

이미 SVN과의 차이점을 알았을 것이다. SVN에서는 사용자 ID가 서버측에 있으며 이 ID를 사용해야만 한다. 하지만 Git에서는 원한다면 The Wonderful Monkey Of Wittgenstein이 될 수도 있다. (다행히 필자는 유혹에 넘어가지 않았다.)

이제 데이터 파일을 설정한 후 이 파일을 사용하여 저장소를 초기화할 차례이다. (GitHub에서는 공용 SVN 저장소에서도 가져오므로 유용하게 사용할 수 있다.)


Listing 4. 디렉토리 설정 및 첫 번째 커미트
# grab some files
% cp -rp ~/.gdbinit gdbinit
% mkdir fortunes
% cp -rp ~/.fortunes.db fortunes/data.txt
# initialize
% git init
# "Initialized empty Git repository in /Users/tzz/datatest/.git/"
# add the file and the directory
% git add gdbinit fortunes
% git commit -m 'initializing'
#[master (root-commit) b238ddc] initializing
# 2 files changed, 2371 insertions(+), 0 deletions(-)
# create mode 100644 fortunes/data.txt
# create mode 100644 gdbinit

위 출력을 보면 파일 모드에 대한 정보를 볼 수 있다. 여기서 100644는 파일의 권한 비트를 나타내는 8진수 버전이다. 이에 대해서는 크게 신경 쓰지 않아도 되지만 2371 insertions의 의미가 궁금할 것이다. 두 개의 파일만 변경되었을 뿐이다. 이 숫자의 실제 의미는 삽입된 행의 번호이다. 그리고 아무 것도 삭제하지 않았다.

새로운 변경 사항을 GitHub 서버에 push하려면 어떻게 해야 할까? 설명서를 보면 "origin"이라는 원격 서버를 추가하는 방법을 알 수 있다. 이 경우 원격 서버에 원하는 이름을 지정할 수도 있다. 그리고 GIT 명령에 대해 알고 싶은 경우에는 예를 들어, git remote에 대한 정보가 필요한 경우에는 git remote --help 또는 git help remote를 입력하면 된다. 이 방법은 명령행 도구에서 일반적으로 사용되는 방법으로 SVN에서도 유사한 방법으로 필요한 정보를 얻을 수 있다.


Listing 5. 원격 서버에 변경 사항 push하기
# remember the remote repository is called "datatest"?
% git remote add origin git@github.com:tzz/datatest.git
# push the changes
% git push origin master
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#Counting objects: 5, done.
#Delta compression using 2 threads.
#Compressing objects: 100% (4/4), done.
#Writing objects: 100% (5/5), 29.88 KiB, done.
#Total 5 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# * [new branch]      master -> master

github.com이 아직 알려진 호스트가 아니기 때문에 OpenSSH에서 경고가 발생하지만 걱정하지 않아도 된다.

Git 메시지는 말 그대로 완벽하다. SVN 메시지와는 달리 쉽게 이해할 수 있다. Git는 멘타트를 위해 멘타트가 작성한 프로그램이기 때문이다. Frank Herbert의 Dune 세계에서 휴먼 컴퓨터로 훈련 받은 인물이라면 아마도 고유 버전의 Git를 이미 작성했을 것이다. 그럴 수 있는 능력을 지닌 존재이니까 말이다. 그런 존재가 아닌 우리는 Git에 사용되는 델타 압축 및 수많은 스레드와 별로 관련이 없으며 알려고 하면 도리어 머리만 아플 뿐이다.

SSH를 통해 push 작업이 수행되었지만 HTTP, HTTPS, rsync 및 file 등의 다른 프로토콜도 사용할 수 있다(git push --help 참조).

SVN과 Git의 차이점 중 가장 중요하고 기본적인 차이점은 바로 커미트에 있다. SVN의 커미트는 "이 변경 사항을 중앙 서버에 push하는 것"을 의미한다. SVN에서는 커미트 전까지의 변경 사항이 언제라도 사라질 수 있다. 하지만 Git에서는 커미트가 로컬에서 수행되고 로컬 저장소를 사용하기 때문에 원격측에서 어떤 일이 발생하더라도 문제가 되지 않는다. 원격 서버와의 상호 작용 없이 변경 사항을 되돌리고, 분기를 만들고, 분기에 대한 커미트를 수행하는 등의 작업을 수행할 수 있다. Git를 사용하여 push 작업을 수행하면 로컬 저장소의 상태와 원격 서버를 효과적으로 동기화할 수 있다.

이제 마지막으로 발생한 작업에 대한 Git 로그를 살펴보자.


Listing 6. Git 로그
% git log
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

로그에는 커미트에 대한 정보만 있다. 그리고 SVN 개정 번호와는 달리 임의로 생성된 긴 커미트 ID를 볼 수 있다. 하지만 git push를 통한 동기화에 대한 정보가 없다는 것을 알 수 있다.


Git를 통해 협업하기

지금까지는 Git를 SVN에 대한 대안으로 사용해 왔다. 이제 흥미를 높이기 위해 사용자와 변경 세트를 추가해 보자. 이를 위해 저장소를 다른 시스템에 체크아웃한다. 이 시스템에서는 Ubuntu GNU/Linux가 실행되고 있으므로 git 대신 git-core를 설치해야 한다.


Listing 7. 다른 Git ID 설정 및 저장소 체크아웃하기
% git config --global user.name "The Other Ted"
% git config --global user.email "tzz@bu.edu"
% git clone git@github.com:tzz/datatest.git
#Initialized empty Git repository in /home/tzz/datatest/.git/
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#remote: Counting objects: 5, done.
#remote: Compressing objects: 100% (4/4), done.
#Indexing 5 objects...
#remote: Total 5 (delta 0), reused 0 (delta 0)
# 100% (5/5) done
% ls datatest
#fortunes  gdbinit
% ls -a datatest/.git
# .  ..  branches  config  description  HEAD  hooks  index  info  logs  objects  refs
% ls -a datatest/.git/hooks
# .  ..  applypatch-msg  commit-msg  post-commit  post-receive post-update
#  pre-applypatch  pre-commit  pre-rebase  update

다시 한번 이 시스템에서 SSH를 통해 GitHub와 작업한 적이 없음을 나타내는 OpenSSH 경고가 표시된다. git clone 명령은 SVN checkout과 유사하지만 병합된 버전의 컨텐츠(특정 개정 또는 최신 개정의 스냅샷)를 가져오는 대신 전체 저장소를 가져온다.

이 기사에서는 모든 컨텐츠를 볼 수 있도록 datatest/.git 디렉토리 및 hooks 서브디렉토리의 컨텐츠를 포함시켰다. Git에서는 기본적으로 모든 내용이 공개되지만 이에 반해 SVN에서는 저장소가 기본적으로 비공개로 설정되며 스냅샷에 대한 액세스만 허용된다.

부수적으로 Git 저장소에서 모든 커미트 또는 일부 커미트에 대해 규칙을 적용하려는 경우에는 hook를 사용할 수 있다. 후크는 SVN 후크와 매우 유사하며 두 후크 모두 "성공 시 0을 리턴하고, 실패 시 임의 값을 리턴하는" 표준 UNIX 규칙을 따른다. 이 기사에서는 후크에 대해 자세히 설명하지 않겠지만 Git를 팀에서 사용할 경우에는 후크에 대해 잘 알고 있어야 한다.

이제 활기에 찬 "The Other Ted"가 마스터 분기(SVN의 TRUNK에 해당)에 새 파일을 추가하고 새 분기를 작성한 후 gdbinit 파일에 변경 사항을 적용하려고 한다.


Listing 8. 파일 추가 및 새 분기 만들기
# get a file to add...
% cp ~/bin/encode.pl .
% git add encode.pl
% git commit -m 'adding encode.pl'
#Created commit 6750342: adding encode.pl
# 1 files changed, 1 insertions(+), 0 deletions(-)
# create mode 100644 encode.pl
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git branch empty-gdbinit
% git branch
#  empty-gdbinit
#* master
% git checkout empty-gdbinit
#Switched to branch "empty-gdbinit"
% git branch
#* empty-gdbinit
#  master
% git add gdbinit
% git commit -m 'empty gdbinit'
#Created commit 5512d0a: empty gdbinit
# 1 files changed, 0 insertions(+), 1005 deletions(-)
% git push
#updating 'refs/heads/master'
#  from b238ddca99ee582e1a184658405e2a825f0815da
#  to   675034202629e5497ed10b319a9ba42fc72b33e9
#Generating pack...
#Done counting 4 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 0), reused 0 (delta 0)

예제가 너무 길어서 보다가 졸지 않았기를 바란다. 만일 졸았다면 변경 세트의 왈츠를 들으며 Git 저장소를 동기화하는 꿈을 꾸었기를 바란다. (하긴 앞으로 그런 꿈을 꾸게 될지도 모르겠다.)

먼저 파일을 추가하고(encode.pl, 단 한 줄) 커미트했다. 커미트 이후 GitHub에 있는 원격 저장소에서는 방금 전의 변경 사항을 모른다. 그런 다음 empty-gdbinit이라는 새 분기를 작성한 후 이 분기로 전환했다. (필자는 git checkout -b empty-gdbinit을 사용하여 이 작업을 수행했다.) 이 분기에서 필자는 gdbinit 파일을 비우고 해당 변경 사항을 커미트했다. 마지막으로 원격 서버에 push했다.

마스터 분기로 전환하면 로그에 빈 gdbinit이 표시되지 않을 것이다. 따라서 각 분기에는 당연히 고유 로그가 있다.


Listing 9. 분기 간 로그 살펴보기
# we are still in the empty-gdbinit branch
% git log
#commit 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    empty gdbinit
#
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git checkout master
#Switched to branch "master"
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

push를 수행하면 Git가 GitHub의 서버에 "새 파일 encode.pl이 있어"라고 말한다.

이제 GitHub의 웹 인터페이스에 encode.pl이 표시되지만 아직도 GitHub에는 하나의 분기만 있다. 왜 empty-gdbinit 분기가 동기화되지 않았을까? 왜냐하면 Git는 사용자가 분기와 변경 사항을 기본적으로 push하려고 한다고 가정하지 않기 때문이다. 이를 위해서는 모든 내용을 push해야 한다.


Listing 10. 모두 push하기
% git push -a
#updating 'refs/heads/empty-gdbinit'
#  from 0000000000000000000000000000000000000000
#  to   5512d0a4327416c499dcb5f72c3f4f6a257d209f
#updating 'refs/remotes/origin/HEAD'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#updating 'refs/remotes/origin/master'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#Generating pack...
#Done counting 5 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 1), reused 0 (delta 0)

여기에서 다시 한번 자세한 멘타트 인터페이스를 볼 수 있다. 하지만 충분히 이해할 수 있을 것이다. 멘타트는 아니지만 상식적으로 생각해 보면 0000000000000000000000000000000000000000은 일종의 특별한 초기 태그라는 것을 알 수 있다. 또한 Listing 9의 로그를 보면 5512d0a4327416c499dcb5f72c3f4f6a257d209f 태그가 empty-gdbinit 분기의 마지막이자 유일한 커미트라는 것도 알 수 있다. 나머지 부분도 대부분의 사용자에게는 아람말처럼 보이겠지만 염두에 두지 않아도 된다. 이제 GitHub에 새 분기와 변경 사항이 표시된다.

git mvgit rm을 사용하면 각각 파일을 관리하는 작업과 이름 바꾸기 및 제거 작업을 수행할 수 있다.


결론

이 기사에서는 기본적인 Git 개념을 설명한 후 Git를 사용하여 버전 제어 아래에 있는 간단한 디렉토리의 컨텐츠를 관리하는 방법을 설명하면서 Git와 Subversion도 함께 비교했다. 그리고 간단한 예제를 사용하여 분기에 대해서도 설명했다.

Part 2에서는 병합, 차이 생성 등을 비롯한 기타 Git 명령에 대해 설명한다. 이해하기 쉬운 Git 설명서나 튜토리얼을 읽어 보기를 권장한다. 이러한 자료는 모두 Git 홈 페이지에서 구할 수 있으므로 약간의 시간을 투자해서 살펴보기를 바란다. (아래 참고자료에서 링크를 볼 수 있다.) SVN 사용자라면 그 이상의 자료가 필요하지는 않을 것이다.

반면 Git는 다양한 기능을 갖춘 DVCS이므로 Git의 기능을 잘 알고 있으면 이러한 기능을 활용하여 VCS 워크플로우를 단순화하고 향상시키는 데 큰 도움이 될 것이다. 게다가 Git 저장소에 대한 꿈을 한두번 꿀지도 모르겠다.


참고자료

교육

제품 및 기술 얻기

  • Git를 다운로드하고 Git download 페이지를 비롯한 수많은 문서와 도구를 살펴보자.

  • developerWorks에서 직접 다운로드할 수 있는 IBM 시험판 소프트웨어를 사용하여 Linux와 관련된 후속 개발 프로젝트를 구현해 볼 수 있다.

토론

  • 사용자의 개인 프로파일과 사용자 정의 홈 페이지가 제공되는 My developerWorks community에서는 관심을 가지고 있는 developerWorks의 여러 주제를 추적할 수 있으며 다른 developerWorks 사용자들과 의견을 나눌 수도 있다.

필자소개

사진 - teodor zlatanov

Teodor Zlatanov는 1999년에 Boston University에서 컴퓨터 공학 석사 학위를 받았으며 1992년부터 Perl, Java, C 및 C++를 사용하는 프로그래머로 활약했다. 텍스트 구문 분석, 데이터베이스 아키텍처, 사용자 인터페이스 및 UNIX 시스템 관리에 대한 오픈 소스 활동에 관심을 두고 있다.


출처 : http://www.ibm.com/developerworks/kr/library/l-git-subversion-1/

Posted by 1010
98..Etc/Git2010. 11. 26. 16:32
반응형
javascript 관련 오픈 소스를 찾다보면 자주 들리게 되는 github.com 를 써보기로 하고, 사용해 본 결과를 정리한다.


일단 git 에 대해 간략하게 설명하면 다음과 같다.

1. 백과사전:
Git은 프로그램 등의 소스코드 관리를 위한 분산 버전 관리 시스템이다. 리누스 토르발스에 의해 개발되었다.

2. 공식 사이트:
무료 오픈소스인 분산 버전 관리 시스템인데, 작은 프로젝트나 큰 프로젝트나 할 것 없이 속도와 효율성이 있게 처리할 수 있게 설계되어있다고 한다.
모든 Git 클론은 모든 히스토리와 리비전 트래킹 기능을 지닌 완전한 레파지토리인데, 네트워크 억세스나 중앙 서버에 의존하지 않는다고 한다. 브랜칭과 머징이 빠르고 쉽게 된댄다.

3. 그림:
그림 한장에 대부분의 기능이 다 설명 되는듯.
SVN과 다르게 commit이 로컬에서만 이루어지고, 서버에 반영은 push라는 동작으로 이루어 진다는 점이 중요한듯. 서버에서 처음에 모두 받을 때는 pull로 왕 끌여당기고, 그 후로는 fetch와 checkout을 통해 받게 되고, commit 전에 add 라는 작업이 있는 정도.

4. 다른 좋은 글:


일단 목적은 내가 쓰는 환경인 windows 에서 편리하게 사용하는 것. 윈도우즈 관련 툴을 찾아보니 다음과 같은 것이 있었다.
지금 현재 최신 버전은 1.7.0.2 인데 이 것을 첨부한다.


일단 설치는 쉽다. 계속 넥스트만 눌러주면 된다.



다 하고 나면 아래 처럼 탐색기 오른쪽 버튼 메뉴에 뭔가가 생긴다.


나의 프로젝트를 git를 이용해서 관리할려면 해당 폴더로 가서 Git Init Here 눌러주면 된다(bash로 하는 방법도 있는데, 그건 찾아보면 관련 자료가 많이 나온다. 검색해보시길). 윈도우7에서는 저게 안되니 GIT GUI 를 구동한 다음에 Create New Repository 를 클릭해서 해당 폴더를 선택해주면 된다. 세가지 방법 모두 동일한 작업이다.


init 시켜주면 위와 같이 숨겨진 폴더가 생기고 거기에 레파지토리가 생기는듯.
이제 다시 오른쪽 버튼을 클릭하면 다음과 같이 새로운 메뉴가 뜬다. Add all files now 해보자.


GIT GUI 툴에서는 아래 이미지 처럼 추가가 가능하다. Rescan 눌러서 스캔한 다음에 Commit 메뉴안의 Stage To Commit 누르면 동일한 작업이 가능하다. 윈도우7 환경에서는 두번째 방법으로만 가능하다.


윈도우XP에서는 GIT GUI를 아래 처럼 쉽게 띄울 수 있다(Git Commit Tool 클릭).


요기까지 했다면 아래와 같은 화면이..


커밋 메시지를 대충 적고 Commit 버튼을 누르면 로컬 레파지토리에 커밋된다.


다시 탐색기에서 오른쪽 버튼을 누르면 Git History 메뉴가 보이게 된다.


Git History 를 눌러서 커밋이 잘 되었는지 확인해 보자.


잘 된듯.
혼자 로컬 레파지토리에서 커밋하고 버전 컨트롤하는 부분은 이걸로 완성.
이제 github.com 을 이용하기 위한 세팅을 해보자.


일단 회원 가입은 알아서..


가입 한 후에는 New Repository 를 눌러 레파지토리를 만들자.



레파지토리는 완성이 된듯. 아래 이미지 중간에 보이는 "git@github.com:azki/simpletank.git" 라는 주소가 읽기/쓰기 접근을 할 수 있는 URL 이다.


다시 툴로 돌아와서 Remote 메뉴의 Add 를 눌러보자.


이름을 아무거나 마음에 드는걸로 대충 적고 아까 그 주소를 적어주자. 스샷에는 azki로 되어있는데 프로젝트 이름을 적는 것이 더 좋을듯하다.
참고로, 내가 해본 결과 저 이름을 한글로 쓰면 나중에 툴에서 문자가 깨져서 오동작하는 부분도 있었다. 왠만하면 저런건 영어로 하는 것이 좋을듯.


그 후 SSH Key 를 만들자. Help 안에 Show SSH Key 를 누르면 된다.


Generate Key 를 누르면 키가 생성되는데, 현재 버전에서는 따로 이메일 주소를 입력할 수 없다. 이메일 주소를 입력하고 키를 발급 받고 싶다면 아래와 같이 Git Bash 를 이용하자. 명령어는
ssh-keygen -C "메일주소" -t rsa
이다.


사용자 폴더안의 .ssh 폴더안에 키값이 저장된다. 내 경우 Win7 이기 때문에 "C:\Users\azki\.ssh" 에 저장되었지만, XP 일 경우 "C:\Documents and Settings\azki\.ssh" 에 저장될 것이다. 나중에 지우고 싶으면 찾아 들어가서 지워버리면 된다.

아무튼 툴에서 보여주는 SSH Key 를 복사하자. 위에서 설명한 경로안의 id_rsa.pub 파일을 텍스트에디터로 열어서 봐도 된다.


키를 github 에 등록하자. 프로젝트 첫 화면에서 상단의 Admin 버튼을 누르면 설정할 수 있는 관리자 화면으로 넘어간다. 그 곳에서 왼쪽 메뉴의 Deploy Keys 를 누르면 아래 처럼 등록할 수 있다. 타이틀은 메일 주소로ㅋ


등록 완료!


이제 다시 툴로 돌아와서 push를 해보자(커밋은 이미 아까 저 위에 위에 위에 위에 위에 위에서 했으니까 바로 푸시!).


아까 Remote의 Add로 추가했던 레파지토리가 보인다. 선택하고 push하자. 등록 안하고 Arbitrary Location으로 URL을 써줘도 되긴함.


push를 누르면 잘 푸시된다. 혹시나 SSH키를 만들 때 passphrase를 입력해놓았다면 암호를 물어보는데 입력해주면 된다.


http://github.com/azki/simpletank 에서 새로고침 해주면 아래 처럼 푸시한 녀석들이 잘 올라가 있는 것을 볼 수 있다.


프로젝트의 루트 폴더에 README 파일을 넣어주면 파일 내용이 화면에 나타나기도 한다. 다만 한글 등을 쓸려면 UTF-8 형식으로 된 파일로 올려야 될듯(아래 처럼 글씨 깨진다).


작성한 사용자를 정확히 표시할려면 툴에서 다음과 같은 설정을 해줘야하는 듯하다. Edit의 Options...


유저 이름과 이메일 어드레스를 넣어주자.


이렇게 유저 설정을 한 다음에 아까 그 README 파일도 UTF-8 형식으로 다시 커밋하고 푸시한 다음에 보면 아래 처럼 매우 잘 나온다. :]


일반적인 사용에 대한 사용기 끝~!


느낀점.
커밋이 로컬에서 이루어지기 때문에 무척 빠르다. 마찬가지로 히스토리 관리나 리버전 따위 작업 역시 엄청난 속도..
특히 이전에 쓰던 SVN의 경우는 내부 네트워크에 놓고 쓸 경우에도 꽤 처리 시간이 걸렸었다. 그리고 외부 서버에 놓고 쓸 경우 엄청난 인내심이 요구되었었다. 예전에 구글에서 제공하는 레파지토리 호스팅(code.google.com/hosting)도 쓸려고 프로젝트를 만들어보고, 네이버 개발센터(dev.naver.com)에서도 프로젝트를 만들어보고 했는데 그걸 못쓰고 버린건 다 그런 이유. 특히 구글은 소스 파일 몇개 커밋 하는데 10초 이상 걸리는 등 엄청나게 느려서 10분 써보고 바로 포기했었다. 일단 이런 면에서 git는 매우 합격이다. 참 좋은 녀석인듯. 앞으로 쫌 많이 활용해야 겠다.

ps.
github의 모토가 원래 "소셜 소스 코드 공유"라는데 그런 부분에 대한 것이나, SVN에 없는 멋진 기능 등을 발견하면 나중에 또 포스팅 하겠음. ㅋㅋ 읽어주신 분들 ㄳ~!


출처 : http://dev.azki.org/entry/Git-%EC%82%AC%EC%9A%A9%EA%B8%B0-windows-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-GIT-GUI-%EC%99%80-githubcom-%EC%9D%84-%EC%A4%91%EC%A0%90%EC%9C%BC%EB%A1%9C
Posted by 1010
98..Etc/Git2010. 11. 26. 16:29
반응형
목차
  1. 저장소와 브랜치
    1. git 저장소 가져오기
    2. 다른 버전의 프로젝트 체크아웃 하기
    3. 변경 이력 이해하기: 커밋
      1. 변경 이력 이해하기: 커밋, 부모, 도달 가능성
      2. 변경 이력 이해하기: 변경 이력 다이어그램
      3. 변경 이력 이해하기: 브랜치란 무엇인가?
    4. 브랜치 다루기
    5. 새 브랜치를 만들지 않고 이전 버전 살펴보기
    6. 원격 저장소의 브랜치 살펴보기
    7. 브랜치, 태그 및 다른 참조에 대한 이름 붙이기
    8. git-fetch를 이용하여 저장소 업데이트하기
    9. 다른 저장소에서 브랜치 가져오기
  2. Git 변경 이력 조사하기
    1. regression을 찾기 위해 bisect 이용하기
    2. 커밋 이름 붙이기
    3. 태그 만들기
    4. 버전 살펴보기
    5. 차이점 생성하기
    6. 예전 버전의 파일 보기
    7. 예제
      1. 특정 브랜치 상의 커밋 개수 세기
      2. 두 브랜치가 동일한 지점을 가리키는지 검사하기
      3. 특정 커밋을 포함하는 것 중에서 제일 먼저 태그가 붙은 버전 찾기
      4. 주어진 브랜치에만 존재하는 커밋 보기
      5. 소프트웨어 배포를 위한 변경 로그 및 tarball 만들기
      6. 파일의 특정 내용을 참조하는 커밋 찾기
  3. Git를 이용하여 개발하기
    1. git에게 이름 말해주기
    2. 새 저장소 만들기
    3. 커밋을 만드는 방법
    4. 좋은 커밋 메시지 작성하기
    5. 파일 무시하기
    6. 머지하는 방법
    7. 머지 (충돌) 해결하기
      1. 머지 중에 충돌을 해결하기 위해 필요한 정보 얻기
    8. 머지 되돌리기
    9. 고속 이동 머지
    10. 실수 바로잡기
      1. 새로운 커밋을 만들어 실수 바로잡기
      2. 변경 이력을 수정하여 실수 바로잡기
      3. 이전 버전의 파일을 체크아웃하기
      4. 작업 중인 내용을 임시로 보관해 두기
    11. 좋은 성능 보장하기
    12. 신뢰성 보장하기

      1. 저장소가 망가졌는지 검사하기
      2. 잃어버린 작업 내용 복구하기
        1. Reflogs
        2. 댕글링 객체 살펴보기
  4. 개발 과정 공유하기
    1. git pull 명령으로 업데이트하기
    2. 프로젝트에 패치 제출하기
    3. 패치를 프로젝트로 가져오기
    4. 공개 git 저장소

      1. 공개 저장소 설정하기
      2. git 프로토콜을 이용해 git 저장소 공개하기
      3. http를 이용해 git 저장소 공개하기
      4. 공개 저장소에 변경 이력 올리기
      5. push 실패 시의 처리
      6. 공유 저장소 설정하기
      7. 저장소를 웹으로 살펴볼 수 있게 공개하기
    5. 예제
      1. 리눅스 하위시스템 관리자를 위한 주제별 브랜치 관리하기
  5. 변경 이력 수정하기와 패치 묶음 관리하기
    1. 완벽한 패치 묶음 만들기
    2. git rebase를 이용하여 패치 묶음을 최신 상태로 유지하기
    3. 하나의 커밋 수정하기
    4. 패치 모음에서 커밋 순서 변경 및 커밋 선택하기
    5. 다른 도구들
    6. 변경 이력을 수정하는 것에 따른 문제점
    7. 머지 커밋 상에서 bisect를 수행하는 것이 선형 변경 이력 상에서 bisect를 수행하는 것보다 어려운 이유
  6. 고급 브랜치 관리

    1. 각각의 브랜치 가져오기
    2. git fetch 명령과 고속 이동
    3. git fetch 명령이 고속 이동이 아닌 업데이트를 하도록 강제 지정하기
    4. 원격 브랜치 설정하기
  7. Git 개념 정리
    1. 객체 데이터베이스
      1. 커밋 객체
      2. 트리 객체
      3. 블롭 객체
      4. 신뢰
      5. 태그 객체
      6. git가 객체를 효율적으로 저장하는 방법: 팩 파일
      7. 댕글링 객체
      8. 저장소 손상 시 복구하기
    2. 인덱스
  8. 서브 모듈
    1. 서브모듈에 관한 함정
  9. 저수준 git 연산
    1. 객체 접근 및 조작
    2. 작업 순서
      1. 작업 디렉터리 -> 인덱스
      2. 인덱스 -> 객체 데이터베이스
      3. 객체 데이터베이스 -> 인덱스
      4. 인덱스 -> 작업 디렉토리
      5. 이들을 모두 합치기
    3. 데이터 살펴보기
    4. 여러 트리를 머지하기
    5. 여러 트리를 머지하기 (계속)
  10. Git 해킹하기
    1. 객체 저장 형식
    2. git 소스 코드의 개략적인 설명
  11. Git 용어
  12. 부록 A: Git 빠른 참조
    1. 새 저장소 만들기
    2. 브랜치 관리하기
    3. 변경 이력 살펴보기
    4. 변경 사항 만들기
    5. 머지
    6. 변경 사항을 공유하기
    7. 저장소 관리
  13. 부록 B: 이 설명서에 대한 노트 및 할 일 목록
  14. 번역 용어표

 

 

 

Git 사용자 설명서 (버전 1.5.3 이상)

원문 : http://www.kernel.org/pub/software/scm/git/docs/user-manual.html

번역 : 김남형 <namhyung_at_gmail_dot_com> 2009-04-13 ~ 2009-08-26


서문

git는 빠른 분산형 버전 관리 시스템이다.


이 문서는 기본적인 유닉스 명령행 도구에 익숙한 사람들을 위해 작성되었으며, git에 대한 사전 지식이 필요하지는 않다.


1장 "저장소와 브랜치"2장 "Git 변경 이력 조사하기"에서는 git를 이용하여 프로젝트를 가져오고 검토하는 방법을 설명한다. 특정 버전의 소프트웨어를 빌드하고 테스트하거나, 퇴보한(regression) 부분을 검색하는 등의 일을 하고 싶다면 이 부분을 읽어보기 바란다.


실제로 개발에 참여하고 싶은 사람들은 3장 "Git를 이용하여 개발하기"4장 "개발 과정 공유하기" 부분도 읽어야 할 것이다.


이후의 장들은 좀 더 특수한 주제들을 다룬다.


자세한 참조 문서는 man 페이지 혹은 git-help(1) 명령을 통해 볼 수 있다. 예를 들어 "git clone <저장소>" 명령에 대한 문서를 보고 싶다면 다음의 두 명령 중 하나를 이용할 수 있다.


  1. $ man git-clone

혹은

  1. $ git help clone

두 번째 경우라면 여러분이 원하는 설명서 보기 프로그램을 선택할 수 있다. 자세한 내용은 git-help(1)을 보기 바란다.


자세한 설명 없이 git 명령 사용법을 간략히 보고 싶다면 부록 A "Git 빠른 참조" 부분을 살펴보도록 하자.


마지막으로 부록 B "이 설명서에 대한 노트 및 할 일 목록" 부분을 살펴보고 이 설명서가 보다 완벽해지도록 도와줄 수 있다.


저장소와 브랜치#

git 저장소 가져오기#

이 설명서를 읽으면서 각 명령들을 실험해 볼 git 저장소가 있다면 유용할 것이다.


git 저장소를 가지기 위한 가장 좋은 방법은 git-clone(1) 명령을 이용하여 기존의 저장소의 복사본을 다운로드하는 것이다. 여러분이 딱히 정해둔 프로젝트가 없다면 아래의 흥미로운 예제들을 이용해 보기 바란다.


  1.         # git 프로그램 자체 (대략 10MB 정도 다운로드):
    $ git clone git://git.kernel.org/pub/scm/git/git.git
            # 리눅스 커널 (대략 150MB 정도 다운로드):
    $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

규모가 큰 프로젝트의 경우 최초 복사(clone) 작업에는 꽤 많은 시간이 소요될 수 있다. 하지만 이 과정은 오직 한 번만 필요하다.


clone 명령은 프로젝트의 이름에 해당하는 디렉토리를 새로 만든다. (위의 예제에서는 "git"와 "linux-2.6"에 해당한다.) 생성된 디렉토리 안으로 이동해 보면 프로젝트 파일의 복사본들과 (이를 작업 트리라고 부른다) 프로젝트의 변경 이력에 대한 모든 정보를 포함하고 있는 ".git" 라는 특수한 최상위 디렉토리가 존재하는 것을 볼 수 있을 것이다.


다른 버전의 프로젝트 체크아웃 하기#

git는 여러 파일들에 대한 변경 이력을 저장하기 위한 도구로 최선의 선택이다. git는 변경 이력을 프로젝트 내용물의 상호 연관된 스냅샷들을 압축하여 모아두는 방식으로 관리한다. git에서는 이러한 각각의 버전들을 커밋이라고 부른다.


이러한 스냅샷들은 꼭 시간 순으로만 일렬로 배열될 필요는 없다. 작업 내용은 개발 과정에 따라 동시에 병렬로 이루어 질 수 있으며 (이를 브랜치라고 부른다) 이는 하나로 합쳐지거나 여러 개로 나누어질 수 있다.


하나의 git 저장소는 여러 브랜치의 개발 과정을 관리할 수 있다. 이는 각 브랜치의 마지막 커밋에 대한 참조를 나타내는 헤드의 목록을 관리하는 방식으로 수행된다. git-branch(1) 명령은 브랜치 헤드의 목록을 보여준다.


  1. $ git branch
    * master

새로 복사된 저장소는 기본적으로 "master"라는 이름을 가지는 하나의 브랜치와 이에 대한 헤드가 참조하는 프로젝트의 상태로 초기화된 작업 디렉토리를 포함한다.


대부분의 프로젝트에서는 태그도 함께 사용한다. 태그는 헤드와 마찬가지로 프로젝트의 변경 이력에 대한 참조이며, git-tag(1) 명령으로 태그의 목록을 볼 수 있다.


  1. $ git tag -l
    v2.6.11
    v2.6.11-tree
    v2.6.12
    v2.6.12-rc2
    v2.6.12-rc3
    v2.6.12-rc4
    v2.6.12-rc5
    v2.6.12-rc6
    v2.6.13
    ...

태그는 항상 프로젝트의 동일한 버전을 가리키도록 되어 있지만, 헤드는 개발 과정에 따라 계속 변경되도록 되어 있다.


git-checkout(1) 명령을 이용하여 이러한 버전 중의 하나에 대한 브랜치 헤드를 만들고 코드를 체크아웃 할 수 있다.


  1. $ git checkout -b new v2.6.13

이제 작업 디렉토리는 v2.6.13으로 태그를 붙인 시점의 프로젝트의 내용을 반영하게 되며, git-branch(1) 명령은 두 개의 브랜치를 보여 준다. 이 중 별표(*) 표시된 것은 현재 체크아웃된 브랜치임을 나타낸다.


  1. $ git branch
      master
    * new

만약 (2.6.13 버전 대신) 2.6.17 버전의 코드를 보기로 결정했다면, 다음과 같은 방법으로 현재 브랜치가 v2.6.17을 가리키도록 수정할 수 있다.


  1. $ git reset --hard v2.6.17

기억해 두어야 할 것은 위의 경우에서 만약 현재 브랜치 헤드가 변경 이력 중의 특정 지점을 가리키는 유일한 참조였다면, 브랜치를 초기화(reset)하는 과정에서 이전의 위치로 돌아갈 수 있는 방법이 사라지게 된다는 점이다. 따라서 이 명령은 주의해서 사용해야 한다.


변경 이력 이해하기: 커밋#

프로젝트의 변경 이력에 포함된 모든 변경 사항은 커밋으로 표현된다. git-show(1) 명령은 현재 브랜치 상의 가장 최신 커밋 정보를 보여준다:



  1. $ git show
  2. commit 17cf781661e6d38f737f15f53ab552f1e95960d7
  3. Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)>
  4. Date:   Tue Apr 19 14:11:06 2005 -0700
  5.     Remove duplicate getenv(DB_ENVIRONMENT) call
  6.     Noted by Tony Luck.
  7. diff --git a/init-db.c b/init-db.c
  8. index 65898fa..b002dc6 100644
  9. --- a/init-db.c
  10. +++ b/init-db.c
  11. @@ -7,7 +7,7 @@
  12.  int main(int argc, char **argv)
  13.  {
  14. -       char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
  15. +       char *sha1_dir, *path;
  16.         int len, i;
  17.         if (mkdir(".git", 0755) < 0) {


위에서 볼 수 있듯이, 커밋은 최신 변경 사항을 누가, 무엇을, 왜 수정했는지에 대한 정보를 보여준다.


모든 커밋은 40자의 16진수 ID를 가지며 (이는 때때로 "객체 이름" 혹은 "SHA-1 id"라고 부른다) 이는 "git-show" 명령의 출력에서 첫 번째 줄에 나타난다. 보통 각각의 커밋은 태그 혹은 브랜치와 같은 더 짧은 이름으로 참조하지만, 이러한 긴 이름이 유용할 때도 있다. 가장 중요한 점은 이것은 해당 커밋에 대한 유일한 (globally unique) 이름이라는 것이다. 따라서 여러분이 다른 사람에게 객체 이름을 이야기 하게되면 (예를 들어 이메일 등을 통해) 여러분의 저장소 내의 커밋과 동일한 (다른 사람의 저장소 내에 있는) 커밋을 가리킨다고 보장할 수 있는 것이다. (그 저장소에는 이미 여러분의 모든 커밋 정보가 다 들어있다고 가정한다.) 객체 이름은 커밋 내용들에 대한 해시값으로 계산되기 때문에 객체 이름이 함께 바뀌지 않은 한 커밋 내용도 바뀌지 않는다고 보장받을 수 잇다.


사실 7장 "Git 개념 정리" 부분에서 우리는 파일 데이터 및 디렉토리 내용을 포함하여 git 변경 이력 안에 저장되는 모든 것은 객체 안에 저장되고, 그 객체의 이름은 객체의 내용에 대한 해시값으로 만들어 진다는 것을 알게 될 것이다.


변경 이력 이해하기: 커밋, 부모, 도달 가능성#

(프로젝트의 최초 커밋을 제외한) 모든 커밋은 해당 커밋 바로 전에 변경된 내용을 포함하는 부모 커밋을 가진다. 이러한 부모 커밋의 연결을 따라가면 결국에는 프로젝트의 시작점에 다다르게 될 것이다.


하지만 커밋들은 단순한 경로로 연결되지 않는다. git는 개발의 진행 단계가 (여러 경로로) 나눠지고 다시 합쳐지는 것을 허용하며, 이러한 두 경로가 다시 합쳐지는 지점을 "머지"라고 부른다. 따라서 머지를 가리키는 커밋은 둘 이상의 부모 커밋을 가지며, 각 부모 커밋은 각 개발 경로에서 현재 지점까지 이르는 가장 최근의 커밋을 나타낸다.


이 작업이 어떻게 이루어지는지 보기 위한 가장 좋은 방법은 gitk(1) 명령을 이용하는 것이다. 지금 git 저장소에서 gitk 명령을 실행하여 머지 커밋을 찾아본다면 git가 변경 이력을 저장하는 방식을 이해하는 데 도움이 될 것이다.


이후부터는, 만약 커밋 X가 커밋 Y의 조상인 경우에, 커밋 X는 커밋 Y로부터 "도달 가능"하다고 할 것이다. 마찬가지로 커밋 Y는 커밋 X의 후손이다라거나 커밋 Y로부터 커밋 X에 이르는 부모의 연결이 존재한다고 말할 수 있다.


변경 이력 이해하기: 변경 이력 다이어그램#

우리는 때때로 아래와 같은 다이어그램을 이용하여 git 변경 이력을 표시할 것이다. 커밋은 소문자 "o"로 표시하며, 커밋 간의 경로는 -,/,\ 기호를 이용한 선으로 표시한다. 시간은 왼쪽에서 오른쪽으로 흐른다.


         o--o--o <-- 브랜치 A

        /

 o--o--o <-- master

        \

         o--o--o <-- 브랜치 B


만약 특정 커밋에 대해 말할 필요가 있을 때는, 소문자 "o" 대신 다른 기호나 숫자를 사용할 수도 있다.


변경 이력 이해하기: 브랜치란 무엇인가?#

정확한 용어를 사용해야 할 경우에는, "브랜치"란 용어는 개발 경로를 뜻하고, "브랜치 헤드" (혹은 그냥 "헤드")가 브랜치 상의 가장 최근 커밋을 의미하는 용어로 사용된다. 위의 예제에서 A라는 이름의 "브랜치 헤드"는 특정한 커밋을 가리키는 포인터이고, 이 지점에 도달하기 위한 세 커밋이 속한 경로는 "브랜치 A"의 일부라고 말한다.


하지만 혼동의 여지가 없는 경우에는 "브랜치"라는 용어를 브랜치 및 브랜치 헤드를 나타낼 때 모두 사용한다.


브랜치 다루기#

브랜치를 만들거나, 지우거나, 고치는 일은 쉽고 빠르게 처리된다. 다음은 이러한 명령들을 요약해 둔 것이다:


  • git branch

모든 브랜치의 목록을 보여준다

  • git branch <브랜치>

<브랜치>라는 이름으로, 변경 이력 상에서 현재 브랜치와 동일한 지점을 참조하는 브랜치를 새로 만든다

  • git branch <브랜치> <시작지점>

<브랜치>라는 이름으로, <시작지점>을 참조하는 브랜치를 새로 만든다. <시작지점>은 다른 브랜치 이름이나 태그 이름 등을 포함한 어떠한 방식으로도 지정 가능하다.

  • git branch -d <브랜치>

<브랜치>라는 이름의 브랜치를 삭제한다. 만약 삭제하려는 브랜치가 현재 브랜치에서 도달할 수 없는 커밋을 가리키는 경우에는, 경고를 보여주며 이 명령을 실패한다.

  • git branch -D <브랜치>

삭제하려는 브랜치가 현재 브랜치에서 도달할 수 없는 커밋을 가리키는 경우에도, 해당 커밋이 다른 브랜치 혹은 태그를 통해 접근할 수 있다는 것을 알고 있을 수 있다. 이러한 경우 이 명령을 사용하여 git가 강제로 브랜치를 삭제하도록 할 수 있다.

  • git checkout <브랜치>

<브랜치>를 현재 브랜치로 만든다. <브랜치>가 가리키는 버전을 반영하도록 작업 디렉토리를 내용을 갱신한다.

  • git checkout -b <새브랜치> <시작지점>

<새브랜치>라는 이름으로, <시작지점>을 참조하는 브랜치를 새로 만들고, 체크아웃을 수행한다.


"HEAD"라는 이름의 특수 심볼은 항상 현재 브랜치의 최신 커밋을 가리키는 데 사용된다. 사실 git는 현재 브랜치를 기억하기 위해 .git 디렉토리 내에 "HEAD"라는 이름의 파일을 사용한다.


  1. $ cat .git/HEAD
  2. ref: refs/heads/master

새 브랜치를 만들지 않고 이전 버전 살펴보기#

git checkout 명령은 보통 브랜치 헤드를 인자로 받지만, 임의의 커밋을 인자로 넘길 수도 있다. 예를 들어 다음과 같이 특정 태그가 가리키는 커밋을 체크아웃할 수 있다:


  1. $ git checkout v2.6.17
  2. Note: moving to "v2.6.17" which isn't a local branch
  3. If you want to create a new branch from this checkout, you may do so
  4. (now or later) by using -b with the checkout command again. Example:
  5.   git checkout -b <new_branch_name>
  6. HEAD is now at 427abfa... Linux v2.6.17

이제 HEAD는 브랜치를 가리키는 대신 해당 커밋의 SHA-1 해시값을 가리키며, git branch 명령은 여러분이 이제 더 이상 브랜치 상에 있지 않다는 것을 보여준다:


  1. $ cat .git/HEAD
  2. 427abfa28afedffadfca9dd8b067eb6d36bac53f
  3. $ git branch
  4. * (no branch)
  5.   master

이러한 경우를 HEAD가 "분리"되었다고 (detached) 말한다.


이것은 새로운 브랜치를 만들지 않고 특정 버전을 체크아웃하기 위한 가장 손쉬운 방법이다. 원한다면 나중에 이 버전에 대한 브랜치 (혹은 태그)를 새로 만들 수 있다.


원격 저장소의 브랜치 살펴보기#

clone 명령을 수행할 때 생성되는 "master" 브랜치는 복사해온 저장소 내의 HEAD의 복사본이다. 하지만 이 저장소에는 다른 브랜치들도 존재할 수 있으며, 여러분의 로컬 저장소에도 이러한 원격 브랜치를 추적하기 위한 브랜치가 존재한다. 이러한 원격 브랜치들은 git-branch(1) 명령의 "-r" 옵션을 이용하여 볼 수 있다.


  1. $ git branch -r
      origin/HEAD
      origin/html
      origin/maint
      origin/man
      origin/master
      origin/next
      origin/pu
      origin/todo

이러한 원격 추적 브랜치를 직접 체크아웃할 수는 없다. 하지만 태그를 이용하는 것과 같이 브랜치를 직접 생성한 후 살펴볼 수 있다.


  1. $ git checkout -b my-todo-copy origin/todo

"origin"이라는 이름은 단지 git가 복사해 온 저장소를 가리키기 위해 기본적으로 사용하는 이름이라는 것을 기억해 두기 바란다.


브랜치, 태그 및 다른 참조에 대한 이름 붙이기#

브랜치, 원격 추적 브랜치, 태그는 모두 커밋을 참조한다. 모든 참조의 이름은 "refs"로 시작하고 슬래시(/) 기호로 구분되는 경로명으로 구성된다. 지금껏 우리가 사용해 온 이름들은 사실 상 줄임말에 해당한다:


  • "test" 라는 브랜치는 "refs/heads/test"의 줄임말이다.
  • "v2.6.18"이라는 태그는 "refs/tags/v2.6.18"의 줄임말이다.
  • "origin/master"는 "refs/remotes/origin/master"의 줄임말이다.

완전한 이름은 (같은 이름의 브랜치와 태그가 동시에 존재하는 경우와 같이) 때때로 유용하게 사용된다.


(새로 생성된 참조들은 실제로 .git/refs 디렉토리 내의 주어진 이름의 경로에 해당하는 곳에 저장된다. 하지만 성능 상의 이유로 인해 이들은 하나의 파일로 합쳐질 수도 있다 - git-pack-refs(1) man 페이지를 살펴보기 바란다)


또 다른 유용한 줄임말로, 저장소의 "HEAD"는 단지 저장소의 이름으로 참조할 수 있다. 예를 들어 "origin"은 보통 "origin" 저장소 내의 HEAD 브랜치에 대한 줄임말로 사용된다.


git가 참조를 검사하는 경로의 완전한 목록과, 동일한 줄임말이 여러 경로 상에 존재할 때 어떤 것을 사용할 지 결정하는 순서는 git-rev-parse(1) man 페이지의 "리비전 지정하기" 부분을 살펴보기 바란다.


git-fetch를 이용하여 저장소 업데이트하기#

저장소를 복사해 온 개발자는 결국 자신의 저장소 내에서 추가적인 작업을 하고, 새로운 커밋을 생성하고, 새 커밋을 가리키도록 브랜치를 키워나갈 것이다.


"git fetch" 명령을 아무 인자 없이 실행하면, 모든 원격 추적 브랜치들은 해당 저장소 내의 가장 최신 버전으로 업데이트할 것이다. 이 명령은 여러분이 생성한 브랜치들은 전혀 손대지 않는다 - 여러분이 복사해 온 "master" 브랜치 자체도 변경되지 않는다.


다른 저장소에서 브랜치 가져오기#

git-remote(1) 명령을 이용하면 최초 복사해 온 저장소 이외의 저장소에서도 브랜치를 추적할 수 있다:


  1. $ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
    $ git fetch linux-nfs
    * refs/remotes/linux-nfs/master: storing branch 'master' ...
      commit: bf81b46

새로 만들어진 원격 추적 브랜치는 "git remote add" 명령에서 인자로 넘긴 줄임말 이름 아래에 저장된다. 위의 예제에서는 linux-nfs에 해당한다:


  1. $ git branch -r
    linux-nfs/master
    origin/master

나중에 "git fetch <원격브랜치>" 명령을 실행하면 <원격브랜치>라는 이름의 원격 추적 브랜치가 업데이트 될 것이다.


.git/config 파일을 살펴본다면, git가 새로운 내용을 추가했음을 확인할 수 있다:


  1. $ cat .git/config
    ...
    [remote "linux-nfs"]
            url = git://linux-nfs.org/pub/nfs-2.6.git
            fetch = +refs/heads/*:refs/remotes/linux-nfs/*
    ...

이것은 git가 원격 저장소의 브랜치들을 추적할 수 있게 해 주는 것이다. 텍스트 편집기를 이용하여 .git/config 파일을 직접 수정하면 이러한 설정 옵션들을 변경하거나 삭제할 수 있다. (자세한 내용은 git-config(1) man 페이지의 "설정 파일" 부분을 살펴보기 바란다.)


Git 변경 이력 조사하기#

git는 여러 파일들에 대한 변경 이력을 저장하기 위한 도구로 최선의 선택이다. git는 파일 및 디렉토리의 내용들을 압축된 스냅샷으로 저장하고 각 스냅샷 간의 관계를 보여주는 "커밋"을 함께 저장하는 방식으로 이러한 작업을 수행한다.


git는 프로젝트의 변경 이력을 조사하기 위한 매우 유연하고 빠른 도구를 제공한다.


여기서는 프로젝트 내에 특정 버그를 만들어 낸 커밋을 찾아내는 데 유용한 도구를 먼저 살펴보도록 하자.


regression을 찾기 위해 bisect 이용하기#

여러분의 프로젝트가 2.6.18 버전에서는 동작하지만, "master" 버전에서는 문제가 발생한다고 가정해 보자. 때때로 이러한 regression의 원인을 찾기 위한 가장 좋은 방법은 문제를 발생시킨 특정 커밋을 찾을 때 까지 프로젝트의 변경 이력을 무식하게 검색하는 것이다. git-bisect(1) 명령은 이러한 작업을 도와줄 수 있다:


  1. $ git bisect start
    $ git bisect good v2.6.18
    $ git bisect bad master
    Bisecting: 3537 revisions left to test after this
    [65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]

만약 이 상태에서 "git branch" 명령을 실행한다면, git는 임시로 "(no branch)" 상태로 전환되었다는 것을 확인할 수 있을 것이다. 이제 HEAD는 모든 브랜치에서 분리되었으며, "master"에서는 도달 가능하지만 v2.6.18에서는 도달할 수 없는 특정 커밋 (커밋 ID는 65934...)을 직접 가리키게된다. 소스를 컴파일해서 실행한 후 문제가 발생하는지 지켜보자. 여기에서는 여전히 문제가 발생한다고 가정한다. 그렇다면 다음과 같이 실행한다:


  1. $ git bisect bad
    Bisecting: 1769 revisions left to test after this
    [7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings

이는 더 오래된 버전을 체크아웃한다. 이와 비슷하게 각 단계 별로 해당 버전이 잘 동작하는지 여부를 git에게 알려주고 이 과정을 반복한다. 눈여겨 볼 것은 테스트할 버전의 수는 각 단계 마다 대략 1/2로 감소한다는 것이다.


(위의 예제에서) 13번의 테스트 끝에, 문제가 되는 커밋의 커밋 ID를 출력하게 되었다. 이제 git-show(1) 명령을 통해 커밋 정보를 살펴보고, 누가 이 커밋을 작성했는지 알아냈다면 이메일로 커밋 ID를 포함한 버그 보고서를 보낼 수 있다. 마지막으로는 다음을 실행한다.


  1. $ git bisect reset

이는 이전에 작업하던 브랜치로 돌아가도록 해 준다.


'git bisect' 명령이 각 단계 별로 검사하는 버전은 단지 제안일 뿐이며, 그것이 올바르다고 판단되는 경우에는 다른 버전을 대신 검사해 볼 수 있다. 예를 들어, 때때로 관련 없는 부분에서 문제를 일으키는 커밋에 도달했을 수도 있다. 이 때 다음 명령을 실행한다


  1. $ git bisect visualize

이는 gitk 명령을 실행하고 선택한 커밋에 "bisect"라고 써진 마크와 레이블을 보여줄 것이다. 그 근처에 있는 안전해 보이는 커밋을 선택하고, 커밋 ID를 메모해 둔 후 다음과 같이 해당 커밋을 체크아웃 한다:


  1. $ git reset --hard fb47ddb2db...

다시 테스트를 수행하고 그 결과에 따라 "bisect good" 혹은 "bisect bad"를 실행하는 과정을 반복한다.


"git bisect visualize" 명령과 "git reset --hard fb47ddb2db..." 명령을 수행하는 대신, git가 현재 커밋을 건너뛰도록 하기를 원할 수도 있다:


  1. $ git bisect skip

하지만 이 경우에 git는 최초에 건너뛴 커밋과 이후의 문제 있는 커밋 사이에서 어느 것이 처음으로 문제가 발생된 커밋인지 판단하지 못할 수도 있다.


만약 해당 커밋에 문제가 있는지 여부를 판단할 수 있는 테스트 스크립트를 가지고 있다면 이진 탐색(bisect) 과정을 자동화할 수 있는 방법들도 존재한다. 이에 대한 자세한 내용 및 다른 "git bisect" 기능에 대한 내용은 git-bisect(1) man 페이지를 살펴보기 바란다.


커밋 이름 붙이기#

지금까지 특정 커밋에 이름을 붙이는 여러가지 방법을 살펴 보았다:

  • 40자의 16진수 객체 이름
  • 브랜치 이름 : 해당 브랜치의 헤드에 있는 커밋을 참조한다
  • 태그 이름 : 해당 태그가 가리키는 커밋을 참조한다 (우리는 브랜치와 태그가 특별한 형태의 참조라는 것을 살펴 보았다)
  • HEAD : 현재 브랜치의 헤드에 있는 커밋을 참조한다

이 외에도 여러가지 방법이 존재한다. 커밋에 이름을 붙이는 방법에 대한 완전한 목록은 git-rev-parse(1) man 페이지의 "리비전 지정하기" 부분을 살펴보기 바란다. 다음은 몇 가지 예제를 보여준다:


  1. $ git show fb47ddb2 # 보통은 객체 이름 첫 부분의 몇 글자로도
  2.                     # 해당 커밋을 지정하기에 충분하다
  3. $ git show HEAD^    # HEAD의 부모 커밋
  4. $ git show HEAD^^   # HEAD의 부모의 부모 커밋
  5. $ git show HEAD~4   # HEAD의 부모의 부모의 부모의 부모 커밋

머지 커밋의 경우에는 둘 이상의 부모를 가질 수 있다는 것을 기억하자. 기본적으로 ^ 와 ~ 는 해당 커밋에 연결된 첫 번째 부모를 따라간다. 하지만 다음 명령을 사용하여 다른 부모를 선택할 수 있다:


  1. $ git show HEAD^1   # HEAD의 첫 번째 부모 커밋
  2. $ git show HEAD^2   # HEAD의 두 번째 부모 커밋

HEAD 이외에도 커밋을 가리키는 특수한 이름들이 더 존재한다.


(이후에 살펴 볼) 머지 및 'git reset'과 같이 현재 체크아웃된 커밋을 변경하는 명령들은 일반적으로 해당 명령이 수행되기 전의 HEAD의 값으로 ORIG_HEAD를 설정한다.


'git fetch' 명령은 항상 마지막으로 가져온 브랜치의 헤드를 FETCH_HEAD에 저장한다. 예를 들어 명령의 대상으로 로컬 브랜치를 지정하지 않고 'git fetch' 명령을 실행했다면


  1. $ git fetch git://example.com/proj.git theirbranch

가져온 커밋들은 FETCH_HEAD를 통해 접근할 수 있을 것이다.


머지에 대해 설명할 때 현재 브랜치와 통합하는 다른 브랜치를 가리키는 MERGE_HEAD라는 특수한 이름도 살펴볼 것이다.


git-rev-parse(1) 명령은 때때로 커밋에 대한 이름을 해당 커밋의 객체 이름으로 변환할 때 유용하게 사용되는 저수준 명령이다.


  1. $ git rev-parse origin
    e05db0fd4f31dde7005f075a84f96b360d05984b

태그 만들기#

특정 커밋을 참조하는 태그를 만드는 것이 가능하다. 다음 명령을 실행하고 나면


  1. $ git tag stable-1 1b2e1d63ff

커밋 1b2e1d63ff를 가리키기 위해 stable-1을 사용할 수 있다.


이것은 "가벼운" (lightweight) 태그를 만든다. 태그에 설명을 추가하거나 이에 암호화된 서명을 하려는 경우에는 가벼운 태그 대신 태그 객체를 만들어야 한다. 이에 대한 자세한 내용은 git-tag(1) man 페이지를 살펴보기 바란다.


버전 살펴보기#

git-log(1) 명령은 커밋의 목록을 보여줄 수 있다. 기본적으로 이 명령은 부모 커밋에서부터 도달할 수 있는 모든 커밋들을 보여주지만 특정한 범위를 지정할 수도 있다:


  1. $ git log v2.5..        # v2.5부터의 커밋 (v2.5에서는 도달할 수 없는 커밋)
    $ git log test..master  # master에서는 도달할 수 있지만 test에서는 도달할 수 없는 커밋
    $ git log master..test  # test에서는 도달할 수 있지만 master에서는 도달할 수 없는 커밋
    $ git log master...test # test 혹은 master 중 하나에서 도달할 수 있는 있는 커밋
                            #    하지만 둘 다 도달할 수 있는 커밋들은 제외
    $ git log --since="2 weeks ago" # 지난 2 주 간의 커밋
    $ git log Makefile      # Makefile 파일을 수정한 커밋
    $ git log fs/           # fs/ 디렉토리 내의 파일들을 수정한 커밋
    $ git log -S'foo()'     # 문자열 'foo()'에 매칭되는 어떠한 파일 데이터라도
                            # 추가 혹은 삭제한 커밋

물론 이러한 조건들을 함께 사용할 수 있다. 아래의 명령은 v2.5 이후로 Makefile이나 fs 디렉토리 내의 어떤 파일을 수정한 커밋들을 찾아준다:


  1. $ git log v2.5.. Makefile fs/

또한 git log에게 패치 형식으로 보여달라고 요청할 수도 있다.


  1. $ git log -p

다양한 표시 옵션에 대해서는 git-log(1) man 페이지의 "--pretty" 옵션 부분을 살펴보기 바란다.


git log는 가장 최신의 커밋에서부터 시작하여 부모의 경로를 따라 역방향으로 출력한다는 것을 알아두도록 하자. 하지만 git 변경 이력은 다중 개발 경로를 포함할 수 있기 때문에 커밋의 목록이 출력되는 상세한 순서는 약간 달라질 수 있다.


차이점 생성하기#

git-diff(1) 명령을 이용하여 임의의 두 버전 간의 차이점(diff)를 생성할 수 있다:


  1. $ git diff master..test

위 명령은 두 브랜치의 최신 내용(tip) 간의 차이점을 만들어 낼 것이다. 만약 두 브랜치의 공통 조상으로부터 test까지의 차이점을 찾아보고 싶다면 점(.)을 두개 대신 세 개 사용하면 된다.


  1. $ git diff master...test

때로는 여러 개의 패치 형태로 출력하고 싶을 수도 있다. 이를 위해서는 git-format-patch(1) 명령을 이용할 수 있다.


  1. $ git format-patch master..test

위 명령은 test에서는 도달할 수 있지만 master에서는 도달할 수 없는 각 커밋에 대한 패치에 해당하는 파일들을 생성할 것이다.


예전 버전의 파일 보기#

예전 버전으로 체크아웃하고 나면 해당 버전의 파일을 언제나 볼 수 있다. 하지만 때때로 체크아웃없이 한 파일의 예전 버전을 볼 수 있다면 좀 더 편리할 것이다. 다음 명령은 이 같은 작업을 수행한다:


  1. $ git show v2.5:fs/locks.c

콜론(:) 앞에는 커밋을 가리키는 어떠한 이름도 올 수 있고, 콜론 뒤에는 git에서 관리하는 어떠한 파일에 대한 경로도 올 수 있다.


예제#

특정 브랜치 상의 커밋 개수 세기#

여러분이 "origin" 브랜치에서 "mybranch" 브랜치를 만든 이후에 얼마나 많은 커밋을 했는지 알고 싶다고 가정해 보자:


  1. $ git log --pretty=oneline origin..mybranch | wc -l

다른 방법으로, 이러한 작업을 주어진 모든 커밋에 대한 SHA-1 해시값의 목록을 보여주는, 보다 저수준 명령인 git-rev-list(1) 명령을 통해 수행하는 것을 볼 수 있을 것이다:


  1. $ git rev-list origin..mybranch | wc -l

두 브랜치가 동일한 지점을 가리키는지 검사하기#

두 브랜치가 변경 이력 상의 같은 지점을 가리키는지 알고 싶다고 가정해 보자.


  1. $ git diff origin..master

위 명령은 두 브랜치에서 프로젝트의 내용이 동일한지 여부를 보여줄 것이다. 하지만 이론적으로, 동일한 프로젝트의 내용이 서로 다른 변경 이력을 거쳐 만들어졌을 수도 있다. 객체 이름을 이용하여 이를 비교할 수 있다:


  1. $ git rev-list origin
    e05db0fd4f31dde7005f075a84f96b360d05984b
    $ git rev-list master
    e05db0fd4f31dde7005f075a84f96b360d05984b

혹은 ... 연산자가 두 참조 중의 하나에서 도달할 수 있지만 둘 다에서는 도달할 수 없는 모든 커밋들을 선택한다는 것을 기억해 볼 수 있다. 따라서


  1. $ git log origin...master

두 브랜치가 동일할 경우 위 명령은 아무 커밋도 보여주지 않을 것이다.


특정 커밋을 포함하는 것 중에서 제일 먼저 태그가 붙은 버전 찾기#

e05db0fd 커밋이 어떤 문제를 해결한 것이라고 가정해 보자. 여러분은 이 해결책을 포함한 것 중 가장 먼저 태그가 붙은 배포 버전을 찾고 싶다.


물론 이를 위한 방법은 여러가지 존재한다. 만약 변경 이력 상에서 e05db0fd 커밋 이후에 브랜치가 생성되었다면 여러 개의 "가장 먼저" 태그가 붙은 배포 버전이 존재할 수 있다.


다음과 같이 e05db0fd 이후의 커밋들을 그래프로 볼 수 있다:


  1. $ gitk e05db0fd..

또는 해당 커밋의 자손 중의 하나를 가리키는 태그를 찾아 이를 바탕으로 해당 커밋에 이름을 붙이는 git-name-rev(1) 명령을 이용할 수 있다:


  1. $ git name-rev --tags e05db0fd
    e05db0fd tags/v1.5.0-rc1^0~23

git-describe(1) 명령은 반대의 작업을 수행하는 데, 이는 주어진 커밋에서 도달할 수 있는 태그를 사용하여 버전에 이름을 붙인다:


  1. $ git describe e05db0fd
    v1.5.0-rc0-260-ge05db0f

하지만 이는 때때로 해당 커밋 다음에 어떤 태그를 사용해야 할 지 결정할 때 도움을 줄 수 있다.


만약 어떤 태그가 가리키는 버전이 주어진 커밋을 포함하는지 만을 알아보고 싶다면 git-merge-base(1) 명령을 이용한다:


  1. $ git merge-base e05db0fd v1.5.0-rc1
    e05db0fd4f31dde7005f075a84f96b360d05984b

merge-base 명령은 주어진 커밋들의 공통 조상을 찾는데, 주어진 두 커밋 중의 하나가 다른 것의 후손이라면 항상 (조상에 해당하는) 이 둘 중의 하나를 반환한다. 즉 위의 결과에서 e05db0fd 커밋은 실제로 v1.5.0-rc1의 조상임을 보여준다.


다른 방법으로


  1. $ git log v1.5.0-rc1..e05db0fd

오직 v1.5.0-rc1 커밋이 e05db0fd 커밋을 포함하고 있는 경우에만 위 명령은 아무런 결과도 출력하지 않을 것이다. 왜냐하면 위 명령은 v1.5.0-rc1에서 도달할 수 없는 커밋 만을 출력하기 때문이다.


또 다른 방법으로 git-show-branch(1) 명령은 주어진 인자에서 도달할 수 있는 커밋의 목록을 보여주는데, 왼편에 어떤 인자들이 해당 커밋에 도달할 수 있는지를 표시해준다. 따라서 다음과 같은 명령을 실행하고


  1. $ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2
    ! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
    available
     ! [v1.5.0-rc0] GIT v1.5.0 preview
      ! [v1.5.0-rc1] GIT v1.5.0-rc1
       ! [v1.5.0-rc2] GIT v1.5.0-rc2
    ...

다음과 같은 줄을 검색한다


  1. + ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
    available

이 결과는 e05db0fd 커밋이 자기 자신과 v1.5.0-rc1, v1.5.0-rc2에서는 도달 가능하지만 v1.5.0-rc0에서는 도달할 수 없다는 것을 보여준다.


주어진 브랜치에만 존재하는 커밋 보기#

"master"라는 이름의 브랜치 헤드에서는 도달할 수 있지만 저장소 내의 다른 어떤 헤드에서도 도달할 수 없는 모든 커밋들을 보고 싶다고 가정해 보자.


git-show-ref(1) 명령을 이용하여 저장소 내의 모든 헤드의 목록을 볼 수 있다:


  1. $ git show-ref --heads
    bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial
    db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint
    a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master
    24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-2
    1e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes

cut과 grep이라는 표준 도구를 사용하여 "master"를 제외한 모든 브랜치 헤드의 목록을 얻을 수 있다:


  1. $ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master'
    refs/heads/core-tutorial
    refs/heads/maint
    refs/heads/tutorial-2
    refs/heads/tutorial-fixes

그럼 이제 master에서는 도달할 수 있지만 위의 헤더에서는 도달할 수 없는 커밋들의 목록을 보여달라고 요청할 수 있다:


  1. $ gitk master --not $( git show-ref --heads | cut -d' ' -f2 |
                                    grep -v '^refs/heads/master' )

물론 이를 계속 변형하는 것이 가능하다. 예를 들어 어떤 헤드에서는 도달할 수 있지만 저장소 내의 어떤 태그에서도 도달할 수 없는 커밋들의 목록을 보려면 다음 명령을 실행한다:


  1. $ gitk $( git show-ref --heads ) --not  $( git show-ref --tags )

('--not'과 같은 커밋 선택 문법에 대한 설명은 git-rev-parse(1) man 페이지를 보기 바란다)


소프트웨어 배포를 위한 변경 로그 및 tarball 만들기#

git-archive(1) 명령은 프로젝트 내의 어떤 버전에서도 tar 혹은 zip 압축 파일을 만들 수 있다. 예를 들어:


  1. $ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz

위 명령은 HEAD 버전에 해당하는 tar 아카이브 파일을 만드는데, 모든 파일 이름의 앞에는 "project/"가 추가될 것이다.


만약 여러분이 소프트웨어 프로젝트의 새 버전을 배포하려 한다면 배포 공지 사항에 변경 로그도 추가하고 싶을 것이다.


예를 들면, 리누스 토발즈는 커널을 배포할 때, 태그를 붙인 다음 아래와 같은 명령을 실행한다:


  1. $ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7

release-script는 다음과 같은 쉘 스크립트이다:


  1. #!/bin/sh
    stable="$1"
    last="$2"
    new="$3"
    echo "# git tag v$new"
    echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz"
    echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz"
    echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new"
    echo "git shortlog --no-merges v$new ^v$last > ../ShortLog"
    echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"

그리고 정상적으로 출력되는 지 확인한 후, 출력된 명령들을 잘라서 붙여넣기로 실행한다.


파일의 특정 내용을 참조하는 커밋 찾기#

누군가 여러분에게 어떤 파일의 복사본을 주면서, 어떤 커밋이 이 파일을 수정하였는지, 즉 해당 커밋의 이전 혹은 이후에 주어진 내용이 포함되어 있는지 물어본다. 다음 명령을 이용하여 이를 알아볼 수 있다:


  1. $  git log --raw --abbrev=40 --pretty=oneline |
            grep -B 1 `git hash-object filename`

이것이 어떻게 가능한지는 (우수한) 학생들을 위한 숙제로 남겨두겠다. git-log(1), git-diff-tree(1), git-hash-object(1) man 페이지가 도움이 될 것이다.


Git를 이용하여 개발하기#

git에게 이름 말해주기#

커밋을 생성하기 전에, git에게 여러분을 소개해야 한다. 이를 위한 가장 간단한 방법은 홈 디렉토리 내의 .gitconfig 파일에 다음과 같은 내용을 기록하는 것이다:


  1. [user]
            name = 여기에 이름을 적는다
            email = you@yourdomain.example.com

(설정 파일에 대한 자세한 설명은 git-config(1) man 페이지의 "설정 파일" 부분을 살펴보기 바란다)


새 저장소 만들기#

아무 것도 없는 상태에서 저장소를 새로 만드는 것은 아주 간단하다:


  1. $ mkdir project
    $ cd project
    $ git init

초기에 사용할 어떤 내용물이 있다면 (말하자면 tarball 같은 것) 다음 명령을 실행한다:


  1. $ tar xzvf project.tar.gz
    $ cd project
    $ git init
    $ git add . # 최초 커밋 시에 ./ 아래의 모든 것을 추가한다
    $ git commit

커밋을 만드는 방법#

새로운 커밋을 만드려면 다음과 같은 세 단계를 거친다:


  1. 주로 사용하는 편집기를 이용하여 작업 디렉토리 상에 어떤 변경 사항을 만든다
  2. git에게 변경 사항을 알려준다
  3. 위 단계에서 git에게 알려준 내용을 이용하여 커밋을 만든다

실제로는 원하는 만큼 과정 1과 2를 계속 (순서를 섞어서도)  반복할 수 있다. 3 단계에서 커밋할 내용들을 추적하기 위해 git는 "인덱스"라고 하는 특별한 준비 영역에 트리 내용들의 스냅샷을 유지한다.


처음에는, 인덱스의 내용은 HEAD의 내용과 같을 것이다. 따라서 HEAD와 인덱스 간의 차이점을 보여주는 "git diff --cached" 명령은, 이 시점에서는 아무 것도 출력하지 않을 것이다.


인덱스를 수정하는 것은 간단하다:


수정한 파일의 새 내용을 이용하여 인덱스를 갱신하려면 다음을 실행한다.


  1. $ git add path/to/file

새 파일의 내용을 인덱스에 추가하려면 다음을 실행한다.


  1. $ git add path/to/file

인덱스와 작업 트리에서 파일을 삭제하려면 다음을 실행한다.


  1. $ git rm path/to/file

각각의 단계에서 다음 명령을 실행하면


  1. $ git diff --cached

항상 HEAD와 인덱스 간의 차이점을 보여준다는 것을 확인할 수 있다. 이것은 지금 커밋을 만든다고 할 때 적용되는 내용이다. 그리고


  1. $ git diff

위 명령은 작업 트리와 인덱스 간의 차이점을 보여준다.


기억해야 할 것은 "git add" 명령은 단지 해당 파일의 현재 내용을 인덱스에 추가할 뿐이므로, 이 파일을 더 수정하는 경우 다시 'git add' 명령을 수행하지 않는다면 이후의 수정 사항들은 무시될 것이라는 점이다.


준비가 되었다면 단지 아래의 명령을 실행한다.


  1. $ git commit

그러면 git가 커밋 메시지를 입력하도록 프롬프트를 띄울 것이고, 그 후에 커밋을 생성한다. 커밋 메시지를 입력할 때는 아래의 명령을 실행했을 때 나타나는 것과 비슷한 형태로 작성한다.


  1. $ git show

특수한 바로 가기로써


  1. $ git commit -a

위 명령은 수정 혹은 삭제된 모든 파일을 이용하여 인덱스를 갱신하고, 커밋을 생성하는 일을 한 번에 다 수행할 것이다.


다음의 몇 가지 명령은 커밋 시에 어떤 것들이 반영되는 지 추적하기에 유용하다:


  1. $ git diff --cached # HEAD와 인덱스 간의 차이점. 지금 바로
                        # "commit" 명령을 수행할 경우 반영되는 내용들
    $ git diff          # 인덱스 파일과 작업 디렉토리 간의 차이점.
                        # 지금 바로 "commit" 명령을 수행할 경우
                        # 반영되지 않는 변경 사항들
    $ git diff HEAD     # HEAD와 작업 디렉토리 간의 차이점. 지금 바로
                        # "commit -a" 명령을 수행할 경우 반영되는 내용들
    $ git status        # 위 내용에 대한 파일 별 간략한 요약 정보

또는 git-gui(1) 명령을 이용하여 인덱스 혹은 작업 디렉토리 내의 변경 사항을 보거나, 커밋을 생성하는 것이 가능하다. 또한 인덱스 내의 차이점들 중에서 (마우스 오른쪽 버튼을 클릭하여 "Stage Hunk for Commit"을 선택하면) 커밋 시에 포함할 내용들을 별도로 선택하는 일도 할 수 있다.


좋은 커밋 메시지 작성하기#

이것이 반드시 지켜져야 하는 것은 아니지만, 커밋 메시지를 작성할 때는 변경 사항을 요약하는 (50자 이내의) 짧은 한 줄 짜리 메시지로 시작하고, 빈 줄을 넣은 뒤, 보다 상세한 설명을 기록하는 형식으로 작성하는 것이 좋다. 예를 들어, 커밋 내용을 이메일로 변환하는 도구는 첫 줄의 내용을 메일의 제목으로 사용하고 나머지 부분은 메일의 본문으로 사용한다.


파일 무시하기#

프로젝트는 때로 git가 관리하지 않아야 할 파일들을 생성하기도 한다. 보통 여기에는 빌드 과정에서 생기는 파일이나 여러분이 사용하는 편집기가 만드는 임시 백업 파일 등이 포함된다. 물론 이러한 파일들을 git에서 관리하지 않게 하려면 단지 해당 파일에 대해 'git add' 명령을 수행하지 않으면 된다. 하지만 이렇게 관리되지 않는 파일들이 생기면 성가신 문제들이 발생한다. 예를 들면, 이러한 파일들이 있을 때는 'git add .' 명령을 거의 이용할 수 없으며, 'git status' 명령의 출력에도 항상 포함된다.


작업 디렉토리의 최상위에 .gitignore라는 파일을 만들어두면 git에게 특정 파일을 무시하라고 알려줄 수 있다. 이 파일의 내용은 다음과 같은 내용을 포함할 수 있다:


  1. # '#'으로 시작하는 줄은 주석으로 처리된다
    # foo.txt라는 이름의 파일들은 모두 무시한다
    foo.txt
    # (자동 생성된) HTML 파일들을 무시한다
    *.html
    # 하지만 직접 작성하는 foo.html 파일은 예외로 한다
    !foo.html
    # 오브젝트 파일과 아카이브 파일들을 무시한다
    *.[oa]

문법에 대한 자세한 설명은 gitignore(5) man 페이지를 살펴보기 바란다. 또한 .gitignore 파일을 작업 디렉토리 상의 어떤 디렉토리에도 넣어둘 수 있으며, 이 경우 그 내용은 해당 디렉토리와 그 하위 디렉토리에 적용된다. '.gitignore' 파일은 다른 파일들과 마찬가지로 (평소와 같이 'git add .gitignore' 와 'git commit' 명령을 수행하여) 저장소에 추가될 수 있으며, 무시할 패턴이 (빌드 출력 파일과 매칭되는 패턴과 같이) 여러분의 저장소를 복사해 갈 다른 사용자들에게도 적용되는 경우에 편리하다.


만약 무시할 패턴이 (해당 프로젝트에 대한 모든 저장소가 아닌) 특정 저장소에서만 동작하도록 하고 싶다면, 이러한 내용을 해당 저장소 내의 .git/info/exclude 파일에 적어두거나 'core.excludefile' 설정 변수에 지정된 파일에 적어두면 된다. 몇몇 git 명령은 명령행에서 무시할 패턴을 직접 넘겨받을 수도 있다. 자세한 내용은 gitignore(5) man 페이지를 살펴보기 바란다.


머지하는 방법#

git-merge(1) 명령을 이용하여 개발 과정에서 나누어진 두 브랜치를 다시 합치는 것이 가능하다.


  1. $ git merge branchname

위 명령은 "branchname"이라는 브랜치의 개발 내용을 현재 브랜치로 통합(머지)한다. 만약 충돌이 발생한 경우 (예를 들면 원격 브랜치와 로컬 브랜치에서 동일한 파일이 서로 다른 방식으로 변경된 경우) 경고를 보여준다. 출력되는 내용은 아래와 같은 형태가 될 것이다.


  1. $ git merge next
     100% (4/4) done
    Auto-merged file.txt
    CONFLICT (content): Merge conflict in file.txt
    Automatic merge failed; fix conflicts and then commit the result.

문제가 생긴 파일에는 충돌 표시가 남아있게 된다. 이를 직접 해결하고나면 새 파일을 만들 때와 같이 변경된 내용으로 인덱스를 갱신하고 "git commit" 명령을 수행한다.


만약 이러한 커밋을 gitk 명령으로 살펴본다면, 해당 커밋이 (현재 브랜치의 끝을 가리키는 것과 다른 브랜치의 끝을 가리키는) 두 개의 부모를 가지고 있는 것을 보게 될 것이다.


머지 (충돌) 해결하기#

머지 시에 (충돌이) 자동으로 해결되지 않는 경우, git는 인덱스와 작업 디렉토리를 충돌을 해결하기에 필요한 모든 정보를 저장하는 특수한 상태로 남겨둔다.


충돌이 발생한 파일들은 인덱스 내에 특별하게 표시되므로, 문제점을 해결하고 인덱스를 갱신하기 전까지 git-commit(1) 명령은 실패할 것이다:


  1. $ git commit
    file.txt: needs merge

또한 git-status(1) 명령은 이 파일들을 "머지되지 않은" (unmerged) 상태로 출력하며, 충돌이 발생한 파일들은 아래와 같이 충돌 표시를 포함하게 될 것이다:


  1. <<<<<<< HEAD:file.txt
    Hello world
    =======
    Goodbye
    >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

여러분이 해야할 일은 파일을 편집하여 충돌을 해결하고 다음 명령을 실행하는 것이다.


  1. $ git add file.txt
    $ git commit

커밋 메시지는 머지에 대한 정보를 포함하여 미리 작성된다는 것을 알아두기 바란다. 일반적으로 이러한 기본 메시지를 그대로 사용할 수 있지만 원한다면 부가적인 메시지를 더 입력할 수 있다.


위 사항은 단순한 머지 시의 문제를 해결하기 위해 반드시 알아두어야 할 것이다. 하지만 git는 충돌을 해결하기 위해 필요한 자세한 정보들을 제공해 준다.


머지 중에 충돌을 해결하기 위해 필요한 정보 얻기#

git가 자동으로 머지할 수 있는 모든 변경 사항들은 인덱스 파일에 이미 추가되었으므로, git-diff(1) 명령은 오직 충돌한 내용 만을 보여준다. 여기에는 보통과는 다른 문법이 사용된다:


  1. $ git diff
    diff --cc file.txt
    index 802992c,2b60207..0000000
    --- a/file.txt
    +++ b/file.txt
    @@@ -1,1 -1,1 +1,5 @@@
    ++<<<<<<< HEAD:file.txt
     +Hello world
    ++=======
    + Goodbye
    ++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

이 충돌 사항을 해결한 후에 생성될 커밋은 보통과는 달리 두 개의 부모를 가진다는 것을 기억해 보자. 하나는 현재 브랜치의 끝인 HEAD이고, 다른 하나는 임시로 MERGE_HEAD에 저장된, 다른 브랜치의 끝이 될 것이다.


머지 중에 인덱스는 각 파일들에 대한 세 가지 버전을 가지고 있다. 이러한 세 "파일 스테이지" 각각은 해당 파일의 서로 다른 버전을 나타낸다:


  1. $ git show :1:file.txt  # 두 부모의 공통 조상에 속한 파일
    $ git show :2:file.txt  # HEAD에 있는 버전
    $ git show :3:file.txt  # MERGE_HEAD에 있는 버전

충돌 내용을 보기 위해 git-diff(1) 명령을 실행하면, 양쪽에서 함께 온 변경 사항들만을 같이 보여주기 위해 스테이지 2와 스테이지 3의 작업 트리 내의 머지 충돌 결과 간의 3-방향 차이점 보기를 실행한다. (다시 말해, 머지 결과 내의 어떤 변경 사항이 스테이지 2에서만 왔다면 이 부분은 충돌이 발생하지 않으며 따라서 출려되지 않는다. 이는 스테이지 3에 대해서도 마찬가지이다.)


위의 diff 명령은 작업 트리 버전의 file.txt와 스테이지 2와 스테이지 3 버전 간의 차이점을 보여준다. 따라서 각 줄의 왼쪽에 하나의 "+" 혹은 "-" 기호를 사용하는 대신 두 개의 열(column)을 사용한다. 첫 번째 열은 첫 번째 부모와 작업 디렉토리 복사본 간의 차이점을 위해 사용되고, 두 번째 열은 두 번째 부모와 작업 디렉토리 복사본 간의 차이점을 위해 사용된다. (이 형식에 대한 자세한 설명은 git-diff-files(1) man 페이지의 "통합 차이점 형식" 부분을 살펴보기 바란다.)


명확한 방식으로 충돌을 해결한 후에 (하지만 아직 인덱스를 갱신하기 전에) diff 명령을 실행하면 다음과 같이 보일 것이다:


  1. $ git diff
    diff --cc file.txt
    index 802992c,2b60207..0000000
    --- a/file.txt
    +++ b/file.txt
    @@@ -1,1 -1,1 +1,1 @@@
    - Hello world
     -Goodbye
    ++Goodbye world

이것은 우리가 해결한 버전이 첫 번째 부모에서 "Hello world"를 지우고, 두 번째 부모에서 "Goodbye"를 지우고, 양 쪽 부모에 모두 포함되지 않았던 "Goodbye world"를 추가했다는 것을 보여준다.


몇 가지 특별한 diff 옵션은 작업 디렉토리의 각각의 스테이지에 대한 차이점을 보여줄 수 있다:


  1. $ git diff -1 file.txt          # 스테이지 1에 대한 차이점
    $ git diff --base file.txt      # 위와 동일
    $ git diff -2 file.txt          # 스테이지 2에 대한 차이점
    $ git diff --ours file.txt      # 위와 동일
    $ git diff -3 file.txt          # 스테이지 3에 대한 차이점
    $ git diff --theirs file.txt    # 위와 동일

git-log(1) 명령과 gitk(1) 명령은 머지에 대한 특별한 도움도 제공한다:


  1. $ git log --merge
    $ gitk --merge

위 명령은 머지되지 않은 파일을 수정하는 모든 커밋 중 HEAD 혹은 MERGE_HEAD에만 존재하는 것들을 보여줄 것이다.


또는 이맥스나 kdiff3과 같은 외부 도구를 이용하여 머지되지 않은 파일을 머지할 수 있도록 해 주는 git-mergetool(1) 명령을 이용할 수도 있다.


파일 내의 충돌을 해결하고 인덱스를 갱신할 때 마다:


  1. $ git add file.txt

'git diff' 명령이 더 이상 (기본 상태대로) 해당 파일에 대한 아무런 출력도 보여주지 않게된 후에, 해당 파일의 서로 다른 스테이지들은 "사라질" (collapse) 것이다.


머지 되돌리기#

만약 여러분이 머지 과정에서 엉켜버려서 그냥 포기하고 원래 상태로 되돌리고 싶다면, 언제나 다음 명령을 이용하여 머지 이전의 상태로 돌아갈 수 있다.


  1. $ git reset --hard HEAD

혹은 머지한 것을 이미 커밋했을 때 되돌리려면 다음 명령을 실행한다.


  1. $ git reset --hard ORIG_HEAD

하지만, 위의 명령은 몇몇 경우에 위험해 질 수 있다. 기존에 생성한 어떤 커밋이 다른 브랜치에 머지된 경우 해당 커밋을 절대로 되돌리면 안된다. 이 경우 이후의 머지 작업에 혼란을 일으킬 수 있다.


고속 이동 머지#

위에서 언급하지 않은, 특별히 처리되는 한 가지 특수한 경우가 있다. 보통 머지의 결과는 두 개의 개발 경로를 하나로 합치는 머지 커밋으로 나타난다.


하지만 만약 현재 브랜치가 다른 브랜치의 자손 (역주: "조상"이 맞는 듯..) 인 경우 (따라서 한 쪽에 존재하는 모든 커밋들이 다른 쪽에 이미 포함되어 있다면) git는 단지 "고속 이동" (fast-forward)을 수행한다. 이는 아무런 커밋도 생성하지 않고 현재 브랜치의 헤드를 머지된 브랜치의 헤드가 가리키는 곳으로 이동시킨다.


실수 바로잡기#

만약 여러분이 작업 트리를 망가뜨렸지만 아직 이 실수에 대한 커밋을 생성하지 않았다면 다음 명령을 이용하여 작업 트리 전체를 이전의 커밋 상태로 되돌릴 수 있다


  1. $ git reset --hard HEAD

만약 커밋으로 만들지 말아야 할 사항을 만들어 버렸다면, 이 문제를 해결하기 위한 두 가지 다른 방법이 존재한다:


  1. 이전 커밋에서 작업한 내용을 모두 되돌린 새로운 커밋을 만들 수 있다. 이것은 여러분의 실수가 이미 외부에 공개된 경우에 적당하다.
  2. 뒤로 되돌아가서 이전 커밋을 수정한다. 만약 변경 이력이 이미 공개되었다면 절대 이렇게 해서는 안 된다. git는 일반적으로 프로젝트의 "변경 이력"이 변경되는 것을 고려하지 않기 때문에, 변경 이력이 변경된 브랜치로의 반복된 머지 작업은 올바로 동작하지 않을 것이다.

새로운 커밋을 만들어 실수 바로잡기#

이전의 변경 사항을 되돌리는 새로운 커밋을 만드는 일은 아주 간단하다. git-revert(1) 명령에게 실수한 커밋에 대한 참조를 넘겨주면 된다. 예를 들어 가장 최근의 커밋을 되돌리려면 다음을 실행한다:


  1. $ git revert HEAD

위 명령은 HEAD에서 변경한 내용들을 되돌리는 새 커밋을 만들고 그에 대한 커밋 메시지를 편집하도록 물어볼 것이다.


또한 더 이전의 변경 내용, 예를 들면 최근의 커밋 바로 전의 변경 내용을 되돌리는 것도 가능하다:


  1. $ git revert HEAD^

이 경우 git는 해당 변경 내용을 되돌리면서 그 이후에 변경된 내용들은 그대로 남겨두려고 시도한다. 만약 이후의 변경 내용이 되돌린 변경 내용 중 일부를 포함한다면, 머지 (충돌) 해결하기의 경우과 같이 직접 충돌을 해결하도록 요청할 것이다.


변경 이력을 수정하여 실수 바로잡기#

만약 문제가 있는 커밋이 가장 최신의 커밋이고, 이 커밋을 외부에 아직 공개하지 않았다면 'git reset' 명령을 이용하여 커밋을 제거할 수 있다.


아니면, 새 커밋을 만드는 것처럼 작업 디렉토리를 변경하고 인덱스를 갱신한 후에 다음을 실행한다


  1. $ git commit --amend

위 명령은 먼저 이전 커밋 메시지를 변경하도록 하고, 이전 커밋을 변경된 내용을 포함하는 새 커밋으로 바꿀 것이다.


다시 말하지만, 이미 다른 브랜치에 머지되었을 수도 있는 커밋에 이러한 작업을 수행하서는 안 된다. 이 경우에는 git-revert(1) 명령을 이용하자.


커밋들은 변경 이력 상의 더 이전 상태로 되돌리는 것도 가능하다. 하지만 이러한 복잡한 주제는 다른 장에서 설명하도록 남겨 둔다.


이전 버전의 파일을 체크아웃하기#

이전의 실수를 되돌리는 과정에서, git-checkout(1) 명령을 이용하여 특정 파일의 이전 버전을 체크아웃하는 것이 유용하다는 것을 알게 될 수 있다. 우리는 지금껏 'git checkout' 명령을 브랜치를 전환하기 위해 사용했었지만, 경로 이름이 주어진 경우에는 다른 동작을 수행한다:


  1. $ git checkout HEAD^ path/to/file

위 명령은 path/to/file의 내용을 HEAD^ 커밋에 있는 것으로 바꾸고, 그에 따라 인덱스도 갱신한다. 이 명령은 브랜치를 전환하지 않는다.


만약 작업 디렉토리의 변경없이, 단지 이전 버전의 파일을 보고 싶을 뿐이라면 git-show(1) 명령을 이용할 수 있다:


  1. $ git show HEAD^:path/to/file

위 명령은 해당 파일의 특정 버전을 보여줄 것이다.


작업 중인 내용을 임시로 보관해 두기#

복잡한 내용을 수정하고 있는 도중에, 지금 작업 중인 내용과 관련은 없지만 단순하고 명확한 버그를 발견했다고 하자. 여러분은 작업을 계속 진행하기 전에 이 버그를 먼저 수정하고 싶다. git-stash(1) 명령을 이용하여 현재 작업 상태를 저장하고, 버그를 수정한 후에 (혹은 다른 브랜치에서 이를 수정하고 다시 원래로 돌아온 후에) 작업 상태를 다시 복구할 수 있다.


  1. $ git stash save "work in progress for foo feature"

위 명령은 변경 내용을 'stash' (은닉처)에 저장하고, 작업 트리와 인덱스를 현재 브랜치의 끝과 동일하게 초기화할 것이다. 그러면 보통 때처럼 문제점을 수정할 수 있다.


  1. ... 수정 및 테스트...
  2. $ git commit -a -m "blorpl: typofix"

그 후에는 'git stash apply' 명령을 이용하여 이전 작업 내용으로 돌아갈 수 있다.


  1. $ git stash apply

좋은 성능 보장하기#

거대한 저장소에서 git는 변경 이력 정보가 디스크 혹은 메모리 상의 너무 많은 공간을 차지하지 않도록 하기 위해 압축을 수행한다.


이 압축 과정은 자동으로 수행되지는 않는다. 따라서 여러분은 때때로 git-gc(1) 명령을 실행해야 한다.


  1. $ git gc

위 명령은 아카이브를 다시 압축한다. 이 작업은 아주 많은 시간이 걸릴 수도 있으므로 다른 작업을 하지 않을 때 'git gc' 명령을 실행하는 것이 좋을 것이다.


신뢰성 보장하기#

저장소가 망가졌는지 검사하기#

git-fsck(1) 명령은 저장소 상에서 몇 가지 일관성 검사를 수행한 후 문제가 발생하면 보고한다. 이 과정은 어느 정도 시간이 걸린다. 가장 많이 발생하는 경고는 "댕글링" (dangling) 객체에 대한 것이다:


  1. $ git fsck
    dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
    dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
    dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
    dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb
    dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f
    dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e
    dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085
    dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
    ...

댕글링 객체들은 문제가 되지는 않는다. 가장 최악의 상황으로는 추가적으로 약간의 디스크 공간을 소비한다는 것 정도이다. 이러한 댕글링 객체들은 때로 잃어버린 작업 내용을 복구하기 위한 최후의 수단으로 사용되기도 한다. 자세한 내용은 "댕글링 객체" 부분을 살펴보기 바란다.


잃어버린 작업 내용 복구하기#

Reflogs#

여러분이 'git reset --hard' 명령을 이용하여 브랜치를 수정했다고 하자. 그런데 알고보니 이 브랜치가 해당 변경 이력에 대한 유일한 참조였다.


다행스럽게도 git는 각 브랜치에 대한 이전의 모든 값들을 기록하는 "reflog"라는 로그를 유지한다. 따라서 이 경우 다음과 같은 명령을 이용하여 이전의 변경 이력에 여전히 접근할 수 있다.


  1. $ git log master@{1}

위 명령은 이전 버전의 "master" 브랜치 헤드에서 접근할 수 있는 커밋의 목록을 보여준다. 이 문법은 단지 git log 명령 뿐 아니라 커밋을 인자로 받는 모든 git 명령에서 사용될 수 있다. 다음은 이에 대한 예제이다:


  1. $ git show master@{2}           # 2번의 변경 전의 브랜치가 가리키던 내용
    $ git show master@{3}           # 3번의 변경 전의 브랜치가 가리키던 내용
    $ gitk master@{yesterday}       # 어제 브랜치가 가리키던 내용
    $ gitk master@{"1 week ago"}    # 지난 주에 브랜치가 가리키던 내용
    $ git log --walk-reflogs master # master 브랜치의 reflog 항목들을 표시

HEAD에 대해서는 별도의 reflog가 유지된다. 따라서


  1. $ git show HEAD@{"1 week ago"}

위 명령은 현재 브랜치가 일주일 전에 가리키던 내용이 아니라, HEAD가 일주일 전에 가리키던 내용을 보여줄 것이다. 이것은 여러분이 체크아웃했던 내역들을 볼 수 있게 해 준다.


reflog는, 해당 커밋들이 정리될 (prune) 수 있는 상태가 된 후에, 기본적으로 30일 간 저장된다. 이러한 정리 작업을 조정하는 방법은 git-reflog(1) 혹은 git-log(1) man 페이지를 살펴보고, 자세한 내용은 git-rev-parse(1) man 페이지의 "리비전 지정하기" 부분을 살펴보기 바란다.


reflog 변경 이력은 일반 git 변경 이력과는 매우 다르다는 것을 알아두도록 하자. 일반 변경 이력은 같은 프로젝트를 수행하는 모든 저장소에서 공유하지만, reflog 변경 이력은 공유되지 않는다. 이것은 오직 로컬 저장소 내의 브랜치들이 시간에 따라 어떻게 변경되어 왔는지 만을 보여준다.


댕글링 객체 살펴보기#

어떤 상황에서는 reflog도 도움이 되지 않을 수가 있다. 예를 들어 여러분이 어떤 디렉토리를 지웠다고 가정해 보자. 나중에 여러분은 이 브랜치에 포함된 변경 이력이 필요하다는 것을 알았다. reflog도 이미 삭제되었다. 하지만 아직 저장소에서 이를 정리(prune)하지 않았다면, 아직은 'git-fsck' 명령이 보여주는 댕글링 객체 내의 잃어버린 커밋들을 찾을 수 있다. 자세한 내용은 "댕글링 객체" 부분을 살펴보기 바란다.


  1. $ git fsck
    dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
    dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
    dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
    ...

예를 들면 다음과 같이 댕글링 객체 중의 하나를 살펴볼 수 있다


  1. $ gitk 7281251ddd --not --all

위 명령은 다음과 같은 주문대로 동작한다: 댕글링 객체(들)로 설명하는 커밋 변경 이력을 보여주되, 다른 모든 브랜치나 태그에서 설명하는 변경 이력들은 보이지 않도록 하고 싶다. 따라서 잃어버린 커밋에서 도달할 수 있는 변경 이력 만을 얻을 수 있다. (그리고 이것은 하나의 커밋에만 해당하는 것이 아니라는 것을 알아두자: 우리는 지금껏 댕글링 객체가 개발 경로의 끝인 경우 만을 살펴보았지만, 길고 복잡한 변경 이력 전체가 제거되었을 수도 있다.)


만약 이 변경 이력을 복구하기로 결정했다면, 이 커밋을 가리키는 새로운 참조를 (예를 들면, 새 브랜치를) 언제든 만들 수 있다:


  1. $ git branch recovered-branch 7281251ddd

다른 타입의 댕글링 객체 (블롭 혹은 트리)들도 가능하다. 그리고 이러한 댕글링 객체들은 다른 상황에서 발생한다.


개발 과정 공유하기#

git pull 명령으로 업데이트하기#

저장소를 복사해와서 약간의 작업을 한 후에는, 원본 저장소의 업데이트가 있었는지 살펴보고 이를 여러분의 작업 내용에 머지하고 싶을 경우가 있을 것이다.


이미 git-fetch(1) 명령을 이용하여 원격 추적 브랜치를 최신 상태로 유지하는 법과 이를 머지하는 법을 살펴보았다. 따라서 원본 저장소의 "master" 브랜치의 변경 사항들을 머지하려면 다음과 같이 할 수 있다:


  1. $ git fetch
    $ git merge origin/master

하지만 git-pull(1) 명령은 이 두 가지를 한 번에 수행한다:


  1. $ git pull origin master

사실, "master" 브랜치를 체크아웃한 상태이면, "git pull" 명령은 기본적으로 원본 저장소의 HEAD 브랜치를 머지해 온다. 따라서 위의 경우는 때로 다음과 같이 간단하게 수행될 수 있다:


  1. $ git pull

좀 더 일반적으로 말하면, 원격 브랜치로부터 생성된 브랜치는 기본적으로 해당 원격 브랜치의 내용을 가져올 (pull) 것이다. 이러한 기본값을 제어하는 방법은 git-config(1) man 페이지의 branch.<이름>.remote 및 branch.<이름>merge 옵션에 대한 설명과 git-checkout(1) 명령의 '--track' 옵션에 대한 논의를 살펴보기 바란다.


입력해야 할 글자를 줄이는 것 말고도, "git pull" 명령은 가져올 브랜치와 저장소를 기록한 기본 커밋 메시지를 작성해 준다.


(하지만 고속 이동 머지의 경우 이러한 커밋 메시지는 생성되지 않을 것이다. 대신 브랜치는 업스트림 브랜치의 최신 커밋을 가리키도록 갱신되어 있을 것이다.)


'git pull' 명령은 (현재 디렉토리를 나타내는) "."을 "원격" 저장소로 지정할 수 있다. 이 경우 현재 저장소의 브랜치와 머지를 수행한다. 따라서


  1. $ git pull . branch
    $ git merge branch

위의 명령들은 거의 동일하다. 실제로는 위쪽의 방식이 주로 사용된다.


프로젝트에 패치 제출하기#

만약 여러분이 소스를 몇 가지 수정했다면, 이를 제출하기 위한 가장 간단한 방법은 이메일에 패치를 넣어 보내는 것이다.


먼저 git-format-patch(1) 명령을 실행한다. 예를 들어


  1. $ git format-patch origin

위 명령은 현재 디렉토리에 연속된 숫자가 붙은 여러 파일을 만들어 낼 것이다. 각각의 파일은 현재 브랜치에는 존재하지만 origin/HEAD에는 존재하지 않는 패치에 해당한다.


그 후에 이 파일들을 이메일 클라이언트로 가져와서 직접 보낼 수 있다. 하지만 한 번에 보내기에는 너무 많은 작업을 수행했다면, 이 과정을 자동화하기 위해 git-send-email(1) 스크립트를 이용하는 것이 나을 것이다. 먼저 이러한 패치들을 어떻게 보내는 것이 좋은지, 해당 프로젝트의 메일링리스트에 문의하도록 하자.


패치를 프로젝트로 가져오기#

git는 위와 같은 이메일로 받은 일련의 패치들을 적용하기 위한 git-am(1) 도구도 제공해준다. (am은 "apply mailbox"의 줄임말이다.) 단지 패치를 포함한 모든 메일 메시지들을 차례대로 한 메일함 파일로 저장하고 (여기서는 "patches.mbox" 라고 가정한다) 다음 명령을 실행한다.


  1. $ git am -3 patches.mbox

git는 각각의 패치들을 순서대로 적용할 것이다. 만약 적용하는 도중 충돌이 발생한다면 작업은 중지될 것이다. "머지 (충돌) 해결하기" 부분에서 설명한 대로 이를 해결할 수 있다. ("-3" 옵션은 git에게 머지를 수행하라고 지정한다. 만약 작업을 중지하고 작업 트리와 인덱스를 원래 상태로 유지하기를 원한다면, 단순히 이 옵션을 빼 버리면 된다.)


충돌을 해결하는 결과로 새로운 커밋을 만드는 대신, 인덱스를 갱신했다면 다음 명령을 수행한다


  1. $ git am --resolved

그러면 git는 새로운 커밋을 만들고 메일함에 남아있는 나머지 패치들을 다시 적용해 갈 것이다.


최종 결과는 일련의 커밋으로 나타난다. 각각의 커밋은 원래 메일함에 들어있던 각 패치들에 해당하며, 작성자와 커밋 로그 메시지는 각 패치에 포함된 메시지로부터 추출된다.


공개 git 저장소#

프로젝트에 변경 내용을 제출하는 또 다른 방법은 해당 프로젝트의 관리자에게 git-pull(1) 명령을 이용하여 여러분의 저장소 내의 변경 이력을 가져가라고 말하는 것이다. "git pull 명령으로 업데이트하기" 부분에서 이 방법을 이용하여 "메인" 저장소의 변경 내용을 업데이트하는 방법을 설명하였지만, 이것은 반대의 경우에도 잘 동작한다.


만약 여러분과 관리자가 동일한 머신 상의 계정을 가지고 있다면, 다른 사람의 저장소로부터 변경 이력을 직접 가져올 수 있다. 저장소 URL을 인자로 받는 명령들은 로컬 디렉토리 이름도 인자로 받을 수 있다:


  1. $ git clone /path/to/repository
    $ git pull /path/to/other/repository

혹은 SSH URL도 가능하다.


  1. $ git clone ssh://yourhost/~you/repository

적은 개발자로 이루어진 프로젝트나 몇 개의 개인 저장소와 동기화하는 경우에는, 이것만으로도 충분할 것이다.


하지만 이런 일을 수행하는 좀 더 일반적인 방법은 (보통은 별도의 호스트 상에) 별도의 공개된 저장소를 두고 다른 사람들이 여기서 변경 사항을 내려받을 수 있도록 하는 것이다. 이 방법이 보통 더 편리하며, 개인적으로 작업 중인 내용과 외부에 공개된 것을 확실히 구분해 준다.


작업은 매일매일 개인 저장소에서 이루어지지만, 주기적으로 개인 저장소의 내용을 공개 저장소로 보내서 (push) 다른 개발자들이 공개 저장소로부터 작업 내용을 내려받을 수 있도록 해야 할 것이다. 따라서 다른 개발자 한 명이 공개 저장소를 가지고 있는 경우, 변경 사항들은 다음과 같이 이동할 것이다:



                               자신이 push
          
          자신의 개인 저장소 ------------------> 자신의 공개 저장소
          
           ^                                     |
          
           | 자신이 pull                          | 다른 사람이 pull
          
           |                  다른 사람이 push     v
          
          다른 사람의 공개 저장소 <------------- 다른 사람의 저장소
          

아래에서는 이 과정에 대해 설명한다.


공개 저장소 설정하기#

여러분의 개인 저장소가 ~/proj 디렉토리에 있다고 가정하자. 먼저 해당 저장소에 대한 복사본을 만들고, 'git 대몬'에게 이 저장소가 공개 저장소 임을 알려준다:


  1. $ git clone --bare ~/proj proj.git
  2. $ touch proj.git/git-daemon-export-ok

결과로 생성되는 proj.git 디렉토리는 "bare" git 저장소를 포함한다. 이것은 단지 ".git" 디렉토리의 내용이며, 이를 체크아웃한 어떤 파일도 포함하지 않는다.


다음으로 proj.git 디렉토리를 공개 저장소를 운영할 서버로 복사한다. 이 때 scp, rsync 및 다른 익숙한 방식을 사용할 수 있다.


git 프로토콜을 이용해 git 저장소 공개하기#

이것이 권장하는 방법이다.


만약 다른 사람이 서버를 관리하고 있다면, 관리자는 git 저장소가 있어야 할 디렉토리와 그에 따른 git:// URL을 알려주어야 한다. 그렇다면 아래의 "공개 저장소에 변경 내용 올리기" 부분으로 건너뛸 수 있다.


그렇지 않다면 git-daemon(1)을 실행해야 한다. git 대몬은 9418 포트를 이용하며, 기본적으로 git-daemon-export-ok라는 특수 파일을 가지고 있으며 git 디렉토리로 보이는 모든 디렉토리에 대한 접근을 허용한다. 'git daemon' 명령에 인자로 디렉토리 목록을 넘겨주면 해당 디렉토리 만을 공개하도록 제한할 것이다.


또한 'git 대몬'을 inetd 서비스로 실행할 수 있다. 자세한 내용은 git-daemon(1) man 페이지를 (특히 예제 부분을) 살펴보기 바란다.


http를 이용해 git 저장소 공개하기#

git 프로토콜이 더 나은 성능과 안정성을 제공하지만, 이미 웹 서버가 구동 중인 시스템이라면 http를 이용하여 저장소를 공개하는 것이 더 간단할 것이다.


이를 위해서는 새로 생성한 bare git 저장소를 웹 서버를 통해 공개된 디렉토리에 복사하고, 웹 클라이언트에게 필요한 부가 정보들을 제공할 수 있도록 약간의 작업을 수행하기만 하면 된다:


  1. $ mv proj.git /home/you/public_html/proj.git
    $ cd proj.git
    $ git --bare update-server-info
    $ mv hooks/post-update.sample hooks/post-update

(마지막 두 줄에 대한 설명은 git-update-server-info(1)githooks(5) man 페이지를 살펴보기 바란다.)


proj.git의 URL을 알린다. 이제 다른 사람들이 해당 URL로부터 저장소를 복사(clone)하거나 변경 이력을 받아갈(pull) 수 있다. 예를 들어 명령행에서는 다음과 같이 할 수 있다:


  1. $ git clone http://yourserver.com/~you/proj.git

 

(WebDAV를 이용하여 http 상에서 변경 이력을 올릴(push) 수 있도록 하는 좀 더 복잡한 설정 방법은 "http 상의 git 서버 설정" 문서를 살펴보기 바란다.)


공개 저장소에 변경 이력 올리기#

위에서 설명한 두 가지 (git 및 http) 방법은 다른 관리자들이 여러분의 최신 작업 사항을 받아갈 수 있도록 해 주지만, 쓰기 접근은 허용하지 않는다는 것을 알아두길 바란다. 여러분은 개인 저장소의 최신 변경 내용을 공개 저장소에 업데이트해 주어야 한다.


이를 위한 가장 간단한 방법은 git-push(1) 명령과 ssh를 이용하는 것이다. "master"라는 원격 브랜치를 여러분이 작업한 "master" 브랜치의 최신 내용으로 업데이트하려면 다음을 실행한다.


  1. $ git push ssh://yourserver.com/~you/proj.git master:master

혹은 단순히 다음과 같이 실행할 수 있다.


  1. $ git push ssh://yourserver.com/~you/proj.git master

'git fetch' 명령과 같이, 'git push' 명령은 이 작업이 '고속 이동' 머지로 이루어지지 않으면 경고를 보여줄 것이다. 다음 부분에서 이 경우를 처리하는 법에 대해 자세히 설명한다.


일반적으로 'push' 명령의 대상은 "bare" 저장소이다. 체크아웃한 작업 트리를 가지는 저장소에도 'push' 명령을 수행할 수 있지만, 이것만으로는 작업 트리 자체가 변경되지는 않는다. 이것은 여러분이 'push' 명령을 수행한 브랜치가 현재 체크아웃한 브랜치라면 예상치 못한 결과가 발생할 수 있다!


'git fetch' 명령 때와 같이, 타이핑을 줄이기 위한 설정 옵션을 세팅할 수 있다. 예를 들어 다음과 같이 설정했다면


  1. $ cat >>.git/config <<EOF
    [remote "public-repo"]
            url = ssh://yourserver.com/~you/proj.git
    EOF

위의 push 명령을 다음과 같이 수행할 수 있다.


  1. $ git push public-repo master

자세한 내용은 git-config(1) man 페이지의 remote.<이름>.url, branch.<이름>.remote, remote.<이름>.push 옵션의 설명을 살펴보기 바란다.


push 실패 시의 처리#

만약 push 명령이 원격 브랜치의 고속 이동으로 이어지지 않았다면, 다음과 같은 에러를 내며 실패할 것이다:


  1. error: remote 'refs/heads/master' is not an ancestor of
  2.  local  'refs/heads/master'.
  3.  Maybe you are not up-to-date and need to pull first?
  4. error: failed to push to 'ssh://yourserver.com/~you/proj.git'

이것은 다음과 같은 경우에 발생할 수 있다:


  • 이미 공개된 커밋을 'git reset --hard' 명령으로 제거한 경우
  • 이미 공개된 커밋을 'git commit --amend' 명령으로 수정한 경우 ('변경 이력을 수정하여 실수 바로잡기' 부분에서 설명)
  • 이미 공개된 커밋에 대해 'git rebase' 명령을 수행한 경우 ('git rebase 명령으로 여러 패치들을 최신 상태로 유지하기' 부분에서 설명)

브랜치 이름 앞에 '+' 기호를 붙이면 'git push' 명령이 강제로 업데이트를 수행하도록 지정할 수 있다:


  1. $ git push ssh://yourserver.com/~you/proj.git +master

일반적으로 공개 저장소 내의 브랜치 헤드가 변경될 때마다, 이전에 가리키던 커밋의 자손을 가리키도록 변경된다. 이 상황에서 강제로 push 명령을 수행하면, 이러한 관례를 깨뜨리는 것이다. ("변경 이력을 수정함에 따른 문제점" 부분을 살펴보기 바란다.)


그럼에도 불구하고, 이것은 작업 중인 패치들을 간단히 공개하기를 원하는 사람들이 주로 사용하는 방식이며, 여러분이 다른 개발자들에게 브랜치를 이런 식으로 관리할 것이라고 미리 얘기해 두었다면 용납될 만한 부분이다.


또한 다른 사람이 동일한 저장소에 push 명령을 수행할 수 있는 권한을 가지고 있는 경우, 이 방식은 실패할 수 있다. 이 경우 이를 해결하기 위한 올바른 방법은 먼저 pull 명령이나 fetch 및 rebase 명령을 수행하여 여러분의 작업 내용을 갱신하고나서 변경 내용을 다시 올리는(push) 것이다. 이후의 내용은 다음 절gitcvs-migration(7) man 페이지를 살펴보기 바란다.


공유 저장소 설정하기#

다른 개발자들과 작업을 공유하기 위한 또 다른 방법은, CVS 등에서 주로 사용되는 것과 비슷한 모델로, 특수한 권한을 가진 몇몇 개발자들이 모두 하나의 공유 저장소에 pull/push 명령을 수행하는 것이다. 이를 위한 설정 방법은 gitcvs-migration(7) man 페이지를 살펴보기 바란다.


하지만, git의 공유 저장소에 대한 지원이 아무 문제가 없다 할지라도, 이러한 방식의 운영은 일반적으로 추천하지 않는다. 왜냐하면 단지 git가 지원하는 (패치를 보내고 공개 저장소에서 내려받는) 협업 모델이 하나의 공유 저장소를 사용하는 것에 비해 매우 많은 장점을 가지기 때문이다.


  • git가 제공하는 빠른 패치 관리 기능은, 매우 활발히 진행되는 프로젝트에서도 한 명의 관리자가 간단히 변경 사항들을 처리할 수 있도록 해 준다. 또한 이를 넘어서는 상황이 발생하는 경우라도, 'git pull' 명령은 다른 관리자에게 이러한 작업을 넘겨줄 수 있는 손쉬운 방법을 제공하며 그 상황에서도 새로운 변경 사항들에 대한 추가적인 검토를 수행할 수 있다.
  • 모든 개발자들의 저장소가 프로젝트의 변경 이력에 대한 완전한 복사본을 가지므로 특별한 저장소가 존재하지 않으며, 협의에 의한 경우 혹은 기존 관리자가 활동이 없거나 더 이상 함께 작업하기 힘들어진 경우에 다른 개발자가 프로젝트의 관리 작업을 맡는 일이 아주 간단해진다.
  • 중심적인 "커미터"(committer) 그룹이 없어지므로, 누가 이 그룹에 포함되어야 하는지 아닌지에 대한 공식적인 결정을 할 일이 적어진다.

저장소를 웹으로 살펴볼 수 있게 공개하기#

gitweb이라는 cgi 스크립트는 다른 사람들이 git를 설치하지 않고도 여러분의 저장소 내의 파일들과 변경 이력들을 살펴볼 수 있는 손쉬운 방법을 제공한다. gitweb을 설정하는 방법은 git 소스 트리 내의 gitweb/INSTALL 파일을 살펴보기 바란다.


예제#

리눅스 하위시스템 관리자를 위한 주제별 브랜치 관리하기#

이 부분은 Tony Luck이 리눅스 커널의 IA64 아키텍처 관리자로서 git를 사용하는 방법을 설명한다.


그는 2 개의 공개 브랜치를 사용한다:


  • 패치가 최초 적용되는 "test" 트리. 이 트리는 다른 개발 과정에 통합되어 테스트를 받을 수 있도록 하기 위한 것이다. 이 트리는 Andrew가 원할 때마다 -mm 트리로 통합할 수 있도록 제공된다.
  • 테스트를 통과한 패치들이 최종 검사를 위해 옮겨지는 "release" 트리. 이 트리는 Linus에게 보내서 업스트림에 반영되도록 하기 위한 것이다. (Linus에게 "please pull" 요청을 보낸다.)

그는 또한 패치들의 논리적인 그룹을 포함하는 몇 가지 임시 브랜치 ("주제별 브랜치")를 사용한다.


이를 설정하기 위해서, 먼저 Linus의 공개 트리를 복사하여 작업 트리를 만든다:


  1. $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work
  2. $ cd work

Linus의 트리는 origin/master라는 이름의 원격 브랜치로 저장되며, git-fetch(1) 명령을 통해 업데이트할 수 있다. 다른 공개 트리를 추적하려면 git-remote(1) 명령을 이용하여 "원격" 브랜치를 설정하고 git-fetch(1) 명령으로 이를 최신 상태로 유지하면 된다. 1장 저장소와 브랜치 부분을 살펴보도록 하자.


이제 작업을 진행할 새 브랜치들을 만든다. 이 브랜치들은 origin/master 브랜치의 최신 상태에서 시작되며, 기본적으로 (git-branch(1) 명령의 --track 옵션을 이용하여) Linus의 변경 사항들을 머지하도록 설정되어야 한다.


  1. $ git branch --track test origin/master
  2. $ git branch --track release origin/master

이들은 git-pull(1) 명령을 이용하면 쉽게 최신 상태로 만들 수 있다.


  1. $ git checkout test && git pull
  2. $ git checkout release && git pull

중요한 사항! 만약 이 브랜치에 로컬 변경 사항들이 있는 경우에는, 머지 과정에서 변경 이력 상에 커밋 객체를 만들 것이다. (로컬 변경 사항이 없는 경우에는 단순히 "고속 이동" 머지를 수행할 것이다.) 많은 사람들은 리눅스 변경 이력 상에 이렇게 만들어진 "노이즈"가 섞이는 것을 싫어하기 때문에, "release" 브랜치에서 이러한 작업을 자주 수행하는 것은 피해야 한다. 여러분이 Linus에게 "release" 브랜치에서 변경 이력을 내려받도록(pull) 요청하면, 이러한 노이즈 커밋들이 영구적으로 변경 이력 상에 남아 있게 되기 때문이다.


몇 가지 설정 변수들은 (git-config(1) man 페이지를 보자) 두 개의 브랜치 모두에서 여러분의 공개 트리로 변경 이력을 올리는(push) 것을 간편하게 도와줄 수 있다. ("공개 저장소 설정하기" 부분을 살펴보기 바란다.)


  1. $ cat >> .git/config <<EOF
  2. [remote "mytree"]
  3.         url =  master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
  4.         push = release
  5.         push = test
  6. EOF

이제 git-push(1) 명령을 이용하여 test 브랜치와 release 브랜치 모두를 올릴 수 있다:


  1. $ git push mytree

혹은 다음과 같이 test 나 release 브랜치 중의 하나 만 올릴 수 있다:


  1. $ git push mytree test

혹은

  1. $ git push mytree release

이제 커뮤니티에서 받은 몇 가지 패치들을 적용해 보자. 이 패치들 (혹은 관련 패치 모음들)을 담아둘 브랜치를 위한 짧은 이름을 하나 생각해서, Linus의 브랜치의 최신 상태로부터 새로운 브랜치를 만든다:


  1. $ git checkout -b speed-up-spinlocks origin

이제 패치들을 적용해서, 테스트를 수행하고, 변경 내용들을 커밋한다. 만약 패치가 여러 개로 나누어져 있다면 이들을 각각 별도의 커밋으로 나누어 적용해야 한다.


  1. $ ... 패치 ... 테스트  ... 커밋 [ ... 패치 ... 테스트 ... 커밋 ]*

이 변경 내용에 대해 만족한 상태가 되면, 이것을 "test" 브랜치로 가져와서(pull) 공개할 준비를 한다.


  1. $ git checkout test && git pull . speed-up-spinlocks

이 과정에서는 거의 충돌이 발생하지 않는다. 하지만 이 과정에서 많은 시간을 소비했고, 업스트림에서 최신 버전을 내려받은 경우에는 충돌이 발생할 수도 있다.


충분한 시간이 지나고 테스트가 완료된 어느 시점에는, 업스트림에 올릴 수 있도록 동일한 브랜치를 "release" 트리로 가져올 수 있다. 이 부분이 바로 각 패치들을 (혹은 일련의 패치 모음을) 별도의 브랜치로 유지하는 것에 대한 가치를 느낄 수 있는 부분이다. 이것은 패치가 "release" 트리에 어떤 순서로도 이동될 수 있다는 것을 뜻한다.


  1. $ git checkout release && git pull . speed-up-spinlocks

조금 지나면 수 많은 브랜치들이 생겨나게 될 것이다. 비록 각각에 대해 적당한 이름을 붙였다고해도, 이들 각각이 무엇을 위한 것인지 혹은 현재 상태가 어느 정도인지 잊어버릴 수 있다. 특정 브랜치에 어떤 변경 사항이 있었는지 기억하려면 다음을 실행한다:


  1. $ git log linux..branchname | git shortlog

해당 브랜치가 test 혹은 release 브랜치에 머지되었는지 알아보려면 다음을 실행한다:


  1. $ git log test..branchname

혹은

  1. $ git log release..branchname

(만약 해당 브랜치가 아직 머지되지 않았다면, 로그 메시지들이 출력될 것이다. 만약 머지되었다면 아무런 메시지도 출력되지 않을 것이다.)


패치가 거대한 순환 (test 브랜치에서 release 브랜치로 이동되고, Linus가 내려받은 후 최종적으로 로컬의 "origin/master" 브랜치로 돌아오는 것)을 마치고 나면, 해당 변경 사항을 위한 브랜치는 더 이상 필요치 않다. 이를 알아보려면 다음을 실행한다:


  1. $ git log origin..branchname

위 명령이 아무런 메시지를 출력하지 않으면 해당 브랜치를 삭제할 수 있다:


  1. $ git branch -d branchname

어떤 변경 사항들은 너무 사소해서, 별도의 브랜치를 만든 후 test와 release 브랜치로 머지할 필요가 없을 수도 있다. 이런 사항들은 직접 "release" 브랜치에 적용하고, 그 후에 "test" 브랜치로 머지한다.


Linus에게 보낼 "please pull" 요청에 포함될 차이점 통계(diffstat)와 짧은 로그 요약을 만들려면 다음을 실행한다:


  1. $ git diff --stat origin..release

그리고

  1. $ git log -p origin..release | git shortlog

아래는 지금까지 설명한 것들을 실행하는 스크립트들이다.


  1. ==== 업데이트 스크립트 ====
    # 로컬 GIT 트리 내의 브랜치들을 업데이트한다. 만약 업데이트할
    # 브랜치가 origin이면, kernel.org를 내려받는다. 그렇지않으면
    # origin/master 브랜치를 test|release 브랜치로 머지한다.

    case "$1" in
    test|release)
            git checkout $1 && git pull . origin
            ;;
    origin)
            before=$(git rev-parse refs/remotes/origin/master)
            git fetch origin
            after=$(git rev-parse refs/remotes/origin/master)
            if [ $before != $after ]
            then
                    git log $before..$after | git shortlog
            fi
            ;;
    *)
            echo "Usage: $0 origin|test|release" 1>&2
            exit 1
            ;;
    esac

  1. ==== 머지 스크립트 ====
    # 주어진 브랜치를 test 혹은 release 브랜치로 머지한다.

    pname=$0

    usage()
    {
            echo "Usage: $pname branch test|release" 1>&2
            exit 1
    }

    git show-ref -q --verify -- refs/heads/"$1" || {
            echo "Can't see branch <$1>" 1>&2
            usage
    }

    case "$2" in
    test|release)
            if [ $(git log $2..$1 | wc -c) -eq 0 ]
            then
                    echo $1 already merged into $2 1>&2
                    exit 1
            fi
            git checkout $2 && git pull . $1
            ;;
    *)
            usage
            ;;
    esac

  1. ==== 상태 스크립트 ====
    # 로컬의 ia64 GIT 트리의 상태를 보여준다.

    gb=$(tput setab 2)
    rb=$(tput setab 1)
    restore=$(tput setab 9)

    if [ `git rev-list test..release | wc -c` -gt 0 ]
    then
            echo $rb Warning: commits in release that are not in test $restore
            git log test..release
    fi

    for branch in `git show-ref --heads | sed 's|^.*/||'`
    do
            if [ $branch = test -o $branch = release ]
            then
                    continue
            fi

            echo -n $gb ======= $branch ====== $restore " "
            status=
            for ref in test release origin/master
            do
                    if [ `git rev-list $ref..$branch | wc -c` -gt 0 ]
                    then
                            status=$status${ref:0:1}
                    fi
            done
            case $status in
            trl)
                    echo $rb Need to pull into test $restore
                    ;;
            rl)
                    echo "In test"
                    ;;
            l)
                    echo "Waiting for linus"
                    ;;
            "")
                    echo $rb All done $restore
                    ;;
            *)
                    echo $rb "<$status>" $restore
                    ;;
            esac
            git log origin/master..$branch | git shortlog
    done

변경 이력 수정하기와 패치 묶음 관리하기#

일반적으로 커밋은 프로젝트에 추가될 뿐이고, 사라지거나 수정되지 않는다. git는 이러한 가정을 두고 설계되었으므로, 이를 위반하게되면 (예를 들어) git의 머지 동작을 오동작하게 만들 수 있다.


하지만, 이러한 가정을 위반하는 것이 유용한 상황이 존재한다.


완벽한 패치 묶음 만들기#

여러분이 거대한 프로젝트의 공헌자 중의 하나라고 가정해 보자. 여러분은 복잡한 기능을 추가하고 싶고, 다른 개발자들이 여러분이 작업한 변경 내용을 보고, 올바로 동작하는 지 확인하고, 왜 이러한 작업을 하게 됐는지 이해하기 쉽도록 해 주고 싶다.


만약 모든 변경 사항을 하나의 패치(혹은 커밋)로 만들어 준다면, 다른 사람들이 한 번에 살펴보기가 매우 힘들것이다.


만약 여러분의 작업에 대한 전체 변경 이력을 준다면, 실수했던 부분과 이를 수정한 부분은 물론 잘못된 방향으로 가서 막힌 부분들에 대한 정보까지도 모두 공개되어, 다른 개발자들을 질리게 만들 것이다.


따라서 이상적인 방법은 보통 다음과 같은 패치 묶음의 형태로 만드는 것이다:


  1. 각 패치들은 순서대로 적용될 수 있다.
  2. 각 패치들은 하나의 논리적인 변경 사항과, 이 변경 사항을 설명하는 메시지를 포함한다.
  3. 어떤 패치에도 regression이 없어야 한다. 패치 묶음 중 어느 단계까지를 적용하더라도, 프로젝트는 정상적으로 컴파일되어 동작해야 하며, 이전에 없었던 버그를 포함해서는 안된다.
  4. 패치 묶음을 모두 적용한 최종 결과는, 여러분의 개발 과정(아마도 더 지저분할 것이다!)에서 얻은 결과와 동일해야 한다.

이제 이러한 작업을 도와주는 몇 가지 도구들에 대해 알아보면서, 이들을 사용하는 방법과 변경 이력을 수정했을 때 발생할 수 있는 문제점들에 대해서 설명할 것이다.


git rebase를 이용하여 패치 묶음을 최신 상태로 유지하기#

"origin"이라는 원격 추적 브랜치에서 "mywork"라는 브랜치를 만들고, 몇 가지 커밋을 생성했다고 가정해 보자:


  1. $ git checkout -b mywork origin
  2. $ vi file.txt
  3. $ git commit
  4. $ vi otherfile.txt
  5. $ git commit
  6. ...

mywork 브랜치에는 아무런 머지도 이루어지지 않았기 때문에, 이것은 단지 "origin" 브랜치로부터의 단순 선형 연결이다:


o--o--o <-- origin

        \

         o--o--o <-- mywork


이제 업스트림 프로젝트에서 다른 작업이 이루어졌고, "origin" 브랜치도 변경되었다:


 o--o--O--o--o--o <-- origin

        \

         a--b--c <-- mywork


이 시점에서, 여러분의 변경 사항을 다시 머지하기 위해 "pull" 명령을 수행할 수 있다. 이 결과로 아래와 같이 새로운 머지 커밋이 생길 것이다:


 o--o--O--o--o--o <-- origin

        \        \

         a--b--c--m <-- mywork


하지만, 만약 mywork 브랜치 내의 변경 이력을 머지 커밋 없이 단순히 연결된 커밋 상태로 유지하고 싶다면, git-rebase(1) 명령을 이용해 볼 수 있다:


  1. $ git checkout mywork
  2. $ git rebase origin

위 명령은 mywork 브랜치의 커밋들을 삭제하여, 패치의 형태로 (".git/rebase-apply"라는 디렉토리 내에) 임시 저장해두고, mywork 브랜치를 origin 브랜치의 최신 버전을 가리키도록 갱신한 후, 저장된 패치들을 새 mywork 브랜치에 적용할 것이다. 결과는 다음과 같은 형태가 될 것이다:


o--o--O--o--o--o <-- origin

                 \

                  a'--b'--c' <-- mywork


 이 과정에서 충돌이 발생할 수도 있다. 충돌이 발생한 경우에는, 동작을 멈추고 충돌을 수정할 수 있게 해 준다. 충돌이 수정되고 나면, 'git add' 명령을 이용하여 해당 내용에 대한 인덱스를 갱신한 다음, 'git commit' 명령을 실행하는 대신 다음을 실행한다:


  1. $ git rebase --continue

그러면 git는 나머지 패치들을 계속해서 적용할 것이다.


어떤 단계에서도 '--abort' 옵션을 이용하면, 이 과정을 중지하고 rebase를 실행하기 전의 상태로 mywork 브랜치를 되돌릴 수 있다:


  1. $ git rebase --abort

하나의 커밋 수정하기#

"변경 이력을 수정하여 실수 바로잡기" 부분에서 가장 최신 커밋을 수정하는 방법을 살펴보았다.


  1. $ git commit --amend

위 명령은 이전 커밋 메시지를 변경할 수 있는 기회를 준 후에, 이전 커밋을 변경 사항을 포함하는 새 커밋으로 바꿀 것이다.


이 명령과 git-rebase(1) 명령을 함께 사용하면 변경 이력 상의 이전 커밋을 수정하고, 그 이후의 변경 사항들을 다시 만들 수 있다. 먼저 문제가 있는 커밋에 다음과 같이 태그를 붙인다:


  1. $ git tag bad mywork~5

(gitk 혹은 'git log' 명령을 이용하면 해당 커밋을 찾기가 편리할 것이다.)


그리고 해당 커밋을 체크아웃하고, 문제점을 수정한 뒤, 그 위에 나머지 커밋들에 대해 'rebase' 명령을 수행한다. (알아두어야 할 점은, 해당 커밋을 임시 브랜치 상에 체크아웃할 수도 있었지만, 대신 "분리된 헤드"를 사용하였다는 것이다.)


  1. $ git checkout bad
    $ # 문제를 수정하고 인덱스를 갱신한다
    $ git commit --amend
    $ git rebase --onto HEAD bad mywork

이 과정이 완료되면, mywork 브랜치가 체크아웃된 상태로 남아 있고, mywork 브랜치의 최근 패치들은 수정된 커밋 상에 다시 적용되어 있을 것이다. 이제 불필요한 것을 정리한다:


  1. $ git tag -d bad

git 변경 이력의 불변성은, 실제로 기존 커밋을 "수정"하지 않는다는 것을 뜻한다. 대신 이전의 커밋을 새로운 객체 이름을 가지는 새 커밋으로 바꾼 것이다.


패치 모음에서 커밋 순서 변경 및 커밋 선택하기#

기존의 커밋에 대해서,  git-cherry-pick(1) 명령은 해당 커밋의 변경 내용을 적용하여 새로운 커밋으로 만들어 준다. 예를 들어, 만약 "mywork"가 "origin" 브랜치 상에서 작업한 일련의 패치들을 가리킨다고 하면, 다음을 실행하고:


  1. $ git checkout -b mywork-new origin
    $ gitk origin..mywork &

 gitk를 이용하여 mywork 브랜치 상의 패치 목록을 살펴본 후, cherry-pick 명령을 이용하여 (아마도 다른 순서로) 적용할 수 있다. 혹은 'git commit --amend' 명령을 통해 커밋을 수정할 수도 있다. 또한 git-gui(1) 명령은 인덱스 내의 차이점들 중에서 특정한 차이점들을 개별적으로 선택하여 적용할 수 있도록 해 준다. (차이점 상에서 오른쪽 클릭 후 "Stage Hunk for Commit" 항목을 선택한다.)


또 다른 방법은 'git format-patch' 명령을 이용하여 패치 묶음을 만든 뒤, 트리의 상태를 패치가 만들어지기 전으로 되돌리는 것이다:


  1. $ git format-patch origin
    $ git reset --hard origin

 그 후 원하는 대로 패치를 수정하거나 순서를 바꾸거나 삭제한 후에, git-am(1) 명령을 이용하여 패치 묶음을 다시 적용한다.


다른 도구들#

패치 모음을 관리하기 위한 StGIT 등의 다른 많은 도구들이 존재한다. 이들은 이 설명서의 범위를 벗어나므로 여기서는 더 이상 설명하지 않는다.


변경 이력을 수정하는 것에 따른 문제점#

브랜치의 변경 이력을 수정하는 것에 따르는 가장 큰 문제점은 머지에 대한 것이다. 누군가가 여러분의 브랜치의 작업 내용을 가져가서(fetch) 자신의 브랜치에 머지했다고 가정해 보자. 그 결과는 다음과 같을 것이다:


 o--o--O--o--o--o <-- origin
        \        \
         t--t--t--m <-- 다른 사람의 브랜치:

그 후에 여러분이 마지막 3개의 커밋을 수정했다고 하자:


         o--o--o <-- 수정 후의 origin 헤드
        /
o--o--O--o--o--o <-- 수정전의 origin 헤드

만약 이러한 모든 사항을 하나의 저장소 내에서 살펴본다면 다음과 같을 것이다:


                    o--o--o <-- 수정 후의 origin 헤드
          
                  / 
          
          o--o--O--o--o--o <-- 수정 전의 origin 헤드
          
                  \        \
          
                    t--t--t--m <-- 다른 사람의 브랜치:
          

 git는 (수정 후의) 새로운 헤드가 이전 헤드에서 업데이트된 버전이라는 것을 알지 못한다. git는 이 상황을 두 명의 개발자가 서로 다른 두 버전에 대해서 따로 개발해 왔을 때와 완전히 똑같이 동일하게 처리한다. 이 시점에서, 다른 사람이 자신의 브랜치에 새로운 헤드를 머지하려고 하면, git는 수정 전의 헤드를 수정 후의 헤드로 바꾸려고 시도하는 대신, 두 개발 경로 (수정 전과 수정 후)를 머지하려고 시도할 것이다. 이것은 아마도 예상할 수 없는 결과를 가져올 것이다.


여러분은 변경 이력이 수정된 트리도 공개하기를 원할 수 있고, 이는 다른 사람들이 내려받아서 변경 사항을 살펴보거나 테스트 하는 데 유용하게 사용될 수 있다. 하지만 이를 자신이 작업한 브랜치에 머지하려고 해서는 안 된다.


적절한 머지를 지원하는 진정한 분산 개발에서는, 공개된 브랜치의 변경 이력을 수정해서는 절대 안 된다.


머지 커밋 상에서 bisect를 수행하는 것이 선형 변경 이력 상에서 bisect를 수행하는 것보다 어려운 이유#

git-bisect(1) 명령은 머지 커밋을 포함한 변경 이력 상에서도 올바로 동작한다. 하지만 bisect가 찾아낸 커밋이 머지 커밋이라면, 사용자는 왜 해당 커밋이 문제를 일으켰는지 알아내기 위해 평소보다 더 노력해야 할 수도 있다.


다음과 같은 변경 이력을 상상해 보자:

            ---Z---o---X---...---o---A---C---D
          
                \                      / 
          
                  o---o---Y---...---o---B
          

 위쪽의 개발 경로에서, Z에 존재하는 함수 중 하나의 의미가 X에서 변경되었다고 가정해 보자. Z와 A 사이의 커밋들은 함수의 구현과 Z 상에 존재하는 이 함수를 호출하는 부분을 변경된 의미에 맞게 모두 수정하였으며, 새로 이 함수를 호출하는 부분도 추가하였다. A 커밋 상에서 버그는 없다.


동시에 아래쪽 개발 경로에서는, 누군가가 (위에서 수정한) 해당 함수를 호출하는 부분을 새로 추가했다고 가정해 보자. Z와 B 사이의 커밋들은 해당 함수의 의미가 변경되지 않았다고 생각하며, 함수의 구현과 호출하는 부분은 일관성을 가진다. B 커밋에서도 버그는 없다.


또한 두 개발 경로가 C 커밋에서 정상적으로 머지되었다고 가정해 보자. 이 시점에서 해결해야 할 충돌은 발생하지 않았다.


하지만 C의 코드는 문제가 있다. 왜냐하면 아래쪽 개발 경로에서 해당 함수를 호출한 부분은, 위쪽 개발 경로에서 의미를 변경한 대로 수정되지 않았기 때문이다. 만약 여러분이 단지 D에서는 문제가 있고 Z에서는 문제가 없다는 사실 만을 알고 있고, git-bisect(1) 명령이 C가 문제라고 찾아냈다면, 이 문제가 함수의 의미가 변경되었기 때문에 발생한 것이라는 어떻게 알아낼 수 있을까?


만약 'git bisect' 명령이 찾아낸 것이 머지 커밋이 아니라면, 일반적으로 해당 커밋을 자세히 살펴보고 문제를 발견할 수 있을 것이다. 개발자들은 자신의 변경 내용을 작은 단위의 완전한 커밋으로 나누어서, 쉽게 문제를 발견하도록 만들 수 있다. 하지만 위의 경우에는 이러한 방법도 통하지 않는데, 그것은 이 문제가 각각의 커밋을 자세히 살펴본다고 해도 명확하게 드러나지 않기 때문이다. 대신 이 경우에는 전체적인 개발 과정에 대한 안목이 필요하다. 문제가 되었던 함수의 의미가 변경된 것이 위쪽 개발 경로 상의 변경 내용 중 극히 일부일 경우에는, 상황이 더욱 악화될 수 있다.


 다른 방법으로, C 시점에서 머지를 하는 대신에, A 상에서 Z부터 B 사이의 변경 이력을 수정(rebase)하는 방법이 있다. 이 경우 다음과 같은 선형 변경 이력을 갖게 될 것이다:


---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*

Z와 D*에 대해 bisect 명령을 수행하면, Y* 커밋이 문제라는 것을 찾아낼 것이고, Y* 커밋이 문제가 되는 이유를 찾아보는 것이 아마도 더 쉬울 것이다.


이런 이유에서라도, 많은 숙련된 git 사용자들은, 머지 작업이 많은 시간을 소모하는 프로젝트에서 작업하는 경우에도, 변경 내용을 공개하기 전에 최신 업스트림 버전에 대해 변경 이력을 정리(rebase)하여 변경 이력을 선형으로 유지한다.


고급 브랜치 관리#

각각의 브랜치 가져오기#

git-remote(1) 명령을 이용하는 대신, 한 번에 하나의 브랜치를 선택하여 임의의 이름으로 로컬에 저장할 수 있다:


  1. $ git fetch origin todo:my-todo-work

첫 번째 인자인 "origin"은 git가 원래 복사해 온 저장소에서 소스를 내려받도록 지정한다. 두 번째 인자는 git가 원격 저장소의 "todo" 브랜치를 가져와서 로컬의 refs/heads/my-todo-work에 저장하도록 지정한다.


또한 다른 저장소에서 브랜치를 내려받을 수도 있다.


  1. $ git fetch git://example.com/proj.git master:example-master

위 명령은 주어진 저장소에서 "master"라는 이름의 브랜치를 내려받아서 "example-master"라는 새 브랜치에 저장할 것이다. 만약 example-master라는 브랜치가 이미 존재한다면, example.com의 master 브랜치의 커밋까지 고속 이동을 시도할 것이다. 더 자세한 사항은 다음에 설명한다:


git fetch 명령과 고속 이동#

앞의 예제에서 기존의 브랜치를 업데이트했을 때, "git fetch" 명령은 새 커밋이 가리키는 곳으로 브랜치를 업데이트하기 전에 원격 브랜치 상의 가장 최신 커밋이 로컬의 브랜치 복사본 상의 최신 커밋의 자손인지 확인한다. 이러한 과정을 git에서는 고속 이동 (fast-forward)이라고 한다.


고속 이동은 다음과 같이 볼 수 있다:


 o--o--o--o <-- 브랜치의 이전 헤드
           \
            o--o--o <-- 브랜치의 새로운 헤드

어떤 경우에는 새로운 헤드가 실제로 이전 헤드의 자손이 아닌 경우가 있을 수 있다. 예를 들어 개발자가 무언가를 수정한 후에 실수를 깨달았고, 변경 이력을 되돌아가기로 결정했다면 다음과 같은 상황이 될 것이다:


 o--o--o--o--a--b <-- 브랜치의 이전 헤드
           \
            o--o--o <-- 브랜치의 새로운 헤드

위의 경우, "git fetch" 명령은 경고를 보여주며 실패할 것이다.


이 경우 다음에서 설명하듯이 강제로 git가 새로운 헤드로 업데이트 하도록 지정할 수 있다. 하지만 이러한 상황에서 강제 업데이트를 수행한다면, (따로 해당 커밋에 대한 참조를 만들어두지 않았다면) "a"와 "b" 커밋에 대한 참조를 잃어버린다는 것을 뜻한다.


git fetch 명령이 고속 이동이 아닌 업데이트를 하도록 강제 지정하기#

만약 새로운 헤드가 이전 헤드의 자손이 아니라는 이유로 'git fetch' 명령이 실패한다면, 다음과 같이 강제 업데이트를 수행하도록 지정할 수 있다:


  1. $ git fetch git://example.com/proj.git +master:refs/remotes/example/master

"+" 기호가 추가된 것에 유의하자. 또는 "-f" 플래그를 이용하여 모든 내려받은(fetched) 브랜치들을 강제로 업데이트하도록 지정할 수 있다:


  1.  $ git fetch -f origin

앞에서 설명한대로 example/master가 가리키던 이전 버전의 커밋들을 잃어버릴 수도 있다는 것을 기억하기 바란다.


원격 브랜치 설정하기#

앞서 "origin"은 단지 원래 내려받은 저장소를 가리키는 줄임말에 불과하다는 것을 살펴보았다. 이 정보는 git-config(1) 명령으로 확인할 수 있는 git 설정 변수에 저장된다:


  1. $ git config -l
    core.repositoryformatversion=0
    core.filemode=true
    core.logallrefupdates=true
    remote.origin.url=git://git.kernel.org/pub/scm/git/git.git
    remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
    branch.master.remote=origin
    branch.master.merge=refs/heads/master

만약 자주 이용하는 다른 저장소가 있다면, 타이핑 횟수를 줄이기 위해 비슷한 설정 옵션을 만들 수도 있다. 예를 들어


  1. $ git config remote.example.url git://example.com/proj.git

위 명령을 실행한 후에는, 다음 두 명령이 같은 작업을 수행할 것이다:


  1. $ git fetch git://example.com/proj.git master:refs/remotes/example/master
    $ git fetch example master:refs/remotes/example/master

게다가, 다음과 같은 옵션도 추가했다고 하면:


  1. $ git config remote.example.fetch master:refs/remotes/example/master

다음 명령들은 모두 같은 작업을 수행할 것이다:


  1. $ git fetch git://example.com/proj.git master:refs/remotes/example/master
    $ git fetch example master:refs/remotes/example/master
    $ git fetch example

또한 매번 강제 업데이트를 하도록 지정하기 위해 "+" 기호를 추가할 수도 있다:


  1. $ git config remote.example.fetch +master:ref/remotes/example/master

"git fetch" 명령이 example/master 상의 커밋들을 잃어버리게 만들어도 상관없다는 확신이 없다면 이 기능을 사용하지 않는 것이 좋다.


그리고 위의 모든 설정은 git-config(1) 명령을 이용하는 대신 직접 .git/config 파일을 편집해서도 설정이 가능하다.


위에서 설명한 설정 옵션들에 대한 자세한 정보는 git-config(1) man 페이지를 살펴보기 바란다.


Git 개념 정리#

git는 단순하지만 강력한 여러 가지 아이디어들로 구성되어 있다. 비록 이러한 내용들을 이해하지 않고서도 git를 잘 사용할 수 있지만, 이를 이해한다면 git를 좀 더 직관적으로 사용할 수 있을 것이다.


먼저 가장 중요한 객체 데이터베이스인덱스에 대해서 살펴보기로 하자.


객체 데이터베이스#

우리는 이미 "변경 이력 이해하기: 커밋"이라는 부분에서 모든 커밋들은 40자의 "객체 이름"으로 저장된다는 것을 보았다. 사실, 프로젝트의 변경 이력을 보여주는 데 필요한 모든 정보는 이 이름과 함께 객체에 저장되어 있다. 각각의 경우에 대해서 객체 이름은 객체의 내용에 SHA-1 해시값을 계산한 것이다. SHA-1는 암호화에 쓰이는 해시 함수이다. 이것은 두 개의 서로 다른 객체가 같은 이름을 가질 수 없다는 것을 의미한다. 이것은 다음과 같은 장점을 가진다:


  • git는 두 객체의 이름 만을 비교하여 두 객체가 서로 같은지 다른지 빨리 판단할 수 있다.
  • 객체 이름은 모든 저장소에서 동일한 방법으로 계산되기 때문에, 서로 다른 저장소에 저장된 동일한 객체는 항상 같은 이름으로 저장된다.
  • 객체를 읽을 때 객체의 이름과 객체의 내용에 대한 해시값이 일치하는 검사하여 에러가 발생했는지 알 수 있다.

(객체 형식과 SHA-1 계산에 대한 자세한 내용은 "객체 저장 형식" 부분을 살펴보기 바란다.)


객체에는 다음과 같은 네 가지 종류가 있다: "블롭", "트리", "커밋", "태그"


"블롭" 객체는 파일 내용을 저장하기 위해 사용된다.

"트리" 객체는 하나 이상의 "블롭" 객체들을 디렉터리 구조로 묶는다. 또한 트리 객체는 다른 트리 객체를 참조할 수 있으며, 따라서 디렉터리 계층이 구성된다.

"커밋" 객체는 이러한 디렉터리 계층들을 리비전들의 방향있는 비순환 그래프로 묶는다. 각 커밋은 커밋이 만들어진 시점의 디렉터리 계층을 지정하는 오직 하나의 트리의 객체 이름을 포함한다. 그리고 부모 커밋 객체를 참조하는 커밋은 해당 디렉토리 계층에 도달하기까지의 변경 이력을 설명한다.

"태그" 객체는 다른 객체를 이름으로 참조하거나 서명하는 데 사용될 수 있다. 태그 객체는 다른 객체의 이름과 타입, (당연히!) 태그 이름을 포함하며, 선택적으로 서명을 추가할 수도 있다.


이들 객체 타입에 대해서는 아래에서 자세히 설명한다:


커밋 객체#

"커밋" 객체는 트리의 물리적인 상태와 그 상태에 도달하기까지의 방법과 이유에 대한 설명을 연결한다. 커밋 내용을 살펴보려면 git-log(1) 혹은 git-show(1) 명령에 --pretty=raw 옵션을 사용하기 바란다:


  1. $ git show -s --pretty=raw 2be7fcb476
    commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
    tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
    parent 257a84d9d02e90447b149af58b271c19405edb6a
    author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
    committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700

        Fix misspelling of 'suppress' in docs

        Signed-off-by: Junio C Hamano <gitster@pobox.com>

위에서 볼 수 있듯이 커밋은 다음과 같은 사항들로 정의된다:


  • 트리: 특정한 시간의 디렉터리의 내용을 나타내는 (아래에서 설명할) 트리 객체의 SHA1 이름
  • 부모(들): 프로젝트의 변경 이력 상에서 바로 이전의 단계를 나타내는 커밋(들)의 SHA1 이름. 위의 예제에서는 하나의 부모를 가지고 있지만, 머지 커밋의 경우에는 둘 이상의 부모를 가질 수 있다. 부모를 가지지 않는 커밋은 "루트" 커밋이라고 하며, 프로젝트의 최초 버전을 나타낸다. 모든 프로젝트는 최소 하나의 루트 커밋을 가져야 한다. 프로젝트는 여러 개의 루트 커밋을 가질 수도 있지만, 이런 상황은 일반적이지 않다 (또한 그리 좋은 아이디어도 아니다).
  • 작성자: 이 변경 사항을 작성한 사람의 이름과 작성한 날짜
  • 커미터: 실제로 커밋을 만든 사람의 이름과 만든 날짜. 이것은 작성자와 달라질 수 있는데, 예를 들어 누군가가 패치를 만들어서 개발자에게 이메일로 보낸 경우 등이 있다.
  • 이 커밋에서 변경한 내용에 대한 설명

커밋 그 자체로는 실제로 어떤 사항들이 변경되었는지에 대한 정보는 포함하지 않는다는 것에 주의하자. 모든 변경 사항은 이 커밋이 가리키는 트리의 내용과 부모가 가리키는 트리를 비교하여 계산된다. 특히, git는 파일 이름 변경을 명시적으로 기록하지 않는다. 하지만 동일한 파일 데이터가 다른 경로에 존재하는 것을 인식하여 이름이 변경되었음을 나타낸다. (예를 들어 git-diff(1) man 페이지의 -M 옵션 부분을 살펴보기 바란다).


커밋은 보통 git-commit(1) 명령을 통해 만들어지며, 이 커밋의 부모는 보통 현재 HEAD로, 트리는 현재 인덱스 상에 저장된 내용이 가리키는 값으로 설정된다.


트리 객체#

트리 객체를 살펴보기 위해서는 다양한 기능을 가진 git-show(1) 명령을 이용할 수도 있지만, git-ls-tree(1) 명령이 더욱 자세한 정보를 보여준다:


  1. $ git ls-tree fb3a8bdd0ce
    100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c    .gitignore
    100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d    .mailmap
    100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING
    040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745    Documentation
    100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200    GIT-VERSION-GEN
    100644 blob 289b046a443c0647624607d471289b2c7dcd470b    INSTALL
    100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1    Makefile
    100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52    README
    ...

위에서 볼 수 있듯이, 트리 객체는 이름 순으로 정렬된 여러 항목들을 가지고 있으며 각 항목들은 모드, 객체 타입, SHA1 이름 및 (일반) 이름을 가진다.


객체 타입은 파일의 내용을 나타내는 블롭(blob)이나 하위 디렉터리의 내용을 나타내는 다른 트리가 될 것이다. 트리와 블롭은 다른 객체들과 마찬가지로 내용에 대한 SHA1 해시값에 해당하는 이름을 가지므로, 두 트리는 오직 (하위 디렉터리의 내용들을 재귀적으로 포함하여) 트리의 내용이 동일한 경우에만 같은 SHA1 이름을 가질 수 있다. 이러한 특성은 git가 동일한 객체 이름을 가지는 항목들을 무시할 수 있기 때문에, 연관된 두 트리 객체 간의 차이점을 빨리 판단할 수 있도록 해 준다.


(주의: 서브 모듈이 있는 상황에서, 트리는 커밋 객체도 항목에 포함할 수 있다. 자세한 내용은 8장 서브 모듈 부분을 살펴보기 바란다.)


모든 파일들의 권한이 644 혹은 755로 설정된 것에 주의하자. git는 실제로 실행 권한 비트에 대해서만 관심을 가진다.


블롭 객체#

블롭의 내용을 살펴보기 위해서는 git-show(1) 명령을 이용할 수 있다. 예를 들어 위의 트리에서 COPYING 항목 내의 블롭을 살펴보기로 하자:


  1. $ git show 6ff87c4664

     Note that the only valid version of the GPL as far as this project
     is concerned is _this_ particular version of the license (ie v2, not
     v2.2 or v3.x or whatever), unless explicitly otherwise stated.
    ...

"블롭" 객체는 데이터의 바이너리 표현일 뿐이며, 다른 객체를 가리키거나, 어떠한 속성도 가지지 않는다.


블롭은 오직 데이터에 의해서만 정의되기 때문에, 디렉토리 트리 내의 (혹은 저장소의 여러 버전 내의) 두 파일의 내용이 동일하다면 같은 블롭 객체를 공유하게 될 것이다. 블롭 객체는 디렉터리 트리 내의 위치와 전혀 무관하며, 파일의 이름을 바꾸는 것은 그 파일에 해당하는 블롭 객체를 변경하지 않는다.


어떠한 트리 객체 혹은 블롭 객체든지 <리비전>:<경로> 형식을 통해 git-show(1) 명령으로 살펴볼 수 있다는 것을 기억하자. 이것은 현재 체크아웃하지 않은 트리의 내용을 살펴볼 때 유용하게 사용될 수 있다.


신뢰#

만약 여러분이 어떤 곳으로부터 블롭 객체의 SHA1 이름을 받고 다른 곳에서 블롭 객체의 내용을 받았다면, SHA1 이름이 일치하는 한 이 객체의 내용이 올바르다는 것을 보장할 수 있다. 이것은 SHA1가 서로 다른 내용에서 동일한 해시를 생성할 수 없도록 설계되었기 때문이다.


마찬가지로, 어떤 트리 객체가 가리키는 디렉터리의 전체 내용을 신뢰하기 위해서는 최상위 트리 객체의 SHA1 이름 만을 신뢰할 수 있으면 된다. 또한 신뢰할 수 있는 곳으로부터 커밋의 SHA1 이름을 받았다면, 해당 커밋의 부모를 통해 도달할 수 있는 커밋들의 전체 변경 이력과 이 커밋이 참조하는 트리의 모든 내용들을 쉽게 검증할 수 있다.


따라서 시스템에 진정한 신뢰성을 제공하려면, 최상위 커밋의 이름을 포함하는 단지 하나의 특별한 노트에 디지털 서명을 하기만 하면 된다. 여러분의 디지털 서명은 다른 사람들이 여러분이 이 커밋을 신뢰하고 있다는 것을 보여주며, 커밋 변경 이력의 불변성은 다른 사람들이 전체 변경 이력을 신뢰할 수 있다는 것을 보여준다.


다시 말하면, 최신 커밋의 이름(SHA1 해시값)을 (PGP/GPG와 같은 도구를 이용하여) 디지털 서명해서 다른 사람들에게 이메일로 보내기만 하면, 전체 내용이 올바르다는 것을 간단히 검증해 보일 수 있는 것이다.


이를 돕기 위해 git는 태그 객체를 제공한다..


태그 객체#

태그 객체는 객체, 객체 타입, 태그 이름, 태그를 만든 사람("tagger")의 이름과 (디지털 서명을 포함할 수 있는) 메시지를 포함하며 이는 git-cat-file(1) 명령을 통해 볼 수 있다:


  1. $ git cat-file tag v1.5.0
    object 437b1b20df4b356c9342dac8d38849f24ef44f27
    type commit
    tag v1.5.0
    tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000

    GIT 1.5.0
    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.6 (GNU/Linux)

    iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
    nLE/L9aUXdWeTFPron96DLA=
    =2E+0
    -----END PGP SIGNATURE-----

태그 객체를 만들거나 검증하는 방법은 git-tag(1) man 페이지를 살펴보기 바란다. (git-tag(1) 명령은, 실제 태그 객체는 아니지만 단지 이름이 "refs/tags/"로 시작하는 단순한 참조인 "가벼운" 태그를 만드는 데 사용할 수도 있다는 것을 기억하자.)


git가 객체를 효율적으로 저장하는 방법: 팩 파일#

새로 만들어진 객체들은 (.git/objects 디렉터리에  저장되는) 객체의 SHA1 해시값을 이름으로 하는 파일 내에 생성된다.


불행히도 이 방식은 프로젝트가 많은 객체를 소유하게 되면 비효율적이 된다. 오래된 프로젝트에서 다음 명령을 실행해 보기 바란다:


  1. $ git count-objects
    6930 objects, 47620 kilobytes

첫 번째 숫자는 별도의 파일로 저장된 객체들의 개수이며, 두 번째 숫자는 이러한 "느슨한" (loose) 객체들이 차지하는 공간의 양이다.


이러한 느슨한 객체들을 효율적인 압축 방식으로 저장하는 "팩 파일"로 옮기면 공간을 절약하는 것은 물론 git의 속도도 빠르게 할 수 있다. 팩 파일의 형식에 대한 자세한 정보는 technical/pack-format.txt 부분을 살펴보기 바란다.


느슨한 객체들을 팩 파일로 옮기려면 단지 git repack 명령을 실행한다:


  1. $ git repack
    Generating pack...
    Done counting 6020 objects.
    Deltifying 6020 objects.
     100% (6020/6020) done
    Writing 6020 objects.
     100% (6020/6020) done
    Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
    Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.

그리고 다음 명령을 실행하면


  1. $ git prune

이제 팩 파일에 저장된 느슨한 객체들(의 원본)을 삭제할 수 있다. 또한 이 명령은 참조되지 않는 모든 객체들도 삭제할 것이다. (이러한 객체들은 예를 들어 "git-reset" 명령을 통해 커밋을 삭제한 경우에 만들어진다.) .git/objects 디렉터리를 확인하거나 다음 명령을 실행하여 느슨한 객체들이 사라진 것을 확인할 수 있다.


  1. $ git count-objects
    0 objects, 0 kilobytes

비록 객체 파일을 사라졌지만, 이 객체들을 참조하는 모든 명령은 전과 동일하게 동작할 것이다.


git-gc(1) 명령은 이러한 packing, pruning 등의 작업을 대신해 주므로, 일반적으로는 gc 명령 하나 만을 이용하면 된다.


댕글링 객체#

git-fsck(1) 명령은 때때로 댕글링 객체에 대한 경고를 보여줄 것이다. 이것은 문제가 아니다.


댕글링 객체가 발생하는 주된 원인은 rebase 명령을 이용하여 브랜치의 변경 이력을 수정하였거나, 그렇게 변경 이력을 수정한 사람으로부터 변경 이력을 내려 받은 (pull) 경우이다. - 5장 변경 이력 수정하기와 패치 묶음 관리하기 부분을 살펴보기 바란다. 이 경우 원래 브랜치의 이전 헤드와 그것이 가리키는 모든 것들은 여전히 존재하며, 브랜치 포인터 자체는 변경되었으므로 이전의 것이 존재하지 않는다.


물론 댕글링 객체를 만드는 다른 경우도 존재한다. 예를 들어 어떤 파일에 대해 "git add" 명령을 수행하고 나서 커밋을 실행하기 전에, 다른 변경도 함께 적용하기 위해 해당 파일의 다른 부분을 수정하고, 수정된 파일을 커밋하는 경우에는 "댕글링 블롭"이 만들어 진다. 원래 add 명령을 수행한 이전의 상태는 더이상 어떠한 커밋이나 트리에서 참조하지 않으므로 댕글링 블롭 객체가 된다.


마찬가지로 "recursive" 머지 전략이 실행될 때, 서로 얽힌 (criss-cross) 머지들이 존재하고 따라서 둘 이상의 머지 베이스가 존재한다는 것을 (이런 경우는 매우 드물지만 분명히 존재한다) 찾아내면 임시 내부 머지 베이스로 사용할 하나의 (만약 많은 수의 서로 얽힌 머지들과 셋 이상의 머지 베이스가 존재한다면 더 많아질 수 있다) 임시 중간 트리(temporary midway tree)를 생성할 것이다. 여기서도 이들은 실제 객체이지만 최종 결과는 이들을 가리키지 않기 때문에 저장소 내에 "댕글링" 객체로 남게 된다.


일반적으로, 댕글링 객체는 전혀 신경쓸 만 한 것이 아니다. 오히려 어떤 경우에는 유용하게 사용할 수 있다. 만약 작업 내용이 엉망이 된 경우에는 댕글링 객체를 이용하여 이전 트리를 복구할 수도 있다. (예를 들어 rebase 명령을 수행한 후에 보니 이것이 원하던 것이 아니었음을 알게된다면, 댕글링 객체들을 살펴보고 헤드를 이전의 댕글링 객체의 상태로 되돌리도록 (reset) 할 수 있다.)


커밋 객체에 대해서는 다음 명령을 이용할 수 있다:


  1. $ gitk <댕글링 커밋의 SHA1 해시값> --not --all

위 명령은 주어진 커밋으로부터는 도달할 수 있지만 다른 어떤 브랜치, 태그 및 다른 참조로부터는 도달할 수 없는 모든 변경 이력을 보여준다. 만약 이것이 원하던 것이라고 판단되면 언제든 이를 가리키는 새로운 참조를 만들 수 있다. 예를 들면 다음과 같다.


  1. $ git branch recovered-branch <dangling-commit-sha-goes-here>

블롭이나 트리 객체에 대해서는 위와 동일한 작업을 수행할 수 없지만, 여전히 그 내용을 볼 수는 있다. 다음 명령을 이용하면


  1. $ git show <댕글링 블롭/트리의 SHA1 해시값>

해당 블롭의 원래 내용 (트리에 대해서는 기본적으로 "ls" 명령의 결과)를 볼 수 있으며, 이를 통해 댕글링 객체를 생성한 작업 내용에 대한 아이디어를 얻을 수도 있을 것이다.


보통 댕글링 블롭과 댕글링 트리 객체는 별로 중요하지 않다. 이들은 거의 대부분 처리가 완료되지 않은 (half-way) 머지 베이스의 결과(때때로 머지 충돌이 발생한 경우 수동으로 이를 해결했다면 댕글링 블롭 객체는 머지 충돌 표식을 포함하고 있을 수도 있다)이거나 단순히 "git-fetch" 명령이 수행될 때 ^C 혹은 그와 비슷한 방식으로 중단된 경우 객체 데이터베이스 내에 일부 새로운 객체들이 남아있는 경우이며 이들은 쓸모가 없다.


어쨌든 어떠한 댕글링 상태도 필요하지 않다고 판단되면, 도달할 수 없는 모든 객체들을 제거(prune)해 버릴 수 있다:


  1. $ git prune

이제 댕글링 객체들은 사라질 것이다. 하지만 "git prune" 명령은 저장소에 변경 사항이 없을 때에만 실행해야 한다. 이것은 파일 시스템에 fsck 명령을 실행하여 복구하는 것과 비슷하다. 여러분은 파일 시스템이 마운트된 상태에서 이러한 작업을 하고 싶지는 않을 것이다.


(이 내용은 "git-fsck" 명령 자체에도 적용된다. 하지만 git-fsck 명령은 실제로 아무 것도 변경하지 않고, 발견한 내용을 보고만 해 주기 때문에 git-fsck 명령 자체를 실행하는 것은 전혀 "위험하지 않다". 누군가가 저장소의 내용을 변경하고 있는 동안 git-fsck 명령을 실행한다면 혼란스러운 메시지를 보여줄 수도 있지만 실제로 문제가 될 만한 작업은 수행하지 않는다. 반대로 누군가가 저장소의 내용을 변경하고 있는 동안 git-prune 명령을 실행하는 것은 좋은 생각이 아니다.)


저장소 손상 시 복구하기#

git는 자신이 신뢰하는 데이터를 조심스럽게 다루도록 설계되었다. 하지만 git 자체에는 버그가 없다 하더라도 하드웨어 혹은 운영체제의 에러로 인해 데이터가 손상될 수 있다.


이러한 문제에 대한 일차적인 대책은 백업이다. git 디렉토리는 clone 명령 혹은 단순한 cp, tar 등의 다른 백업 방법을 통해 백업할 수 있다.


마지막 방법으로는 손상된 객체들을 찾아서 일일이 변경해 줄 수도 있다. 이 방법을 사용하기 전에는 저장소가 더욱 손상되지 않도록 미리 백업을 해 두도록 하자.


우리는 문제가 하나의 손상되었거나 사라진 블롭으로인해 생긴 것이라고 가정할 것이다. 이러한 문제는 때때로 해결이 가능하다. (사라진 트리나 특히 커밋들을 복구하는 것은 무척 힘들다.)


시작하기 전에 git-fsck(1) 명령을 통해 저장소가 손상되었는지, 그렇다면 손상된 부분이 어디인지 확인한다. 이 작업은 시간이 꽤 걸릴 수도 있다.


여기서는 다음과 같이 출력되었다고 가정하자:


  1. $ git fsck --full
    broken link from    tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
                  to    blob 4b9458b3786228369c63936db65827de3cc06200
    missing blob 4b9458b3786228369c63936db65827de3cc06200

(보통은 댕글링 객체에 대한 메시지들도 함께 출력될 것이지만, 여기에서는 고려하지 않는다.)


이제 2d9263c6 트리가 가리키는 4b9458b3 블롭 객체가 사라진 것을 알았다. 만약 (아마도 다른 저장소에서) 해당 블롭 객체의 복사본을 찾을 수 있다면, 이 객체를 .git/objects/4b/9458b3… 로 복사하여 문제를 해결할 수 있다. 하지만 이것이 불가능하다고 가정하자. git-ls-tree(1) 명령을 이용하여 해당 블롭을 가리키고 있는 트리를 살펴볼 수 있다. 이 명령은 다음과 같은 메시지를 출력할 것이다:


  1. $ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
    100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8    .gitignore
    100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883    .mailmap
    100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c    COPYING
    ...
    100644 blob 4b9458b3786228369c63936db65827de3cc06200    myfile
    ...

이제 사라진 블롭이 "myfile"이라는 이름의 파일이라는 것을 알 수 있다. 그리고 해당 파일이 있는 디렉터리를 알아낼 경우도 있다. 여기서는 "somedirectory"라는 디렉터리 내에 myfile이 존재한다고 해 보자. 만약 운이 좋다면 사라진 블롭이 현재 체크아웃한 작업 트리 내의 "somedirectory/myfile"의 복사본과 동일할 수 있다. 이를 확인하려면 git-hash-object(1) 명령을 이용할 수 있다:


  1. $ git hash-object -w somedirectory/myfile

위 명령은 somedirectory/myfile의 내용으로 블롭 객체를 만들어서 저장하고, 객체의 SHA-1 해시값을 출력한다. 만약 아주 운이 좋다면 예상대로 이 값은 4b9458b3786228369c63936db65827de3cc06200이 될 것이고, 손상은 해결된다!


그렇지 않다면 더 많은 정보가 필요하다. 해당 파일의 어느 버전이 사라진 것인지 어떻게 알려줄 것인가?


가장 간단한 방법은 다음 명령을 실행하는 것이다:


  1. $ git log --raw --all --full-history -- somedirectory/myfile

위 명령에서 raw 객체를 요청하였기 때문에 다음과 같이 출력될 것이다.


  1. commit abc
    Author:
    Date:
    ...
    :100644 100644 4b9458b... newsha... M somedirectory/myfile


    commit xyz
    Author:
    Date:

    ...
    :100644 100644 oldsha... 4b9458b... M somedirectory/myfile

이것은 해당 파일의 바로 이전 버전이 "newsha"이고, 바로 다음 버전이 "oldsha"라는 것을 보여준다.(?) 또한 oldsha에서 4b9458b까지의 변경 사항과 4b9458b에서 newsha까지의 변경 사항에 대한 커밋 메시지들도 알 수 있다.


만약 변경 사항을 작은 단위로 나누어서 커밋해 왔다면, 4b9458b 사이의 커밋들로부터 내용을 복구할 수 있는 지점(goot shot)을 찾을 수도 있을 것이다.


그렇다면 다음과 같이 사라진 객체를 다시 만들어 낼 수 있다:


  1. $ git hash-object -w <다시 만든 파일>

이제 저장소는 복구되었다!


(그런데, fsck는 무시하고 다음을 실행하여 문제 해결을 시도해 볼 수 있다.


  1. $ git log --raw --all

위 명령의 출력에서 사라진 객체의 SHA1 해시값(4b9458b..)을 찾아볼 수 있다. 이것은 선택하기 나름이다. git는 수 많은 정보를 가지고 있고, 이것은 단지 특정한 하나의 사라진 블롭 버전일 뿐이다.


인덱스#

인덱스는 (일반적으로 .git/index 폴더에 저장되며) 블롭 객체의 이름(path name)으로 정렬된 목록을 포함하는 바이너리 파일이며, 각 블롭 객체의 접근 권한과 SHA-1 해시값도 함께 포함한다. git-ls-files(1) 명령을 통해 인덱스의 내용을 볼 수 있다:


  1. $ git ls-files --stage
    100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0       .gitignore
    100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0       .mailmap
    100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0       COPYING
    100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0       Documentation/.gitignore
    100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0       Documentation/Makefile
    ...
    100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0       xdiff/xtypes.h
    100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0       xdiff/xutils.c
    100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0       xdiff/xutils.h

이전 버전의 문서에서 인덱스는 "현재 디렉터리 캐시" 혹은 단지 "캐시"라고 했던 것을 보았을 지도 모른다. 인덱스는 다음과 같은 세 가지 중요한 특징을 가진다.


  1. 인덱스는 하나의 (고유한) 트리 객체를 생성하기 위해 필요한 모든 정보를 포함한다.

    예를 들어 git-commit(1) 명령을 실행하면 인덱스로부터 이 트리 객체를 만들고, 객체 데이터베이스에 저장한 뒤, 이를 새로운 커밋에 연관된 트리 객체로 사용한다.
  2. 인덱스는 자신이 정의한 트리 객체와 작업 트리를 재빨리 비교할 수 있게 도와준다.

    이것은 각 항목들에 대해 (최종 수정 시간과 같은) 추가적인 정보를 저장하기 때문에 가능하다. 이러한 정보들은 위에서 출력되지 않았고, 생성된 트리 객체 내에 저장되지도 않았지만, 작업 트리 내의 어떤 파일이 인덱스가 저장될 때와 다른지 빨리 판단하는 데 사용될 수 있다. 따라서 git는 변경 사항을 알아내기 위해 이러한 파일들의 모든 데이터를 읽지 않아도 된다.
  3. 서로 다른 트리 객체 간의 머지 충돌에 대한 정보를 효율적으로 표현할 수 있다. 각 경로명은 자신이 속한 트리에 대한 충분한 정보를 가질 수 있게 되므로 이들 간에 3-방향 머지를 만들 수 있다.

    "머지 중에 충돌을 해결하기 위해 필요한 정보 얻기" 부분에서, 머지 중에 인덱스는 한 파일의 여러 버전(스테이지라고 한다)을 저장할 수 있다는 것을 살펴보았다. git-ls-files(1) 명령의 출력에서 세 번째 열은 이러한 스테이지 번호이며, 머지 충돌이 발생한 파일의 경우에는 0 이외의 값을 가질 것이다.

따라서 인덱스는 작업 중인 트리의 내용을 저장하는 일종의 임시 저장 공간이라고 볼 수 있다.


만약 인덱스를 완전히 제거한다고 해도, 일반적으로 인덱스가 가리키는 트리의 이름이 남아있는 한 어떠한 정보도 사라지지 않을 것이다(?).


서브 모듈#

대규모 프로젝트들은 때때로 상대적으로 작은 단위의, 완전한 기능을 갖춘 모듈들로 이루어져 있다. 예를 들어 임베디드 리눅스 배포판의 소스 트리는 각각의 수정된 소프트웨어들을 포함할 것이다. 동영상 재생기는 동작이 확인된 특정한 버전의 압축 라이브러리와 빌드될 것이며, 여러 가지 독립적인 프로그램들은 동일한 빌드 스크립트를 공유할 수도 있다.


중앙 집중적인 버전 관리 시스템을 이용한다면 이를 위해 모든 모듈을 하나의 저장소에 포함시킬 것이다. 개발자는 작업하려는 특정 모듈 혹은 전체 모듈을 체크아웃 할 수 있다. 심지어는 파일의 위치를 변경하고, API와 번역을 업데이트하는 동안, 여러 모듈에 걸친 파일들을 하나의 커밋으로 제출할 수도 있다.


git는 일부만 체크아웃 하는 것을 허용하지 않는다. 따라서 위와 같은 방식을 git에서 사용하려면 개발자가 수정하지 않는 모듈들의 로컬 버전도 저장하도록 해야 한다. 대규모의 체크아웃에 커밋하는 일은 예상보다 오래 걸릴 수 있는데, 이는 git가 변경 사항을 위해 모든 디렉토리를 검사해야 하기 때문이다. 만약 모듈이 아주 많은 변경 이력을 가지고 있다면 복사(clone) 작업은 굉장히 오랜 시간이 걸릴 것이다.


장점을 살펴보자면, 분산 버전 관리 시스템은 외부 소스를 보다 쉽게 통합할 수 있다. 중앙 집중적인 방식에서는 외부 프로젝트의 원본 버전 관리 시스템에서 한 스냅샷이 공개되면(export) 그것을 로컬 버전 관리 시스템의 브랜치로 가져온다(import). 이 때 모든 변경 이력은 사라진다. 분산 버전 관리 시스템을 사용하면 전체 변경 이력을 복사할(clone) 수 있으며 보다 쉽게 개발 과정을 따라가거나 로컬의 변경 사항을 다시 머지할 수 있다.


git의 서브모듈 지원은 저장소가 다른 프로젝트의 체크아웃을 하위 디렉터리 형태로 저장할 수 있도록 해 준다. 서브모듈은 자신의 정체성을 유지하며, 서브모듈 지원은 단지 서브모듈의 저장소와 커밋 ID를 저장하여, 서브모듈을 포함한 상위 프로젝트 ("superproject")를 복사한 다른 개발자가 동일한 버전의 모든 서브모듈을 쉽게 복사할 수 있도록 해 준다. 상위 프로젝트의 일부 만을 체크아웃 하는 것이 가능하다. git에게 서브모듈을 전혀 포함하지 않거나, 일부 만 포함하거나, 모두 포함하도록 지정할 수 있다.


git 버전 1.5.3 이후부터는 git-submodule(1) 명령을 이용할 수 있다. git 1.5.2 사용자는 저장소 내의 서브모듈 커밋들을 확인하여 직접 체크아웃 할 수 있다. 그 이전 버전들은 서브모듈을 전혀 인식하지 못할 것이다.


서브모듈 지원이 동작하는 방식을 보기 위해, 이후에 서브모듈로 사용될 4 개의 예제 저장소를 만들어 보자:


  1. $ mkdir ~/git
    $ cd ~/git
    $ for i in a b c d
    do
            mkdir $i
            cd $i
            git init
            echo "module $i" > $i.txt
            git add $i.txt
            git commit -m "Initial commit, submodule $i"
            cd ..
    done

이제 상위 프로젝트를 만들고 모든 서브모듈을 추가한다:


  1. $ mkdir super
    $ cd super
    $ git init
    $ for i in a b c d
    do
            git submodule add ~/git/$i $i
    done

주의

만약 상위 프로젝트를 공개할 예정이라면, 여기서 서브모듈의 로컬 URL을 사용해서는 안된다.


'git-submodule' 명령이 생성한 파일들을 살펴보자:


  1. $ ls -a
    .  ..  .git  .gitmodules  a  b  c  d

'git-submodule add <저장소> <경로>' 명령은 다음과 같은 작업을 수행한다:

  • <저장소>에서 서브모듈을 복사하여 현재 디렉토리 내의 <경로>에 저장한다. 기본적으로 master 브랜치를 체크아웃 한다.
  • 서브모듈의 복사 경로를 gitmodule(5) 파일에 복사하고 이 파일을 인덱스에 추가하여 커밋할 준비를 한다.
  • 서브모듈의 현재 커밋 ID를 인덱스에 추가하여 커밋할 준비를 한다.

상위 프로젝트를 커밋한다:


  1. $ git commit -m "Add submodules a, b, c and d."

이제 상위 프로젝트를 복사한다:


  1. $ cd ..
    $ git clone super cloned
    $ cd cloned

서브모듈 디렉터리는 존재하지만, 비어있다:


  1. $ ls -a a
    .  ..
    $ git submodule status
    -d266b9873ad50488163457f025db7cdd9683d88b a
    -e81d457da15309b4fef4249aba9b50187999670d b
    -c1536a972b9affea0f16e0680ba87332dc059146 c
    -d96249ff5d57de5de093e6baff9e0aafa5276a74 d

주의

위에 보이는 커밋 객체 이름은 이름은 다를 수 있지만, 이들은 저장소의 HEAD 커밋 객체의 이름과 일치해야 한다. 이것은 'git ls-remote ../a' 명령을 실행하여 확인할 수 있다.


서브모듈을 내려받는 것은 두 단계를 거치는 작업이다. 먼저 'git submodule init' 명령을 실행하여 서브 모듈 저장소 URL을 .git/config 파일에 추가한다:


  1. $ git submodule init

이제 'git submodule update' 명령을 실행하여 저장소를 복사하고 상위 프로젝트 내에 지정된 커밋들을 체크아웃 한다:


  1. $ git submodule update
    $ cd a
    $ ls -a
    .  ..  .git  a.txt

'git submodule update' 명령과 'git submodule add' 명령의 중요한 차이점 중 하나는, 'git submodule update'는 브랜치의 최신 커밋이 아닌, 지정한 커밋을 체크아웃 한다는 점이다. 이것은 태그를 체크아웃 하는 것과 비슷한데, 헤드가 분리되어 브랜치에서 작업하는 것이 아니다.


  1. $ git branch
    * (no branch)
      master

만약 서브모듈 내에서 수정을 하고 싶은데 헤드가 분리되어 있다면, 브랜치를 만들거나 체크아웃 한 후, 소스를 수정하고, 서브모듈 내에서 변경 사항을 공개하고, 상위 프로젝트가 새로운 커밋을 참조하도록 업데이트한다:


  1. $ git checkout master

혹은

  1. $ git checkout -b fix-up

그리고 다음을 수행한다:


  1. $ echo "adding a line again" >> a.txt
    $ git commit -a -m "Updated the submodule from within the superproject."
    $ git push
    $ cd ..
    $ git diff
    diff --git a/a b/a
    index d266b98..261dfac 160000
    --- a/a
    +++ b/a
    @@ -1 +1 @@
    -Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
    +Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
    $ git add a
    $ git commit -m "Updated submodule a."
    $ git push

만약 'git pull' 명령을 실행한 후에 서브모듈도 업데이트 하고 싶다면 'git submodule update' 명령을 실행해야 한다.


서브모듈에 관한 함정#

서브모듈을 포함하는 상위 프로젝트에 변경 사항을 공개하기(publish) 전에 항상 서브모듈의 변경 사항을 먼저 공개해야 한다. 만약 서브모듈의 변경 사항을 공개하는 것을 잊어버렸다면 다른 사람들이 저장소를 복사할 수 없게 될 것이다:


  1. $ cd ~/git/super/a
    $ echo i added another line to this file >> a.txt
    $ git commit -a -m "doing it wrong this time"
    $ cd ..
    $ git add a
    $ git commit -m "Updated submodule a again."
    $ git push
    $ cd ~/git/cloned
    $ git pull
    $ git submodule update
    error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.
    Did you forget to 'git add'?
    Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'

또한 상위 프로젝트에 저장된 커밋 이후에 서브모듈 내의 브랜치를 되돌려서는(rewind) 안 된다.


브랜치를 체크아웃 하지 않은 채로 서브모듈 내의 변경 사항을 커밋했다면, 'git submodule update' 명령을 실행하는 것이 안전하지 않다. 이러한 사항들은 조용히 덮어씌워질 것이다.:


  1. $ cat a.txt
    module a
    $ echo line added from private2 >> a.txt
    $ git commit -a -m "line added inside private2"
    $ cd ..
    $ git submodule update
    Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
    $ cd a
    $ cat a.txt
    module a

주의

이러한 변경 사항들은 서브모듈의 reflog를 통해 여전히 볼 수 있다.


이것은 커밋하지 않은 경우에는 적용되지 않는다.



저수준 git 연산#

 많은 수의 고수준 명령들은 원래 작고 핵심적인 저수준 git 명령들을 이용한 쉘 스크립트로 구현되었다. 이러한 저수준 명령들은 git로 일상적이지 않은 작업을 수행하거나, git의 내부 동작을 이해하는 데 유용하다.


객체 접근 및 조작#

git-cat-file(1) 명령은 어떠한 객체의 내용도 표시할 수 있다. 하지만 고수준의 git-show(1) 명령이 보통 더 유용하게 사용된다.


git-commit-tree(1) 명령은 임의의 부모와 트리들로 커밋을 구성하도록 해 준다.


트리는 git-write-tree(1) 명령으로 만들 수 있으며, 트리 데이터는 git-ls-tree(1) 명령으로 볼 수 있다. 두 트리를 비교할 때는 git-diff-tree(1) 명령을 이용할 수 있다.


태그는 git-mktag(1) 명령으로 만들고, 서명은 git-verify-tag(1) 명령으로 확인할 수 있다. 하지만 일반적으로 두 경우 모두 git-tag(1) 명령을 사용하는 것이 더 간편하다.


작업 순서#

git-commit(1), git-checkout(1), git-reset(1)과 같은 고수준 명령들은 데이터를 작업 트리, 인덱스, 객체 데이터베이스로 이동시키며 작업한다. git는 이러한 각 단계를 개별적으로 수행하는 저수준의 연산을 제공한다.


일반적으로 모든 "git" 연산들은 인덱스 파일 상에서 이루어진다. 몇몇 연산들은 순전히 인덱스 파일 상에서만 동작하지만 (인덱스의 현재 상태를 보여주는 연산), 많은 연산들은 데이터를 인덱스 파일과 객체 데이터베이스 혹은 작업 트리 사이로 이동시킨다. 따라서 다음과 같은 4가지 조합이 존재한다:


작업 디렉터리 -> 인덱스#

 git-update-index(1) 명령은 작업 디렉터리의 정보로부터 인덱스를 갱신한다. 일반적으로 다음과 같이 갱신하고자 하는 파일의 이름을 지정하기만 하면 인덱스 정보를 갱신한다:


  1. $ git update-index filename

하지만 파일 이름 글로빙(globbing - 역주: *, ? 기호 등을 이용한 여러 파일 선택) 등으로 인한 일반적인 실수를 방지하기 위해, 이 명령은 일반적으로 새로운 항목을 추가하거나 기존의 항목을 삭제하는 작업을 수행하지 않는다. 즉, 일반적으로 기존의 캐시 항목을 업데이트하는 작업 만을 수행할 것이다.


이를 수행하려면, 여러분이 어떤 파일들은 더 이상 존재하지 않거나 새로운 파일들을 추가해야 한다는 것을 제대로 인식하고 있다고 git에게 알려주기 위해, 각각 --remove 플래그와 --add 플래그를 사용해야 한다.


주의! --remove 플래그는 지정한 파일이 삭제되어야 한다는 것을 의미하지 않는다. 만약 해당 파일이 계속 디렉터리 구조 내에 남아있다면, 인덱스는 제거되지 않고 새로운 상태로 갱신될 것이다. --remove 플래그가 의미하는 유일한 내용은 update-index 명령이 삭제된 파일을 올바른 것으로 인식하도록 하며, 만약 해당 파일이 더 이상 존재하지 않으면 그에 따라 인덱스를 적절히 갱신하는 것이다.


특별한 경우로, 각 인덱스의 현재 "상태"(stat) 정보를 현재 상태 정보와 일치하도록 갱신하는 'git update-index --refresh' 명령을 이용할 수 있다. 이 명령은 객체 상태 자체를 갱신하는 것이 아니라, 객체가 이전에 저장된 객체와 일치하는지 빨리 알아보는 데 사용하는 필드만을 갱신할 것이다.


앞서 설명한 git-add(1) 명령은 git-update-index(1) 명령의 래퍼(wrapper)일 뿐이다.


인덱스 -> 객체 데이터베이스#

 다음 명령을 이용하여 현재 인덱스 파일을 "트리" 객체에 기록한다.


  1. $ git write-tree

위 명령은 아무런 인자도 받지 않으며, 현재 인덱스를 해당 상태가 지정하는 트리 객체 집합에 쓰고, 그 결과로 나온 최상위 트리 객체의 이름을 반환할 것이다. 이 객체는 언제라도 다른 방향으로 이동하여 인덱스를 새로 생성하는 데 사용할 수 있다. (아래 참고)


객체 데이터베이스 -> 인덱스#

객체 데이터베이스에서 "트리" 파일을 읽어서, 현재 인덱스를 채운다. (그리고 덮어쓴다 - 만약 인덱스가 저장되지 않은 상태를 포함하고 있으며, 나중에 이 상태로 돌아가고 싶다면 이 작업을 수행해서는 안 된다.) 일반적인 작업 과정은 다음과 같다.


  1. $ git read-tree <트리의 SHA-1 해시값>

위 명령은 인덱스 파일을 이전에 저장했던 트리와 동일하게 만들 것이다. 하지만 이것은 오직 인덱스 파일 만을 수정하는 것이며 작업 디렉토리의 내용은 변경되지 않는다.


인덱스 -> 작업 디렉토리#

파일들을 "체크아웃" 하여 인덱스로부터 작업 디렉터리를 갱신할 수 있다. 이것은 아주 일상적인 연산은 아니다. 보통은 파일을 최신 상태로 유지하기를 원하며, 작업 디렉터리에 쓰기 보다는 작업 디렉터리의 변경 사항을 인덱스에 저장하기 때문이다. (git update-index)


하지만 새로운 버전으로 변경(jump)하거나, 다른 사람의 버전을 체크아웃하거나, 이전 트리를 복구하기로 결정했다면, read-tree 명령을 통해 인덱스 파일을 채우고(populate), 그 결과를 체크아웃해야 한다.


  1. $ git checkout-index filename

혹은, 모든 인덱스를 체크아웃하려면 -a 플래그를 이용한다.


주의! 'git checkout-index' 명령은 일반적으로 이전 버전의 파일을 덮어쓰지 않을 것이다. 따라서 만약 이전 버전의 트리를 이미 체크아웃했다면, 강제로 체크아웃을 수행하기 위해 ("-a" 플래그나 파일 이름 앞에) "-f" 플래그를 사용해야 한다.


마지막으로, 한 형태(representation)에서 다른 형태로 완전히(purely) 이동하지 않는 몇 가지 것(a few odds and ends)들이 존재한다.


이들을 모두 합치기#

"git write-tree" 명령을 통해 생성한 트리를 커밋하려면, 해당 트리와 그에 대한 변경 이력(대부분 변경 이력 상의 바로 이전에 위치하는 "부모" 커밋을 말한다)을 가리키는 "커밋" 객체를 만들어야 한다.


일반적으로 "커밋" 객체는 하나의 부모 커밋을 가진다. 이 부모 커밋은 트리에서 특정한 수정이 이루어지기 전의 상태를 가리킨다. 하지만, 우리가 "머지"라고 하는 경우에 있어서는 둘 이상의 부모 커밋을 가질 수 있다. 이는 이러한 커밋이 다른 커밋들이 가리키는 둘 이상의 이전 상태가 합쳐져서(머지되어) 이루어진 것이기 때문이다.


다시 말하면, "트리"는 작업 디렉터리의 특정한 디렉터리 상태를 말하지만, "커밋"은 특정한 "시간"에서의 상태와 그 상태에 이르기까지의 과정을 나타낸다.


커밋이 이루어지는 시각에서의 상태를 나타내는 트리와 부모 커밋의 목록을 이용하여 다음과 같이 커밋 객체를 만들 수 있다:


  1. $ git commit-tree <tree> -p <parent> [-p <parent2> ..]

그리고 표준 입력을 통해 커밋에 대한 설명을 추가한다. (파이프나 파일 재지정(redirection)을 이용하거나 터미널에 직접 입력하면 된다.)


git commit-tree 명령은 생성된 커밋을 나타내는 객체의 이름을 반환하며, 나중을 위해 이를 저장해 두어야 한다. 일반적으로 새로운 HEAD 상태를 커밋하는 데, git는 이 상태에 대한 정보를 저장하는 위치를 고려하지 않기 때문에 실제로는 .git/HEAD가 가리키는 파일에 객체의 이름을 직접 저장해야 하며, 그렇게 되면 마지막 커밋 상태가 무엇인지 항상 확인할 수 있다.


아래는 여러 과정들이 함께 동작하는 과정을 설명하기 위해 Jon Loeliger가 그린 ASCII art이다.


  1.                      commit-tree
                          commit obj
                           +----+
                           |    |
                           |    |
                           V    V
                        +-----------+
                        | Object DB |
                        |  Backing  |
                        |   Store   |
                        +-----------+
                           ^
               write-tree  |     |
                 tree obj  |     |
                           |     |  read-tree
                           |     |  tree obj
                                 V
                        +-----------+
                        |   Index   |
                        |  "cache"  |
                        +-----------+
             update-index  ^
                 blob obj  |     |
                           |     |
        checkout-index -u  |     |  checkout-index
                 stat      |     |  blob obj
                                 V
                        +-----------+
                        |  Working  |
                        | Directory |
                        +-----------+

데이터 살펴보기#

여러 가지 보조 도구를 이용하여 객체 데이터베이스 내에 표현된 데이터나 인덱스를 살펴볼 수 있다. 모든 객체들은 git-cat-file(1) 명령을 이용하여 해당 객체에 대한 상세한 내용을 살펴볼 수 있다:


  1. $ git cat-file -t <객체 이름>

위 명령은 객체의 타입 정보를 보여준다. 객체 타입을 알고나면 (일반적으로 객체 타입 정보는 객체를 찾을 때는 표시되지 않는다) 다음 명령을 이용할 수 있다.


  1. $ git cat-file blob|tree|commit|tag <objectname>

위 명령은 객체의 내용을 보여준다. 주의! 트리 객체는 이진 데이터를 포함한다. 따라서 트리 객체의 내용을 표시하기 위한 git-ls-tree라는 특별한 명령이 별도로 존재하며, 이는 이진 데이터를 읽기 쉬운 형식으로 변환해 준다.


"커밋" 객체를 살펴보는 것은 특히 도움이 된다. 커밋 객체는 보통 크기가 작으며 자체적으로 충분한 정보를 제공하기 때문이다. 특히 최종 커밋의 이름을 .git/HEAD가 가리키는 파일에 저장하는 관례를 따르는 (일반적인) 경우라면, 다음 명령을 이용하여 최종 커밋의 내용을 확인할 수 있다.


  1. $ git cat-file commit HEAD

여러 트리를 머지하기#

git는 3방향 머지를 수행할 수 있도록 도와주며, 최종적으로 "커밋"할 상태에 이르기까지 이 작업을 반복함으로서 n방향 머지를 수행할 수도 있다. 일반적인 상황은 (2개의 부모가 있는) 3방향 머지를 한 번 수행하고 커밋하는 것이지만, 원한다면 여러 부모를 머지한 후 한 번에 커밋할 수 있다.


3방향 머지를 수행하려면, 머지할 두 개의 "커밋" 객체들이 있어야 하며, 이들을 이용하여 가장 가까운 공통 부모(세번째 "커밋" 객체)를 찾고, 이러한 커밋 객체들을 이용하여 각 지점에서의 디렉터리의 상태("트리" 객체)를 찾는다.


머지의 "베이스"를 얻으려면, 먼저 다음 명령을 이용하여 두 커밋의 공통 부모를 찾는다:


  1. $ git merge-base <커밋1> <커밋2>

위 명령은 두 커밋들이 공통적으로 참조하는 커밋을 반환할 것이다. 이제 각 커밋들의 "트리" 객체를 살펴봐야 한다. 이는 (예를 들어) 다음 명령을 이용하여 간단히 수행할 수 있다.


  1. $ git cat-file commit <커밋 이름> | head -1

트리 객체 정보는 항상 커밋 객체의 첫 번째 줄에 나타나기 때문이다.


머지를 수행할 3개의 트리를 얻고 나면 (하나의 "원본" 트리(또는 공통 트리)와 두 개의 "결과" 트리(머지를 수행할 브랜치)) 인덱스로 "머지" 읽기를 수행한다. 이 과정은 이전 인덱스의 내용을 덮어쓰게 되는 경우 경고를 보여줄 것이다. 따라서 인덱스의 내용을 모두 커밋했는지 확인해야 한다. 실제로는 거의 항상 최신 커밋에 대해 머지를 수행할 것이므로 현재 인덱스의 내용과 일치할 것이다.


머지를 수행하려면 다음 명령을 실행한다.


  1. $ git read-tree -m -u <원본 트리> <여러분의 작업 트리> <대상 트리>

위 명령은 인덱스 파일 내에서 직접 여러 간단한 머지 작업들을 수행할 것이며, 그 결과를 git write-tree 명령을 통해 기록할 수 있다.


여러 트리를 머지하기 (계속)#

슬프게도 많은 머지 작업은 단순하지가 않다. 만약 추가, 이동, 삭제된 파일이 있거나 두 브랜치에서 모두 같은 파일을 수정했다면 "머지 항목"들을 포함한 인덱스 트리를 가지게 될 것이다. 이러한 인덱스 트리는 트리 객체로 쓸 수 없으며, 결과를 기록하기 전에 다른 도구를 이용하여 머지 충돌 사항들을 해결해야 한다.


git ls-files --unmerged 명령을 이용하여 이러한 인덱스 상태를 살펴볼 수 있다. 예를 들면:


  1. $ git read-tree -m $orig HEAD $target
    $ git ls-files --unmerged
    100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1       hello.c
    100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2       hello.c
    100644 cc44c73eb783565da5831b4d820c962954019b69 3       hello.c

git ls-files --unmerged 명령의 출력의 각 줄은 블롭 모드 비트, 블롭의 SHA1 해시값, 스테이지 번호, 파일 이름으로 이루어진다. 스테이지 번호는 git에서 해당 객체가 어느 트리에서 온 것인지 표현하기 위한 방법이다. 스테이지 1, 2, 3은 각각 $orig, HEAD, $target 트리에 해당한다.


앞에서 간단한 머지 작업은 git-read-tree -m 명령을 통해 수행된다고 하였다. 예를 들어 어떤 파일이 $orig에서부터 HEAD나 $target까지 변경되지 않았거나, 동일한 방식으로 변경되었다면 당연히 최종 결과는 HEAD의 내용이 된다. 위의 예제에서는 hello.c 파일이 $orig부터 HEAD까지와 $orig부터 $target까지 다른 방식으로 변경되었다는 것을 보여준다. 이것은 각 스테이지의 블롭 객체들에 대해 직접 여러분이 주로 사용하는 3방향 머지 프로그램을 (예를 들어, diff3, merge 혹은 git merge-file) 이용하여 다음과 같이 해결할 수 있다:


  1. $ git cat-file blob 263414f... >hello.c~1
    $ git cat-file blob 06fa6a2... >hello.c~2
    $ git cat-file blob cc44c73... >hello.c~3
    $ git merge-file hello.c~2 hello.c~1 hello.c~3

위 명령은 머지 결과를 hello.c~2 파일에 저장하며, 만약 충돌이 발생했다면 충돌 표시를 남긴다. 머지 결과가 올바른지 확인한 후에는, git에게 해당 파일의 최종 머지 결과를 알려줄 수 있다:


  1. $ mv -f hello.c~2 hello.c
    $ git update-index hello.c

만약 경로가 "unmerged" 상태에 있었다면, git-update-index 명령은 git에게 해당 경로의 충돌이 해결되었다고 표시하도록 알려준다.


지금까지 내부에서 개념적으로 어떠한 일들이 수행되는지 이해하기 위해 git의 머지 과정을 가장 낮은 수준에서 설명하였다. 실제로는 (git를 자체를 포함하여) 아무도 이를 위해 git cat-file 명령을 3번 수행하지 않는다. 임시 파일에서 각 스테이지를 추출하기 위한 git-merge-index 명령이 존재하며, 이를 이용하여 "merge" 스크립트를 호출한다:


  1. $ git merge-index git-merge-one-file hello.c

이것은 상위 수준의 git-merge -s resolve 명령이 구현된 방식이다.



Git 해킹하기#

이 부분에서는 아마도 git 개발자들 만이 이해할 필요가 있는 git 내부 구현의 세부 사항들을 설명한다.


객체 저장 형식#

모든 객체는 해당 객체의 형식을 나타내는 고정된 "타입"을 가지고 있다. 이 타입은 해당 객체가 어떻게 사용되며, 어떻게 다른 객체를 참조하는 지를 결정한다. 현재는 다음과 같은 4가지 타입이 존재한다: "블롭", "트리", "커밋", "태그"


객체의 타입과는 별도로 모든 객체는 다음과 같은 공통적인 특징을 가진다. 모든 객체는 zlib으로 압축되며, 객체의 형식과 객체 내의 데이터의 크기 정보를 제공하는 헤더를 포함한다. 객체의 이름으로 사용되는 SHA-1 해시값은 원본 데이터와 이 헤더를 합친 데이터에 대한 해시값이므로, 해당 파일에 sha1sum 명령을 수행한 값과 객체 이름은 다르다는 것을 알아두기 바란다. (역사적인 정보: git 초창기의 객체 이름은 압축된 객체의 SHA1 해시값을 사용하였다.)


따라서, 일반적인 객체의 일관성은 객체의 종류 및 그 내용과는 무관하게 항상 테스트할 수 있다. 객체의 일관성을 검사하려면 (a) 해시값이 객체의 내용과 일치하는지 (b) 객체의 압축이 <타입을 나타내는 ASCII 문자열 (공백없음)> + <공백> + <크기를 나타내는 ASCII 문자열 (10진수)> + <바이트 \0> + <바이너리 객체 데이터> 형식으로 잘 해제되는지 확인하면 된다.


구조화된 객체는 추가로 자신의 구조를 가질 수 있으며, 다른 객체와의 연결도 확인해야 한다. 이는 일반적으로 git-fsck 프로그램을 이용하여 확인할 수 있다. git-fsck는 모든 객체의 완전한 의존 그래프를 생성하고 (해사값을 통한 외부 일관성을 확인하는 것과 별도로) 각각의 내부 일관성을 검사한다.


git 소스 코드의 개략적인 설명#

새로운 개발자가 git 소스 코드에서 적절한 방향을 찾는 것이 항상 쉽지는 않다. 이 부분에서는 git 소스를 살펴보는데 도움이 되는 약간의 조언을 하려한다.


가장 좋은 출발점은 다음과 같은 명령을 통해 최초 커밋의 내용을 살펴보는 것이다:


  1. $ git checkout e83c5163

최초의 버전은 현재 git가 가진 거의 대부분의 기능을 기본적으로 구현하고 있지만 한 번에 살펴볼 수 있을 만큼 적은 양이다.


용어는 계속 변경되어 왔다. 예를 들어 최초 버전의 README 파일에 포함된 "changeset"이라는 용어는 지금의 커밋에 해당한다.


또 한 현재는 "캐시"라는 용어는 사용하지 않고 "인덱스"라는 용어를 사용한다. 하지만 파일 이름은 그대로 cache.h로 남아있다. 참고: 현재 이를 변경할 큰 이유는 없다. 특히 이를 대체할 단일 용어가 마땅치 않으며, 이 파일은 git 내의 모든 C 소스 파일에서 include하고 있는 헤더 파일이기 때문이다.


만약 최초 버전내의 아이디어들을 이해했다면, 좀 더 최신 버전을 체크아웃하여 cache.h, object.h, commit.h 파일들을 살펴보기 바란다.


초 기의 git는 (유닉스의 전통에 따라) 스크립트 내에서 파이프를 이용하여 한 프로그램의 출력을 다른 프로그램으로 전달하는 아주 작은 프로그램들의 집합이었다. 이 방식은 새로운 기능을 테스트하기에 편하기 때문에 초기 개발 형태로 적당하다. 하지만 최근에는 이러한 많은 부분들이 내장되었고 (builtin), 핵심 기능 중 일부는 라이브러리 형태로 구성되었다. (libified) 즉 성능, 이식성, 코드 중복 방지 등을 위해 libgit.a 라이브러리 내에 포함되었다.


지금까지 인덱스(및 cache.h 내의 관련된 자료 구조)에 대해 살펴보았고, struct object에서 공통적인 구조를 상속받은 (즉 구조체의 첫 멤버가 struct object인) 여러 객체 타입들(블롭, 트리, 커밋, 태그)이 있다는 것을 알게되었다. (따라서 (struct object *) commit와 같은 캐스트 연산은 &commit --> object와 동일하며 이를 통해 객체 이름과 플래그 등을 얻을 수 있다.)


이제 이러한 정보들을 머리 속에 담아두기 위해 잠시 휴식을 취하는 것이 좋겠다.


다음 단계: 객체 이름에 익숙해지기. 커밋 이름 붙이기 부분을 읽어보자. 객체(리비전 만이 아니다!)에 이름을 붙이는 몇 가지 방법이 존재한다. 이 모든 방법들은 sha1_name.c 파일 내에서 처리한다. get_sha1() 함수를 대강 살펴보자. 대부분의 특별한 처리는 get_sha1_basic() 및 이와 비슷한 함수들이 수행한다.


이제 git의 기능 중에서 가장 라이브러리화 된 부분인 리비전 탐색기(revision walker)를 사용하는 법을 살펴보기로 하자.


기본적으로 초기 버전의 git-log 명령은 다음과 같은 쉘 스크립트였다:


  1. $ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
            LESS=-S ${PAGER:-less}

이것은 어떤 의미일까?


git-rev-list 명령은 리비전 탐색기의 원래 버전으로, 항상 리비전 목록을 표준 출력으로 내보낸다. 이 명령은 아직도 동작하며, 필요하다. 왜냐하면 대부분의 새로운 git 프로그램들은 git-rev-list를 사용하는 스크립트로부터 시작하기 때문이다.


git-rev-parse 명령은 더 이상 그리 중요하지 않다. 이 명령은 여러 스크립트에서 사용하는 서로 다른 명령들과 관련된 옵션을 필터링 하기 위해서만 사용되었다.


git-rev-list 명령에서 수행하던 대부분의 작업은 revision.c와 revision.h 파일에 포함되었다. 여러 옵션들은 리비전 탐색 방법 및 어떤 리비전을 탐색할 지 등에 대한 정보를 포함하는 rev_info 구조체 내에 포함된다.


원 래 git-rev-parse에서 수행하던 작업은 이제 리비전 정보와 리비전 탐색기에 관련된 공통 명령행 옵션들은 분석하는 setup_revision() 함수에서 처리한다. 이 정보는 나중에 참조하기 위해 rev_info 구조체에 저장한다. setup_revision() 함수를 호출한 뒤에 자신만의 명령행 옵션 분석을 수행할 수도 있다. 그 후에는 초기화를 수행하는 prepare_revision_walk() 함수를 호출해야 하며, 그리고 get_revision() 함수를 통해 각 커밋을 하나씩 얻을 수 있다.


만약 리비전 탐색 과정을 좀 더 자세하게 알아보고 싶다면 cmd_log() 함수의 최초 구현을 살펴보는 것이 좋다. 이를 살펴보려면 git show v1.3.0~155^2~4 명령을 수행하고 해당 함수를 찾아본다. (더 이상 setup_pager() 함수를 직접 호출할 필요가 없다는 것에 주의하라.)


최근에는 git-log 명령은 빌트인 명령으로 구현된다. 즉 git 명령 내에 포함되었다. 빌트인 명령은 소스에서 다음과 같이 존재한다:


  • cmd_<bla>라는 함수는 일반적으로 builtin-<bla>.c 파일 내에서 정의하며, builtin.h 파일에서 선언한다.
  • git.c 파일 내의 commands[] 배열 내에 해당 항목을 포함한다.
  • Makefile 내의 BUILTIN_OBJECTS 내에 해당 항목을 포함한다.

때 때로, 둘 이상의 빌트인 명령이 한 소스 파일 내에 포함되기도 한다. 예를 들어 cmd_whatchanged() 함수와 cmd_log() 함수는 모두 builtin-log.c 파일 내에 구현되어 있는데, 이는 이들이 많은 코드를 공유하기 때문이다. 이 경우 c 파일과 이름이 다른 명령은 Makefile 내의 BUILT_INS 내에 포함되어야 한다.


git-log 명령은 원래 스크립트로 구현되었을 때 보다 C로 구현한 버전이 더 복잡해 보이지만, 이는 보다 높은 유연성과 성능을 가져다 준다.


여기에서 다시 한 번 쉬어가는 기회를 가지는 것이 좋겠다.


세 번째 레슨은 코드 연구하기이다. 실제로 이것은 (기본 개념을 이해한 이후에) git가 어떻게 구성되어 있는지 파악할 수 있는 가장 좋은 방법이다.


따 라서, 여러분이 흥미를 가지고 있는 부분에 대해서 생각해 보자. 이를테면 "어떻게 객체 이름 만 가지고 블롭에 접근할 수 있을까?"와 같은 부분이다. 먼저 해야할 일은 해당 작업을 수행할 수 있는 git 명령을 찾는 것이다. 이 경우는 git-show 명령과 git-cat-file 명령이 모두 가능하다.


확실히 하기 위해 여기서는 git-cat-file의 경우를 살펴볼 것이다. 왜냐하면


git-cat-file은 plumbing하다(???)

git-cat-file은 최초 커밋 시에도 존재하였다. (cat-file.c의 형태로 약 20번 정도 수정되었으며, 빌트인이 도입된 후 builtin-cat-file.c 파일로 변경된 후에는 10번 이하로 수정되었다.)


따라서 builtin-cat-file.c 파일에서 cmd_cat_file() 함수를 찾아서 내용을 살펴보자.


  1.   git_config(git_default_config);
            if (argc != 3)
                    usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
            if (get_sha1(argv[2], sha1))
                    die("Not a valid object name %s", argv[2]);

뻔 한 내용들은 건너뛰기로 한다. 여기서 우리가 관심을 가지는 부분은 오직 get_sha1() 함수를 호출하는 부분이다. 여기서는 argv[2]를 객체 이름으로 해석하려고 시도하며, 만약 이 이름이 현재 디렉터리 내에 존재하는 객체를 가리킨다면 sha1 변수에 객체의 SHA1 해시값을 저장한다.


다음과 같은 두 가지 사항을 눈여겨 보자.


  • get_sha1() 함수는 성공 시 0을 반환한다. 어쩌한 이것은 몇몇 새로운 git 해커들에게는 놀라울지도 모르지만, 유닉스에는 여러 에러 상황의 경우 서로 다른 음수를 반환하고 성공 시 0을 반환하는 오랜 전통이 있다.
  • get_sha1() 함수의 원형에서 sha1 변수의 타입은 unsinged char *이지만, 실제로는 unsigned char[20]에 대한 포인터라고 가정한다. 이 변수는 주어진 커밋에 대한 160비트 크기의 SHA-1 해시값을 저장할 것이다. 주의할 점은 SHA-1 해시값이 unsigned char * 타입으로 넘겨지면 이는 바이너리 표현이며, char * 타입으로 넘겨질 때는 16진수 문자에 대한 ASCII 표현이라는 것이다.

코드를 살펴보다보면 이 두 가지 경우를 모두 보게될 것이다.


이제 핵심 부분이다:


  1.         case 0:
                    buf = read_object_with_reference(sha1, argv[1], &size, NULL);

위 의 코드가 블롭(실제로는 블롭 만이 아니라 모든 타입의 객체에 해당한다)을 읽는 부분이다. read_object_with_reference() 함수가 실제로 어떻게 동작하는지 알아보고 싶다면, 소스 코드에서 그 부분을 찾아서 (git 저장소에서 git grep read_object_with | grep ":[a-z]"와 같이 실행해보자) 읽어보면 된다.


이 결과를 사용하는 방법은 cmd_cat_file() 함수를 계속 읽어보면 다음과 같이 나온다:


  1.         write_or_die(1, buf, size);

어떤 때는 특정한 기능을 어디서 찾아야 할 지 모를 때가 있다. 이러한 때 중 많은 경우는 git log 명령의 출력을 찾아본 뒤 해당 커밋에 대해 git show 명령을 수행해 보는 것이 도움이 된다.


예 제: 여러분은 git-bundle 명령의 테스트 케이스가 있다는 것을 알고 있지만 어디에 있는지는 기억하지 못한다고 가정해 보자. (그렇다. git grep bundle t/ 명령을 이용하여 이를 알아낼 수 있지만, 이는 지금 여기서 얘기하고자 하는 바가 아니다.):


  1. $ git log --no-merges t/

페이저 (less) 상에서 "bundle"을 검색하고 몇 줄 뒤로 가보면 이것이 18449ab0... 커밋 내에 포함되어 있다는 것을 알 수 있다. 이제 객체 이름을 복사해서 다음과 같이 명령행에 붙여넣는다:


  1. $ git show 18449ab0

완료.


또 다른 예제: 스크립트를 빌트인으로 만들기 위해 해야하는 일들을 알아 보자:


  1. $ git log --no-merges --diff-filter=A builtin-*.c

여기서 보았듯이 git는 git 소스 자체를 살펴보기 위한 최고의 도구이다.



Git 용어#

alternate object database (대리 객체 데이터베이스)

대리 (alternate) 메커니즘을 통하여, 저장소는 "대리" 객체 데이터베이스라고 하는 다른 객체 데이터베이스에서 객체 데이터베이스의 일부를 상속받을 수 있다.


bare repository (bare 저장소)

bare 저장소는 일반적으로 .git라는 접미사가 붙은 디렉터리로 버전 컨트롤 중인 어떤 파일도 체크아웃하지 않은 상태를 말한다. 즉 git에서 사용하는 모든 관리용 및 제어용 파일들은 repository.git 디렉터리 바로 아래의 .git라는 숨겨진 디렉터리 내부에 존재하며, 다른 파일들은 존재하지 않는다. 보통 공개 저장소를 관리하는 사람들은 bare 저장소 형태로 공개한다.


blob object (블롭 객체)

타입이 없는(untyped) 객체로, 파일의 내용과 같은 것이다.


branch (브랜치)

"브랜치"는 개발이 활발하게 진행되는 노선(active line of development)이다. 브랜치의 가장 최근 커밋은 해당 브랜치의 팁(tip)이라고 부른다. 브랜치의 팁은 브랜치의 헤드가 참조한다. 브랜치의 헤드는 해당 브랜치에 추가적이 개발이 진행되면 앞으로 옮겨진다. 하나의 git 저장소는 여러 브랜치를 관리(track)할 수 있지만, 작업 트리는 그 중 하나에만 연관되며 (이를 :현재" 브랜치 혹은 "체크아웃한" 브랜치라고 한다) HEAD는 해당 브랜치를 가리킨다.


cache (캐시)

더 이상 사용하지 않는다. 대신 index라는 용어를 사용한다.


chain (체인)

객체의 목록(list)이다. 목록 내의 각 객체는 다음 객체(successor)에 대한 참조를 포함한다. (예를 들어 어떤 커밋의 다음 객체는 그 부모 중 하나가 될 수 있다.)


changeset (변경 세트)

BitKeeper/cvsps에서 "커밋"을 지칭하는 말이다. git는 변경 사항이 아닌, 상태를 저장하기 때문에 "변경 세트"라는 표현은 git에서는 올바르지 않다.


checkout (체크아웃)

작업 트리 전체 혹은 일부를 객체 데이터베이스에서 가져온 트리 객체 또는 블롭으로 갱신(update)하는 것을 말한다. 또한 인덱스를 업데이트하며 만약 작업 트리가 새로운 브랜치를 가리키게 되는 경우에는 HEAD도 갱신한다.


cherry-picking (체리피킹)

SCM 용어에서 "체리피킹"이라는 말은 일련의 변경 사항 (일반적으로 커밋) 중에서 일부를 선택하여 별도의 코드 베이스 상에 새로운 일련의 변경 사항으로 기록한다는 것을 뜻한다. GIT에서 이것은 "git cherry-pick" 명령을 통해 수행하며 기존의 커밋에서 변경한 내용들 중 일부를 추출하여 현재 브랜치의 팁에 새로운 커밋으로 기록한다.


clean (깨끗하다)

작업 트리가 깨끗하다는 것은 현재 헤드가 참조하는 리비전과 작업 트리가 같다는 것을 뜻한다. dirty의 설명도 참고하기 바란다.


commit (커밋)

명사로 쓰일 때: git 변경 이력 내의 한 지점. 프로젝트의 전체 변경 이력은 관련된 커밋들의 집합으로 표현한다. git에서 "커밋"이라는 용어는 때때로 다른 버전 관리 시스템에서 사용하는 "리비전" 또는 "버전"이라는 용어와 같은 의미로 사용된다. 또한 커밋 객체의 줄임말로도 쓰인다.


동사로 쓰일 때: 프로젝트 상태의 새로운 스냅샷을 git 저장소 내에 저장하는 작업을 뜻한다. 이는 인덱스의 현재 상태를 나타내는 새로운 커밋을 만들고 HEAD가 이 커밋을 가리키도록 이동시키는 것이다.


commit object (커밋 객체)

특정 리비전을 나타내는 정보, 이를테면 부모, 커밋한 사람, 수정한 사람, 날짜 및 저장된 리비전의 최상위 디렉터리에 대응하는 트리 객체들을 포함하는 객체이다.


core git (코어 git)

git의 기본 자료 구조와 도구들. 제한된 소스 코드 관리 도구 만을 제공한다.


DAG

Directed Acyclic Graph. (비순환 방향 그래프) 커밋 객체들은 비순환 방향 그래프를 이룬다. 커밋은 부모를 가지며 (directed) 커밋 객체의 그래프는 순환하지 않기 (acyclic, 동일한 객체에서 시작하고 끝나는 체인이 존재하지 않는다) 때문이다.


dangling object (댕글링 객체)

도달할 수 없는 객체. 심지어는 다른 도달할 수 없는 객체로부터도 이 객체에 도달할 수 없다. 댕글링 객체는 저장소 내의 어떤 참조나 객체로부터 참조되지 않는다.


detached HEAD (분리된 HEAD)

일반적으로 HEAD브랜치의 이름을 저장한다. 하지만 git에서는 임의의 커밋체크아웃할 수 있는데, 이 커밋은 특정 브랜치의 팁이 아닌 경우도 가능하며 이 때 HEAD가 분리되었다고 한다.


dircache

아주 오래된 용어이다. index를 참고하기 바란다.


directory (디렉터리)

"ls" 명령을 통해 얻을 수 있는 목록. :-)


dirty (변경되다)

작업 트리가 현재 브랜치에 커밋되지 않은 변경 사항을 포함한 경우에 "dirty"하다고 한다.


ent

몇몇 geek들이 사용하는 "tree-ish"에 대한 동의어. 자세한 설명은 http://en.wikipedia.org/wiki/Ent_(Middle-earth) 문서를 참조하기 바란다. 혼란을 주지 않도록 이 용어를 사용하지 않는 것이 좋다.


evil merge

어떠한 부모에도 속하지 않는 변경 사항을 만들어내는 머지

 

fast forward (고속 이동)

고속 이동이란 머지의 특별한 형태로, 어떤 리비전을 가지고 있을 때 이를 다른 브랜치의 변경 사항과 머지하는 경우에, 다른 브랜치의 모든 변경 사항들이 현재 리비전의 자손인 경우이다. 이 때 새로운 머지 커밋을 만드는 대신 현재 리비전을 다른 브랜치의 최신 리비전으로 갱신한다. 이는 원격 저장소추적하는 브랜치에서 자주 발생할 것이다.


fetch (내려받기)

브랜치를 머지하는 것은 원격 저장소의 헤드에 대한 참조(head ref)를 가져오고, 로컬 객체 데이터베이스에 없는 객체를 찾아서 이러한 객체들도 함께 가져오는 것이다. git-fetch(1) 문서를 살펴보기 바란다.


file system (파일 시스템)

리누스 토발즈는 원래 git가 사용자 공간 파일 시스템이 되도록 설계했다. 즉, 파일과 디렉토리를 포함할 수 있는 기반 구조인 것이다. 이 점이 git의 효율성과 속도를 보장해 준다.


git archive (git 아카이브)

(arch 사용자들을 위한) 저장소의 다른 이름이다.


grafts

grafts는 가상의(fake) 조상 커밋을 생성하여 두 개의 서로 다른 개발 경로를 하나로 합치도록 해 준다. 이러한 방식으로 git가 커밋을 생성할 때 저장된 것과 다른 부모 커밋들을 가장할 수 있게 해 준다. .git/info/grafts 파일을 통해 설정한다.


hash (해시)

git에 대해서는 객체 이름과 같은 의미이다.


head (헤드)

브랜치의 팁에 있는 커밋을 가리키는 이름있는 참조.


HEAD

현재 브랜치. 더 자세히는 일반적으로 HEAD를 통해 참조할 수 있는 트리의 상태에서 만들어내는 작업 트리. HEAD는 저장소 내의 헤드 중의 하나에 대한 참조이다. 단 분리된 HEAD를 사용하는 경우는 예외이며, 이 때는 임의의 커밋에 대한 참조가 될 수 있다.


head ref (헤드 참조)

헤드와 동일하다.


hook (훅)

여러 git 명령이 일반적으로 수행되는 과정에서, 개발자가 추가적인 기능이나 검사를 수행하도록 외부 스크립트를 실행할 수 있다. 일반적으로 훅을 이용하여 명령을 미리 검증하여 필요한 경우 이를 중지하거나, 작업이 끝난 후에 이를 알려주는 기능을 수행할 수 있다. 훅 스크립트는 $GIR_DIR/hooks/ 디렉터리에서 찾을 수 있으며, 이들을 실행 가능하게 만들면 바로 사용할 수 있다.


index (인덱스)

상태 정보를 포함한 파일의 모음. 각 파일의 내용은 객체로 저장된다. 인덱스는 작업 트리의 저장된 버전이다. 사실 인덱스는 머지 시에 사용되는 두번째 버전을 (심지어는 세번째 버전도) 포함할 수도 있다.


index entry (인덱스 항목)

인덱스 내에 저장된 특정 파일에 대한 정보. 만약 머지가 시작된 후 아직 끝나지 않았다면 (인덱스가 해당 파일의 여러 버전을 포함한 경우) 인덱스 항목을 머지에서 제외할 수 있다.


master

기본 개발 브랜치. git 저장소를 생성할 때마다 "master"라는 이름의 브랜치가 만들어지고, 활성 브랜치가 된다. 대부분의 경우 이 브랜치는 로컬의 개발 내용을 포함하지만, 이는 순전히 관례적인 것이며 필수 사항은 아니다.


merge (머지)

동사로 쓰일 때: 다른 브랜치(외부 저장소에 있을 수도 있음)의 내용을 현재 브랜치로 가져오는 것을 말한다. 머지해 오는(merged-in) 브랜치가 다른 저장소에 속한 경우에는, 먼저 원격 브랜치의 내용을 내려받고(fetch) 그 결과를 현재 브랜치에 머지한다. 이러한 내려받기와 머지의 조합을 pull이라고 한다. 머지는 브랜치가 나뉜 이후의 변경 사항들을 찾아낸 후, 이러한 모든 변경 사항들을 함께 적용하는 과정을 자동으로 수행하는 것이다. 변경 사항이 충돌하는 경우에는, 머지를 완료하기 위해 개발자가 직접 개입해야 할 수도 있다.


명사로 쓰일 때: 고속 이동이 아니라면, 머지가 성공적으로 끝난 후에 이에 대한 결과를 나타내는 새 커밋이 만들어지며, 머지된 브랜치들의 팁을 부모로 가지게 된다. 일러한 커밋을 "머지 커밋"이라고 부르며, 때로는 단순히 "머지"라고도 한다.


object (객체)

git 내의 저장 단위. 객체는 그 내용에 대한 SHA1 해시값으로 구분된다. 따라서, 객체는 변경될 수 없다.


object database (객체 데이터베이스)

"객체"의 집합을 저장하며, 각각의 객체객체 이름으로 구분한다. 객체는 보통 $GIR_DIR/objects/ 내에 존재한다.


object identifier (객체 식별자)

객체 이름과 동일하다.


object name (객체 이름)

객체에 대한 고유한 식별자. Secure Hash Algorithm 1을 이용한 객체의 내용에 대한 해시값이며, 보통 40글자의 16진수 인코딩으로 표현한다.


object type (객체 형식)

객체의 타입을 나타내는 "commit", "tree", "tag", "blob" 중의 하나


octopus

3개 이상의 브랜치머지하는 것을 말한다. 또한 영리한 포식 동물(?)을 말한다.


origin

기본 업스트림 저장소. 대부분의 프로젝트는 최소 하나 이상의 추적하는 업스트림 프로젝트를 가진다. origin은 기본적으로 이러한 목적으로 사용된다. 새 업스트림 업데이트는 "git branch -r" 명령을 통해 볼 수 있는 origin/<업스트림 브랜치 이름>의 원격 추적 브랜치로 내려받아지게 될 것이다.


pack (팩)

(공간을 절약하고 전송 효율을 높이기 위해) 하나의 파일로 압축된 여러 객체들의 집합


pack index (팩 인덱스)

의 내용을 효율적으로 접근할 수 있도록 해주는 팩 내의 객체에 대한 식별자 및 다른 정보의 목록.


parent (부모)

개발 경로 상에 논리적으로 바로 앞서 있는 것들을 포함하는 (비어있을 수도 있음) 커밋 객체.


pickaxe

pickaxe란 용어는 diffcore 루틴이 주어진 텍스트를 추가하거나 지운 변경 사항을 선택하도록 도와주는 옵션을 가리킨다. --pickaxe-all 옵션을 이용하면 특정한 텍스트 줄을 추가하거나 삭제한 전체 변경 세트를 볼 수 있다. git-diff(1) man 페이지를 살펴보기 바란다.


plumbing

코어 git에 대한 별칭


porcelain

코어 git에 의존하며 고수준의 접근 기능을 제공하는 프로그램 및 프로그램 스위트에 대한 별칭. porcelain은 plumbing에 비해 더 풍부한 SCM 인터페이스를 제공한다.


pull

브랜치를 pull한다는 것은 브랜치를 내려받아(fetch) 머지(merge)한다는 것을 의미한다. git-pull(1) man 페이지를 살펴보기 바란다.


push

브랜치를 push한다는 것은 원격 저장소에서 헤드 참조를 얻어와서, 해당 참조가 브랜치의 로컬 헤드 참조의 직접적인 조상인지를 검사하고 그렇다면 로컬 헤드 참조에서 도달 가능하고 원격 저장소에는 없는 모든 객체들을 원격 객체 데이터베이스로 보내고 원격 헤드 참조를 업데이트하는 것을 의미한다. 만약 원격 헤드가 로컬 헤드의 직접적인 조상이 아니라면 push는 실패한다.


reachable (도달 가능)

주어진 커밋의 모든 조상들은 해당 커밋에서 "도달 가능"하다고 말한다. 좀 더 일반적으로 표현하면, 어떤 객체가 다른 객체에서 도달 가능한 경우는 해당 객체가 만든 태그 혹은 부모나 트리에 대한 커밋 혹은 트리나 그를 포함한 블롭에 대한 트리 등의 체인을 통해 다른 객체에 닿을 수 있는 경우이다.


rebase

어떤 브랜치에서의 일련의 변경 사항들을 다른 베이스에 다시 적용하고 그 결과로 브랜치의 헤드를 리셋하는 것을 말한다.


ref (참조)

특정 객체를 나타내는 40바이트 16진수 표현의 SHA1 해시값 혹은 이름. 참조는 $GIT_DIR/refs/ 내에 저장될 수 있다.


reflog

reflog는 참조의 로컬 "변경 이력"을 보여준다. 다시 말하면 이 저장소의 3번째 이전 리비전이 무엇인지, 이 저장소의 현재 상태가 무엇인지를 알려줄 수 있다. 자세한 정보는 git-reflog(1) man 페이지를 살펴보기 바란다.


refspec

"refspec"은 fetchpush  명령 수행 시 원격 참조와 로컬 참조 간의 매핑을 나타내기 위해 사용된다. 이들은 <소스>:<목적지>의 형식으로 콜론(:)으로 구분되며 앞에 + 기호가 올 수도 있다. 예를 들어 git fetch $URL refs/heads/master:refs/heads/origin 명령은 "$URL에서 master 브랜치 헤드를 가져와서 로컬의 origin 브랜치 헤드에 저장"하라는 의미이다. git push $URL refs/heads/master:refs/heads/to-upstream 명령은 "로컬의 master 브랜치 헤드를 $URL의 to-upstream 브랜치로 공개"하라는 의미이다. git-push(1) man 페이지도 함께 살펴보기 바란다.


repository (저장소)

참조 및 해당 참조로부터 도달할 수 있는 모든 객체들을 포함하는 객체 데이터베이스의 모음이며 하나 이상의 porcelain에서 사용하는 메타데이터도 포함될 수 있다. 저장소는 대리 메커니즘을 통해 다른 저장소와 객체 데이터베이스를 공유할 수 있다.


resolve (충돌 해결)

자동 머지의 실패로 남아있는 것을 직접 수정하는 행위.


revision (리비전, 버전)

객체 데이터베이스에 저장된 파일과 디렉터리들의 특정 상태. 이는 커밋 객체를 통해 참조된다.


rewind

개발된 내용의 일부를 날려버리는 것. 즉, 헤드를 이전 리비전으로 할당하는 것을 뜻한다.


SCM

Source Code Management. 소스 코드 관리 도구


SHA1

객체 이름과 동일하다.


shallow repository (shallow 저장소)

shallow 저장소는 일부 커밋들의 부모가 사라진(cauterized away) 불완전한 변경 이력을 가진다. (다시 말해 git는 이러한 커밋들이 커밋 객체 상에는 부모에 대한 정보가 기록되어 있지만, 마치 부모가 없는 것처럼 보여준다.) 이것은 업스트림에 저장된 실제 변경 이력이 매우 크지만, 오직 프로젝트의 최신 변경 이력에만 관심이 있는 경우에 유용하게 사용될 수 있다. shallow 저장소는 git-clone(1) 명령에 --depth 옵션을 주어 만들 수 있으며. 변경 이력은 나중에 git-fetch(1) 명령을 통해 확장(deepen)될 수 있다.


symref

심볼릭 참조. SHA1 id 자체를 포함하는 대신 ref: refs/some/thing 형태이며, 참조되는 경우 재귀적으로 이 참조로 역참조된다. HEAD가 대표적인 symref의 예이다. 심볼릭 참조는 git-symbolic-ref(1) 명령을 통해 제어할 수 있다.


tag (태그)

태그 혹은 커밋 객체를 가리키는 참조. 헤드와 달리 태그는 커밋에 의해 변경되지 않는다. (태그 객체가 아닌) 태그는 $GIT_DIR/refs/tags 내에 저장된다. git 태그는 Lisp 태그(git에서는 객체 형식이라고 부를 수 있는)와는 아무런 관련이 없다. 태그는 거의 대부분 커밋 조상 체인 내의 특정 지점을 표시하기 위해 사용한다.


tag object (태그 객체)

다른 객체를 가리키는 참조를 포함하는 객체이며, 커밋 객체와 같이 메시지를 포함할 수 있다. 또한 (PGP) 서명을 포함할 수도 있으며, 이 경우에는 "서명된 태그 객체"라고 불린다.


topic branch (주제별 브랜치)

개발자가 개념적인 개발 경로를 구분하기 위해 사용하는 일반 git 브랜치. 브랜치는 매우 쉽고 가벼우므로, 잘 정의된 개념들을 포함하거나 서로 관련이 없는 변경 사항들을 모아둔 작은 브랜치를 여러 개 가지는 것이 때로는 더 좋은 경우가 있다.


tracking branch (추적 브랜치)

다른 저장소의 변경 사항들을 따라가기 위해 사용하는 일반 git 브랜치. 추적 브랜치는 직접적인 수정이나 그에 대한 로컬 커밋을 가져서는 안된다. 추적 브랜치는 보통 pull 명령 수행 시 오른쪽에 오는 참조로 구분할 수 있다. refspec 부분을 살펴보기 바란다.


tree (트리)

작업 트리거나, 의존적인 블롭트리 객체(작업 트리의 저장된 표현)를 포함한 트리 객체 중의 하나이다.


tree object (트리 객체)

파일 이름과 모드 및 그에 대한 블롭 또는 트리 객체에 대한 참조들의 목록을 포함하는 객체. 트리디렉터리와 동일하다.


tree-ish

커밋 객체, 트리 객체 혹은 태그, 커밋, 트리 객체를 가리키는 태그 객체 중의 하나를 가리키는 참조.


unmerged index (머지되지 않은 인덱스)

머지되지 않은 인덱스 항목을 포함하는 인덱스.


unreachable object (도달할 수 없는 객체)

브랜치태그 혹은 다른 어떤 객체에서도 도달할 수 없는 객체.


working tree (작업 트리)

실제로 체크아웃된 파일들의 트리. 작업 트리는 일반적으로 HEAD와 로컬에서 변경하였지만 아직 커밋되지 않은 사항들을 합친 것과 같다.



부록 A: Git 빠른 참조#

이 부분은 git의 주요 명령에 대한 간단한 요약이다. 앞 장에서 각 명령이 동작하는 방법에 대해 자세히 설명한다.


새 저장소 만들기#

tarball에서 만들기:


  1. $ tar xzf project.tar.gz
    $ cd project
    $ git init
    Initialized empty Git repository in .git/
    $ git add .
    $ git commit

원격 저장소에서 만들기:


  1. $ git clone git://example.com/pub/project.git
    $ cd project

브랜치 관리하기#

  1. $ git branch         # 이 저장소 내의 모든 로컬 브랜치의 목록을 보여줌
    $ git checkout test  # 작업 디렉터리를 "test" 브랜치로 전환
    $ git branch new     # 현재 HEAD에서 시작하는 "new" 브랜치 생성
    $ git branch -d new  # "new" 브랜치 삭제

현재 HEAD (기본값)가 아닌 다른 위치에서 시작하는 브랜치를 만들기:


  1. $ git branch new test    # "test"라는 이름의 브랜치에서 시작
    $ git branch new v2.6.15 # tag named v2.6.15 태그에서 시작
    $ git branch new HEAD^   # 가장 최근 커밋 바로 전의 커밋에서 시작
    $ git branch new HEAD^^  # 위의 커밋 바로 전의 커밋에서 시작
    $ git branch new test~10 # "test" 브랜치 팁의 10번째 앞의 커밋에서 시작

새 브랜치를 만듦과 동시에 해당 브랜치로 전환:


  1. $ git checkout -b new v2.6.15

clone 명령을 수행했던 저장소로부터 브랜치를 업데이트하거나 살펴보기:


  1. $ git fetch             # 업데이트
    $ git branch -r         # 목록 보기
      origin/master
      origin/next
      ...
    $ git checkout -b masterwork origin/master

다른 저장소에서 브랜치를 내려받아서 로컬 저장소에 다른 이름으로 저장:


  1. $ git fetch git://example.com/project.git theirbranch:mybranch
    $ git fetch git://example.com/project.git v2.6.15:mybranch

정기적으로 작업하는 저장소들의 목록 관리:


  1. $ git remote add example git://example.com/project.git
    $ git remote                    # 원격 저장소의 목록
    example
    origin
    $ git remote show example       # 자세한 정보
    * remote example
      URL: git://example.com/project.git
      Tracked remote branches
        master next ...
    $ git fetch example             # example로부터 브랜치 업데이트
    $ git branch -r                 # 모든 원격 브랜치의 목록

변경 이력 살펴보기#

  1. $ gitk                      # 변경 이력을 그래피컬하게 살펴보기
    $ git log                   # 모든 커밋의 목록
    $ git log src/              # src/ 를 변경한 모든 커밋의 목록
    $ git log v2.6.15..v2.6.16  # v2.6.15과 v2.6.16 사이의 모든 커밋의 목록
    $ git log master..test      # test 브랜치에는 있지만 master 브랜치에는 없는 모든 커밋의 목록
    $ git log test..master      # master 브랜치에는 있지만 test 브랜치에는 없는 모든 커밋의 목록
    $ git log test...master     # 둘 중 하나의 브랜치에만 속한 모든 커밋의 목록
    $ git log -S'foo()'         # "foo()"를 변경한 모든 커밋의 목록
    $ git log --since="2 weeks ago"
    $ git log -p                # 패치도 함께 보여줌
    $ git show                  # 가장 최근 커밋
    $ git diff v2.6.15..v2.6.16 # 두 태그 버전 간의 차이
    $ git diff v2.6.15..HEAD    # 현재 헤드와의 차이
    $ git grep "foo()"          # "foo()"를 포함하는 작업 트리 검색
    $ git grep v2.6.15 "foo()"  # "foo()"를 포함하는 예전 트리 검색
    $ git show v2.6.15:a.txt    # 예전 버전의 a.txt 파일 보기

regression 찾기:

  1. $ git bisect start
    $ git bisect bad                # 현재 버전은 문제 있음
    $ git bisect good v2.6.13-rc2   # 잘 동작하는 최근 버전
    Bisecting: 675 revisions left to test after this
                                    # 여기서 테스트를 수행:
    $ git bisect good               # 현재 버전이 문제가 없다면 수행
    $ git bisect bad                # 현재 버전이 문제가 있다면 수행
                                    # 완료될 때까지 반복

변경 사항 만들기#

git에게 사용자 정보를 제공:


  1. $ cat >>~/.gitconfig <<\EOF
    [user]
            name = 이름 입력
            email = 이메일 주소 입력
    EOF

다음 커밋에 포함할 파일 내용들을 선택하고 커밋:


  1. $ git add a.txt    # 업데이트된 파일
    $ git add b.txt    # 새 파일
    $ git rm c.txt     # 예전 파일
    $ git commit

혹은 커밋을 준비하고 수행하는 것을 한 번에 처리:


  1. $ git commit d.txt # d.txt의 최신 내용 만을 사용
    $ git commit -a    # 추적하는 모든 파일의 최신 내용을 사용

머지#

  1. $ git merge test   # "test"브랜치를 현재 브랜치로 머지
    $ git pull git://example.com/project.git master
                       # 원격 브랜치의 내용을 내려받아서 머지
    $ git pull . test  #git merge test와 동일

변경 사항을 공유하기#

패치 가져오기(import) 혹은 내보내기(export):


  1. $ git format-patch origin..HEAD # HEAD에는 속하고 origin에는 속하지 않는
                                    # 모든 커밋들에 대한 패치 생성
    $ git am mbox # "mbox" 메일함에서 패치 가져오기

다른 git 저장소에서 브랜치를 내려받고 현재 브랜치에 머지:


  1. $ git pull git://example.com/project.git theirbranch

내려받은 브랜치를 머지하기 전에 로컬 브랜치로 저장하기:


  1. $ git pull git://example.com/project.git theirbranch:mybranch

로컬 브랜치에 커밋을 수행한 후에, 해당 커밋으로 원격 브랜치를 업데이트:


  1. $ git push ssh://example.com/project.git mybranch:theirbranch

원격 브랜치와 로컬 브랜치의 이름이 모두 "test"인 경우:


  1. $ git push ssh://example.com/project.git test

자주 사용하는 원격 저장소를 위한 별칭:


  1. $ git remote add example ssh://example.com/project.git
    $ git push example test

저장소 관리#

손상 검사:


  1. $ git fsck

압축 및 사용하지 않는 객체(cruft?) 제거:


  1. $ git gc

부록 B: 이 설명서에 대한 노트 및 할 일 목록#

이 설명서는 계속 작업 중이다.


기본적인 요구 사항은:

  • 이 설명서는 기본적인 유닉스 명령행에 대한 내용을 이해하고 있지만 git에 대해서는 아무런 지식도 없는 (지적인) 사람이 처음부터 끝까지 차례대로 읽고 이해할 수 있어야 한다.
  • 가능하다면 각 절의 제목은 불필요한 지식을 요구하지 않는 선에서 설명하고자 하는 작업을 분명히 나타내야 한다. 예를 들어 "git-am 명령"보다는 "프로젝트로 패치 가져오기"가 낫다.

사람들이 중간의 모든 내용을 읽지 않고도 중요한 주제를 바로 접할 수 있도록 확실한 chapter dependency graph를 만드는 방법을 생각해 보자.


Documentation/ 디렉터리에서 남겨진 다른 주제들이 남아있는지 찾아보자. 특히:

  • HOWTO 문서
  • 기술적인 문서
  • 훅(hook)에 대한 설명
  • git(1) 내의 명령의 목록

이메일 아키이브를 살펴보고 다른 주제들이 남아있는지 찾아보자.


man 페이지들을 살펴보고 이 설명서에서 제공하는 것보다 더 많은 배경 지식이 필요한 것이 있는지 찾아보자.


임시 브랜치를 만드는 대신 연결이 끊어진 헤드를 제안하여 시작하는 것(?)을 단순화시켜보자.


좋은 예제들을 더 추가하자. 전체가 cookbook 형식의 예제 만으로 이루어진 장(chapter)를 만드는 것도 좋을 것 같다. 각 장의 마지막에 "고급 예제" 부분을 추가할 수도 있을 것이다.


적절한 곳에 git 용어에 대한 상호 참조를 추가하자.


shallow clone에 대한 문서화를 추가하자? 자세한 내용은 1.5.0 릴리스 노트의 제안을 살펴보자.


CVS, SVN 등의 다른 버전 관리 시스템과의 연동을 설명하는 부분을 추가하자. 또한 일련의 릴리스 tarball들을 가져오는(import) 하는 방법도 추가하자.


gitweb에 대한 자세한 설명을 추가하자.


plumbing을 이용하고 스크립트를 작성하는 장을 추가하자.


대리 메커니즘, clone -reference 등..


저장소 손상으로부터 복구하는 방법에 대해서는 다음 문서들을 살펴보기 바란다:

http://marc.theaimsgroup.com/?l=git&m=117263864820799&w=2

http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2

 

 

번역 용어표#

일반적인 용어들은 번역 용어집 참조할 것!

  • builtin : 빌트인
  • conflict : 충돌
  • diff : 차이점, (명령) diff
  • fast-forward : 고속 이동
  • linear : 선형(의)
  • local : 로컬
  • patch series : 패치 묶음. 일련의 패치
  • prune : (댕글링 객체 등을) 정리(하다)
  • shortcut : 줄임말


출처 : http://namhyung.springnote.com/pages/3132772

Posted by 1010
98..Etc/JMeter2010. 11. 26. 16:24
반응형

Apache JMeter를 활용한 테스트

Summary : Apache JMeter(이하 JMeter)를 다운 받아서 설치하는 과정에 대하여 살펴본다. 설치한 JMeter를 실행하여 TestPlan을 작성해보고 실행 결과를 확인하는 방법에 대하여 살펴본다.

Apache JMeter 소개

하나의 시스템을 개발하기 위해 무수히 많은 단위테스트를 진행하게 된다. 단위테스트를 얼마나 자주, 정교하게 하느냐는 프로젝트의 Quality와 성패가 좌우되는 경우가 많다. 그러나 단위 테스트만으로 모든 프로젝트의 성패가 좌우되는 것은 아니다. 프로젝트의 최종 결과는 각 단위 모듈들을 통합한 다음 통합 테스트할 때 결정된다. 특히 웹 환경과 같이 동시 접속자가 제한되어 있지 않은 환경에서는 더더욱 중요하다.

JMeter는 이와 같이 통합테스트를 진행할 때(물론 단위 테스트 용으로도 가능하다.) 유용하게 사용할 수 있는 순수 Java로 만들어진 테스트 프레임워크이다.

애플리케이션 하나를 오픈하기 전에 실 환경과 같이 테스트할 필요성이 있다. 특히 퍼포먼스에 있어 중요한 애플리케이션일수록 이에 대한 검증은 더더욱 중요하다. JMeter 이처럼 실환경과 같은 테스트가 가능하도록 해주는 Stess 테스트 툴이다. 계속해서 진행되는 강좌를 통하여 JMeter가 어떤 역할을 하는 녀석인지 알 수 있을 것이다.

JMeter에 대한 더 자세한 소개는
JMeter Introduction 문서
를 참조하기 바란다.

Apache JMeter 다운 로드 및 설치

JMeter를 설치는 JMeter 바이너리 파일을 다운 받은 다음 실행파일을 시작하면 바로 실행해 볼 수 있다. JMeter 설치에서부터 실행까지 순차적으로 살펴보자.

  • JMeter 다운로드 URL
    에서 최신 소스를 다운받는다.

  • 다운받은 JMeter 바이너리 압축파일을 각자 원하는 위치에 압축을 푼다. 압축을 푼 디렉토리를 JMETER_HOME 디렉토리 생각하고 강좌를 진행하겠다.
  • JMETER_HOME/bin 디렉토리의 jmeter.bat파일(Windows 시스템일 경우)을 실행한다. 잠시후에 아래 화면과 같이 SWING으로 만들어져 있는 JMeter화면을 볼 수 있을 것이다. 만약 정상적으로 실행되지 않는다면 JDK가 설치되어 있는지 확인해 보고 JAVA_HOME/bin디렉토리가 Path로 설정되어 있는지 확인해 본다.

 

JMeter에 설치에 대한 더 자세한 설명은
JMeter Getting Started 문서
를 참조하기 바란다.

JMeter를 이용한 Test Plan 작성

JMeter를 이용하여 Test Plan을 작성하기 위해서는 먼저 테스트 시나리오가 존재해야 한다. 애플리케이션 요구사항에 따라 작성된 테스트 시나리오에 따라 Test Plan을 작성한 다음 Test Plan을 실행하고 결과를 확인하면 된다.

본 강좌에서 테스트할 시나리오는 다음과 같다. 자바지기 사이트의 메인 페이지에 20명의 개발자가 동시에 접속할 경우의 퍼포먼스를 측정하고 싶다. 또한 20명의 접속자가 장시간 지속적으로 접근한다는 가정하에서 테스트를 진행해보자. 이 강좌는 JMeter를 실행해보는 것에 의의를 가지므로 테스트 시나리오는 최대한 간단하게 작성하였다. 이 테스트 시나리오는 만족하도록 JMeter의 Test Plan을 만들어 보자.

  • JMeter 초기화면의 Test Plan 오른쪽 클릭 < Add < Thread Group을 선택한다. Thread Group은 Test Plan에서 사용할 동시접속자수와 반복 횟수등을 지정할 수 있다. 테스트 시나리오에서 테스트할 동시 접속자수는 20명이였으므로 Thread수는 20, Lamp Up 시간은 10으로 설정한다. 반복 횟수는 시간적인 제한이 없었으므로 무한정 반복하는 것으로 설정한다.

 
  • Thead Group에서 오른쪽 클릭 < Add < Sampler < HTTP Request를 선택한다. 테스트 시나리오가 HTTP 프로토콜을 사용하고 있는 자바지기 사이트이므로 Thread Group에 HTTP Request Sampler를 추가하였다. 테스트할 Server 주소는 www.javajigi.net이며 path는 index.html을 입력한다.

 
  • 마지막으로 추가할 구성원은 테스트를 진행한 결과를 볼 수 있는 화면이다. Thead Group에서 오른쪽 클릭 < Add < Listener < Graph Results를 선택한다. Graph Results는 테스트한 결과를 Graph로 확인할 수 있는 Listener이다.

 

이상으로 JMeter를 이용하여 최소한의 테스트를 진행해 볼 수 있는 Test Plan을 작성하였다. 이 강좌의 주목적이 JMeter를 소개하는 것이기 때문에 가능한한 최소화하여 설명하였다. 좀 더 복잡하고 정교한 Test Plan을 작성하는 방법에 대해서는 JMeter에서 제공하는 User Manual과 자카르타 서울 프로젝트의 User Manual 번역 문서를 참고하기 바란다. 또한 앞에서 사용한 Thread Group, Samples등의 설명 및 용도에 대해서도 User Manual 문서를 통하여 익히기 바란다.

JMeter는 HTTP, FTP, Database, Webservice, LDAP, TCP 등에 대한 기본적인 테스트가 가능하도록 클라이언트 모듈을 제공하고 있다. 물론 이 외에 필요로하는 테스트 기능이 필요하다면 새로운 테스트 기능을 만들어 추가하는 것이 가능하다. JMeter에서 기본적으로 제공하고 있는 각각의 프로토콜에 대한 Test Plan을 작성하는 방법에 대한 자세한 방법은
JMeter User Manual 문서
를 참조하기 바란다. 영어에 거부감이 있는 개발자라면
자카르타 서울 프로젝트
에서 한글로 번역한
JMeter User Manual 문서
를 참조하기 바란다.

작성한 Test Plan 실행

앞절에서 작성한 Test Plan을 실행하는 방법은 간단하다. JMeter 메뉴의 Run에서 Start를 실행하면 테스트가 진행된다. 진행되는 테스트의 결과는 Graph Results Listener를 통하여 확인할 수 있다.

 

지금까지 JMeter를 설치한 다음 Test Plan을 작성하고 실행하는 과정에 대하여 살펴보았다. JMeter를 이용하여 실환경과 비슷한 가상의 테스트를 진행할 수 있다는 것이 매력적이지 않은가? 다음 강좌에서는 JMeter에서 기본적으로 지원하지 않는 기능들을 테스트하기 위하여 새로운 테스트 파일을 만드는 방법에 대하여 살펴본다.

강좌에 대하여

작성자 : 박재성
작성일 : 2005년 2월 20일

문서이력 :

  • 2005년 2월 20일 박재성 문서 최초 생성

참고 자료


출처 : http://www.javajigi.net/pages/viewpage.action?pageId=183

Posted by 1010
52.Apache Project &.../Maven2010. 11. 25. 17:39
반응형
Install Maven
  1. Maven (version2.0.5)에서 구하는 파일 : / / Iwdbackup / iwd_apps / Maven 또는 http://maven.apache.org/download.html Obtain Maven (version2.0.5) from file://Iwdbackup/iwd_apps/Maven or http://maven.apache.org/download.html
  2. 지퍼 maven - 당신은 Maven을 설치하고자하는 디렉토리로 2.0.5 - bin.zip. Unzip maven-2.0.5-bin.zip to the directory you wish to install Maven.
  3. 예를 들어, 귀하의 경로에 bin 디렉토리를 추가합니다. Add the bin directory to your path, eg. 설정의 PATH = "에 C : \ 프로그램 파일 \ maven - 2.0.5 \ Bin입니다"; % 경로 % SET PATH="C:\Program Files\maven-2.0.5\bin";%PATH%
  4. JAVA_HOME은 JDK는의 위치로 설정되어 있는지 확인합니다. Make sure that JAVA_HOME is set to the location of your JDK.
  5. 실행 mvn - 그건가 올바르게 설치되었는지 확인하는 버전입니다. Run mvn --version to verify that it is correctly installed.

참고 내부 Intelliware 리포지 토리에 수정 maven의 settings.xml. Modify maven's settings.xml to reference internal Intelliware repositories.

  1. <maven에 덮어쓰기 표준 settings.xml이 하나 directory> / conf의 설치 : settings.xml. Overwrite a standard settings.xml in <maven install directory>/conf with this one: settings.xml.

를 사용하여 Maven은 응용 프로그램을 만드는 Use Maven to create an application

Maven은 신속하게 프로젝트를 여러 종류를 만들 수 있도록 원형 메커니즘을 제공합니다. Maven provides an archetype mechanism to allow you to quickly create different kinds of projects. 몇 가지 일반 전형과 같습니다 : Some common archetypes are:

  • 웹 어플 리케이션을위한 maven - 원형 - webapp maven-archetype-webapp for web applications
  • maven - 원형 - 간단한 - 퀵 스타트 원형 간단한 프로젝트를 생성하는 maven-archetype-simple - quick start archetype to generate simple project
  • maven - 원형 사이트 원형 - 귀하의 프로젝트를위한 사이트 docummentation을 만들 수 maven-archetype-site archetype - to create a site docummentation for your project

리스트에 대해보다 포괄적인 걸 볼 Codehaus 전형 목록 For a more comprehensive list see Codehaus's Archetypes List
당신은 또한 자신의 전형을 만들 수 있습니다. You can also create your own archetypes. 이렇게하려면의 maven 참조 전형 만들기 가이드 To do so, refer to maven's Guide to Creating Archetypes

예 : 웹 응용 프로그램 만들기 Example: Creating a web application

당신의 프로젝트가 생성되고 싶어 어디 디렉토리에 다음 명령을 실행 : Run a following command in the directory where you want your project to be created:

mvn의 원형 : 작성 mvn archetype:create
     = ca.intelliware - DgroupId -DgroupId=ca.intelliware
     = simpleWebApp - DartifactId -DartifactId=simpleWebApp
     - DarchetypeArtifactId = maven - 원형 - webapp -DarchetypeArtifactId=maven-archetype-webapp

그 결과는 : The result is:
simpleWebApp simpleWebApp
+ - pom.xml +-- pom.xml
+ - src에 / +-- src/
    + - 메인 / +-- main/
         + - 자원 / +-- resources/
         + - webapp / +-- webapp/
            + - index.jsp +-- index.jsp
            + - 웹 inf를 / +-- WEB-INF/
                + - web.xml +-- web.xml

당신이 (또는 다른) 명령을 실행하여 처음, Maven은 명령을 수행하기 위해 필요로하는 모든 플러그인과 관련된 종속성을 다운로드해야합니다. The first time you run this (or any other) command, Maven will need to download all the plugins and related dependencies it needs to fulfill the command. 그것을 실행할 시간을 먼저 그러므로 당신은 언제 시간이 몇 가지가 있습니다 가져가라. Therefore it might take some time when you run it for the first time.

Maven이 의존성을 관리하기 위해 저장소의 개념을 사용합니다. Maven uses the concept of repositories to manage your dependencies. 당신은 maven을 다운로드했을 때 그것을 설치, 그것은 귀하의 지역 저장소를 만듭니다. When you download maven and install it, it creates your local repository. 언제부터 우리가 말한 "maven 다운로드 종속성 그들을"도착이 말은 우리가 maven repo의 로컬 저장소에 복사 그들을. When we say "maven downloads dependencies" we mean it gets them from the maven repo and copies them to your local repository.

예 : 간단한 응용 프로그램 만들기 : Example: Creating a simple application:

mvn의 원형 : 생성해주 - DgroupId = ca.intelliware - DartifactId = simpleWebApp mvn archetype:create -DgroupId=ca.intelliware -DartifactId=simpleWebApp

pom.xml의 이해가 Making sense of pom.xml

당신은 당신의 주 프로젝트 디렉터리에 pom.xml 파일을 만들어 프로젝트를, 통지 만들 maven 사용하신 경우. When you've used maven to create your project, notice that it created a pom.xml file in your main project directory. 이 파일은 (모델 객체 arcronym의 프로젝트) 프로젝트에 대한 정보의 중요한 조각을 포함하는 모든과 프로젝트를위한 찾는 것도 관련하여 쇼핑 - 그만 - 하나입니다 본질적으로. This file (arcronym of Project Object Model) contains every important piece of information about your project and is essentially one-stop-shopping for finding anything related to your project. 그것에 대해 자세히 알아보려면 다음 사이트를 방문 폼은 소개로 . To learn more about it, visit Introduction to the POM .

Intelliware에 대한 샘플 폼은 : Intelliware Maven 인프라 표시에 대한 구성을 포함하는 폼은 파일의 예를 들면. For an example of a POM file that includes configuration for the Intelliware Maven infrastructure see: Sample POM for Intelliware.

롤에 Maven Maven on a roll

우리는 우리의 프로젝트가 만든 이제, 우리는 코드에 추가하고 Maven 속임수의 완전히 새로운 가방을 이용하고 있습니다. Now that we have our project created, we can add in our code and utilize a whole new bag of Maven tricks.

파일을 프로젝트의 pom.xml과 같은 디렉토리에서 실행해야합니다 모든 Maven 명령을해야합니다. Note all Maven commands must be run in the same directory as the project's pom.xml file.

  • mvn 시험 : 응용 프로그램에 대한 실행 JUnit 테스트. mvn test: Runs the JUnit tests for the application.
  • mvn 패키지 :. 우리의 프로젝트에서 WAR 파일을 생성합니다. mvn package: Creates a .war file from our project.
  • 설치 mvn가 : 우리의 프로젝트를 추가 전쟁을 저장소에 필요한 경우 종속성으로 사용하기 위해.. mvn install: Adds our project .war to the repository for use as a dependency if needed.
  • mvn 사이트 : 생성하는 프로젝트 웹사이트. mvn site: Generates the project website.
  • mvn 청소 : 청소하기 출력이 대상 디렉토리에 만들었습니다. mvn clean: Cleans the output created in the target directory.
  • mvn 일식 : 일식 : 생성 이클립스 프로젝트 파일입니다. mvn eclipse:eclipse: Generates an Eclipse project file.

어디, "테스트", "패키지"및 Maven 라이프 사이클 단계는 "사이트"및 "일식"입니다 Maven 플러그인있는 "설치". Where, "test", "package", and "install" are Maven lifecycle phases, and "site" and "eclipse" are Maven plugins.

웹서버 배포 및 실행 Deploy to WebServer and Run

빠르고 쉽게 The Quick and Easy Way

우리는 지금 부두 웹 서버에 응용 프로그램을 배포할 수 있으며, 입력하여 그것을 실행하십시오 : We can now deploy the application to the Jetty webserver and run it by entering:

mvn의 org.mortbay.jetty가 : maven - 부두 - 플러그인 : 실행 mvn org.mortbay.jetty:maven-jetty-plugin:run
우리는 다음 페이지에 충돌하고 행동하는 우리의 응용 프로그램을 참조하십시오 : We can then hit the page and see our application in action:

http://localhost:8080/simpleWebApp/ http://localhost:8080/simpleWebApp/

pom.xml에서 웹서버를 정의 Defining the Webserver in your pom.xml

지정 프로젝트의 pom.xml에 귀하의 웹 서버 (예를 들면 아래와 부두입니다)에 대한 플러그인 : Specify the plugin for your web server in the project's pom.xml (the example below is for Jetty):

<plugin> <plugin> 
    <groupId>의 org.mortbay.jetty의 </ groupId> <groupId>org.mortbay.jetty</groupId> 
    <artifactId> maven은 - 부두 - 플러그인 </ artifactId> <artifactId>maven-jetty-plugin</artifactId>
<이 / 플러그인> </plugin>
우리는 실행하여 다음 시작하는 웹 애플 리케이션을 수 있습니다 : We can then startup the web app by running:
mvn 부두 : 실행 mvn jetty:run


--------------------

Maven 설치하기
  1. Obtain Maven (version2.0.5) from file://Iwdbackup/iwd_apps/Maven or http://maven.apache.org/download.html Maven (version2.0.5)에서 구하는 파일 : / / Iwdbackup / iwd_apps / Maven 또는 http://maven.apache.org/download.html
  2. Unzip maven-2.0.5-bin.zip to the directory you wish to install Maven. 지퍼 maven - 당신은 Maven을 설치하고자하는 디렉토리로 2.0.5 - bin.zip.
  3. Add the bin directory to your path, eg. 예를 들어, 귀하의 경로에 bin 디렉토리를 추가합니다. SET PATH="C:\Program Files\maven-2.0.5\bin";%PATH% 설정의 PATH = "에 C : \ 프로그램 파일 \ maven - 2.0.5 \ Bin입니다"; % 경로 %
  4. Make sure that JAVA_HOME is set to the location of your JDK. JAVA_HOME은 JDK는의 위치로 설정되어 있는지 확인합니다.
  5. Run mvn --version to verify that it is correctly installed. 실행 mvn - 그건가 올바르게 설치되었는지 확인하는 버전입니다.

Modify maven's settings.xml to reference internal Intelliware repositories. 참고 내부 Intelliware 리포지 토리에 수정 maven의 settings.xml.

  1. Overwrite a standard settings.xml in <maven install directory>/conf with this one: settings.xml. <maven에 덮어쓰기 표준 settings.xml이 하나 directory> / conf의 설치 : settings.xml.

Use Maven to create an application 를 사용하여 Maven은 응용 프로그램을 만드는

Maven provides an archetype mechanism to allow you to quickly create different kinds of projects. Maven은 신속하게 프로젝트를 여러 종류를 만들 수 있도록 원형 메커니즘을 제공합니다. Some common archetypes are: 몇 가지 일반 전형과 같습니다 :

  • maven-archetype-webapp for web applications 웹 어플 리케이션을위한 maven - 원형 - webapp
  • maven-archetype-simple - quick start archetype to generate simple project maven - 원형 - 간단한 - 퀵 스타트 원형 간단한 프로젝트를 생성하는
  • maven-archetype-site archetype - to create a site docummentation for your project maven - 원형 사이트 원형 - 귀하의 프로젝트를위한 사이트 docummentation을 만들 수

For a more comprehensive list see Codehaus's Archetypes List 리스트에 대해보다 포괄적인 걸 볼 Codehaus 전형 목록
You can also create your own archetypes. 당신은 또한 자신의 전형을 만들 수 있습니다. To do so, refer to maven's Guide to Creating Archetypes 이렇게하려면의 maven 참조 전형 만들기 가이드

Example: Creating a web application 예 : 웹 응용 프로그램 만들기

Run a following command in the directory where you want your project to be created: 당신의 프로젝트가 생성되고 싶어 어디 디렉토리에 다음 명령을 실행 :

mvn archetype:create mvn의 원형 : 작성
     -DgroupId=ca.intelliware = ca.intelliware - DgroupId
     -DartifactId=simpleWebApp = simpleWebApp - DartifactId
     -DarchetypeArtifactId=maven-archetype-webapp - DarchetypeArtifactId = maven - 원형 - webapp

The result is: 그 결과는 :
simpleWebApp simpleWebApp
+-- pom.xml + - pom.xml
+-- src/ + - src에 /
    +-- main/ + - 메인 /
         +-- resources/ + - 자원 /
         +-- webapp/ + - webapp /
            +-- index.jsp + - index.jsp
            +-- WEB-INF/ + - 웹 inf를 /
                +-- web.xml + - web.xml

The first time you run this (or any other) command, Maven will need to download all the plugins and related dependencies it needs to fulfill the command. 당신이 (또는 다른) 명령을 실행하여 처음, Maven은 명령을 수행하기 위해 필요로하는 모든 플러그인과 관련된 종속성을 다운로드해야합니다. Therefore it might take some time when you run it for the first time. 그것을 실행할 시간을 먼저 그러므로 당신은 언제 시간이 몇 가지가 있습니다 가져가라.

Maven uses the concept of repositories to manage your dependencies. Maven이 의존성을 관리하기 위해 저장소의 개념을 사용합니다. When you download maven and install it, it creates your local repository. 당신은 maven을 다운로드했을 때 그것을 설치, 그것은 귀하의 지역 저장소를 만듭니다. When we say "maven downloads dependencies" we mean it gets them from the maven repo and copies them to your local repository. 언제부터 우리가 말한 "maven 다운로드 종속성 그들을"도착이 말은 우리가 maven repo의 로컬 저장소에 복사 그들을.

Example: Creating a simple application: 예 : 간단한 응용 프로그램 만들기 :

mvn archetype:create -DgroupId=ca.intelliware -DartifactId=simpleWebApp mvn의 원형 : 생성해주 - DgroupId = ca.intelliware - DartifactId = simpleWebApp

Making sense of pom.xml pom.xml의 이해가

When you've used maven to create your project, notice that it created a pom.xml file in your main project directory. 당신은 당신의 주 프로젝트 디렉터리에 pom.xml 파일을 만들어 프로젝트를, 통지 만들 maven 사용하신 경우. This file (arcronym of Project Object Model) contains every important piece of information about your project and is essentially one-stop-shopping for finding anything related to your project. 이 파일은 (모델 객체 arcronym의 프로젝트) 프로젝트에 대한 정보의 중요한 조각을 포함하는 모든과 프로젝트를위한 찾는 것도 관련하여 쇼핑 - 그만 - 하나입니다 본질적으로. To learn more about it, visit Introduction to the POM . 그것에 대해 자세히 알아보려면 다음 사이트를 방문 폼은 소개로 .

For an example of a POM file that includes configuration for the Intelliware Maven infrastructure see: Sample POM for Intelliware. Intelliware에 대한 샘플 폼은 : Intelliware Maven 인프라 표시에 대한 구성을 포함하는 폼은 파일의 예를 들면.

Maven on a roll 롤에 Maven

Now that we have our project created, we can add in our code and utilize a whole new bag of Maven tricks. 우리는 우리의 프로젝트가 만든 이제, 우리는 코드에 추가하고 Maven 속임수의 완전히 새로운 가방을 이용하고 있습니다.

Note all Maven commands must be run in the same directory as the project's pom.xml file. 파일을 프로젝트의 pom.xml과 같은 디렉토리에서 실행해야합니다 모든 Maven 명령을해야합니다.

  • mvn test: Runs the JUnit tests for the application. mvn 시험 : 응용 프로그램에 대한 실행 JUnit 테스트.
  • mvn package: Creates a .war file from our project. mvn 패키지 :. 우리의 프로젝트에서 WAR 파일을 생성합니다.
  • mvn install: Adds our project .war to the repository for use as a dependency if needed. 설치 mvn가 : 우리의 프로젝트를 추가 전쟁을 저장소에 필요한 경우 종속성으로 사용하기 위해..
  • mvn site: Generates the project website. mvn 사이트 : 생성하는 프로젝트 웹사이트.
  • mvn clean: Cleans the output created in the target directory. mvn 청소 : 청소하기 출력이 대상 디렉토리에 만들었습니다.
  • mvn eclipse:eclipse: Generates an Eclipse project file. mvn 일식 : 일식 : 생성 이클립스 프로젝트 파일입니다.

Where, "test", "package", and "install" are Maven lifecycle phases, and "site" and "eclipse" are Maven plugins. 어디, "테스트", "패키지"및 Maven 라이프 사이클 단계는 "사이트"및 "일식"입니다 Maven 플러그인있는 "설치".

Deploy to WebServer and Run 웹서버 배포 및 실행

The Quick and Easy Way 빠르고 쉽게

We can now deploy the application to the Jetty webserver and run it by entering: 우리는 지금 부두 웹 서버에 응용 프로그램을 배포할 수 있으며, 입력하여 그것을 실행하십시오 :

mvn org.mortbay.jetty:maven-jetty-plugin:run mvn의 org.mortbay.jetty가 : maven - 부두 - 플러그인 : 실행
We can then hit the page and see our application in action: 우리는 다음 페이지에 충돌하고 행동하는 우리의 응용 프로그램을 참조하십시오 :

http://localhost:8080/simpleWebApp/ http://localhost:8080/simpleWebApp/

Defining the Webserver in your pom.xml pom.xml에서 웹서버를 정의

Specify the plugin for your web server in the project's pom.xml (the example below is for Jetty): 지정 프로젝트의 pom.xml에 귀하의 웹 서버 (예를 들면 아래와 부두입니다)에 대한 플러그인 :

<plugin> <plugin> 
    <groupId>org.mortbay.jetty</groupId> <groupId>의 org.mortbay.jetty의 </ groupId> 
    <artifactId>maven-jetty-plugin</artifactId> <artifactId> maven은 - 부두 - 플러그인 </ artifactId>
</plugin> <이 / 플러그인>
We can then startup the web app by running: 우리는 실행하여 다음 시작하는 웹 애플 리케이션을 수 있습니다 :
Posted by 1010