6P by GN⁺ | ★ favorite | 댓글 1개
  • Git은 오래 써도 헷갈리는 용어 중복과 재사용이 많아, 명령을 외우는 것만으로는 실제 동작을 예측하기 어려움
  • HEAD, heads, detached HEAD, ours/theirs, ../...처럼 같은 단어와 기호가 문맥에 따라 다른 의미로 바뀜
  • index, staged, cached는 모두 .git/index와 연결되지만, reset, revert, restore는 비슷한 이름과 달리 커밋 이력·작업 트리·브랜치 포인터에 미치는 영향이 다름
  • main이 원격을 track하는 것과 origin/main 같은 remote-tracking branch는 별개이며, checkout은 브랜치 전환과 파일 복원을 한 명령에 함께 담고 있음
  • Git을 이해하려면 개별 명령보다 브랜치, 참조, 커밋, 인덱스, 원격 추적 상태가 맞물리는 내부 모델을 함께 봐야 함

HEAD, 브랜치, detached HEAD

  • Git에서 heads는 브랜치를 가리키며, 내부적으로 브랜치는 .git/refs/heads 디렉터리에 저장됨
    • 공식 Git 용어집은 branch를 그 위의 모든 커밋, head를 가장 최근 커밋으로 구분하지만 같은 대상을 보는 두 방식으로 볼 수 있음
  • HEAD는 현재 브랜치를 가리키며 .git/HEAD에 저장됨
    • head는 브랜치이고, HEAD는 현재 브랜치”라는 표현 자체가 Git 용어의 혼란을 잘 보여줌
  • detached HEAD stateHEAD가 브랜치가 아니라 커밋 ID를 직접 가리키는 상태임
    • 태그를 checkout하면 태그는 브랜치가 아니기 때문에 detached HEAD 상태가 될 수 있음
    • 이 상태에서는 git pull이 동작하지 않고, git push도 특별한 방식이 아니면 동작하지 않음
    • git commit, git merge, git rebase, git cherry-pick은 가능하지만 새 커밋이 어떤 브랜치에도 연결되지 않아 찾기 어려워질 수 있음
    • 새 브랜치를 만들거나 기존 브랜치로 전환하면 detached HEAD 상태에서 벗어날 수 있음

merge와 rebase에서 달라지는 ourstheirs

  • 충돌 해결에서 git checkout --ours file.txt는 “ours” 쪽 파일을 고르지만, merge와 rebase의 기준점이 서로 다름
  • merge에서는 현재 브랜치가 ours, 병합해 들어오는 브랜치가 theirs
    • 예: git checkout merge-into-oursgit merge from-theirs를 실행하면 현재 브랜치가 ours가 됨
  • rebase에서는 현재 브랜치가 theirs, rebase 대상 브랜치가 ours
    • 예: git checkout theirsgit rebase ours를 실행하면 대상 브랜치가 ours가 됨
  • 이 차이는 git rebase main이 내부적으로 현재 브랜치의 커밋들을 main 브랜치 복사본에 반복해서 병합하는 방식과 관련 있음
  • ourstheirsourstheirs 용어를 짧게 설명함
    • VSCode의 “current change”와 “incoming change”도 같은 방식으로 헷갈릴 수 있음

원격 상태 메시지와 fast-forward

  • Your branch is up to date with 'origin/main'은 현재 main이 원격의 최신 상태라는 뜻이 아님
    • 마지막 git fetch 또는 git pull 시점의 origin/main과 로컬 브랜치가 같다는 뜻임
    • 마지막 fetch가 5일 전이었다면 “5일 전 기준으로” 최신 상태인 셈임
  • Git은 reflog에 최근 fetch 시점 정보를 저장하지만, 상태 메시지에는 “마지막 fetch 기준” 시간이 표시되지 않음
  • can be fast-forwarded는 원격 브랜치가 로컬 브랜치보다 앞에 있고, 로컬에 별도 커밋이 없어 포인터만 앞으로 이동할 수 있다는 뜻임
    • 예: mainA-B-C, origin/mainA-B-C-D-E라면 git pull 후 둘 다 A-B-C-D-E가 됨
    • 이 경우 병합 충돌 가능성이 없음
  • fast-forward가 불가능한 경우는 양쪽 브랜치가 갈라진 상태임
    • 예: mainorigin/main에는 없는 X 커밋이 있고, origin/main에는 main에 없는 D, E가 있으면 fast-forward가 불가능함
    • 이때 Git은 두 브랜치가 diverged 상태이며 각각 다른 커밋 수를 가진다고 표시함

