EXQT EXQT

Git을 쓰면서 미리 알았으면 좋았을 것들

2026-06-20

Git을 사용하면서 처음부터 알았으면 좋았을 개념과 습관을 정리해 보았습니다. Git 명령어를 외우는 것보다 브랜치와 커밋이 어떤 관계인지 이해하는 쪽이 문제를 해결할 때 훨씬 도움이 됩니다.

추천 클라이언트: TortoiseGit

Rider나 VS Code에 통합된 Git 기능도 충분히 좋지만, rebase나 cherry-pick처럼 히스토리를 직접 다루는 작업은 별도의 GUI 클라이언트가 있으면 편합니다.

TortoiseGit은 UI가 조금 투박하지만 대부분의 Git 기능을 GUI로 제공합니다. 특히 Windows에서 탐색기 컨텍스트 메뉴와 파일 아이콘 오버레이로 파일 상태를 보여주는 점이 유용합니다. 에디터 밖에서 파일을 확인하거나 Unity 프로젝트처럼 에디터 외부 도구를 자주 오갈 때 도움이 됩니다.

1. 브랜치는 커밋을 가리키는 포인터다

처음 Git을 접했을 때 가장 헷갈렸던 부분은 브랜치가 별도의 히스토리 묶음처럼 보인다는 점이었습니다. 예를 들어 main에서 feature 브랜치를 만들고 로그를 보면, feature에서 만든 커밋뿐 아니라 main의 커밋까지 한 줄로 이어져 보입니다.

이건 정상입니다. 브랜치는 커밋 하나를 가리키는 이름표에 가깝습니다. 브랜치 자체가 “어느 시점에 갈라졌는지”, “이 브랜치에 속한 커밋이 무엇인지” 같은 정보를 별도로 들고 있는 것이 아닙니다. Git은 커밋들이 부모 커밋을 가리키는 그래프를 보고 히스토리를 계산합니다.

특정 브랜치에서 새로 만든 커밋만 보고 싶다면 브랜치 이름 하나만 보는 대신 비교 기준을 함께 줘야 합니다.

git log main..feature
git log feature --not main

두 명령은 feature에서는 도달할 수 있지만 main에서는 도달할 수 없는 커밋을 보여줍니다. 브랜치가 갈라진 지점을 보고 싶다면 다음 명령도 유용합니다.

git merge-base main feature

태그와 브랜치는 둘 다 ref라는 점에서는 비슷하지만, 태그는 보통 특정 버전을 고정하기 위해 움직이지 않는 이름으로 사용하고 브랜치는 작업 흐름에 따라 계속 움직입니다. HEAD도 현재 체크아웃한 위치를 나타내는 ref이며, 보통은 브랜치를 가리키지만 detached HEAD 상태에서는 커밋을 직접 가리킬 수 있습니다.

2. 같은 브랜치를 여러 사람이 쓸 때는 pull —rebase를 고려하자

가장 깔끔한 방식은 보통 1인 1브랜치로 작업하고 Pull Request로 합치는 것입니다. 하지만 바이너리 파일, Unity Scene 파일, 대형 에셋처럼 충돌을 줄이기 위해 브랜치를 적게 가져가야 하는 프로젝트도 있습니다. 이때 여러 사람이 같은 브랜치에 직접 push하면 히스토리가 쉽게 복잡해집니다.

git pull은 기본적으로 fetch 후 merge를 수행합니다. 내가 로컬에서 커밋한 사이 다른 사람이 같은 브랜치에 push했다면, 일반 pull은 내 커밋과 원격 커밋을 합치는 merge commit을 만들 수 있습니다. 이 merge commit이 자주 쌓이면 실제 변경보다 동기화 흔적이 로그를 더 많이 차지합니다.

이럴 때 pull --rebase를 쓰면 원격 브랜치를 먼저 가져온 뒤, 내 로컬 커밋을 그 위에 다시 얹습니다.

git switch feature
git pull --rebase

조금 더 명시적으로 쓰면 다음과 같습니다.

git fetch origin
git rebase origin/feature

이 방식은 로그를 선형에 가깝게 유지하는 데 도움이 됩니다. 다만 rebase가 충돌을 없애주는 것은 아닙니다. 같은 파일의 같은 부분을 여러 사람이 수정했다면 rebase 중에도 충돌은 납니다. 특히 Unity Scene이나 바이너리 파일 충돌은 Git이 의미 있게 병합하기 어렵기 때문에 파일 잠금, 작업 영역 분리, 작은 단위의 자주 동기화 같은 운영 규칙이 더 중요합니다.

자주 쓴다면 설정으로 기본 pull 방식을 바꿀 수 있습니다.

git config --global pull.rebase true
git config --global rebase.autoStash true

주의할 점도 있습니다. rebase는 커밋을 새로 작성하는 작업입니다. 이미 다른 사람이 기반으로 삼은 공개 히스토리를 임의로 rebase하고 force push하면 다른 사람의 로컬 히스토리를 꼬이게 만들 수 있습니다. 같은 브랜치를 공유한다면 pull --rebase는 “아직 원격에 없는 내 로컬 커밋을 최신 원격 커밋 위로 올리는 용도”로 쓰고, 히스토리를 강제로 바꾸는 작업은 팀과 합의한 경우에만 해야 합니다.

충돌이 나면 파일을 수정한 뒤 다음 순서로 진행합니다.

git status
git add <resolved-file>
git rebase --continue

중간에 되돌리고 싶다면 다음 명령으로 rebase 시작 전 상태로 돌아갈 수 있습니다.

git rebase --abort

3. 필요한 커밋만 가져올 때는 cherry-pick을 쓰자

