같은 작업이라도 명령에 따라 이력 모양이 달라집니다.
merge (병합 커밋 M 을 새로 만듦)
main A---B-------M
\ /
feature C---D
rebase (C, D 를 B 뒤에 C', D' 로 다시 적용)
main A---B---C'---D'merge 는 feature 의 커밋(C, D)을 그대로 두고 병합 커밋 M 만 추가합니다. rebase 는 C, D 를 새 커밋 C', D' 로 다시 만들어 해시가 바뀌고 이력이 한 줄이 됩니다.
git switch feature; git rebase main 은 feature 에만 있는 커밋(공통 조상 이후)을 임시로 떼어낸 뒤, feature 브랜치를 main 의 최신 위치로 옮기고 떼어낸 커밋을 하나씩 새로 커밋합니다. 새로 커밋하므로 해시가 바뀝니다.
원본 커밋은 저장소 안에 한동안 남아 있다가(reflog 로 확인 가능) 가비지 컬렉션 대상이 됩니다.
충돌(conflict)은 재적용 중인 커밋의 변경과 새 밑동의 내용이 같은 줄을 건드릴 때 납니다.
<<<<<<<·=======·>>>>>>> 마커를 정리합니다.git add <파일> 로 해결을 표시합니다.git rebase --continue 로 다음 커밋을 이어 적용합니다.커밋 하나를 포기하려면 git rebase --skip, 처음 상태로 완전히 되돌리려면 git rebase --abort 를 씁니다.
이미 push 해서 동료가 받아간 커밋을 rebase 하면 해시가 바뀝니다. 동료의 저장소와 이력이 어긋나 동료 쪽 push 가 거부되거나, 무심코 강제 push 하면 동료의 작업이 사라질 수 있습니다. 아직 아무도 받아가지 않은 개인 브랜치의 커밋만 rebase 합니다.
git rebase -i HEAD~4 를 실행하면 편집기에 다음과 같은 할 일 목록이 열립니다.
pick 068b10f feat: 검증 메시지 UI 추가
pick f368be4 fix typo in 검증 메시지
pick d08c2ed feat: 검증 메시지 다국어 지원
pick 2d7049f wip: 다국어 문자열 정리
# 명령: pick 그대로, reword 메시지만 수정, edit 멈춰서 수정,
# squash 합치고 메시지도 합침, fixup 합치고 메시지 버림, drop 제외줄 순서를 바꾸면 적용 순서가 바뀌고, pick 을 다른 단어로 바꾸면 그 커밋의 처리 방식이 바뀝니다. 대화형 편집이라 이 화면은 실행 결과가 아니라 예시입니다.
스크립트에서는 편집기 대신 GIT_SEQUENCE_EDITOR 에 sed 명령을 넣어 pick 을 fixup·squash 로 자동으로 바꿉니다.
export GIT_SEQUENCE_EDITOR="sed -i -e '/fix typo/s/^pick/fixup/' -e '/wip: 다국어/s/^pick/squash/'"메시지 reword 는 GIT_EDITOR 에 커밋 메시지 파일을 통째로 바꿔 쓰는 스크립트를 지정해 자동화합니다.
수정할 대상 커밋이 뭔지 알 때는 메시지를 찾아 sed 로 표시하는 대신 --fixup 을 씁니다.
git commit --fixup=<대상 커밋 해시>
git rebase -i --autosquash <대상 커밋 해시>^--fixup 은 메시지를 fixup! <대상 커밋 메시지> 로 자동으로 짓습니다. --autosquash 는 그 메시지를 보고 편집 화면에서 대상 커밋 바로 뒤로 옮기고 fixup 으로 자동 표시합니다.
| 형태 | 의미 |
|---|---|
git cherry-pick <해시> |
커밋 하나를 현재 브랜치에 복사 |
git cherry-pick A..B |
A 는 제외, B 까지 포함해 여러 개 |
git cherry-pick -x <해시> |
메시지에 원본 커밋 해시를 남김 |
git cherry-pick -n <해시> |
커밋을 만들지 않고 스테이징까지만 |
범위 A..B 는 A 를 포함하지 않으므로 A 도 옮기려면 A^..B 로 씁니다. 충돌이 나면 rebase 와 같은 순서(수정 → add → --continue)로 풉니다.
이름은 비슷하지만 쓰는 자리가 다릅니다.
| 방법 | 대상 | 결과 |
|---|---|---|
git merge --squash |
이미 나뉜 다른 브랜치 | 커밋 전체를 하나로 합쳐 내 브랜치에 커밋 |
rebase -i 의 squash |
내 브랜치 안 커밋들 | 병합 전에 브랜치 자체를 정리 |
두 방법 모두 결과 커밋은 부모가 하나뿐인 일반 커밋입니다. git log --format="%h %p" 로 확인하면 부모 해시가 하나만 찍힙니다. 병합 커밋(부모 둘)이 아닙니다.
git pull 은 내부적으로 fetch 뒤 merge 를 해서, 로컬 커밋과 원격 커밋이 갈라졌으면 병합 커밋이 매번 생깁니다. git pull --rebase 는 merge 대신 rebase 를 써서 로컬 커밋을 원격 최신 뒤로 옮기므로 이력이 계속 한 줄로 유지됩니다.
자주 pull 하는 공용 브랜치에서는 git config pull.rebase true 로 기본값을 바꿔 매번 옵션을 쓰지 않게 합니다.
--graph 는 브랜치 갈래를 선으로 그리고, --all 은 현재 브랜치뿐 아니라 모든 브랜치를 함께 보여줍니다. rebase 나 cherry-pick 전후에 이 명령으로 이력 모양이 원하는 대로 바뀌었는지 확인합니다.
* cad051f feat: 홈 화면 인사말 추가
| * 673935c feat: 로그인 폼 초기화 버튼 추가
| * eb8952d feat: 검증 메시지 다국어 지원 완료
| | * 3d0c052 fix: 홈 화면 null 체크 추가feature/greeting, feature/login, hotfix/home-null 세 브랜치가 각자 다른 지점에서 갈라진 모습이 한 화면에 보입니다.
rebase 로 이미 push 했던 브랜치의 해시가 바뀌면 일반 git push 는 거부됩니다. 강제로 밀어 넣어야 하는데, git push --force 는 원격 상태를 확인하지 않고 덮어써 동료의 새 커밋을 지울 수 있습니다. git push --force-with-lease 는 내 remote-tracking 브랜치와 실제 원격이 같을 때만 덮어씁니다.
동료가 그 사이 push 했다면 내 remote-tracking 브랜치(origin/브랜치)가 오래된 상태이므로 --force-with-lease 가 거부합니다. fetch 로 최신을 받아 확인한 뒤 다시 시도합니다.
같은 충돌을 반복해서 겪는 rebase 라면 git config rerere.enabled true 로 rerere(reuse recorded resolution)를 켭니다. 한 번 해결한 충돌 패턴을 기억해 다음에 같은 충돌이 나면 자동으로 적용합니다. rebase 로 커밋을 잘못 지우거나 잃었을 때는 git reflog 로 직전 위치를 찾아 복구합니다. 자세한 사용법은 레슨 03(되돌리기)을 참고합니다.