커밋 선택 문법: ^, ~, .., ...

  • HEAD^HEAD~는 모두 1개 이전 커밋을 가리킴
    • HEAD^^^, HEAD~~~, HEAD~3은 모두 3개 이전 커밋을 가리킴
  • merge commit처럼 부모가 여러 개인 커밋에서는 HEAD^2, HEAD^3의 의미가 달라짐
    • HEAD^는 첫 번째 부모
    • HEAD^2는 두 번째 부모
    • HEAD^3은 세 번째 부모
    • HEAD~3은 부모의 부모의 부모를 뜻하므로 HEAD^3과 다름
  • git log main..testgit log main...test도 서로 다르게 동작함
    • main..testtest에만 있는 커밋을 보여줌
    • test..mainmain에만 있는 커밋을 보여줌
    • main...test는 양쪽에만 있는 커밋을 함께 보여줌
  • git diff에서 .....git log와 다르게 해석됨
    • git diff test..maintest 쪽 변화와 main 쪽 변화를 모두 포함해 비교함
    • git diff test...main은 공통 조상과 main 쪽 끝을 비교해 한쪽 diff만 보여줌
  • 점 표기 혼란은 pain in dots에서도 추가로 다룸

reference, refspec, tree-ish

  • Git에서 reference는 여러 의미로 쓰임
    • main, v0.2 같은 브랜치와 태그
    • 현재 브랜치를 가리키는 HEAD
    • HEAD^^^처럼 커밋 ID로 해석되는 표현
  • Git 문서는 HEAD^^^ 같은 표현을 엄밀히는 “revision parameters”라고 부르지만, 일상적으로는 reference처럼 다뤄질 수 있음
  • symbolic referenceHEAD처럼 다른 참조를 가리키는 참조를 뜻함
    • HEAD는 Git 핵심 명령의 동작에 큰 영향을 주는 중심 개념임
  • .git/config의 remote 설정에는 refspec이 들어감
    • 예: +refs/heads/main:refs/remotes/origin/main
    • 기본 git clone이나 git remote add가 만들어 주는 값을 그대로 쓰는 경우가 많음
  • tree-ishgit checkout THING .에서 THING이 될 수 있는 대상을 가리킴
    • 커밋 ID
    • main, HEAD^^, v0.3.2 같은 커밋 참조
    • main:./docs 같은 커밋 안의 하위 디렉터리
    • 실무적으로는 “커밋 또는 커밋 참조” 정도로 이해해도 되는 경우가 많음

index, staged, cached

  • index, staged, cached는 모두 .git/index 와 관련됨
    • .git/indexgit add로 변경사항을 stage할 때 사용되는 파일임
  • 같은 대상을 가리키는 표현이 여러 명령에 흩어져 있음
    • git diff --cached
    • git rm --cached
    • git diff --staged
    • .git/index
  • 다만 --index--cached 플래그가 항상 같은 의미는 아님
    • Git 리드 메인테이너 Junio Hamano의 이 세부 차이를 설명함
  • index는 성능상의 이유로 untracked files도 나열하지만, 일반적으로 staging area가 untracked files를 포함한다고 보지는 않음

reset, revert, restore

  • reset, revert, restore는 비슷한 단어지만 Git에서 영향을 주는 대상이 다름
  • git revert COMMIT은 현재 브랜치에 COMMIT의 반대 작업을 하는 새 커밋을 만듦
    • COMMIT이 3줄을 추가했다면 새 커밋은 그 3줄을 삭제함
  • git reset --hard COMMIT은 현재 브랜치를 COMMIT 시점으로 강제로 되돌림
    • COMMIT 이후의 새 변경사항을 지우는 매우 위험한 작업임
  • git restore --source=COMMIT PATHPATH의 파일들을 COMMIT 당시 상태로 되돌림
    • 다른 파일이나 커밋 이력은 바꾸지 않음
  • git reset --hardgit restore .는 단독으로 쓰면 대체로 비슷하게 동작하지만, git reset --hard COMMITgit restore --source COMMIT .은 완전히 다르게 동작함