작업하다 보면 다른 브랜치에 있는 변경 사항 중 일부만 현재 브랜치로 가져와야 할 때가 있습니다. 이때 무조건 merge를 하면 원하지 않는 커밋까지 함께 들어올 수 있습니다.

git merge other-branchother-branch의 브랜치가 생성된 시점부터 현재 상태까지 커밋만 가져오는게 아닙니다. 현재 브랜치와 상대 브랜치가 갈라진 지점부터 상대 브랜치 끝까지의 변경을 현재 브랜치에 통합합니다. 그래서 두 브랜치가 같은 작업 흐름에서 자연스럽게 갈라진 관계라면 괜찮지만, 목적이 다른 브랜치에서 커밋 하나만 가져오려는 상황이라면 범위가 너무 커질 수 있습니다.

예를 들어 이런 히스토리가 있다고 합시다.

      A---B---C  feature-a
     /
M---N---O---P    main
\
 X---Y---Z  feature-b

현재 feature-b에서 feature-a를 merge하면, 의도치 않게 N 커밋의 변경 사항도 따라오게 됩니다.

이럴 때는 cherry-pick이 더 적합합니다. cherry-pick은 지정한 커밋이 만든 변경을 현재 브랜치 위에 새 커밋으로 다시 적용합니다.

git switch feature-a
git cherry-pick <commit-sha>

여러 커밋을 순서대로 가져올 수도 있습니다.

git cherry-pick <commit-a> <commit-b> <commit-c>

다만 cherry-pick도 충돌을 피하게 해주는 기능은 아닙니다. 같은 부분을 다르게 수정했다면 cherry-pick 중에도 충돌이 납니다. 또한 커밋이 서로 의존하고 있다면 하나만 가져왔을 때 빌드가 깨지거나 동작이 달라질 수 있습니다. 가져올 커밋이 독립적인 변경인지 먼저 확인하는 것이 좋습니다.

TortoiseGit을 쓴다면 Log 창에서 원하는 커밋을 선택해 cherry-pick할 수 있습니다. 여러 커밋을 가져올 때 어떤 커밋이 적용되고 어디에서 충돌이 나는지 눈으로 확인하기 쉬워서 CLI가 익숙하지 않을 때 특히 편합니다.

가장 좋은 해결책은 애초에 브랜치 전략을 잘 잡아 이런 상황을 줄이는 것입니다. 그래도 이미 다른 흐름의 브랜치에서 일부 변경만 가져와야 한다면, merge보다 cherry-pick이 더 작은 범위로 문제를 다룰 수 있습니다.

4. reflog로 사라진 커밋을 찾을 수 있다

로컬 브랜치를 실수로 삭제했는데 원격 저장소에도 남아 있지 않다면 당황하기 쉽습니다. 하지만 커밋 객체가 아직 로컬 저장소에 남아 있다면 복구할 수 있습니다.

git reflog는 브랜치나 HEAD 같은 ref가 과거에 어디를 가리켰는지 기록합니다. 예를 들어 체크아웃, commit, reset, rebase 같은 작업으로 HEAD가 이동한 이력이 남습니다. 일종의 브라우저의 히스토리 같은 셈입니다.

git reflog --date=local

원하는 커밋을 찾았다면 해당 커밋에 새 브랜치를 만들면 됩니다.

git branch recovered-work <commit-sha>
git switch recovered-work

rebase를 잘못했을 때도 같은 방식으로 이전 위치를 찾을 수 있습니다.

git reflog
git branch before-bad-rebase HEAD@{3}

git reset --hard <sha>로 되돌릴 수도 있지만, 작업 트리 변경 사항을 날릴 수 있으므로 먼저 새 브랜치를 만들어 확인하는 편이 안전합니다.

reflog가 영원히 남는 것은 아닙니다. Git의 기본 설정은 일반 reflog 항목을 90일 뒤 만료하고, 현재 브랜치 끝에서 도달할 수 없는 reflog 항목은 30일 뒤 만료합니다. 이후 git gc 같은 저장소 정리 작업이 실행되면 만료된 reflog와 도달할 수 없는 객체가 삭제될 수 있습니다.

복구가 필요하다는 것을 알았다면 최대한 빨리 커밋을 찾아 브랜치나 태그로 붙잡아 두는 것이 좋습니다.

git tag backup-before-cleanup <commit-sha>

5. 커밋하지 않은 변경 사항은 복구가 어렵다

reflog는 커밋이나 ref 이동 이력을 복구하는 데 강하지만, 커밋하지 않은 작업물을 항상 구해주지는 않습니다.

git add까지 된 파일은 Git 객체 데이터베이스에 blob으로 들어갔을 가능성이 있어 git fsck --lost-found로 일부를 찾을 수도 있습니다. 하지만 파일명이나 path 구조까지 그대로 복원되는 것은 아니고, 이미 정리된 객체라면 찾을 수 없습니다.

git fsck --lost-found

반대로 한 번도 git add나 commit을 하지 않은 워킹 트리 변경 사항은 Git이 추적한 적이 없으므로 Git만으로는 복구하기 어렵습니다. 이 경우에는 에디터 로컬 히스토리, OS 백업, IDE 백업 기능에 기대야 합니다.

그래서 위험한 작업을 하기 전에는 가볍게라도 커밋하거나 stash를 만들어 두는 습관이 좋습니다.

git status
git add -A
git commit -m "WIP: before rebase"

나중에 커밋을 정리하고 싶다면 interactive rebase나 squash로 다듬을 수 있습니다. 일단 Git 안에 들어간 작업물은 찾을 가능성이 생기지만, Git에 들어간 적 없는 작업물은 Git이 책임질 수 없습니다.