6P by GN⁺ | ★ favorite | 댓글 1개
  • Git에서 자주 막히는 작업을 커밋 작성, 브랜치 이동, 변경 비교, 원격 동기화, 변경 취소 명령으로 빠르게 찾아볼 수 있게 정리한 PDF임
  • 로컬 작업 흐름은 git status, git add, git commit, git diff를 중심으로 스테이징 전후 상태를 확인하고 커밋을 만드는 방식임
  • 브랜치 작업은 git switch, git checkout, git branch, git merge, git rebase, git cherry-pick으로 생성·이동·병합·재배치를 처리함
  • 기록 탐색과 복구에는 git log, git blame, git show, git reflog, git reset, git restore가 포함되며, 브랜치·태그·HEAD·원격 브랜치도 커밋 참조로 쓸 수 있음
  • 원격 저장소 작업은 git remote add, git fetch, git pull, git push, git push --force-with-lease, git push --tags로 변경사항을 가져오고 보내는 절차를 다룸

저장소 시작과 현재 상태 확인

  • 새 저장소 시작:
    • git init
  • 기존 저장소 복제:
    • git clone $URL
  • 현재 위치와 상태 확인:
    • git status

커밋 준비와 생성

  • 추적되지 않는 파일 추가:
    • git add $FILE
  • 모든 추적되지 않는 파일과 스테이징되지 않은 변경사항 추가:
    • git add .
  • 파일의 일부 변경사항만 골라 스테이징:
    • git add -p
  • 파일 삭제 또는 이동:
    • git rm $FILE
    • git mv $OLD $NEW
  • 파일을 삭제하지 않고 Git 추적에서 제외:
    • git rm --cached $FILE
  • 모든 스테이징 취소:
    • git reset HEAD
  • 커밋 만들기:
    • git commit
    • 텍스트 편집기를 열어 메시지를 작성함
  • 메시지를 바로 지정해 커밋:
    • git commit -m 'message'
  • 스테이징되지 않은 변경사항까지 포함해 메시지와 함께 커밋:
    • git commit -am 'message'

브랜치 이동과 관리

  • 브랜치 전환:
    • git checkout $NAME
    • git switch $NAME
  • 브랜치 생성:
    • git checkout -b $NAME
    • git switch -c $NAME
  • 브랜치 목록 보기:
    • git branch
  • 최근 커밋 기준으로 브랜치 정렬:
    • git branch --sort=-committerdate
  • 브랜치 삭제:
    • git branch -d $NAME
  • 브랜치 강제 삭제:
    • git branch -D $NAME

기록 탐색과 코드 추적

  • 브랜치 기록 보기:
    • git log main
  • 두 브랜치 관계를 그래프로 보기:
    • git log --graph a b
  • 한 줄 로그 보기:
    • git log --oneline
  • 파일의 각 줄을 마지막으로 바꾼 사람 확인:
    • git blame $FILENAME
  • 파일을 수정한 모든 커밋 보기:
    • git log $FILENAME
  • 특정 텍스트를 추가하거나 제거한 커밋 찾기:
    • git log -S banana

변경사항 비교

  • 커밋과 부모 커밋의 차이 보기:
    • git show $COMMIT_ID
  • 머지 커밋과 병합된 부모들의 차이 보기:
    • git show --remerge-diff $COMMIT_ID
  • diff 요약 보기:
    • git diff $COMMIT_ID --stat
    • git show $COMMIT_ID --stat
  • 두 커밋 비교:
    • git diff $COMMIT_ID $COMMIT_ID
  • 특정 파일만 커밋과 비교:
    • git diff $COMMIT_ID $FILENAME
  • 스테이징된 변경과 스테이징되지 않은 변경 전체 비교:
    • git diff HEAD
  • 스테이징된 변경만 비교:
    • git diff --staged
  • 스테이징되지 않은 변경만 비교:
    • git diff