track, remote-tracking branch, untracked files

  • Git은 track이라는 단어를 세 가지 방식으로 씀
    • git statusUntracked files:는 Git이 관리하지 않고 커밋에 포함되지 않는 파일을 뜻함
    • origin/main 같은 remote-tracking branch는 마지막 git pull 또는 git fetch 시점에 원격 originmain이 가리키던 커밋 ID를 담은 로컬 참조임
    • “branch foo set up to track remote branch bar from origin”처럼 로컬 브랜치가 원격 브랜치를 track하도록 설정될 수 있음
  • main이 원격을 track하는 브랜치라는 말과 origin/main이 remote-tracking branch라는 말은 서로 다름
  • main은 실제 브랜치이므로 커밋을 만들고 merge할 수 있음
    • .git/config에서 원격 main을 track하도록 설정되면 git pullgit push를 편하게 쓸 수 있음
  • origin/main은 “remote-tracking branch”라는 이름에도 일반 브랜치가 아님
    • 직접 커밋할 수 없음
    • git pull 또는 git fetch로 원격의 최신 main 상태를 가져올 때만 업데이트됨

checkout, reflog, merge/rebase/cherry-pick

  • git checkout은 서로 다른 두 작업을 한 명령에 담고 있음
    • git checkout BRANCH는 브랜치를 전환함
    • git checkout file.txtfile.txt의 unstaged 변경사항을 버림
  • Git은 이 혼란을 줄이기 위해 기능을 git switchgit restore로 나눴지만, 기존 checkout도 여전히 사용할 수 있음
  • reflogreference log이며, reference가 과거에 무엇을 가리켰는지의 기록임
    • reference는 브랜치, 태그, HEAD를 포함하는 포괄적 용어임
    • reflog는 중요한 브랜치를 실수로 삭제한 경우처럼 매우 나쁜 Git 상황에서 빠져나오는 데 도움을 줄 수 있음
  • merge, rebase, cherry-pick은 커밋을 합치는 방식이 다름
    • merge는 두 브랜치를 합치는 새 커밋 하나를 만듦
    • rebase는 현재 브랜치의 커밋을 대상 브랜치 위로 하나씩 복사함
    • cherry-pick은 rebase와 비슷하지만 문법이 다르고, 현재 브랜치로 커밋을 복사함
  • git rebase --onto main otherbranch mybranchmybranch의 일부 커밋만 main 위로 옮기는 형태에 쓰일 수 있음
    • 예시 구조에서 F, Gmain 위로 rebase하고 싶을 때 사용할 수 있음
    • 세 브랜치 이름이 들어가 기억하기 어려운 문법임

commit과 그 밖의 헷갈리는 용어들

  • commit은 Git에서 동사와 명사로 모두 쓰임
    • 동사: “자주 commit하라”
    • 명사: main의 가장 최근 commit
  • Git commit은 세 가지 방식으로 볼 수 있음
    • 모든 파일 현재 상태의 스냅샷
    • 부모 커밋과의 diff
    • 이전 모든 커밋의 history
  • 명령마다 commit을 다르게 다룸
    • git show는 commit을 diff처럼 다룸
    • git log는 commit을 history처럼 다룸
    • git restore는 commit을 snapshot처럼 다룸
  • blob, tree, origin, upstream, downstream, fetchpull의 관계, stash, worktree, subtree, submodule도 헷갈리는 Git 용어에 포함됨
  • GitHub 용어 중에는 “pull request”, “squash and merge”, “rebase and merge”가 혼란스러운 사례로 꼽힘

반복되는 혼란의 세 축

  • 특히 혼란스러운 Git 용어는 세 가지로 압축됨
    • head는 브랜치이고, HEAD는 현재 브랜치임
    • “remote tracking branch”와 “branch that tracks a remote”는 서로 다른 것임
    • index, staged, cached는 모두 같은 대상을 가리킴
  • 2012년의 the most confusing git terminology도 Git 용어가 CVS와 Subversion 용어와 어떻게 연결되는지 다룸
  • Git 용어 혼란은 일부 특수 기능이 아니라 핵심 기능 전반에서 반복됨
  • 특히 브랜치를 논할 때 “tracking”이 여러 방식으로 쓰여 처음 보면 차이를 알아차리기 어려움
  • 일부 설명에는 Git의 드문 구석이 포함되어 있어 실수가 있을 수 있다는 단서가 남아 있음

