| 증상 | 원인 | 명령 |
|---|---|---|
브랜치 이름이 안 보이고 HEAD detached |
커밋 해시로 checkout | git switch -c <새브랜치> |
| 커밋을 엉뚱한 브랜치에 했음 | 브랜치 전환을 깜빡함 | git cherry-pick, git reset |
| 비밀번호·키를 push 까지 함 | 민감 파일을 실수로 add | 키 폐기 + filter-repo |
| clone·push 가 느리고 저장소가 큼 | 큰 파일이 히스토리에 남음 | count-objects, rev-list, filter-repo |
| 어느 커밋부터 버그가 생겼는지 모름 | 커밋이 많고 재현이 느림 | git bisect run |
| 이 줄을 누가 왜 바꿨는지 모름 | 변경 이력 확인 안 함 | git blame, git log -S |
| merge·rebase 도중 꼬임 | 충돌 해결 중 잘못 조작 | git merge --abort, git rebase --abort |
Unable to create '.git/index.lock' |
이전 git 프로세스가 남음 | 프로세스 확인 후 lock 삭제 |
| 한글 파일명이 8진수로 깨져 보임 | core.quotepath 기본값 |
git config core.quotepath false |
| 한 줄만 고쳤는데 파일 전체가 바뀐 것처럼 보임 | 줄 끝(CRLF/LF) 차이 | git diff -w |
git checkout <해시> 처럼 브랜치 이름이 아닌 지점으로 이동하면 HEAD 가 브랜치 대신 커밋을 직접 가리킵니다. 이 상태를 detached HEAD 라 합니다. 여기서 만든 커밋은 어떤 브랜치에도 속하지 않아, 다른 브랜치로 이동하면 그 커밋을 가리키는 이름이 사라지고 결국 gc 대상이 됩니다.
구해내는 방법은 git switch -c <새브랜치> 하나뿐입니다. 지금 HEAD 가 가리키는 커밋에 새 브랜치 이름을 붙여 즉시 안전하게 만듭니다.
두 가지 방법이 있습니다. 상황에 따라 고릅니다.
| 방법 | 언제 쓰는가 |
|---|---|
git cherry-pick |
옮길 브랜치가 이미 있고 그 사이에 다른 커밋도 있음 |
git branch + git reset --hard |
옮길 브랜치가 아직 없고 지금 위치를 그대로 살리면 됨 |
옮길 브랜치가 없다면 git branch feature <해시> 로 지금 위치에 브랜치를 만든 뒤 git reset --hard HEAD~N 으로 잘못된 브랜치에서 커밋을 지우는 쪽이 더 짧습니다. 이미 갈라진 브랜치로 옮길 때는 cherry-pick 이 필요합니다.
git revert 는 최신 상태에서 파일 내용을 없앨 뿐, 과거 커밋 안의 값은 히스토리에 그대로 남습니다. clone 이나 fetch 로 과거 커밋을 받으면 여전히 드러나므로 되돌리기로는 부족합니다.
대응 순서는 다음과 같습니다.
git filter-repo --path <파일> --invert-paths 를 씁니다. 더 간단한 대안 도구로 BFG(bfg --delete-files <파일>)도 있습니다.git count-objects -vH 로 저장소 전체 크기를 먼저 확인합니다. 큰 원인을 찾으려면 git rev-list --objects --all 과 git cat-file --batch-check 를 묶어 blob 을 크기순으로 훑습니다(4.1 에서 실제 실행).
큰 blob 을 찾았으면 2.4 와 같은 git filter-repo 로 히스토리에서 제거합니다.
재발 방지는 .gitignore 로 빌드 산출물을 미리 막고, pre-commit 훅으로 파일 크기를 검사합니다(레슨 07 참고). 원래 큰 바이너리가 필요하면 Git LFS 를 씁니다.
커밋이 많고 눈으로 하나씩 확인하기 힘들 때 이분 탐색으로 첫 번째 "나쁜" 커밋을 찾습니다.
| 명령 | 동작 |
|---|---|
git bisect start |
탐색 시작 |
git bisect bad [커밋] |
이 커밋(기본 HEAD)은 버그 있음 |
git bisect good <커밋> |
이 커밋은 버그 없음(정상) |
git bisect run <스크립트> |
스크립트 종료 코드로 자동 판정(0=good, 1=bad) |
git bisect reset |
탐색 종료, 원래 브랜치로 복귀 |
run 에 넘기는 스크립트는 현재 checkout 된 파일을 검사해 정상이면 0, 버그면 1(또는 다른 0이 아닌 값)을 반환하면 됩니다. git 이 good·bad 사이를 자동으로 checkout 하며 반복합니다.
git blame <파일> 은 파일의 각 줄을 마지막으로 바꾼 커밋을 보여줍니다. 자주 쓰는 옵션은 다음과 같습니다.
| 옵션 | 동작 |
|---|---|
-L 10,20 |
10~20 번째 줄만 |
-w |
공백만 바뀐 줄은 이전 커밋 그대로 표시 |
--since=2026-01-01 |
그 날짜 이후 변경만 추적 |
blame 은 "누가·언제" 는 바로 보여주지만 "왜" 는 알려주지 않습니다. 이유는 그 커밋의 메시지나 연결된 PR 을 함께 봐야 합니다.
| 명령 | 동작 |
|---|---|
git log -S"문자열" |
그 문자열의 등장 횟수가 바뀐 커밋(pickaxe) |
git log -G"정규식" |
정규식과 매치되는 줄이 추가·삭제된 커밋 |
git log --follow -- <파일> |
이름이 바뀌어도 그 전 이력까지 추적 |
git log -- <경로> |
특정 파일·폴더의 커밋만 |
-S 는 문자열이 나타나거나 사라진 커밋을, -G 는 그 문자열을 포함한 줄이 바뀐 커밋을 찾습니다. 문자열이 그대로인데 다른 부분만 바뀐 줄도 -G 는 잡아낼 수 있습니다. 지금 저장소 전체에서 검색할 때는 git grep -n "문자열"(과거 커밋은 -- <커밋> 추가)을 씁니다.
충돌이 나면 git status 가 지금 무엇을 해야 하는지 힌트를 함께 보여줍니다. 해결이 막막하면 무리하지 말고 git merge --abort·git rebase --abort·git cherry-pick --abort 로 시작 전 상태로 되돌립니다.
Unable to create '.git/index.lock': File exists 는 이전 git 프로세스가 비정상 종료했거나 다른 터미널에서 아직 실행 중이라는 뜻입니다. 다른 프로세스가 정말 없는지 확인한 뒤에만 lock 파일을 지웁니다.
한글 파일명이 "\355\225..." 처럼 깨져 보이면 git config core.quotepath false 를 설정합니다. 한 줄만 고쳤는데 파일 전체가 바뀐 것처럼 보이면 CRLF·LF 줄 끝 차이이므로 git diff -w 로 실제 변경만 확인합니다.
저장소 차원의 근본 해결은 .gitattributes 이며 레슨 07 에서 다뤘습니다.
git fsck 는 깨진 오브젝트나 매달린 커밋을 검사하고, git gc 는 느슨한 오브젝트를 정리해 압축합니다. 둘 다 충돌 해결 도구가 아니라 저장소 정합성 점검 도구입니다. 그리고 무엇을 되돌리든, 실수를 알아챈 첫 행동은 항상 git reflog 확인입니다.