설정 파일과 옵션

  • 로컬 Git 설정 파일:
    • .git/config
  • 전역 Git 설정 파일:
    • ~/.gitconfig
  • 무시할 파일 목록:
    • .gitignore
  • 설정 옵션 지정:
    • git config user.name 'Julia'
  • 전역 옵션 지정:
    • git config --global ...
  • alias 추가:
    • git config alias.st status
  • 가능한 설정 옵션 확인:
    • man git-config

기록 수정과 파일 복원

  • 가장 최근 커밋 되돌리기, 작업 디렉터리는 그대로 유지:
    • git reset HEAD^
  • 실패한 rebase 되돌리기:
    • git reflog BRANCHNAME
    • git reset --hard $COMMIT_ID
  • 마지막 5개 커밋을 하나로 합치기:
    • git rebase -i HEAD^^^^^^
    • 합칠 커밋은 pickfixup으로 바꿈
  • 다른 브랜치나 커밋의 파일 버전 가져오기:
    • git checkout $COMMIT_ID $FILE
    • git restore $FILE --source $COMMIT_ID
  • 커밋 메시지 변경 또는 빠뜨린 파일 추가:
    • git commit --amend

브랜치 합치기

  • 다른 브랜치로 현재 브랜치를 최신화하는 fast-forward merge:
    • git switch main
    • git merge banana
  • 한 커밋을 다른 브랜치로 복사:
    • git cherry-pick $COMMIT_ID
  • 갈라진 브랜치를 rebase로 합치기:
    • git switch banana
    • git rebase main
  • 갈라진 브랜치를 merge로 합치기:
    • git switch main
    • git merge banana
    • git commit
  • 갈라진 브랜치를 squash merge로 합치기:
    • git switch main
    • git merge --squash banana
    • git commit

원격 저장소와 동기화

  • 원격 저장소 추가:
    • git remote add $NAME $URL
  • main 브랜치를 origin 원격으로 push:
    • git push origin main
  • 처음 push하는 브랜치를 origin에 올리고 추적 브랜치 설정:
    • git push -u origin $NAME
  • 현재 브랜치를 원격 추적 브랜치로 push:
    • git push
  • force push:
    • git push --force-with-lease
  • 태그 push:
    • git push --tags
  • 변경사항 가져오기, 로컬 브랜치는 변경하지 않음:
    • git fetch origin main
  • 모든 브랜치 fetch:
    • git fetch --all
  • fetch 후 현재 브랜치에 rebase:
    • git pull --rebase
  • fetch 후 현재 브랜치에 merge:
    • git pull origin main
    • git pull

커밋을 가리키는 방법

  • $COMMIT_ID 자리에는 여러 참조를 사용할 수 있음:
    • 브랜치: main
    • 태그: v0.1
    • 커밋 ID: 3e887ab
    • 원격 브랜치: origin/main
    • 현재 커밋: HEAD
    • 3개 전 커밋: HEAD^^^
    • 3개 전 커밋: HEAD~3

변경사항 버리기

  • 한 파일의 스테이징된 변경과 스테이징되지 않은 변경 모두 삭제:
    • git checkout HEAD $FILE
  • 한 파일의 스테이징되지 않은 변경 삭제:
    • git checkout $FILE
  • 모든 스테이징된 변경과 스테이징되지 않은 변경 삭제:
    • git reset --hard
  • 스테이징된 변경과 스테이징되지 않은 변경을 stash:
    • git stash
  • 추적되지 않는 파일 삭제:
    • git clean

댓글과 토론

