5.1 interactive rebase — 히스토리 편집기
git rebase -i HEAD~5 # 최근 5개 커밋 편집
git rebase -i abc1234 # 특정 커밋 이후 전부
git rebase -i --root # 최초 커밋부터에디터가 열립니다.
pick abc1234 feat: 로그인 폼
squash def5678 fix: 오타
reword ghi9012 feat: 인증 로직
drop jkl3456 wip
edit mno7890 refactor: 정리| 명령 | 동작 |
|---|---|
pick (p) | 그대로 사용 |
reword (r) | 메시지만 수정 |
edit (e) | 이 커밋에서 멈춤 (내용 수정 가능) |
squash (s) | 이전 커밋과 합침, 메시지 둘 다 유지 |
fixup (f) | 이전 커밋과 합침, 메시지 버림 ★ |
drop (d) | 커밋 삭제 |
exec (x) | 임의 명령 실행 (예: 테스트) |
줄 순서를 바꾸면 커밋 순서가 바뀝니다. 줄을 지우면 커밋이 사라집니다.
자동 fixup 워크플로:
git commit --fixup=abc1234 # "fixup! ..." 커밋 생성
git rebase -i --autosquash HEAD~5 # 자동으로 제자리에 배치interactive rebase는 push 전 내 브랜치를 정리하는 용도입니다. 공유 브랜치에서는 쓰지 마세요. 꼬였다면 언제든
git rebase --abort.
5.2 cherry-pick — 커밋만 골라 가져오기
git cherry-pick abc1234 # 커밋 1개 가져오기
git cherry-pick abc1234 def5678 # 여러 개
git cherry-pick abc1234..def5678 # 범위 (abc 제외)
git cherry-pick -n abc1234 # 커밋 없이 스테이징만
git cherry-pick -x abc1234 # 원본 해시를 메시지에 기록 ★
git cherry-pick --abort # 취소 ★hotfix를 여러 릴리스 브랜치에 전파할 때 주로 씁니다. -x를 붙이면 "cherry picked from commit abc1234"가 메시지에 남아 추적이 쉬워집니다.
남용하면 같은 변경이 서로 다른 해시로 여러 브랜치에 흩어져서 나중에 머지할 때 충돌이 납니다. 정상 경로는 merge, cherry-pick은 예외 수단으로 두세요.
5.3 bisect — 이진 탐색으로 버그 커밋 찾기
커밋 1000개 중 어디서 버그가 들어왔는지 찾을 때, 10번만 테스트하면 됩니다.
git bisect start
git bisect bad # 현재는 버그 있음
git bisect good v1.0.0 # 이 버전은 멀쩡했음
# → Git이 중간 커밋으로 이동시킴
# 테스트 후
git bisect good # 또는 git bisect bad
# → 반복
git bisect reset # 종료, 원래 위치로 ★자동화가 진짜 강력합니다.
git bisect start HEAD v1.0.0
git bisect run npm test # 테스트 스크립트가 exit 0 = good, 그 외 = bad
# → 커피 마시고 오면 범인 커밋이 나와 있음5.4 worktree — 한 저장소, 여러 작업 디렉토리
git worktree add ../hotfix hotfix/urgent # 새 폴더에 브랜치 체크아웃
git worktree add -b feature/new ../new # 브랜치 생성과 동시에
git worktree list # 목록
git worktree remove ../hotfix # 제거
git worktree prune # 정리stash → 브랜치 이동 → 작업 → 복귀 사이클 없이, 동시에 두 브랜치를 열어둘 수 있습니다. 긴급 hotfix가 들어왔는데 현재 작업을 건드리기 싫을 때, 또는 두 브랜치의 동작을 나란히 비교할 때 유용합니다. clone을 또 받는 것보다 디스크와 시간이 절약됩니다(.git을 공유).
5.5 submodule vs subtree
Submodule — 참조만 저장
git submodule add https://github.com/user/lib.git libs/lib
git submodule update --init --recursive # 클론 후 필수 ★
git clone --recurse-submodules <url> # 한 번에
git submodule update --remote # 최신으로 갱신부모 저장소는 서브모듈의 특정 커밋 해시만 기록합니다. 코드는 들어있지 않습니다. 그래서 팀원이 --init을 안 하면 폴더가 비어있는 사고가 흔합니다.
Subtree — 코드를 통째로 포함
git subtree add --prefix=libs/lib https://github.com/user/lib.git main --squash
git subtree pull --prefix=libs/lib https://github.com/user/lib.git main --squash
git subtree push --prefix=libs/lib https://github.com/user/lib.git main| Submodule | Subtree | |
|---|---|---|
| 저장 | 커밋 참조 | 실제 파일 |
| 클론 | 추가 명령 필요 | 그냥 됨 |
| 저장소 크기 | 작음 | 큼 |
| 사용자 학습 비용 | 높음 | 낮음 |
| 원본에 기여 | 쉬움 | 번거로움 |
실무 조언: 요즘은 둘 다 피하고 패키지 매니저(npm/pip/maven)나 모노레포 도구를 쓰는 편이 낫습니다. 서브모듈은 사내 공통 라이브러리를 소스 레벨로 공유해야 하는 폐쇄망 같은 제약 환경에서 여전히 쓰입니다.
5.6 대용량 저장소 다루기
# 얕은 클론 — 최근 커밋만 (CI에서 매우 유용)
git clone --depth 1 <url>
git fetch --unshallow # 나중에 전체 히스토리 받기
# 특정 브랜치만
git clone --single-branch --branch main <url>
# 필요한 디렉토리만 체크아웃
git clone --filter=blob:none --sparse <url>
cd repo
# Git 2.37부터 cone 모드가 기본값이라 --cone은 붙이지 않아도 됩니다(그 아래 버전에서는 필요합니다).
git sparse-checkout set src/ docs/
git sparse-checkout list
git sparse-checkout disablegit sparse-checkout은 Git 문서상 아직 실험적(EXPERIMENTAL) 명령으로 표시돼 있어 동작이 바뀔 수 있습니다.
Git LFS — 대용량 바이너리
git lfs install
git lfs track "*.psd"
git lfs track "*.mp4"
git add .gitattributes # 반드시 커밋할 것 ★
git lfs ls-filesGit은 바이너리에도 압축을 시도하지만 .psd·.mp4처럼 이미 압축된 포맷은 버전 간 차이가 거의 잡히지 않아, 100MB짜리 파일을 10번 수정하면 저장소가 1GB 가까이 불어납니다. LFS는 실제 파일을 별도 서버에 두고 포인터만 커밋합니다. (GitHub Free·Pro는 LFS 저장 10 GiB와 월 다운로드 대역폭 10 GiB가 포함되고, 초과분은 사용량 기반으로 과금됩니다. 예전의 선불 데이터팩 방식은 폐지됐습니다.)
5.7 히스토리에서 민감 정보 제거
API 키를 실수로 커밋했다면:
# 1순위 — 키를 즉시 폐기하고 재발급 ★★★
# 히스토리를 지워도 누군가 이미 clone/fork 했을 수 있습니다
# 2순위 — 히스토리 정리 (Git 공식 문서가 filter-branch 사용을 권장하지 않고 git-filter-repo를 안내합니다)
pip install git-filter-repo
# filter-repo는 "갓 clone한 사본"이 아니면 실행 자체를 거부합니다. HEAD reflog 항목이 2개 이상이면 걸리므로
# 한 번이라도 커밋하고 push한 작업 저장소는 거의 전부 해당됩니다. 그래서 새로 clone한 사본에서 시작합니다.
# 에러 메시지가 안내하는 --force는 이 안전장치를 끄는 옵션입니다. 따로 백업해 둔 사본이 아니면 붙이지 마세요.
git clone https://github.com/user/repo.git repo-clean
cd repo-clean
git filter-repo --path .env --invert-paths # 파일 제거
git filter-repo --replace-text secrets.txt # 문자열 치환
# 3순위 — 강제 푸시 (팀 전원에게 사전 공지 필수 ⚫)
# filter-repo는 실수로 되밀지 못하게 origin을 지웁니다. 다시 등록하고 fetch부터 해야 합니다.
git remote add origin https://github.com/user/repo.git
git fetch origin
# 히스토리를 통째로 다시 쓴 직후에는 --force-if-includes가 반드시 거부합니다(원격의 옛 끝점이
# 새 히스토리에 통합된 적이 없기 때문). 이 한 번은 --force-with-lease까지만 붙입니다.
git push --force-with-lease --all
git push --force-with-lease --tags평소 force push의 기본값은 5편이 권하는 --force-with-lease --force-if-includes 그대로 두세요. 예외는 방금처럼 히스토리를 통째로 다시 쓴 직후 한 번뿐이고, 그때는 --force-if-includes만 뺍니다. --force로 내려가는 것이 아닙니다.
이 작업은 모든 커밋 해시를 바꿉니다. 팀원들은 기존 로컬을 버리고 다시 clone 해야 합니다. 작업 전에 반드시 공지하고, 진행 중인 PR이 없는 시점을 고르세요.
GitHub의 Push protection은 알려진 형식의 토큰이 들어간 push 자체를 차단합니다. 개인 계정에는 2024년 2월부터 사용자 단위로 기본 활성화돼 있고, 개인 소유의 새 공개 저장소는 2024년 3월부터 저장소 단위로도 기본 활성화됩니다. 반면 조직 소유 저장소·기존 저장소·비공개 저장소는 직접 켜야 합니다(비공개 저장소는 GitHub Secret Protection이 필요합니다). 사후 대응보다 이쪽이 훨씬 낫습니다.
5.8 Git Hooks
.git/hooks/에 있는 스크립트로, 특정 시점에 자동 실행됩니다.
| 훅 | 시점 | 용도 |
|---|---|---|
pre-commit | 커밋 직전 | 린트, 포맷, 테스트 |
commit-msg | 메시지 작성 후 | 메시지 형식 검증 |
pre-push | push 직전 | 테스트 실행 |
post-merge | 머지 후 | 의존성 재설치 |
.git/hooks/는 커밋되지 않으므로 팀 공유가 안 됩니다. 그래서 도구를 씁니다.
# Node.js — husky + lint-staged
npm install -D husky lint-staged
npx husky init
echo "npx lint-staged" > .husky/pre-commit # init이 만들어둔 기본 내용(npm test)을 덮어씁니다{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}# 언어 무관 — pre-commit (Python 기반)
pip install pre-commit
pre-commit install# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- id: detect-private-key # 키 커밋 방지 ★rev는 pre-commit autoupdate로 최신 태그에 맞춰 갱신할 수 있습니다.
훅은 우회 가능하다는 점(--no-verify)을 기억하세요. 훅은 편의 장치이고, 진짜 강제는 CI와 브랜치 보호 규칙으로 해야 합니다.
더 깊이
- 커밋된 비밀키를 사후에 지우는 것보다 앞단에서 막는 이야기는 풀스택 개발자를 위한 CI/CD 9강 — 공급망 보안에 있습니다.