{
  "title": "버전 관리 (Git)",
  "excerpt": "버전 관리를 _제대로_ 사용하는 방법과, 재앙으로부터 당신을 구하고, 다른 사람들과 협업하며, 문제가 있는 변경사항을 빠르게 찾아 격리하는 데 활용하는 방법. 더 이상 `rm -rf; git clone`은 없습니다. 더 이상 병합 충돌도 없습니다 (적어도 훨씬 줄어듭니다). 더 이상 주석 처리된 거대한 코드 블록도 없습니다. 더 이상 코드를 망가뜨린 원인을 찾는 방법에 대해 고민할 필요도 없습니다. 더 이상 \"이런, 작동하던 코드를 삭제했나요?!\"도 없습니다.",
  "content_html": "<p>버전 관리 시스템(VCS)은 소스 코드(또는 다른 파일 및 폴더 모음)의 변경사항을 추적하는 데 사용되는 도구입니다. 이름에서 알 수 있듯이, 이러한 도구는 변경 이력을 유지하는 데 도움이 되며, 나아가 협업을 촉진합니다. VCS는 폴더와 그 내용의 변경사항을 일련의 스냅샷으로 추적하며, 각 스냅샷은 최상위 디렉토리 내의 파일/폴더의 전체 상태를 캡슐화합니다. VCS는 또한 각 스냅샷을 누가 생성했는지, 각 스냅샷과 관련된 메시지 등의 메타데이터를 유지합니다.</p>  <p>버전 관리가 왜 유용할까요? 혼자 작업할 때도 프로젝트의 이전 스냅샷을 볼 수 있고, 특정 변경사항이 왜 이루어졌는지 로그를 유지하며, 병렬 개발 브랜치에서 작업하는 등 많은 것을 할 수 있습니다. 다른 사람들과 작업할 때는 다른 사람들이 무엇을 변경했는지 확인하고 동시 개발에서 충돌을 해결하는 데 매우 귀중한 도구입니다.</p>  <p>현대의 VCS는 또한 다음과 같은 질문에 쉽게 (그리고 종종 자동으로) 답할 수 있게 해줍니다:</p>  <ul> <li>이 모듈을 누가 작성했나요?</li> <li>이 특정 파일의 이 특정 줄은 언제 편집되었나요? 누가 편집했나요? 왜 편집되었나요?</li> <li>지난 1000개의 리비전에서 특정 단위 테스트가 언제/왜 작동을 멈췄나요?</li> </ul>  <p>다른 VCS도 존재하지만, <strong>Git</strong>은 버전 관리의 사실상 표준입니다. 이 <a href=\"https://xkcd.com/1597/\">XKCD 만화</a>는 Git의 평판을 잘 보여줍니다:</p>  <p><img src=\"https://imgs.xkcd.com/comics/git.png\" alt=\"xkcd 1597\"></p>  <p>Git의 인터페이스는 추상화가 새는(leaky abstraction) 구조이기 때문에, Git을 하향식으로 학습하는 것(인터페이스/명령줄 인터페이스부터 시작)은 많은 혼란을 야기할 수 있습니다. 몇 가지 명령어를 암기하고 그것들을 마법의 주문처럼 생각하며, 문제가 생길 때마다 위 만화의 접근 방식을 따르는 것이 가능합니다.</p>  <p>Git이 확실히 못생긴 인터페이스를 가지고 있지만, 그 기본 설계와 아이디어는 아름답습니다. 못생긴 인터페이스는 <em>암기</em>해야 하지만, 아름다운 설계는 <em>이해</em>할 수 있습니다. 이러한 이유로, 우리는 Git의 상향식 설명을 제공하며, 데이터 모델부터 시작하여 나중에 명령줄 인터페이스를 다룹니다. 데이터 모델을 이해하면, 명령어들이 기본 데이터 모델을 어떻게 조작하는지의 관점에서 더 잘 이해될 수 있습니다.</p>  <h1>Git의 데이터 모델</h1>  <p>버전 관리에 대해 취할 수 있는 많은 임시 접근 방식이 있습니다. Git은 이력 유지, 브랜치 지원, 협업 활성화와 같은 버전 관리의 모든 좋은 기능을 가능하게 하는 잘 설계된 모델을 가지고 있습니다.</p>  <h2>스냅샷</h2>  <p>Git은 최상위 디렉토리 내의 파일 및 폴더 모음의 이력을 일련의 스냅샷으로 모델링합니다. Git 용어로, 파일은 \"blob\"이라고 불리며, 단순히 바이트의 묶음입니다. 디렉토리는 \"tree\"라고 불리며, 이름을 blob 또는 tree에 매핑합니다(따라서 디렉토리는 다른 디렉토리를 포함할 수 있습니다). 스냅샷은 추적되는 최상위 tree입니다. 예를 들어, 다음과 같은 tree를 가질 수 있습니다:</p>  <pre><code>&lt;root&gt; (tree)\n|\n+- foo (tree)\n|  |\n|  + bar.txt (blob, contents = \"hello world\")\n|\n+- baz.txt (blob, contents = \"git is wonderful\")\n</code></pre>  <p>최상위 tree는 두 개의 요소를 포함합니다: tree \"foo\"(그 자체로 하나의 요소인 blob \"bar.txt\"를 포함) 및 blob \"baz.txt\".</p>  <h2>이력 모델링: 스냅샷 연결하기</h2>  <p>버전 관리 시스템은 스냅샷을 어떻게 연결해야 할까요? 한 가지 간단한 모델은 선형 이력을 갖는 것입니다. 이력은 시간 순서대로 스냅샷의 목록이 될 것입니다. 여러 이유로, Git은 이와 같은 간단한 모델을 사용하지 않습니다.</p>  <p>Git에서 이력은 스냅샷의 방향성 비순환 그래프(DAG)입니다. 이것이 멋진 수학 용어처럼 들릴 수 있지만, 겁먹지 마세요. 이것이 의미하는 것은 Git의 각 스냅샷이 \"부모\"의 집합, 즉 그것에 선행하는 스냅샷을 참조한다는 것입니다. 예를 들어 두 개의 병렬 개발 브랜치를 결합(병합)하는 경우와 같이 스냅샷이 여러 부모로부터 파생될 수 있기 때문에 단일 부모(선형 이력의 경우처럼)가 아닌 부모의 집합입니다.</p>  <p>Git은 이러한 스냅샷을 \"commit\"이라고 부릅니다. 커밋 이력을 시각화하면 다음과 같이 보일 수 있습니다:</p>  <pre><code>o &lt;-- o &lt;-- o &lt;-- o\n            ^\n             \\\n              --- o &lt;-- o\n</code></pre>  <p>위의 ASCII 아트에서 <code>o</code>는 개별 커밋(스냅샷)에 해당합니다. 화살표는 각 커밋의 부모를 가리킵니다(\"이후에 온다\"가 아니라 \"이전에 온다\" 관계입니다). 세 번째 커밋 이후, 이력은 두 개의 별도 브랜치로 분기됩니다. 이것은 예를 들어 서로 독립적으로 병렬로 개발되는 두 개의 별도 기능에 해당할 수 있습니다. 미래에 이러한 브랜치는 병합되어 두 기능을 모두 통합하는 새로운 스냅샷을 생성할 수 있으며, 새로 생성된 병합 커밋이 굵게 표시된 다음과 같은 새로운 이력을 생성합니다:</p>  <pre class=\"highlight\">\n<code>\no &lt;-- o &lt;-- o &lt;-- o &lt;---- <strong>o</strong>\n            ^            /\n             \\          v\n              --- o &lt;-- o\n</code>\n</pre>  <p>Git의 커밋은 불변입니다. 이것이 실수를 수정할 수 없다는 것을 의미하지는 않습니다. 단지 커밋 이력에 대한 \"편집\"이 실제로는 완전히 새로운 커밋을 생성하고, 참조(아래 참조)가 새로운 것을 가리키도록 업데이트된다는 것입니다.</p>  <h2>의사 코드로 표현한 데이터 모델</h2>  <p>Git의 데이터 모델을 의사 코드로 작성하는 것이 도움이 될 수 있습니다:</p>  <pre><code>// 파일은 바이트의 묶음입니다\ntype blob = array&lt;byte&gt;\n\n// 디렉토리는 이름이 지정된 파일과 디렉토리를 포함합니다\ntype tree = map&lt;string, tree | blob&gt;\n\n// 커밋은 부모, 메타데이터 및 최상위 tree를 가집니다\ntype commit = struct {\n    parents: array&lt;commit&gt;\n    author: string\n    message: string\n    snapshot: tree\n}\n</code></pre>  <p>깔끔하고 간단한 이력 모델입니다.</p>  <h2>객체와 내용 주소 지정</h2>  <p>\"객체\"는 blob, tree 또는 commit입니다:</p>  <pre><code>type object = blob | tree | commit\n</code></pre>  <p>Git 데이터 저장소에서 모든 객체는 <a href=\"https://en.wikipedia.org/wiki/SHA-1\">SHA-1 해시</a>로 내용 주소 지정됩니다.</p>  <pre><code>objects = map&lt;string, object&gt;\n\ndef store(object):\n    id = sha1(object)\n    objects[id] = object\n\ndef load(id):\n    return objects[id]\n</code></pre>  <p>Blob, tree 및 commit은 이러한 방식으로 통합됩니다: 그것들은 모두 객체입니다. 다른 객체를 참조할 때, 실제로 디스크 표현에 그것들을 <em>포함</em>하지 않고, 해시로 그것들에 대한 참조를 가집니다.</p>  <p>예를 들어, <a href=\"#snapshots\">위의</a> 예제 디렉토리 구조에 대한 tree(<code>git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d</code>를 사용하여 시각화)는 다음과 같습니다:</p>  <pre><code>100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85    baz.txt\n040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87    foo\n</code></pre>  <p>tree 자체는 그 내용인 <code>baz.txt</code>(blob) 및 <code>foo</code>(tree)에 대한 포인터를 포함합니다. <code>git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85</code>로 baz.txt에 해당하는 해시로 주소 지정된 내용을 보면 다음을 얻습니다:</p>  <pre><code>git is wonderful\n</code></pre>  <h2>참조</h2>  <p>이제 모든 스냅샷은 SHA-1 해시로 식별될 수 있습니다. 그것은 불편합니다. 왜냐하면 인간은 40개의 16진수 문자 문자열을 기억하는 데 능숙하지 않기 때문입니다.</p>  <p>이 문제에 대한 Git의 해결책은 SHA-1 해시에 대한 사람이 읽을 수 있는 이름인 \"참조\"입니다. 참조는 커밋에 대한 포인터입니다. 불변인 객체와 달리, 참조는 가변적입니다(새 커밋을 가리키도록 업데이트될 수 있습니다). 예를 들어, <code>master</code> 참조는 일반적으로 주요 개발 브랜치의 최신 커밋을 가리킵니다.</p>  <pre><code>references = map&lt;string, string&gt;\n\ndef update_reference(name, id):\n    references[name] = id\n\ndef read_reference(name):\n    return references[name]\n\ndef load_reference(name_or_id):\n    if name_or_id in references:\n        return load(references[name_or_id])\n    else:\n        return load(name_or_id)\n</code></pre>  <p>이를 통해 Git은 긴 16진수 문자열 대신 \"master\"와 같은 사람이 읽을 수 있는 이름을 사용하여 이력의 특정 스냅샷을 참조할 수 있습니다.</p>  <p>한 가지 세부 사항은 우리가 종종 이력에서 \"현재 우리가 어디에 있는지\"에 대한 개념을 원한다는 것입니다. 그래서 새 스냅샷을 찍을 때 그것이 무엇에 상대적인지 알 수 있습니다(커밋의 <code>parents</code> 필드를 설정하는 방법). Git에서 \"현재 우리가 어디에 있는지\"는 \"HEAD\"라는 특별한 참조입니다.</p>  <h2>저장소</h2>  <p>마지막으로, Git <em>저장소</em>가 (대략) 무엇인지 정의할 수 있습니다: 그것은 데이터 <code>objects</code>와 <code>references</code>입니다.</p>  <p>디스크에서 Git이 저장하는 모든 것은 객체와 참조입니다: 그것이 Git의 데이터 모델의 전부입니다. 모든 <code>git</code> 명령은 객체를 추가하고 참조를 추가/업데이트하여 커밋 DAG의 일부 조작에 매핑됩니다.</p>  <p>명령을 입력할 때마다, 명령이 기본 그래프 데이터 구조에 어떤 조작을 하고 있는지 생각해보세요. 반대로, 커밋 DAG에 특정 종류의 변경을 하려고 한다면, 예를 들어 \"커밋되지 않은 변경사항을 버리고 'master' 참조가 커밋 <code>5d83f9e</code>를 가리키도록 만들기\"와 같은 경우, 아마도 그것을 수행하는 명령이 있을 것입니다(예: 이 경우 <code>git checkout master; git reset --hard 5d83f9e</code>).</p>  <h1>스테이징 영역</h1>  <p>이것은 데이터 모델과 직교하는 또 다른 개념이지만, 커밋을 생성하는 인터페이스의 일부입니다.</p>  <p>위에서 설명한 대로 스냅샷을 구현하는 한 가지 방법은 작업 디렉토리의 <em>현재 상태</em>를 기반으로 새 스냅샷을 생성하는 \"스냅샷 생성\" 명령을 갖는 것입니다. 일부 버전 관리 도구는 이렇게 작동하지만 Git은 그렇지 않습니다. 우리는 깨끗한 스냅샷을 원하며, 현재 상태에서 스냅샷을 만드는 것이 항상 이상적이지 않을 수 있습니다. 예를 들어, 두 개의 별도 기능을 구현했고, 첫 번째 커밋이 첫 번째 기능을 도입하고 다음 커밋이 두 번째 기능을 도입하는 두 개의 별도 커밋을 생성하려는 시나리오를 상상해보세요. 또는 코드 전체에 디버깅 print 문이 추가되어 있고 버그 수정도 있는 시나리오를 상상해보세요. 모든 print 문을 버리면서 버그 수정을 커밋하고 싶습니다.</p>  <p>Git은 \"스테이징 영역\"이라는 메커니즘을 통해 다음 스냅샷에 포함되어야 하는 수정 사항을 지정할 수 있도록 하여 이러한 시나리오를 수용합니다.</p>  <h1>Git 명령줄 인터페이스</h1>  <p>정보 중복을 피하기 위해, 아래 명령을 자세히 설명하지 않겠습니다. 자세한 정보는 강력히 권장되는 <a href=\"https://git-scm.com/book/en/v2\">Pro Git</a>을 참조하세요.</p>  <h2>기본 사항</h2>  <p><code>git init</code> 명령은 새 Git 저장소를 초기화하며, 저장소 메타데이터는 <code>.git</code> 디렉토리에 저장됩니다:</p>  <pre><code class=\"language-console\">$ mkdir myproject\n$ cd myproject\n$ git init\nInitialized empty Git repository in .git\n$ git status\nOn branch master\nNo commits yet\nnothing to commit (create/copy files and use \"git add\" to track)\n</code></pre>  <p>이 출력을 어떻게 해석할까요? \"No commits yet\"은 기본적으로 우리의 버전 이력이 비어 있다는 것을 의미합니다. 그것을 고쳐봅시다.</p>  <pre><code class=\"language-console\">$ echo \"hello, git\" &gt; hello.txt\n$ git add hello.txt\n$ git status\nOn branch master\nNo commits yet\nChanges to be committed:\n  (use \"git rm --cached &lt;file&gt;...\" to unstage)\n        new file:   hello.txt\n$ git commit -m 'Initial commit'\n[master (root-commit) 4515d17] Initial commit\n 1 file changed, 1 insertion(+)\n create mode 100644 hello.txt\n</code></pre>  <p>이것으로 우리는 파일을 스테이징 영역에 <code>git add</code>했고, 그런 다음 간단한 커밋 메시지 \"Initial commit\"을 추가하여 그 변경사항을 <code>git commit</code>했습니다. <code>-m</code> 옵션을 지정하지 않으면 Git은 텍스트 편집기를 열어 커밋 메시지를 입력할 수 있게 합니다.</p>  <p>이제 비어 있지 않은 버전 이력이 있으므로 이력을 시각화할 수 있습니다. 이력을 DAG로 시각화하는 것은 저장소의 현재 상태를 이해하고 Git 데이터 모델에 대한 이해와 연결하는 데 특히 도움이 될 수 있습니다.</p>  <p><code>git log</code> 명령은 이력을 시각화합니다. 기본적으로 그래프 구조를 숨기는 평면화된 버전을 보여줍니다. <code>git log --all --graph --decorate</code>와 같은 명령을 사용하면 그래프 형태로 시각화된 저장소의 전체 버전 이력을 보여줍니다.</p>  <pre><code class=\"language-console\">$ git log --all --graph --decorate\n* commit 4515d17a167bdef0a91ee7d50d75b12c9c2652aa (HEAD -&gt; master)\n  Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n  Date: Tue Dec 21 22:18:36 2020 -0500\n      Initial commit\n</code></pre>  <p>이것은 단일 노드만 포함하기 때문에 그래프처럼 보이지 않습니다. 몇 가지 변경을 더 하고, 새 커밋을 작성하고, 이력을 다시 시각화해봅시다.</p>  <pre><code class=\"language-console\">$ echo \"another line\" &gt;&gt; hello.txt\n$ git status\nOn branch master\nChanges not staged for commit:\n  (use \"git add &lt;file&gt;...\" to update what will be committed)\n  (use \"git checkout -- &lt;file&gt;...\" to discard changes in working directory)\n        modified:   hello.txt\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n$ git add hello.txt\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD &lt;file&gt;...\" to unstage)\n        modified:   hello.txt\n$ git commit -m 'Add a line'\n[master 35f60a8] Add a line\n 1 file changed, 1 insertion(+)\n</code></pre>  <p>이제 이력을 다시 시각화하면 그래프 구조의 일부를 볼 수 있습니다:</p>  <pre><code>* commit 35f60a825be0106036dd2fbc7657598eb7b04c67 (HEAD -&gt; master)\n| Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n| Date:   Tue Dec 21 22:26:20 2020 -0500\n|     Add a line\n* commit 4515d17a167bdef0a91ee7d50d75b12c9c2652aa\n  Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n  Date: Tue Dec 21 22:18:36 2020 -0500\n      Initial commit\n</code></pre>  <p>또한 현재 브랜치(master)와 함께 현재 HEAD를 보여줍니다.</p>  <p><code>git checkout</code> 명령을 사용하여 이전 버전을 볼 수 있습니다.</p>  <pre><code class=\"language-console\">$ git checkout 4515d17  # 이전 커밋 해시; 당신의 것은 다를 것입니다\nNote: checking out '4515d17'.\nYou are in 'detached HEAD' state. You can look around, make experimental\nchanges and commit them, and you can discard any commits you make in this\nstate without impacting any branches by performing another checkout.\nIf you want to create a new branch to retain commits you create, you may\ndo so (now or later) by using -b with the checkout command again. Example:\n  git checkout -b &lt;new-branch-name&gt;\nHEAD is now at 4515d17 Initial commit\n$ cat hello.txt\nhello, git\n$ git checkout master\nPrevious HEAD position was 4515d17 Initial commit\nSwitched to branch 'master'\n$ cat hello.txt\nhello, git\nanother line\n</code></pre>  <p>Git은 <code>git diff</code> 명령을 사용하여 파일이 어떻게 진화했는지(차이점 또는 diff)를 보여줄 수 있습니다:</p>  <pre><code class=\"language-console\">$ git diff 4515d17 hello.txt\ndiff --git c/hello.txt w/hello.txt\nindex 94bab17..f0013b2 100644\n--- c/hello.txt\n+++ w/hello.txt\n@@ -1 +1,2 @@\n hello, git\n +another line\n</code></pre>  <ul> <li><code>git help &lt;command&gt;</code>: git 명령에 대한 도움말 얻기</li> <li><code>git init</code>: 새 git 저장소를 생성하며, 데이터는 <code>.git</code> 디렉토리에 저장됩니다</li> <li><code>git status</code>: 무슨 일이 일어나고 있는지 알려줍니다</li> <li><code>git add &lt;filename&gt;</code>: 파일을 스테이징 영역에 추가합니다</li> <li><code>git commit</code>: 새 커밋을 생성합니다     <ul>     <li><a href=\"https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html\">좋은 커밋 메시지</a>를 작성하세요!</li>     <li><a href=\"https://chris.beams.io/posts/git-commit/\">좋은 커밋 메시지</a>를 작성해야 하는 더 많은 이유!</li>     </ul> </li> <li><code>git log</code>: 평면화된 이력 로그를 보여줍니다</li> <li><code>git log --all --graph --decorate</code>: 이력을 DAG로 시각화합니다</li> <li><code>git diff &lt;filename&gt;</code>: 스테이징 영역에 상대적으로 만든 변경사항을 보여줍니다</li> <li><code>git diff &lt;revision&gt; &lt;filename&gt;</code>: 스냅샷 간 파일의 차이점을 보여줍니다</li> <li><code>git checkout &lt;revision&gt;</code>: HEAD와 현재 브랜치를 업데이트합니다</li> </ul>  <h2>브랜치와 병합</h2>  <p>브랜치는 버전 이력을 \"분기\"할 수 있게 해줍니다. 독립적인 기능이나 버그 수정을 병렬로 작업하는 데 도움이 될 수 있습니다. <code>git branch</code> 명령을 사용하여 새 브랜치를 생성할 수 있습니다. <code>git checkout -b &lt;branch name&gt;</code>은 브랜치를 생성하고 체크아웃합니다.</p>  <p>병합은 브랜치의 반대입니다: 분기된 버전 이력을 결합할 수 있게 해줍니다. 예를 들어 기능 브랜치를 master로 다시 병합하는 것입니다. <code>git merge</code> 명령이 병합에 사용됩니다.</p>  <ul> <li><code>git branch</code>: 브랜치를 보여줍니다</li> <li><code>git branch &lt;name&gt;</code>: 브랜치를 생성합니다</li> <li><code>git checkout -b &lt;name&gt;</code>: 브랜치를 생성하고 전환합니다     <ul>     <li><code>git branch &lt;name&gt;; git checkout &lt;name&gt;</code>과 동일합니다</li>     </ul> </li> <li><code>git merge &lt;revision&gt;</code>: 현재 브랜치로 병합합니다</li> <li><code>git mergetool</code>: 병합 충돌을 해결하는 데 도움이 되는 멋진 도구를 사용합니다</li> <li><code>git rebase</code>: 패치 세트를 새 베이스로 리베이스합니다</li> </ul>  <h2>원격</h2>  <ul> <li><code>git remote</code>: 원격을 나열합니다</li> <li><code>git remote add &lt;name&gt; &lt;url&gt;</code>: 원격을 추가합니다</li> <li><code>git push &lt;remote&gt; &lt;local branch&gt;:&lt;remote branch&gt;</code>: 객체를 원격으로 보내고 원격 참조를 업데이트합니다</li> <li><code>git branch --set-upstream-to=&lt;remote&gt;/&lt;remote branch&gt;</code>: 로컬 및 원격 브랜치 간의 대응을 설정합니다</li> <li><code>git fetch</code>: 원격에서 객체/참조를 검색합니다</li> <li><code>git pull</code>: <code>git fetch; git merge</code>와 동일합니다</li> <li><code>git clone</code>: 원격에서 저장소를 다운로드합니다</li> </ul>  <h2>실행 취소</h2>  <ul> <li><code>git commit --amend</code>: 커밋의 내용/메시지를 편집합니다</li> <li><code>git reset HEAD &lt;file&gt;</code>: 파일을 언스테이지합니다</li> <li><code>git checkout -- &lt;file&gt;</code>: 변경사항을 버립니다</li> </ul>  <h1>고급 Git</h1>  <ul> <li><code>git config</code>: Git은 <a href=\"https://git-scm.com/docs/git-config\">고도로 사용자 정의 가능</a>합니다</li> <li><code>git clone --depth=1</code>: 전체 버전 이력 없이 얕은 클론</li> <li><code>git add -p</code>: 대화형 스테이징</li> <li><code>git rebase -i</code>: 대화형 리베이스</li> <li><code>git blame</code>: 어떤 줄을 누가 마지막으로 편집했는지 보여줍니다</li> <li><code>git stash</code>: 작업 디렉토리에 대한 수정 사항을 일시적으로 제거합니다</li> <li><code>git bisect</code>: 이력을 이진 검색합니다(예: 회귀에 대해)</li> <li><code>.gitignore</code>: 의도적으로 추적되지 않는 파일을 무시하도록 <a href=\"https://git-scm.com/docs/gitignore\">지정</a>합니다</li> </ul>  <h1>기타</h1>  <ul> <li><strong>GUI</strong>: Git용 많은 <a href=\"https://git-scm.com/downloads/guis\">GUI 클라이언트</a>가 있습니다. 개인적으로 우리는 그것들을 사용하지 않고 대신 명령줄 인터페이스를 사용합니다.</li> <li><strong>셸 통합</strong>: 셸 프롬프트의 일부로 Git 상태를 갖는 것은 매우 편리합니다(<a href=\"https://github.com/olivierverdier/zsh-git-prompt\">zsh</a>, <a href=\"https://github.com/magicmonty/bash-git-prompt\">bash</a>). <a href=\"https://github.com/ohmyzsh/ohmyzsh\">Oh My Zsh</a>와 같은 프레임워크에 종종 포함되어 있습니다.</li> <li><strong>편집기 통합</strong>: 위와 유사하게, 많은 기능을 갖춘 편리한 통합. <a href=\"https://github.com/tpope/vim-fugitive\">fugitive.vim</a>은 Vim의 표준입니다.</li> <li><strong>워크플로</strong>: 우리는 데이터 모델과 몇 가지 기본 명령을 가르쳤습니다. 대규모 프로젝트에서 작업할 때 따라야 할 관행은 알려주지 않았습니다(그리고 <a href=\"https://nvie.com/posts/a-successful-git-branching-model/\">많은</a> <a href=\"https://www.endoflineblog.com/gitflow-considered-harmful\">다른</a> <a href=\"https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow\">접근 방식</a>이 있습니다).</li> <li><strong>GitHub</strong>: Git은 GitHub가 아닙니다. GitHub는 <a href=\"https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests\">풀 리퀘스트</a>라는 다른 프로젝트에 코드를 기여하는 특정 방법을 가지고 있습니다.</li> <li><strong>다른 Git 제공자</strong>: GitHub는 특별하지 않습니다: <a href=\"https://about.gitlab.com/\">GitLab</a> 및 <a href=\"https://bitbucket.org/\">BitBucket</a>과 같은 많은 Git 저장소 호스트가 있습니다.</li> </ul>  <h1>리소스</h1>  <ul> <li><a href=\"https://git-scm.com/book/en/v2\">Pro Git</a>은 <strong>강력히 권장되는 읽을거리</strong>입니다. 1-5장을 읽으면 데이터 모델을 이해했으므로 Git을 능숙하게 사용하는 데 필요한 대부분을 배울 수 있습니다. 이후 장에는 흥미롭고 고급 자료가 있습니다.</li> <li><a href=\"https://ohshitgit.com/\">Oh Shit, Git!?!</a>은 일반적인 Git 실수에서 복구하는 방법에 대한 짧은 가이드입니다.</li> <li><a href=\"https://eagain.net/articles/git-for-computer-scientists/\">Git for Computer Scientists</a>는 이 강의 노트보다 의사 코드는 적고 멋진 다이어그램은 더 많은 Git의 데이터 모델에 대한 짧은 설명입니다.</li> <li><a href=\"https://jwiegley.github.io/git-from-the-bottom-up/\">Git from the Bottom Up</a>은 호기심 많은 사람들을 위한 단순한 데이터 모델을 넘어선 Git의 구현 세부 사항에 대한 자세한 설명입니다.</li> <li><a href=\"https://smusamashah.github.io/blog/2017/10/14/explain-git-in-simple-words\">How to explain git in simple words</a></li> <li><a href=\"https://learngitbranching.js.org/\">Learn Git Branching</a>은 Git을 가르치는 브라우저 기반 게임입니다.</li> </ul>",
  "source_hash": "sha256:1882fed561269610d4ac35bc5a461efe73f8481070dd58a45db04a8899598a98",
  "model": "claude-sonnet-4-5-20250929",
  "generated_at": "2026-01-02T04:09:55.034819+00:00"
}