Hacker News 의견들
  • 숨은 보석 같은 명령이 몇 가지 있음: git diff --staged 는 마지막 커밋과 현재 스테이징한 내용의 차이를 보여줘서, 파일이나 헝크를 세밀하게 올릴 때 의도한 내용만 들어갔는지 확인하기 좋음
    git log <file>은 해당 파일이 변경된 커밋만 보여줘서 전체 git log를 뒤질 필요가 줄고, 특히 자주 바뀌지 않는 파일에서 유용함
    git add --patch를 쓸 때 Git이 나눈 헝크보다 더 세밀하게 고르고 싶으면 e 옵션을 써보면 좋음. 개별 줄을 포함/제외하는 방법이 안내되어 있어서 외울 필요가 없음

    • git diff --stagedgit diff --cached의 별칭이라는 걸 이제 알았음. cached를 몇 년째 써왔는데, staged가 훨씬 직관적이긴 함. 다만 이 근육 기억을 지울 수 있을지는 모르겠음
    • 이런 명령들을 별칭으로 많이 만들어두면 시간이 꽤 절약되고, 작업을 더 잘 정리하는 장벽도 낮아짐
      관심 있으면 보거나 별칭을 제안해도 좋음: https://gitlab.com/Falimonda/gitrc
    • git config --global commit.verbose true를 설정하면 Git이 커밋 메시지 편집기의 주석 영역에 스테이징된 차이를 자동으로 넣어줌
  • 목록에 없지만 좋아하는 명령 중 하나는 git log -LstartLine,endLine:fileName
    확장된 git blame처럼 지정한 줄들이 시간에 따라 어떻게 바뀌었고 누가 바꿨는지 보여줌. 줄 번호가 바뀌거나 파일이 이동해도 추적함
    “누가, 언제, 왜 이렇게 했나?”나 “언제부터 이랬나?” 같은 질문에 답해줌

  • “컴퓨터 과학에는 어려운 일이 두 가지뿐이다. 캐시 무효화와 이름 짓기다.”라는 Phil Karlton의 말이 떠오름
    그런 관점에서 Git 명령 이름이 blame 인 건 꽤 이상하게 느껴졌고, 나만 그렇게 생각한 게 아니었음
    What does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
    Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
    Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
    Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/

    • git이라는 도구 이름을 생각하면 blame브랜드에 맞는 이름 같음
    • 동의함. 나는 설정에서 blamewho 로 바꿨음. 사실 Vim 골수 사용자라 그냥 w로 씀
  • Git을 배운 방식이 마음에 들었음. 처음에는 서드파티 Git GUI인 Sourcetree로 시작했는데, 쓰고 이해하기 아주 쉬웠음
    그다음 git gui로 넘어갔지만 이 단계는 건너뛰어도 됨. 이후 UI를 띄우는 게 귀찮아져서 터미널 명령을 배웠음
    Git 초보자에게 추천하지만, 어디까지나 내 n=1 경험임

    • Git에 대해 아는 건 clone, pull, commit, push 정도뿐이고, 지금까지 그걸로 충분했음. Git은 그렇게 단순할 수도 있음. 물론 필요하면 더 복잡한 사용법도 배울 수 있음
    • 터미널 명령으로 돌아가고 싶지는 않음. Git 저장소와 작업 흐름은 시각화했을 때 훨씬 잘 이해되기 때문에, Git은 사실상 GUI로 다루라고 요구하는 도구에 가까움
      Magit을 쓰기 시작한 순간 Git이 이해되기 시작했음
    • 나는 Sourcetree에서 Fork로 옮겼음. 매우 비슷하지만 성능이 훨씬 좋고 UI가 부드러움. 최근 몇 년 사이 Sourcetree가 개선되지 않았다면 말임
    • 비슷한 경로였지만 GitKraken과 Sublime Merge를 썼고, 복잡한 작업도 쉽게 해줘서 Sublime Merge는 계속 남겨뒀음
      Sublime Merge는 칭찬밖에 할 게 없음
  • Git이 불필요하게 복잡한 데에는 역사적 이유가 있는지 궁금함
    이 치트시트를 보니 그 복잡성이 더 실감남

    • 맞음. Git은 Linus Torvalds가 Linux 커널 개발을 가능하게 하고 발전시키려고 만든 도구였기 때문임
      이렇게 널리 쓰이게 된 건 “행복한 사고”이거나 불행일 수도 있음. GitHub가 “무료 Git 호스팅”으로 Git을 대중화한 공은 크지만, Git이 많은 프로젝트에 이상적인 버전 관리 시스템이라고는 생각하지 않음
      Linus와 Linux를 위해 만들어졌기 때문에 용어와 작업 흐름이 다른 보통의 버전 관리 시스템과 반대로 느껴질 때가 있음. Pull Request라는 이름도 Linux 개발에서 Linus나 메인테이너가 변경을 커널 트리로 끌어오기 때문임. 다른 시스템에서는 보통 Merge Request라고 부르며, 어디서 온 변경이든 트리에 병합한다는 점에서 그쪽이 훨씬 논리적임
      Git은 거대한 코드베이스를 빠르게 처리할 수 있는 거의 유일한 오픈소스 분산 버전 관리 시스템이었고, 그래서 대기업에서도 관성이 커졌음
      개인 프로젝트에는 Mercurial을 훨씬 선호하고 실제로 쓰지만, Android AOSP 같은 큰 코드베이스에서 Hg를 써보면 심하게 버벅임. Mercurial은 사용자 인터페이스와 명령 체계가 더 낫고, 변경 트리 보기를 위해 즉석에서 웹 서버를 띄우는 기능도 있지만, Python으로 작성되어 성능과 확장성은 이상적이지 않음
    • 복잡성의 일부는 초기 버전에서 새 하위 명령을 추가하는 비용이 꽤 컸기 때문임. 그 결과 git checkout 같은 기존 하위 명령에 기능이 계속 붙어서 사용자 경험이 더 혼란스러워졌음
      최신 Git에서는 새 하위 명령 추가 비용이 줄었고, git switch처럼 더 구체적인 명령을 추가하기 시작했음
    • 대부분의 작업 흐름에서는 log, add, commit, pull, push 같은 몇 가지 Git 명령만 알아도 괜찮다고 봄. GUI 도구도 있고
      솔직히 일반적인 작업에서는 Git이 그렇게 복잡하다고 생각하지 않음. 다만 필요할 때 쓸 수 있는 강력한 기능이 많이 있을 뿐임
    • Git은 불필요하게 복잡한 게 아님. Git이 지원하려고 만들어진 작업 흐름 자체가 유난히 복잡했고, 대상 사용자인 커널 개발자들은 매우 기술적이어서 복잡성이 큰 문제가 아니었음
      커널 개발자들은 탈중앙 방식으로 서로 소통하고, 서로의 작업 위에서 다시 작업해야 했음. 15,000명이 코드를 쓰는 프로젝트에서 어떻게 일할 것인가? Git은 그 목적을 위해 만들어졌음
      설계 대상 작업 흐름을 지원하는 데 필요한 만큼 복잡함. 더 단순한 도구를 만드는 건 쉽지만, 그러면 Linux 커널 개발 같은 복잡한 작업 흐름을 지원하지 못함. 그래서 불필요하게 복잡한 건 아니지만, 단순한 용도에는 필요한 것보다 복잡한 건 맞음
      덜 복잡한 프론트엔드가 필요하면 Git을 쉽게 다루게 해주는 도구가 많이 있음. 전업 소프트웨어 개발자가 아니라면 Git을 직접 쓰는 건 추천하지 않음. 하지만 전업 개발자라면 내부 개념을 이해하는 데 시간을 쓸 가치가 있고, 일단 이해하면 Git은 하는 일에 비해 꽤 우아함
      Git을 아느냐로 소프트웨어 개발자 자격을 가르지는 않겠지만, 이런 기본 시스템이 어떻게 동작하는지 스스로 파악하는 일이 도파민을 주지 않는다면 이 분야가 취향이 아닐 수도 있음. 그래도 괜찮고, 누구에게나 맞는 것과 아닌 것이 있음
      Git 이동을 배우는 데는 https://learngitbranching.js.org를 추천함. 물론 https://xkcd.com/1597/도 적용됨
    • 좋은 UI 설계는 별도의 기술이고 추가 노력이 필요해서, 대개는 그냥 무시되기 때문임
  • git add -p 를 몰랐는데 정말 유용함
    -p, --patch는 인덱스와 작업 트리 사이의 패치 헝크를 대화식으로 골라 인덱스에 추가하게 해줌. 수정 내용을 인덱스에 추가하기 전에 차이를 검토할 기회를 줌

    • 가능하면 현재 작업의 헝크를 직접 골라 일관된 커밋으로 조립함. 이렇게 하면 내가 한 일을 머릿속에서 다시 정리하는 데 도움이 되고, 누군가에게 설명해야 할 때 커밋 흐름을 따라가며 설명할 수 있음
  • 여기에 추가하고 싶은 건 git commit --fixup $COMMIT_IDgit rebase -i upstream/master --autosquash 조합임
    최근 작업 흐름에서 필수 요소가 됐음. Julia의 예시는 이전 5개 커밋을 리베이스하려고 HEAD^^^^^를 쓰지만, 나는 최근까지 HEAD~5를 썼음. 그런데 이제는 upstream HEAD까지의 모든 커밋을 그냥 리베이스할 수 있다는 걸 알게 됨

    • git commit --fixup $COMMIT_ID는 바로 도입해야겠음. 나는 항상 “oops GET users/me” 같은 작은 수정용 커밋을 만들고, 다음 대화식 리베이스에서 수동으로 옮겨 fixup했는데 이 방식이 훨씬 나음
    • 이걸 최근에야 배웠는데, Gerrit에서 스택형 패치로 작업할 때 아주 좋음
  • “마지막 5개 커밋을 하나로 합치기: git rebase -i HEAD^^^^^^”는 git rebase -i HEAD~5 가 맞지 않나 싶음
    선형 히스토리만 리베이스하려는 경우라면 그렇다는 전제임
    https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git

  • 가장 큰 깨달음은 모든 커밋이 아직 내부에 남아 있다는 걸 이해한 순간이었음. 태그와 브랜치는 단지 커밋을 가리킬 뿐이고, 브랜치가 예전에 가리키던 커밋도 사라진 게 아님
    많은 명령, 아마 거의 전부라고 해도 될 만큼의 명령은 비파괴적이라서 브랜치의 예전 상태를 쉽게 참조할 수 있음
    git reflog는 본 적이 있었지만 주로 “예전에 작업했던 그 브랜치가 뭐였지?”를 떠올리는 용도로만 썼음
    원치 않는 충돌 해결 한가운데로 들어갈 수 있는 작업들에 대해 반쯤 건강한 두려움이 생겼음. 보통 잘못된 커밋이나 브랜치를 참조해서 충돌이 나곤 했기 때문임
    그 두려움이 완전히 사라진 건 아니지만, 적어도 리베이스 같은 작업 사이에서 예전 작업을 복구하는 데는 꽤 편해졌음

    • Jujutsu는 그런 두려움을 완전히 없애줬음
      어떤 작업 뒤에 충돌이 날지 모르겠으면 그냥 해보고 확인함. 충돌이 나면 해결하거나, 충돌을 만든 작업을 되돌리거나, 다른 곳에 새 커밋을 만들고 충돌은 나중에 처리하게 둠
      충돌이 날 걸 알고 있으면 관련 브랜치를 복제한 뒤 미리 맞춰보거나, 충돌 나는 작업을 실행하고 해결 과정을 밟아봄. 해결이 지저분해지면 일부 해결만 되돌리고 계속 시도하거나, 잠시 미뤄두고 다른 일을 하거나, 전체 트리를 버릴 수 있음
  • 대놓고 말하자면, Git은 내려놓고 JJ를 봐야 함
    https://martinvonz.github.io/jj/v0.17.1/
    Linus조차 Git의 UI가 형편없다고 생각할 것 같음. JJ는 작업 흐름이 훌륭함

    • 인덱스가 없는 점에는 동의하기 어려움. 그게 더 단순한 작업 흐름도 있겠지만, 나는 좋든 나쁘든 Git의 인덱스에 너무 익숙해져서 작업 방식이 꽤 크게 바뀔 것 같음
      다만 조금 만져본 것 말고는 실제 작업에서 깊게 써본 건 아니라서 단정하긴 어려움