댓글과 토론

Hacker News 의견들
  • 이 용어들을 대부분 외우기 어려웠는데, 모든 게 맞아떨어지게 만든 핵심은 Git의 많은 것들이 결국 커밋을 가리키는 포인터라는 점이었음
    브랜치 이름, HEAD, 태그가 모두 포인터임. HEAD는 지금 보고 있는 커밋을 가리키고, my-feature 같은 브랜치 이름은 그 브랜치의 최신 커밋을 가리킴. main에서 git checkout -b my-feature를 하면 main, my-feature, HEAD가 모두 main의 최신 커밋을 가리키게 됨
    my-branch에서 커밋할 때마다 HEADmy-branch가 새 커밋을 가리키도록 이동함. detached HEADHEAD가 가리키는 커밋을 어떤 브랜치도 가리키지 않는 상태라는 뜻임. 태그와 브랜치의 차이는 태그는 특정 커밋을 가리키고 움직이지 않는다는 점임
    또 헷갈렸던 건 많은 명령이 일관 없어 보이는 이유가 Git이 기본 인자를 가정하기 때문이라는 것임. git checkout file.txtgit checkout HEAD -- file.txt와 같고, my-branch에 있을 때 git rebase maingit rebase main my-branch와 같음. 다만 뒤의 형태는 다른 브랜치에서도 실행할 수 있음
    마지막으로 모든 게 꼬였을 때 어떤 이상한 상태에서도 빠져나오게 해주는 명령은 git reflog임. HEAD가 가리켰던 모든 커밋을 보여줌. 그래도 Git이 이런 혼란을 인정하고 덜 놀랍고 더 단순한 인터페이스의 명령을 만들고 있는 건 반가움
    • Git이 이 개념에 붙인 이름을 쓰면 “커밋 포인터”는 refs임. 그래서 reflog라는 이름이 됨
      또 Git을 쓰는 많은 사람이 잘 떠올리지 못하는 점은, 가져온 원격의 브랜치나 태그 ref들도 전부 ref라서 ref 이름을 쓸 수 있는 곳이면 어디서든 참조할 수 있다는 것임
      예를 들어 “이 브랜치에 뭐가 있든 상관없고, fast-forward도 병합도 rebase도 하지 말고, 그냥 로컬 브랜치를 원격 내용으로 덮어써라”라고 하고 싶다면 이렇게 하면 됨: git checkout foo, git reset --hard origin/foo
    • 정확히는 HEAD 포인터가 커밋을 직접 가리키는 게 아니라, 보통은 my-branch 같은 브랜치 포인터를 가리키고 그 브랜치 포인터가 커밋을 가리킴. detached HEAD 상태는 예외임
      my-branch에서 커밋할 때 HEADmy-branch가 둘 다 새 커밋으로 이동하는 게 아니라, HEAD는 계속 my-branch 포인터를 가리키고 my-branch만 새 커밋을 가리키도록 이동함. 물론 HEAD를 따라 my-branch를 거쳐 커밋으로 가면 새 커밋에 도달함
      detached HEAD는 HEAD가 브랜치 포인터 대신 커밋을 직접 가리키는 상태임. detached HEAD 상태에서도 HEAD와 브랜치 포인터가 둘 다 최신 커밋을 가리킬 수 있음. git log --decorate를 보면 보통의 (HEAD -> my-branch) 대신 최신 커밋에 (HEAD, my-branch)처럼 표시됨
    • 이 설명이 좋음. 다만 가끔 부딪히는 더 헷갈리는 부분은 태그가 두 종류라는 점임. 대부분의 태그는 말한 것처럼 커밋 객체를 가리키는 가벼운 태그이고, 그 이상은 아님
      반면 주석 태그는 메시지, 타임스탬프, SHA 등을 담을 수 있음. 이것들은 제대로 된 Git 객체이고 커밋 객체와 꽤 비슷하게 동작하지만, 보통은 여전히 다른 Git 객체, 대개 커밋을 참조함
    • 나처럼 과하게 조심하는 편이면 충돌이 많은 까다로운 절차나 여러 이동을 시작하기 전에 git rev-parse HEAD를 실행하고 그 문자열을 안전한 곳에 복사해두는 것도 좋음
      마지막으로 정상이라고 확신하는 상태를 정확히 표시해뒀다는 안심이 생김. 혼돈 속으로 몇 단계 들어간 지점이 아니라 마지막 정상 상태로 보통 돌아갈 수 있음. 물론 git gcgit filter-branch 같은 걸 부주의하게 쓰지 않았다는 전제임
    • 이 설명은 Git 내부의 트리 구조가 모든 걸 지배한다는 점도 잘 보여줌
      농담 같지만 농담이 아닌 게, Git을 쓰려면 정말 컴퓨터공학 학위가 필요하다는 말이 틀리진 않음. Git의 기본 인자는 매우 일관성 없는 사용 경험을 만든다는 데 동의함
      git checkout ., git reset --hard/soft, git cleanup도 전부 엄청 비슷해 보이지만 실제로는 매우 다름. 그래도 Perforce나 SVN보다는 여전히 Git이 좋음. 예전에는 Mercurial을 조금 더 좋아했지만 지금도 그럴지는 모르겠음
      git reflog가 왕이고 진실을 보여줌
  • Git이 또 한 번의 근거 없는 기술 유행 덕분에 버전 관리 전쟁에서 “승리”한 지 10년쯤 됐는데, 아직도 사소해야 할 작업에서 어려움을 겪고 있음
    Linus Torvalds가 사람들이 더 친절한 계층을 얹지 않고 Git을 직접 쓰기 시작한 데 놀랐다는 글을 본 기억이 있음. 업계가 자기 성찰을 할 수 있다면 Git을 선택한 게 실수였다고 인정하고 다른 버전 관리 시스템으로 옮겼을 것임. 텍스트 저장이 전부인 도구에 엄청난 시간을 낭비하지 말고
    • Git을 텍스트 저장 도구로 보진 않음. 여러 사람이 같은 “텍스트”에 가하는 변경을 꽤 정밀하고 안정적으로 조율하면서, 누구의 진행도 막지 않는 도구임. Word로 한번 해보면 차이가 큼
      물론 더 나아질 수는 있음. 그래도 Git이 받는 증오는 늘 놀라움. Git은 대단한 도구이고, 비개발자용으로 만든 Word나 Google Docs 같은 도구보다 훨씬 앞서 있음
      내가 Subversion이 왕이던 시절을 기억할 만큼 나이가 있어서 그런지도 모름
    • 강하게 동의함
      매주 동료들이 Git 때문에 헷갈려 하는 걸 보고, 온라인에서 도움 요청 글을 보고, 여기 글처럼 단순해야 할 걸 다시 설명하려 애쓰는 글을 또 봄
      코딩하는 동안 SVN, TFVC, Perforce 등에서 자신이 만든 엉망진창을 되돌리느라 몇 시간씩 낭비하는 사람은 기억나지 않음
      도구는 삶을 더 쉽게 만들려고 존재함. 그걸 못 하면 시간을 들일 가치가 없음
    • 2013년만 해도 Git이 이길지는 전혀 명확하지 않았음. darcs, Mercurial, svn, 심지어 cvs 사용자도 꽤 많았고, 그 시절 도구들은 보통 이들을 모두 지원하는 플러그인을 갖고 있었음
      아직도 그런 다양성이 있었으면 좋겠음. Git 단일 문화는 Git을 대체하려는 것이 먼저 Git을 다시 구현해야 한다는 뜻이기도 함. 그래서 ASCII나 Scroll Lock 키처럼 Git에 거의 영원히 묶이게 됨
    • Git은 좋은 이유로 이겼고, 이전 것들보다 분명히 나았음. 지금은 jQuery처럼 Git을 욕하는 게 유행일 수 있지만, 등장 당시에는 명확한 개선이었음
    • 비슷한 문제, 우리가 왜 Git에 갇힐 수 있는지에 대해 최근 글을 썼음
      핵심 걱정은 GitHub가 너무 좋아서, GitHub가 Git을 문자 관리 엔진에서 분리하고 다른 엔진도 허용하지 않는 한 우리가 Git에 묶일 거라는 점임
      https://trolololo.xyz/github
      https://news.ycombinator.com/item?id=38098109
  • Git 용어는 많은 용어가 이미 동작 방식을 이해한 뒤에는 말이 되지만, 구현 세부를 모르거나 다른 용어들과 함께 놓이면 거의 말이 안 되는 임상 사례 같음
    새는 추상화 같은 용어
    • 부분적으로는 맞지만, Git의 서로 다른 부분을 서로 다른 사람들이 만들며 이름을 따로 붙였기 때문이기도 함. Linus는 원래 이를 cache라고 불렀고, 가장 컴퓨터과학적인 용어였음
      이후 Junio가 분산 버전 관리 시스템에 더 특화된 index로 바꾼 것 같고, 대부분의 사용자는 이를 staging area라고 불렀음. 이제 이 용어의 진화가 Git UI와 내부에 화석처럼 남아 있음
    • 말이 되는 게 아니라 그냥 엉성한 이름짓기임. 좋은 이름을 붙이려면 노력이 필요한데 Git 초기 개발자들은 그걸 하지 않았음
    • 기본 사용을 위해 운영체제, IDE, 컴파일러 내부를 알아야 한다고 상상해보면, 사실상 나쁜 API 설계
  • “Your branch is up to date with origin/master”는 매일 듣는 가장 큰 거짓말임. 그래서 매번 그냥 pull
    • 엄밀히는 참임. origin/master는 origin에 있는 master가 아니라, 마지막으로 알려진 origin/master가 가리키던 커밋을 뜻함. 이 값은 fetch할 때 갱신되고, pull은 자동으로 fetch를 함
      ls -l .git/refs/remotes/origin/master를 보면 origin/master가 내 시스템에 있는 파일일 뿐이라는 걸 알 수 있음. 언제 바뀌었는지도 볼 수 있음. 마법처럼 갱신되지 않음
      git fetch origin을 했을 때 변경이 있으면 타임스탬프와 내용이 바뀜. cat .git/refs/remotes/origin/master로 확인할 수 있음
      Git의 기초는 아주 단순해서 핵심 자료구조와 몇 가지 연산은 하루 만에 구현할 수 있음. 이걸 알아두는 건 정말 가치 있음. 어떻게 된 일인지 Git은 꽤 단순한 핵심 위에 매우 복잡한 사용자 인터페이스를 만들어냈음
    • 그 메시지는 Git이 명시적으로 지시하지 않으면 인터넷이나 원격 저장소에 접속해 업데이트를 확인하지 않는다는 뜻일 뿐임. 이 성질 자체는 바람직하다고 봄. 다만 메시지는 더 명확할 수 있음
    • “Your branch is up to date with the local copy of origin/master, last updated on XXXX-XX-XX:XX:XX:XX” 정도였어야 함
    • 나는 로컬 master 브랜치를 지워버림. 보통 master에 push할 수 없거나, 어차피 master에서 직접 작업하고 싶지 않음. 그렇다면 로컬의 변경 가능한 master 브랜치를 둘 이유가 없음
      그래서 origin/master만 갱신하면 되고, 그건 fetch로 충분함
    • git fetch && git pull
      3년 뒤 은퇴해서 이 망가진 쓰레기에서 손 씻을 날을 기다리고 있음
  • Git이 나온 거의 초기부터 써왔는데, 이번 주에야 porcelain을 알게 됨. git status 출력을 파싱하는 프로젝트가 있는데 --porcelain 플래그를 붙이니 정말 도움이 됨. 더 간결하고 프로그램으로 파싱하기 쉬운 출력을 만들어줌
    얼마나 많은 명령에 더 기계가 읽기 쉬운 출력이 있는지 궁금해져서 git-scm의 “Git Internals - Plumbing and Porcelain” 페이지까지 보게 됨. 요약하면 Git은 처음부터 세련된 버전 관리 시스템이라기보다 버전 관리를 다루는 도구 모음으로 작성됐음
    초기부터 Git을 쓴 많은 사람은 이 낮은 수준 명령으로 버전 관리 작업을 배웠고, 그 작업 흐름을 다른 사람들에게도 전파했음. 이게 plumbing 계층임. 이후 Git은 더 다듬어진 계층을 만들었고 이를 porcelain이라고 부름
    아직 어떤 명령이 어느 계층에 속하는지는 완전히 모르겠지만, 최근 몇 년간 추천되는 새 작업 흐름을 이해하는 데 도움이 됐음. 내 작업 흐름을 바꿀 때도 더 잘 판단할 수 있게 해줌
    https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • Linus가 내부 구조인 plumbing에는 만족했지만 UI인 porcelain은 손볼 필요가 있다고 봤다는 분명한 신호임. 원래 porcelain은 전부 plumbing을 호출하는 스크립트였음
      아쉽게도 더 빠르게 만들려고 그 UI를 하드코딩했지만, 개선하지는 않았음
  • Julia의 글쓰기 목소리가 정말 좋음. 베테랑 개발자에게도 매우 가치 있는 내용을 쓰면서, 이 분야에 막 들어온 사람에게도 친근하고 배제하지 않는 톤을 유지함
  • “Git 스톡홀름 증후군”에 걸린 것 같음. 이 용어들이 그렇게까지 헷갈리진 않는데, 다른 시스템을 거의 배우지 않고 2011년쯤부터 Git에 깊이 묶여 있었기 때문이 큰 것 같음
    돌이켜보면 처음 시작할 때는 극도로 헷갈렸을 텐데, 멋있어 보이려고 안 그런 척했던 것 같음
    • Fossil 버전 관리 시스템의 CLI를 보고 나서 내가 스톡홀름 증후군이 있다는 걸 알게 됨. 너무 직관적이었음
    • Git을 쓰기 전에 cvs와 svn을 썼는데, svn과 비교하면 Git이 훨씬 이해하기 쉬웠음
    • 내가 써본 다른 버전 관리는 Plastic SCM뿐인데, 스톡홀름 증후군이든 아니든 Git이 훨씬 좋음
  • 함께 볼 만한 것들: Git staging area는 말 그대로 모두가 동의하는 용어임
    https://news.ycombinator.com/item?id=28143078
    “Your branch is up to date with ‘origin/main’”은 사실 약간 오해의 소지가 있음. main 브랜치가 최신이라는 뜻으로 받아들일 수 있지만, 실제로는 그렇지 않음
    다만 여기에 너무 “엄밀히 말하면”을 붙일 필요는 없음. 두 장군 문제가 적용되는 것 아닌가? 정말 꼼꼼히 따지면 바로 이 순간 “최신”인지 알 수 없음. status 전에 fetch를 해도 마찬가지임
    그렇다면 합리적인 기대는 뭘까? 내 컴퓨터의 객체 데이터베이스 안에 있는 ref가 마지막으로 확인했을 때 저 멀리 있는 다른 객체 데이터베이스의 ref와 같다는 정도임
    단순한 status 호출이 네트워크 fetch를 하거나, 지금 쓰는 도구의 기본 원리를 매번 상기시킬 수는 없음. 개인적으로는 그럼. 그런데 놀랍게도 StackOverflow 답변의 많은 댓글은 바로 그 둘이 필요하다고 봄
    “is up to date with the origin’s main as of your last fetch 5 days ago” 같은 문구는 좀 더 합리적임. 마지막으로 fetch한 지 얼마나 됐는지만 알려주기 때문임
    [1] https://stackoverflow.com/questions/27828404/why-does-git-st...
    또 하나: ORIG_HEADFETCH_HEAD처럼 “origin의 head”와 관련 있다고 생각했음. 하지만 아니었음. 이 “pseudoref”는 rebase 같은 더 복잡한 재작성 전에 저장 지점으로 쓰이는 것 같고, reflog가 생기기 전에 구현됐음. 아마 “original head”라는 뜻인 듯함
    • Git에 꽤 호의적이지만, 저 메시지의 문구에 대해서는 글쓴이에게 동의함. 다만 제안한 대안에는 동의하지 않음
      “Your branch is up to date with ‘origin/main’”은 기술적으로는 맞지만, up to date라는 표현은 대충 읽으면 두 가지를 암시함. mainorigin/main과 같고, origin/main도 최신이라는 것, 즉 명령 중 갱신됐거나 자동으로 최신 상태로 유지되고 최근 동기화가 성공했다는 느낌임
      이건 CPU 한 사이클 동안 참이어야 하는 기계 판독 신호가 아니라 사용자에게 보이는 상태 메시지임. 네트워크 이론까지 가지 않아도 그런 해석은 합리적임
      “Up to date”는 공간이 아니라 시간상 따라잡았다는 뜻에 가까움. 로컬 브랜치 위치는 “이 브랜치가 어디를 가리키나”라는 공간에 가깝고, 원격 ref 상태는 “내가 마지막으로 원격 ref를 언제 갱신했나”라는 시간에 가까움. 물론 매우 주관적이고 둘 다 양쪽 의미가 될 수 있음
      더 나은 문구는 “Your branch matches origin/main”, “Your branch’s head is the same as origin/main”, “Your branch is pointing to the same commit as origin/main” 같은 식일 수 있음. 명확성과 장황함 사이의 타협임. 여기에 글쓴이의 제안처럼 “remote ref last updated 5 days ago”를 괄호로 붙일 수도 있음
    • status 호출이 네트워크 fetch를 하는 게 짜증난다는 데 동의하지 않음. 나는 네트워크 작업은 명시적이어야지 암시적이면 안 된다고 봄. git status를 할 때는 파일 시스템에 있는 내 저장소의 상태를 알고 싶음
      다른 원격에서 무슨 일이 있는지 알고 싶으면 내가 직접 fetch한 다음 비교하면 됨
    • 기대하는 것은 status 명령을 시작한 시점 기준으로 최신 정보를 얻는 것임. 정보를 터미널에 출력하는 사이 경쟁 조건이 있을 수는 있지만 아주 작은 시간 창임
      대체로 이는 fetch를 원한다는 뜻임. 두 장군 문제는 원격 쪽에만 적용됨. 원격은 클라이언트가 데이터를 받았다는 확인을 못 받으면 한동안 재전송을 계속할 수 있음. 그건 클라이언트의 문제가 아님
      클라이언트가 서버에서 데이터를 받지 못하면 올바른 동작은 멈춰 있다가 타임아웃 후 오류를 출력하는 것일 텐데, 현재 fetch가 아마 그렇게 할 것임
  • Git을 배우는 데 가장 가치 있는 단계는 올바른 정신 모델을 갖는 것임. 전부 그래프
    이 도구는 자주 공유되는데 여기에도 잘 맞음. Git의 내부 모델과 여러 명령의 효과를 시각화해줌
    https://learngitbranching.js.org/
    이 도구를 10분 써서 배운 게 그전 약 10년의 Git 경험보다 많았음. 강력 추천함
  • 더 보태고 싶진 않지만 Bitbucket 사람들이 pull request라는 용어를 만든 것도 문제임
    “pull”은 원격 변경을 로컬 저장소로 병합하는 동작임. 사용자가 실제로 요청하는 건 서버가 자신의 원격 변경을 어떤 브랜치로 병합해달라는 것임. GitLab의 “merge request”가 맞는 표현임
    누군가 fetch를 했더니 커밋이 사라진 걸 본 적이 있음. 저장소가 시간상 뒤로 갔기 때문에 숨겨졌던 것임. Git은 그런 세부 정보를 status에 숨겨두는데, git-promptpowerline-shell을 쓰지 않으면 어둠 속에서 일하는 셈임
    • git-request-pull(1)에 남아 있는 원래 아이디어는 우리 모두가 kernel.org/git이나 redhat.com/git 같은 곳에 각자 Git 저장소를 두고, 서로 다른 서버, 조직, 도메인에 있는 저장소에서 pull해달라고 이메일로 요청하는 방식이었음
      GitHub는 Git의 request-pull 명령에서 영감을 받았지만, 이를 한 GitHub 저장소에서 다른 GitHub 저장소로 병합하는 것으로 재해석했음
    • 서버 관점에서는 사용자가 원격이고 서버가 로컬임. 그래서 사용자가 서버에게 자기 쪽에서 pull하라고 요청하는 것임
      적어도 나는 늘 그렇게 읽었음
    • Git 책이 이 용어의 유래와 왜 말이 되는지를 설명함
      요약하면 서버에 하는 요청이 아니라 다른 사람에게 하는 요청임. 프로젝트에 기여하고 싶은 변경을 살펴보도록, 상대에게 내 브랜치를 pull해달라고 요청하는 것임
      [0] https://git-scm.com/book/en/v2/Distributed-Git-Contributing-...
    • “pull request”는 GitHub가 대중화했고, 괜찮은 용어라고 봄. 실제로 다른 저장소에서 pull하는지, 같은 저장소 안에서 “merge request”를 하는지의 구분은 그다지 흥미롭지 않음. “merge request”는 GitLab 특유의 용어처럼 느껴짐
    • 이건 늘 이해하기 가장 어려웠음. 당연히 pull request가 아니라 push request나 merge request여야 하는 것 아닌가 싶음