시리즈 보너스 편입니다. 6편에서 개념만 짚은 rebase를 실제로 써 보고(충돌 풀기, pull --rebase, --force-with-lease, rebase -i로 커밋 정리, reflog로 되돌리기), 다른 브랜치의 커밋 하나만 가져오는 cherry-pick, 그리고 과거 커밋을 열어 보고 파일을 되살리는 법과 detached HEAD 경고의 뜻까지 git 2.55 실제 출력으로 따라갑니다.
7편까지 오면서 민지는 커밋하고, push·pull하고, 브랜치를 나누고, 충돌을 풀고, Pull Request로 리뷰를 주고받고, AI 에이전트에게 일을 맡기는 법까지 익혔습니다. 여기까지만 알아도 팀에서 일하는 데 부족함이 없습니다.
그런데 실제로 일하다 보면 이런 말을 듣게 됩니다.
도윤: "디저트 메뉴 PR 올리기 전에 main 위로 rebase해 주세요. '오타 수정'이랑 'WIP 메모' 커밋은 정리하고요."
도윤: "제 겨울 메뉴 브랜치에 카페라테 가격 고친 커밋 있잖아요. 그거 하나만 main에 먼저 넣어야 해요."
사장님: "지난달 메뉴판에 카페라테가 얼마였죠?"
이번 편은 이 세 가지 부탁을 차례로 해결하는 보너스 편입니다. 첫째는 rebase(커밋을 최신 main 위로 옮기고 정리하기), 둘째는 cherry-pick(커밋 하나만 골라 가져오기), 셋째는 과거 커밋 둘러보기(옛 파일 열어 보기, 되살리기, detached HEAD)입니다. 모르고 지나가도 되지만, 알면 한결 편해지는 도구들입니다.
이 글의 기준 — 터미널 출력은 모두 git 2.55에서 직접 실행해 얻은 것입니다. GitHub 대신 내 컴퓨터에 만든 가짜 원격 저장소(git init --bare)로 재현했고, 출력 속 원격 주소만 https://github.com/minji/cafe-menu.git으로 바꿔 적었습니다. rebase 도중에는 진행률(Rebasing (1/4))이 한 줄에서 덮어쓰이며 지나가므로, 여기서는 화면에 최종적으로 남는 줄만 옮겼습니다.
1. merge와 rebase, 한 장으로 다시 보기
6편의 「main이 먼저 앞서 나갔다면」 절에서 브랜치를 최신 main에 맞추는 방법 두 가지를 봤습니다. 복습 삼아 한 문장씩 정리하면 이렇습니다.
merge: 두 갈래를 그대로 두고 머지 커밋으로 묶는다. 기존 커밋은 하나도 바뀌지 않습니다.
rebase: 내 커밋들을 떼어서 최신 main 끝에 하나씩 다시 붙인다. 내용은 같지만 커밋이 새로 만들어지므로 해시가 바뀝니다.
rebase를 비유하면 "기초 공사를 새로 한 땅으로 집을 통째로 옮겨 짓기"입니다. 설계도(변경 내용)는 같지만, 새 땅 위에 새로 지은 집이라 주소(해시)가 달라집니다.
이번 편의 상황을 먼저 봅시다. 민지는 feature/dessert-menu 브랜치에 커밋 네 개를 쌓아 GitHub에 push해 두었습니다. 그사이 도윤이 main에 README 커밋을 하나 올렸습니다. 아래 위젯에서 merge로 맞출 때와 rebase로 맞출 때의 그래프를 비교해 보세요. 해시는 뒤에서 직접 재현할 값과 같습니다.
비교
git merge main
git rebase main
기존 커밋
그대로 (해시 유지)
새로 만들어짐 (해시 바뀜)
새로 생기는 것
머지 커밋 1개
없음 (커밋 수 그대로)
역사 모양
갈라졌다 합쳐진 흔적
한 줄
이미 push한 브랜치
그냥 push
강제 push 필요
함께 쓰는 브랜치
안전
쓰지 않는다
6편에서 "입문 단계에서는 merge만으로 충분하다"고 했던 말은 지금도 유효합니다. 이번 편은 팀이 rebase를 쓰자고 할 때, 또는 PR 전에 커밋을 정리하고 싶을 때 당황하지 않도록 실제 사용법을 익히는 편입니다.
From https://github.com/minji/cafe-menu
f373b8c..c41bade main -> origin/main
Updating f373b8c..c41bade
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
text
* c41bade README에 영업시간 추가
| * bb91f89 WIP 메모
| * c699520 오타 수정
| * 501fe39 마들렌 추가
| * 2177a68 디저트 메뉴 섹션 추가
|/
* f373b8c 메뉴에 카페라테 추가
* 92229b6 카페 웹사이트 첫 버전
민지의 네 커밋은 f373b8c에서 갈라져 나왔고, main은 그 뒤에 c41bade가 더 붙었습니다. 이제 rebase합니다. "지금 브랜치를 main 위로 옮겨 달라" 는 뜻입니다.
bash
git rebase main
text
Successfully rebased and updated refs/heads/feature/dessert-menu.
bash
git log --oneline --graph --all
text
* ba07aef WIP 메모
* 26e5339 오타 수정
* c15b8dc 마들렌 추가
* fd00b71 디저트 메뉴 섹션 추가
* c41bade README에 영업시간 추가
| * bb91f89 WIP 메모
| * c699520 오타 수정
| * 501fe39 마들렌 추가
| * 2177a68 디저트 메뉴 섹션 추가
|/
* f373b8c 메뉴에 카페라테 추가
* 92229b6 카페 웹사이트 첫 버전
두 가지를 눈여겨보세요.
민지의 커밋 네 개가 c41bade위에 한 줄로 다시 쌓였습니다. 처음부터 최신 main에서 작업을 시작한 것처럼 보입니다.
해시가 전부 바뀌었습니다. 2177a68 → fd00b71, bb91f89 → ba07aef. 아래쪽에 옛 커밋이 아직 보이는 이유는 --all이 GitHub에 올려 둔 옛 브랜치(origin/feature/dessert-menu) 까지 그리기 때문입니다.
git status도 이 사실을 알려 줍니다.
bash
git status
text
On branch feature/dessert-menu
Your branch and 'origin/feature/dessert-menu' have diverged,
and have 5 and 4 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)
nothing to commit, working tree clean
"갈라졌다(diverged)"는 3편에서 본 그 메시지입니다. 내 쪽엔 새 커밋 5개(도윤의 README + 다시 만든 4개), 원격 쪽엔 옛 커밋 4개가 있다는 뜻입니다. 여기서 안내대로 git pull을 하면 안 됩니다. 옛 커밋과 새 커밋이 머지되어 같은 변경이 두 번씩 들어간 복잡한 역사가 됩니다. rebase한 브랜치를 올리는 올바른 방법은 5장에서 다룹니다.
3. rebase 중 충돌: 커밋 하나씩 풀어 나간다
rebase는 커밋을 하나씩 차례로 다시 붙입니다. 그래서 충돌도 커밋 단위로 멈춥니다. 도윤이 이번에는 menu.md 맨 아래에 레몬에이드를 추가해 main에 올렸고, 민지의 첫 커밋도 같은 자리에 디저트 섹션을 붙였습니다. main을 받고 다시 rebase하면,
bash
git rebase main
text
Auto-merging menu.md
CONFLICT (content): Merge conflict in menu.md
error: could not apply fd00b71... 디저트 메뉴 섹션 추가
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply fd00b71... # 디저트 메뉴 섹션 추가
첫 번째 커밋 fd00b71을 붙이다가 멈췄습니다. 선택지는 세 가지입니다. 충돌을 풀고 --continue, 이 커밋은 건너뛰는 --skip, 전부 없던 일로 하는 --abort. 지금 어디쯤인지는 git status가 자세히 알려 줍니다.
bash
git status
text
interactive rebase in progress; onto 2b2ac59
Last command done (1 command done):
pick fd00b71 # 디저트 메뉴 섹션 추가
Next commands to do (3 remaining commands):
pick c15b8dc # 마들렌 추가
pick 26e5339 # 오타 수정
(use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'feature/dessert-menu' on '2b2ac59'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: index.html
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: menu.md
한 줄씩 읽으면 이렇습니다.
onto 2b2ac59 — 도윤의 레몬에이드 커밋 위로 옮기는 중
Last command done — 방금 붙이려던 커밋(여기서 멈춤)
Next commands to do (3 remaining commands) — 남은 커밋. 목록은 두 줄까지만 보여 줍니다
Changes to be committed: index.html — 같은 커밋에 들어 있던 다른 파일(첫 화면의 "디저트 메뉴가 생겻어요!" 안내 문구)은 문제없이 붙었음
rebase 중에는 HEAD가 "내 것"이 아닙니다. merge할 때 HEAD 쪽은 내 브랜치였죠(5편). rebase는 main 위에서 내 커밋을 하나씩 다시 붙이는 작업이라, <<<<<<< HEAD 쪽이 main(과 지금까지 붙인 커밋), >>>>>>> fd00b71 쪽이 내가 붙이려는 커밋입니다. 같은 이유로 5편의 --ours/--theirs도 뒤바뀝니다. rebase 중 git checkout --ours menu.md는 main 쪽, --theirs는 내 커밋 쪽을 가져옵니다(직접 확인했습니다).
일단 멈추고 싶다면: git rebase --abort
당황스러우면 언제든 처음으로 돌아갈 수 있습니다.
bash
git rebase --abort
git status
git log --oneline -3
text
On branch feature/dessert-menu
Your branch and 'origin/feature/dessert-menu' have diverged,
and have 5 and 4 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)
nothing to commit, working tree clean
text
ba07aef WIP 메모
26e5339 오타 수정
c15b8dc 마들렌 추가
rebase를 시작하기 직전 그대로입니다. 해시도 ba07aef로 돌아왔습니다.
풀고 이어 가기: 고치기 → git add → git rebase --continue
다시 git rebase main을 실행해 같은 충돌에서 멈춘 뒤, 두 사람의 의도를 모두 살려 마커를 지웁니다.
그다음은 5편과 거의 같습니다. 다른 점은 마지막에 git commit이 아니라 git rebase --continue 를 친다는 것 하나입니다.
bash
git add menu.md
git status
text
interactive rebase in progress; onto 2b2ac59
Last command done (1 command done):
pick fd00b71 # 디저트 메뉴 섹션 추가
Next commands to do (3 remaining commands):
pick c15b8dc # 마들렌 추가
pick 26e5339 # 오타 수정
(use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'feature/dessert-menu' on '2b2ac59'.
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: index.html
modified: menu.md
bash
git rebase --continue
text
[detached HEAD 07104d1] 디저트 메뉴 섹션 추가
2 files changed, 4 insertions(+)
Successfully rebased and updated refs/heads/feature/dessert-menu.
--continue를 치면 커밋 메시지 편집기가 열릴 수 있습니다. 원래 메시지가 적혀 있으니 그대로 저장하고 닫으면 됩니다. 남은 세 커밋은 충돌 없이 붙어서 끝났습니다. 출력의 [detached HEAD 07104d1]은 rebase가 작업하는 동안 잠시 브랜치에서 떨어진 상태로 커밋을 만들기 때문에 보이는 표시입니다. 이 "detached HEAD"는 11장에서 자세히 봅니다.
bash
git log --oneline --graph --all -8
text
* 4fe71b7 WIP 메모
* c07c7b1 오타 수정
* a82e8f5 마들렌 추가
* 07104d1 디저트 메뉴 섹션 추가
* 2b2ac59 메뉴에 레몬에이드 추가
* c41bade README에 영업시간 추가
| * bb91f89 WIP 메모
| * c699520 오타 수정
💡
커밋이 많으면 충돌도 여러 번일 수 있습니다. merge는 충돌을 한 번에 모아서 풀지만, rebase는 커밋마다 멈춥니다. 같은 줄을 여러 커밋에서 계속 고쳤다면 비슷한 충돌을 몇 번 반복해서 풀어야 할 수도 있습니다. 너무 번거롭다 싶으면 --abort하고 merge로 최신화해도 전혀 문제없습니다.
4. git pull --rebase와 pull.rebase 설정: 팀이 하나로 정하기
3편의 「divergent branches」 절에서 git pull이 "어떻게 합칠지 정해 달라"며 멈췄던 장면을 기억하시나요? 그때 이 시리즈는 pull.rebase false(merge 방식)를 골랐습니다. 이번에는 다른 선택지인 rebase 방식 pull을 직접 써 봅니다.
민지가 main에서 README에 문의 메일을 추가해 커밋했는데, 그사이 도윤도 README에 주소를 추가해 push했습니다. 역사가 갈라진 상태입니다. (브랜치 보호 규칙이 없는 작은 저장소에서 main에 바로 커밋하는 경우라고 생각하세요. 6편의 PR 흐름을 쓰는 팀이라면 내 브랜치에서 똑같이 일어나는 일입니다.)
bash
git pull --rebase
text
From https://github.com/minji/cafe-menu
2b2ac59..4261b5c main -> origin/main
Successfully rebased and updated refs/heads/main.
bash
git log --oneline --graph -5
text
* b5d5628 README에 문의 메일 추가
* 4261b5c README에 주소 추가
* 2b2ac59 메뉴에 레몬에이드 추가
* c41bade README에 영업시간 추가
* f373b8c 메뉴에 카페라테 추가
머지 커밋 없이 민지의 커밋이 도윤의 커밋 뒤로 옮겨져 한 줄이 됐습니다. 이제 평범한 git push로 올라갑니다. 아직 push하지 않은 내 로컬 커밋만 새로 만들었기 때문에 강제 push가 필요 없습니다.
bash
git push
text
To https://github.com/minji/cafe-menu.git
4261b5c..b5d5628 main -> main
둘 다 괜찮은 선택입니다. 중요한 건 팀이 하나로 정해서 모두가 같은 방식으로 pull하는 것입니다. 사람마다 설정이 다르면 main의 역사가 어떤 곳은 머지 커밋, 어떤 곳은 한 줄로 뒤섞입니다. 이 시리즈를 따라온 독자라면 3편의 설정(false)을 그대로 두고, 필요할 때만 git pull --rebase를 쓰는 것도 좋은 방법입니다. 되돌리는 명령은 git config --global pull.rebase false입니다.
5. rebase한 브랜치 올리기: --force-with-lease
2~3장에서 rebase한 feature/dessert-menu는 이미 GitHub에 옛 커밋으로 올라가 있습니다. 그냥 push하면,
bash
git push
text
To https://github.com/minji/cafe-menu.git
! [rejected] feature/dessert-menu -> feature/dessert-menu (non-fast-forward)
error: failed to push some refs to 'https://github.com/minji/cafe-menu.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
거절됩니다. 원격의 bb91f89(옛 WIP 메모)가 내 새 커밋들의 조상이 아니기 때문입니다. push는 "이어 붙이기"만 허락하는데, rebase는 옛 커밋을 새 커밋으로 바꿔치기했으니까요. 힌트는 pull하라고 하지만, 2장 끝에서 말했듯 rebase 직후라면 pull이 답이 아닙니다. 원격의 옛 커밋을 새 커밋으로 덮어써야 합니다. 그 명령이 강제 push입니다.
bash
git push --force-with-lease
text
To https://github.com/minji/cafe-menu.git
+ bb91f89...4fe71b7 feature/dessert-menu -> feature/dessert-menu (forced update)
+와 (forced update)가 "덮어썼다"는 표시입니다. 강제 push에는 두 가지가 있습니다.
명령
동작
쓸 때
git push --force
원격에 무엇이 있든 무조건 내 것으로 덮어씀
쓰지 않는다
git push --force-with-lease
원격이 내가 마지막으로 본 상태 그대로일 때만 덮어씀. 그사이 누가 push했으면 (stale info)로 거절
rebase한 내 브랜치를 올릴 때
--force-with-lease가 동료의 커밋을 지켜 주는 장면은 7편의 「위험한 명령과 더 안전한 대안」에서 직접 재현했습니다. 그래도 lease는 "내가 마지막으로 fetch한 상태"를 기준으로 하므로, 그 뒤에 fetch만 해 두고 내용을 확인하지 않았다면 동료의 커밋을 덮어쓸 수 있습니다. 그래서 규칙은 이렇게 기억하세요.
⚠️
강제 push는 나 혼자 쓰는 브랜치에만. main이나 동료와 함께 커밋하는 브랜치에는 절대 쓰지 않습니다. 저장소 설정에서 main 보호 규칙을 켜 두면(6편) 실수로라도 main에 강제 push가 들어가지 않습니다. 이미 리뷰 중인 PR의 브랜치에 강제 push하면 PR의 커밋이 새 해시의 커밋들로 바뀌니, 리뷰어에게 "rebase했어요"라고 한 줄 남겨 주는 것이 예의입니다.
6. git rebase -i: PR 올리기 전에 커밋 정리하기
도윤의 두 번째 부탁은 "'오타 수정'이랑 'WIP 메모' 커밋은 정리해 달라"였습니다. 지금 브랜치에만 있는 커밋을 봅시다.
bash
git log --oneline main..
text
4fe71b7 WIP 메모
c07c7b1 오타 수정
a82e8f5 마들렌 추가
07104d1 디저트 메뉴 섹션 추가
main..은 "main에는 없고 지금 브랜치에만 있는 커밋"이라는 뜻입니다(7편에서 본 main..브랜치의 줄임). 리뷰어 입장에서 보면 아쉬운 점이 셋입니다. 마들렌 추가에 오타가 있어서 바로 다음 커밋이 오타 수정이고, WIP 메모는 PR에 들어갈 필요가 없는 개인 메모이며, 첫 커밋 메시지는 무엇이 추가됐는지 더 구체적이면 좋겠습니다.
이럴 때 쓰는 것이 interactive rebase(대화형 리베이스), git rebase -i입니다. 같은 자리(main) 위로 rebase하되, 커밋을 하나씩 어떻게 다시 붙일지 목록(todo)을 직접 고쳐서 지시합니다.
bash
git rebase -i main
실행하면 편집기가 열리고 이런 목록이 나옵니다. 위가 오래된 커밋이라 git log와 순서가 반대입니다.
text
pick 07104d1 # 디저트 메뉴 섹션 추가
pick a82e8f5 # 마들렌 추가
pick c07c7b1 # 오타 수정
pick 4fe71b7 # WIP 메모
# Rebase 2b2ac59..4fe71b7 onto 2b2ac59 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
(… label · reset · merge · update-ref 설명 생략 …)
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
맨 앞 단어가 각 커밋에 내리는 명령입니다. 입문자에게 필요한 것은 다섯 개입니다.
명령
뜻
이럴 때
pick (p)
그대로 쓴다
손댈 필요 없는 커밋
reword (r)
쓰되 메시지만 고친다. 그 커밋 차례에 편집기가 한 번 더 열림
메시지가 모호하거나 오타가 있을 때
squash (s)
바로 위 커밋에 합친다. 두 메시지를 합쳐 편집
둘 다 의미 있는 메시지를 하나로 묶을 때
fixup (f)
바로 위 커밋에 합친다. 이 커밋 메시지는 버림
"오타 수정", "리뷰 반영" 같은 자잘한 커밋
drop (d)
커밋을 뺀다 (줄을 지워도 같음)
PR에 넣지 않을 임시 커밋
줄의 순서를 바꾸면 커밋 순서도 바뀝니다. 민지는 목록을 이렇게 고치고 저장했습니다.
text
reword 07104d1 # 디저트 메뉴 섹션 추가
pick a82e8f5 # 마들렌 추가
fixup c07c7b1 # 오타 수정
drop 4fe71b7 # WIP 메모
오타 수정은 바로 위 마들렌 추가에 흡수되고, WIP 메모는 빠지고, 첫 커밋은 메시지를 고칩니다. 저장하고 닫으면 reword 차례에 메시지 편집기가 열리고, 민지는 메시지를 메뉴에 디저트 섹션과 치즈케이크 추가로 바꿨습니다.
text
[detached HEAD 3e7c353] 메뉴에 디저트 섹션과 치즈케이크 추가
Date: Tue Oct 6 10:09:00 2026 +0900
2 files changed, 4 insertions(+)
Successfully rebased and updated refs/heads/feature/dessert-menu.
bash
git log --oneline main..
ls
text
2308fa3 마들렌 추가
3e7c353 메뉴에 디저트 섹션과 치즈케이크 추가
text
README.md
index.html
menu.md
커밋 네 개가 깔끔한 두 개가 됐고, notes.txt는 drop과 함께 사라졌습니다. menu.md에는 오타가 고쳐진 - 마들렌 3,000원이 들어 있습니다. 리뷰어는 이제 "무엇을, 왜" 바꿨는지만 담긴 커밋 두 개를 보면 됩니다.
🧪
이 글의 재현 방법에 대해 — rebase -i는 편집기를 여는 명령이라 자동으로 실행할 수 없습니다. 그래서 위 결과는 편집기 대신 목록을 고쳐 주는 명령을 지정해서 재현했습니다. 목록 화면은 GIT_SEQUENCE_EDITOR=cat으로 출력했고, 실제 수정은 GIT_SEQUENCE_EDITOR="sed -i '' -e '1s/^pick/reword/' -e '3s/^pick/fixup/' -e '4s/^pick/drop/'"처럼 sed로 줄 머리를 바꿔 실행했습니다(메시지 편집기는 GIT_EDITOR로 같은 방식). 여러분은 평소처럼 편집기에서 단어를 고치고 저장하면 됩니다. 편집기가 vim이라면 i로 입력, Esc 후 :wq로 저장·종료입니다.
아래 연습장에서 줄마다 명령을 바꾸거나 순서를 옮겨 보세요. 첫 줄을 fixup으로 바꾸거나, 마들렌 추가만 drop하고 오타 수정을 남기면 어떤 일이 생기는지도 확인할 수 있습니다. 둘 다 직접 실행해 확인한 결과(에러와 충돌)를 그대로 보여 줍니다.
정리가 끝났으면 5장처럼 git push --force-with-lease로 올립니다. 역시 내 브랜치일 때만입니다.
미리 표시해 두기: git commit --fixup과 --autosquash
리뷰에서 "첫 화면 문구에 오타가 있어요(생겻어요 → 생겼어요)"라는 지적을 받았다고 해 봅시다. 고칠 곳은 첫 커밋 3e7c353에 들어 있던 줄입니다. 파일을 고친 뒤, 커밋할 때 "이건 3e7c353을 고치는 커밋" 이라고 표시할 수 있습니다.
메시지가 fixup! <대상 커밋 제목>으로 자동으로 붙었습니다. 나중에 --autosquash를 붙여 rebase하면, git이 이 표시를 보고 todo 목록을 알아서 짜 줍니다.
bash
git rebase -i --autosquash main
text
pick 3e7c353 # 메뉴에 디저트 섹션과 치즈케이크 추가
fixup da68153 # fixup! 메뉴에 디저트 섹션과 치즈케이크 추가
pick 2308fa3 # 마들렌 추가
fixup! 커밋이 대상 커밋 바로 아래로 옮겨지고 명령도 fixup으로 바뀌어 있습니다. 그대로 저장하고 닫으면 끝입니다.
text
Successfully rebased and updated refs/heads/feature/dessert-menu.
bash
git log --oneline main..
text
57779d3 마들렌 추가
bf83423 메뉴에 디저트 섹션과 치즈케이크 추가
fixup! 커밋이 첫 커밋에 흡수되어 다시 두 개가 됐습니다. 이 글에서 쓴 git 2.55에서는 -i 없이 git rebase --autosquash main만 쳐도 편집기를 열지 않고 같은 정리를 해 주었습니다(다음 장 끝에서 확인합니다).
7. rebase를 되돌리고 싶을 때: ORIG_HEAD와 reflog
rebase는 커밋을 새로 만들지만, 옛 커밋을 지우지는 않습니다. 이름표만 새 커밋으로 옮겨 갈 뿐, 옛 커밋은 저장소 안에 남아 있습니다. 그래서 "rebase 괜히 했다" 싶으면 되돌릴 수 있습니다.
먼저 방금(6장 끝) autosquash rebase를 한 직후의 reflog를 봅시다. 5편의 「구명줄 git reflog」에서 본 그 일지입니다.
bash
git reflog -8
text
57779d3 HEAD@{0}: rebase (finish): returning to refs/heads/feature/dessert-menu
57779d3 HEAD@{1}: rebase (pick): 마들렌 추가
bf83423 HEAD@{2}: rebase (fixup): 메뉴에 디저트 섹션과 치즈케이크 추가
3e7c353 HEAD@{3}: rebase (start): checkout main
da68153 HEAD@{4}: commit: fixup! 메뉴에 디저트 섹션과 치즈케이크 추가
2308fa3 HEAD@{5}: rebase (finish): returning to refs/heads/feature/dessert-menu
2308fa3 HEAD@{6}: rebase (fixup): 마들렌 추가
ce42b3a HEAD@{7}: rebase (pick): 마들렌 추가
읽는 요령은 하나입니다. rebase (start) 바로 아래 줄이 rebase를 시작하기 직전의 모습입니다. 여기서는 HEAD@{4}, 즉 da68153입니다. git reset --hard HEAD@{4}로 되돌리거나, git branch rescue da68153으로 이름표를 붙여 구조하면 됩니다.
바로 직전 한 번이라면 더 쉬운 이름이 있습니다. ORIG_HEAD 입니다. rebase·merge·reset처럼 브랜치를 크게 옮기는 명령은 옮기기 직전의 위치를 ORIG_HEAD라는 이름으로 적어 둡니다.
HEAD is now at da68153 fixup! 메뉴에 디저트 섹션과 치즈케이크 추가
text
da68153 fixup! 메뉴에 디저트 섹션과 치즈케이크 추가
2308fa3 마들렌 추가
3e7c353 메뉴에 디저트 섹션과 치즈케이크 추가
rebase 전 상태로 완전히 돌아왔습니다. 단, ORIG_HEAD는 바로 직전 한 번만 기억하고, 그 뒤에 다른 rebase·reset·merge를 하면 덮어써집니다. 더 예전으로 가야 할 때는 reflog를 씁니다. 그리고 5편에서 말했듯 두 방법 모두 한 번이라도 커밋했던 것에만 통합니다. rebase 전에는 작업 폴더를 깨끗하게(working tree clean) 만들어 두세요. 사실 git도 커밋 안 한 변경이 있으면 error: cannot rebase: You have unstaged changes.라며 rebase를 시작하지 않습니다.
되돌려 본 김에, 6장 끝에서 말한 -i 없는 autosquash로 다시 정리하고 올립니다.
bash
git rebase --autosquash main
git log --oneline main..
text
Successfully rebased and updated refs/heads/feature/dessert-menu.
text
a1e67ab 마들렌 추가
1a3ce82 메뉴에 디저트 섹션과 치즈케이크 추가
bash
git push --force-with-lease
text
To https://github.com/minji/cafe-menu.git
+ 4fe71b7...a1e67ab feature/dessert-menu -> feature/dessert-menu (forced update)
같은 내용인데 해시가 57779d3이 아니라 a1e67ab입니다. 커밋을 다시 만든 시각(committer date)이 달라서입니다. rebase는 할 때마다 새 커밋을 만든다는 것을 다시 한번 보여 줍니다.
8. 언제 merge, 언제 rebase
여기까지 오면 "그래서 뭘 쓰라는 거지?" 싶을 수 있습니다. 판단 기준은 거의 하나, "그 커밋을 다른 사람도 갖고 있는가" 입니다.
상황
권장
이유
아직 push 안 한 내 로컬 커밋을 최신 main 위로
git pull --rebase / git rebase
아무도 모르는 커밋이라 바꿔도 피해 없음
나 혼자 쓰는 PR 브랜치를 PR 전에 정리
git rebase -i → --force-with-lease
리뷰하기 좋은 커밋으로 다듬기
동료와 함께 커밋하는 브랜치를 최신화
git merge origin/main
동료가 가진 커밋의 해시를 바꾸지 않음
main 자체
rebase·강제 push 금지
모두가 기대는 역사. 되돌릴 땐 git revert(5편)
PR을 main에 합칠 때
팀이 정한 머지 버튼 하나
6편의 세 가지 버튼 참고
rebase 충돌이 너무 많아 지칠 때
git rebase --abort 후 merge
merge로 최신화해도 틀린 게 아님
1공유된 커밋은 다시 쓰지 않는다. 공유 전(로컬, 내 브랜치)에는 rebase로 다듬고, 공유 후에는 merge·revert로 쌓는다.
2강제 push는 --force-with-lease로, 내 브랜치에만.
3팀이 한 가지로 정한다. pull 방식(pull.rebase)과 머지 버튼을 사람마다 다르게 쓰지 않는다.
이제 도윤의 두 번째 부탁입니다. 도윤은 feature/winter-menu 브랜치에서 겨울 메뉴를 준비하고 있는데, 그 김에 카페라테 가격 수정 커밋도 거기에 올려 두었습니다. 겨울 메뉴는 아직 공개하면 안 되지만, 가격은 오늘부터 바뀌어야 합니다. 브랜치를 통째로 merge하면 미완성 겨울 메뉴까지 딸려 옵니다.
이럴 때 쓰는 것이 cherry-pick(체리픽) 입니다. 나무에서 체리 한 알만 따듯, 다른 브랜치의 커밋 하나를 골라 지금 브랜치에 똑같이 한 번 더 적용합니다.
commit 73a09c86951891601c537a7725469b6eeb7aa664
Author: 도윤 <doyoon@example.com>
Date: Tue Oct 6 10:20:00 2026 +0900
브라우저 탭 제목 수정
(cherry picked from commit 66e70aa67ceb197d1fccabda94c1c9a53a2b0c78)
공개 저장소에서 원래 커밋을 누구나 볼 수 있을 때 특히 유용합니다. 반대로 원래 커밋이 내 로컬에만 있는 임시 브랜치라면 적어 봐야 찾을 수 없으니 생략해도 됩니다.
cherry-pick 충돌: --continue / --abort
그사이 민지가 main에서 아메리카노를 4,200원으로 올렸습니다. 그런데 도윤의 885e789는 같은 줄에 "샷 추가" 표기를 붙인 커밋입니다.
bash
git cherry-pick 885e789
text
Auto-merging menu.md
CONFLICT (content): Merge conflict in menu.md
error: could not apply 885e789... 아메리카노 샷 추가 옵션 표기
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
bash
cat menu.md
text
# 메뉴
## 음료
<<<<<<< HEAD
- 아메리카노 4,200원
=======
- 아메리카노 4,000원 (샷 추가 +500원)
>>>>>>> 885e789 (아메리카노 샷 추가 옵션 표기)
- 카페라테 4,800원
- 레몬에이드 5,000원
cherry-pick에서는 merge 때처럼 HEAD 쪽이 지금 브랜치(main), 아래쪽이 가져오려는 커밋입니다. 그만두려면 git cherry-pick --abort를 칩니다. 출력 없이 조용히 원래대로 돌아갑니다. 풀려면 두 변경을 합친 한 줄만 남기고,
[main f0edeaa] 아메리카노 샷 추가 옵션 표기
Author: 도윤 <doyoon@example.com>
Date: Tue Oct 6 10:21:00 2026 +0900
1 file changed, 1 insertion(+), 1 deletion(-)
cherry-pick을 쓰지 말아야 할 때
🍒
잘 맞는 경우
급한 버그 수정 하나를 main이나 배포 브랜치에 먼저 넣을 때, 잘못된 브랜치에 커밋한 것 하나를 옮겨 올 때처럼 한두 개를 골라 올 때.
🚫
맞지 않는 경우
브랜치의 커밋을 대부분 가져와야 한다면 cherry-pick을 여러 번 하지 말고 브랜치를 merge(또는 PR)하세요. 같은 변경이 해시만 다른 두 커밋으로 양쪽에 남아, 나중에 그 브랜치를 merge할 때 역사가 헷갈리고 충돌이 다시 날 수 있습니다.
✅
요령
가져간 커밋은 원래 브랜치 주인에게 알려 주세요. -x로 출처를 남기면 나중에 같은 변경이 두 번 보여도 "아, cherry-pick한 거구나" 하고 알아볼 수 있습니다. main에 넣는 것도 팀 규칙대로 PR을 거칩니다.
10. 과거 커밋 둘러보기: 옛 파일 열어 보기와 되살리기
사장님의 질문, "지난달 메뉴판에 카페라테가 얼마였죠?"로 갑시다. git은 모든 세이브 포인트의 파일을 다 기억하고 있으니, 파일을 되돌리지 않고도 과거를 들여다볼 수 있습니다.
먼저 기록을 한 줄씩 봅니다.
bash
git log --oneline
text
f0edeaa 아메리카노 샷 추가 옵션 표기
c14322a 아메리카노 가격 4,200원으로 인상
73a09c8 브라우저 탭 제목 수정
acea8c0 카페라테 가격 4,800원으로 수정
b5d5628 README에 문의 메일 추가
4261b5c README에 주소 추가
2b2ac59 메뉴에 레몬에이드 추가
c41bade README에 영업시간 추가
f373b8c 메뉴에 카페라테 추가
92229b6 카페 웹사이트 첫 버전
5편에서 git restore 파일은 "마지막 커밋 상태로" 되돌렸습니다. --source를 주면 원하는 커밋의 상태로 되돌립니다. 다른 파일과 커밋 기록은 그대로입니다.
bash
git restore --source=f373b8c menu.md
git status
git diff
text
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: menu.md
no changes added to commit (use "git add" and/or "git commit -a")
파일 하나가 아니라 프로젝트 전체를 그 시점으로 돌려서 보고 싶을 때도 있습니다. 옛 버전 사이트를 브라우저로 열어 보고 싶을 때처럼요. 4편에서 git switch는 브랜치로 이동하는 명령이었는데, 커밋 해시를 주면 이렇게 됩니다.
bash
git switch f373b8c
text
fatal: a branch is expected, got commit 'f373b8c'
hint: If you want to detach HEAD at the commit, try again with the --detach option.
switch는 실수를 막으려고 "브랜치가 아니라 커밋인데, 정말이면 --detach를 붙이라"고 합니다.
bash
git switch --detach f373b8c
git status
text
HEAD is now at f373b8c 메뉴에 카페라테 추가
text
HEAD detached at f373b8c
nothing to commit, working tree clean
작업 폴더의 파일이 모두 f373b8c 시점으로 바뀌었습니다. 이 상태를 detached HEAD(분리된 HEAD) 라고 합니다. 4편에서 HEAD는 "지금 서 있는 곳"이고 보통 브랜치 이름표에 붙어 있다고 했습니다. 지금은 HEAD가 이름표 없이 커밋에 직접 붙어 있습니다. 브랜치라는 기차에서 내려 옛 역 플랫폼에 서 있는 셈입니다.
예전 방식 git checkout <해시>와 긴 안내문 읽기
예전 방식인 git checkout으로 같은 일을 하면, 처음 보는 사람을 겁먹게 하는 긴 안내문이 나옵니다.
bash
git checkout f373b8c
text
Note: switching to 'f373b8c'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at f373b8c 메뉴에 카페라테 추가
무서운 말이 아닙니다. 문단별로 옮기면 이렇습니다.
You are in 'detached HEAD' state… — "분리된 HEAD 상태입니다. 둘러보고, 실험 삼아 고치고 커밋해도 됩니다. 브랜치로 돌아가면 여기서 만든 커밋은 어떤 브랜치에도 영향을 주지 않고 버릴 수 있습니다."
If you want to create a new branch… — "여기서 만든 커밋을 남기고 싶으면, 지금이든 나중이든 git switch -c <새 브랜치 이름>으로 브랜치를 만드세요."
Or undo this operation with: git switch - — "없던 일로 하려면 git switch -(직전 브랜치로)."
Turn off this advice… — 이 안내가 귀찮으면 끌 수 있다는 설정 안내.
HEAD is now at f373b8c… — 지금 위치.
git branch로 보면 현재 위치가 브랜치 이름 대신 괄호로 표시됩니다.
bash
git branch
text
* (HEAD detached at f373b8c)
feature/dessert-menu
main
돌아가기: git switch - 또는 git switch main
bash
git switch -
text
Previous HEAD position was f373b8c 메뉴에 카페라테 추가
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
둘러보기만 했다면 이것으로 끝입니다. 아무것도 바뀌지 않았습니다.
거기서 커밋했다면: 떠날 때 나오는 경고
detached HEAD에서 커밋도 할 수 있습니다. 민지가 옛 메뉴판 위에서 실험 삼아 한 줄을 고쳐 커밋했습니다.
bash
git commit -am "옛 메뉴판 실험"
text
[detached HEAD fbaa111] 옛 메뉴판 실험
1 file changed, 1 insertion(+)
이 커밋은 어떤 브랜치 이름표도 가리키지 않습니다. 이 상태로 main으로 돌아가면 git이 경고합니다.
bash
git switch main
text
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
fbaa111 옛 메뉴판 실험
If you want to keep it by creating a new branch, this may be a good time
to do so with:
git branch <new-branch-name> fbaa111
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
"어느 브랜치에도 연결되지 않은 커밋 1개를 두고 떠납니다. 남기고 싶으면 지금 git branch <새 이름> fbaa111로 브랜치를 만드세요." 경고가 알려 준 대로 이름표를 붙이면 구조됩니다.
fbaa111 옛 메뉴판 실험
f373b8c 메뉴에 카페라테 추가
92229b6 카페 웹사이트 첫 버전
경고를 놓쳐 해시를 모르겠다면 5편의 git reflog에서 찾을 수 있습니다. 더 좋은 방법은 떠나기 전에 브랜치를 만드는 것입니다. detached HEAD에서 커밋한 뒤 바로 git switch -c를 치면, 지금 위치에 새 브랜치가 생기고 HEAD가 거기에 붙습니다.
bash
git switch -c experiment-2
git branch
text
Switched to a new branch 'experiment-2'
text
* experiment-2
feature/dessert-menu
main
old-menu-experiment
🇰🇷
컴퓨터가 한국어로 설정돼 있다면 — 같은 상황이 이렇게 나옵니다(같은 방식으로 따로 실행한 것이라 해시가 다릅니다). HEAD detached at은 "HEAD가 다음 위치에서 분리", 떠날 때 경고는 아래와 같습니다. 다만 git 2.55의 한국어 번역은 문장마다 적용 정도가 달라서, git checkout <해시>의 긴 안내문은 한국어 환경에서도 영어로 나왔습니다.
text
경고: 브랜치 중에 아무것에도 연결되지 않은 커밋이 뒤에
1개 있습니다:
bd592a2 가격표 실험
새 브랜치를 만들어서 이 커밋을 저장하고 싶으면, 지금 다음과
같이 할 수 있습니다:
git branch <새-브랜치-이름> bd592a2
'main' 브랜치로 전환합니다
아래 위젯에서 커밋을 눌러 그때의 menu.md와 README.md를 열어 보세요. HEAD 상태와, 같은 일을 하는 명령이 함께 바뀝니다.
🧭
detached HEAD가 저절로 생기는 경우 — 3장에서 본 것처럼 rebase 도중([detached HEAD 07104d1])에도 잠깐 이 상태가 됩니다. rebase가 충돌로 멈춘 채 git status가 이상해 보인다면, 브랜치를 옮기지 말고 먼저 git rebase --continue나 --abort로 rebase를 끝내세요. 끝나면 자동으로 브랜치로 돌아옵니다.
12. 태그: 이름 붙인 세이브 포인트
f373b8c 같은 해시는 외우기 어렵습니다. 자주 돌아가 볼 시점에는 태그(tag) 로 이름을 붙여 둘 수 있습니다. 브랜치가 커밋할 때마다 앞으로 가는 "움직이는 이름표"라면, 태그는 한곳에 박아 두는 이름표입니다. 보통 "v1.0 오픈", "가을 메뉴 공개" 같은 배포 시점에 붙입니다.
bash
git tag v1.0 f373b8c
git tag -a v2.0 -m "가을 메뉴 오픈"
git tag
text
v1.0
v2.0
첫 줄은 이름만 붙이는 가벼운 태그, 둘째 줄(-a, 해시를 생략하면 지금 커밋)은 누가·언제·왜를 함께 적는 주석 태그(annotated tag) 입니다. 공식 배포에는 주석 태그를 권합니다.
bash
git show v2.0 --stat
text
tag v2.0
Tagger: 민지 <minji@example.com>
Date: Tue Oct 6 10:23:00 2026 +0900
가을 메뉴 오픈
commit f0edeaa6fd9643c2137016e2ca50efc4925e8586
Author: 도윤 <doyoon@example.com>
Date: Tue Oct 6 10:21:00 2026 +0900
아메리카노 샷 추가 옵션 표기
menu.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
이제 해시 대신 태그 이름으로 과거에 갈 수 있습니다. 역시 detached HEAD가 됩니다.
bash
git switch --detach v1.0
text
HEAD is now at f373b8c 메뉴에 카페라테 추가
태그는 git push만으로는 올라가지 않습니다. 따로 올려야 합니다.
bash
git push origin v1.0
git push --tags
text
To https://github.com/minji/cafe-menu.git
* [new tag] v1.0 -> v1.0
text
To https://github.com/minji/cafe-menu.git
* [new tag] v2.0 -> v2.0
하나만 올리려면 git push origin 태그이름, 로컬 태그를 전부 올리려면 git push --tags입니다. 이미 공유한 태그를 다른 커밋으로 옮기는 것은 공유 브랜치를 rebase하는 것만큼 혼란을 주니, 태그는 한 번 붙이면 그대로 두는 것이 원칙입니다.
AI 에이전트와 함께 쓸 때
🤖 Claude Code 같은 AI 코딩 도구가 rebase·강제 push를 제안한다면
① "그 브랜치, 나만 쓰는 건가?"부터 확인하세요. 에이전트가 "main 위로 rebase하고 force push하겠습니다"라고 하면, 대상 브랜치가 main이 아닌지, 동료가 함께 커밋하는 브랜치가 아닌지 먼저 봅니다. git log --oneline --format='%h %an %s' main..브랜치로 작성자를 보면 나 말고 다른 사람의 커밋이 섞였는지 바로 드러납니다.
② 강제 push는 --force-with-lease로 해 달라고 하세요.7편에서 본 것처럼 Claude Code의 auto 모드는 강제 push를 막는 쪽이고, settings.json의 ask·deny 규칙으로 한 번 더 선을 그을 수 있습니다. 허용하더라도 부탁할 때 "force-with-lease로, feature/dessert-menu에만"처럼 명령과 범위를 분명히 적으세요.
③ 전후 그래프를 보여 달라고 하세요."rebase 전과 후에 git log --oneline --graph --all -15를 각각 보여 줘"라고 부탁하면, 해시가 어떻게 바뀌었고 커밋이 몇 개 남았는지 사람이 확인할 수 있습니다. 결과가 이상하면 7장의 ORIG_HEAD나 reflog로 되돌리면 됩니다.
④ rebase -i는 편집기가 필요한 명령입니다. 이 글에서도 편집기를 직접 조작할 수 없어 GIT_SEQUENCE_EDITOR로 목록을 대신 고쳐 재현했습니다. 에이전트도 같은 제약을 받으므로, 편집기를 여는 명령 대신 편집기가 필요 없는 방법을 쓰게 하는 편이 안전합니다. 예를 들어 "고칠 커밋은 git commit --fixup으로 만들고, 마지막에 git rebase --autosquash main으로 합쳐 줘"처럼 부탁할 수 있습니다. 어떤 방법을 쓰든 실행 전에 계획(어떤 커밋을 합치고 뺄지)을 먼저 말해 달라고 하세요.
⑤ 공유된 커밋을 되돌릴 때는 rebase·reset이 아니라 revert. 에이전트가 이미 push된 커밋을 "정리"하겠다며 역사를 다시 쓰려 하면 멈추고, git revert(5편)를 쓰게 하세요.
자주 하는 질문(FAQ)
Q1. rebase하면 커밋 날짜와 작성자가 바뀌나요?
작성자와 작성 날짜(Author, Date)는 그대로 남습니다. 대신 커밋을 실제로 다시 만든 사람과 시각(Committer)이 새로 기록되고, 그래서 해시가 바뀝니다. 6편에서 Rebase and merge 뒤 %an / %cn이 "민지 / 도윤"으로 나온 것이 바로 이것입니다.
Q2. rebase 도중에 컴퓨터를 껐어요. 어떻게 되나요?
rebase는 진행 상태를 저장소 안에 적어 두기 때문에, 다시 켜서 git status를 치면 interactive rebase in progress처럼 멈췄던 자리를 알려 줍니다. 거기서 --continue로 이어 가거나 --abort로 취소하면 됩니다.
Q3. git pull을 했더니 편집기 없이 "Successfully rebased"가 나와요. 제가 뭘 한 거죠?pull.rebase가 true로 설정되어 있는 것입니다. git config --get pull.rebase로 확인하세요. 회사나 다른 도구가 설정해 두었을 수 있습니다. 4장처럼 팀 방침에 맞춰 true나 false 중 하나로 정하면 됩니다.
Q4. squash merge 버튼(6편)이 있는데 굳이 rebase -i로 정리해야 하나요?
팀이 Squash and merge를 쓴다면 PR 전체가 커밋 하나로 합쳐지므로 로컬 정리는 선택입니다. 다만 리뷰어는 머지 전에 커밋을 하나씩 보는 경우가 많아서, "오타 수정"·"WIP" 같은 커밋을 미리 정리해 두면 리뷰가 쉬워집니다. Create a merge commit이나 Rebase and merge를 쓰는 팀이라면 내 커밋이 그대로 main에 남으니 정리가 더 의미 있습니다.
Q5. cherry-pick한 커밋이 있는 브랜치를 나중에 merge하면 같은 변경이 두 번 들어가나요?
대개는 git이 같은 내용임을 알아보고 문제없이 합칩니다. 다만 그사이 같은 줄을 또 고쳤다면 충돌이 날 수 있습니다. 그래서 브랜치 대부분이 필요하면 cherry-pick 대신 merge를 권합니다.
Q6. detached HEAD 상태에서 실수로 작업을 많이 했어요. 다 날아가나요?
커밋했다면 날아가지 않습니다. 아직 그 자리에 있다면 git switch -c 새-브랜치, 이미 떠났다면 경고에 나온 해시나 git reflog로 찾아 git branch 새-브랜치 <해시>로 구조하세요. 커밋하지 않은 수정이 남은 채로 브랜치를 바꾸려 하면 git이 4편에서 본 것처럼 막아 주니, 먼저 커밋하거나 git stash로 치워 두면 됩니다.
이번 편 요약(치트시트)
하고 싶은 일
명령
기억할 점
내 브랜치를 최신 main 위로
git rebase main
커밋이 새 해시로 다시 만들어짐. 공유 브랜치엔 금지
rebase 충돌 풀기
고치기 → git add → git rebase --continue
HEAD 쪽이 main, 아래쪽이 내 커밋. 커밋마다 멈출 수 있음
rebase 취소
git rebase --abort
시작 전 상태로
받아서 내 커밋을 뒤로
git pull --rebase
기본값은 pull.rebase true/false, 팀이 하나로
rebase한 내 브랜치 올리기
git push --force-with-lease
--force 말고 이것. 내 브랜치에만
PR 전 커밋 정리
git rebase -i main
pick · reword · squash · fixup · drop, 위가 오래된 커밋
고칠 커밋 표시 후 자동 정리
git commit --fixup 해시 → git rebase -i --autosquash main
2.55에선 -i 없이도 동작
방금 rebase 되돌리기
git reset --hard ORIG_HEAD
더 예전은 git reflog의 rebase (start) 아래 줄
커밋 하나만 가져오기
git cherry-pick 해시 (-x)
복사라서 새 해시. 충돌은 --continue/--abort
커밋 자세히 / 그때 파일
git show 해시 · git show 해시:파일
작업 폴더를 건드리지 않음
두 시점 비교
git diff A B -- 파일
--stat으로 요약
옛 파일 하나 되살리기
git restore --source=해시 파일
되살린 뒤 add·commit, 취소는 git restore 파일
과거로 이동 / 돌아오기
git switch --detach 해시 · git switch -
detached HEAD. 예전 방식 git checkout 해시
거기서 한 커밋 살리기
git switch -c 이름 · git branch 이름 해시
떠나기 전엔 앞의 것, 떠난 뒤엔 뒤의 것
이름 붙인 세이브 포인트
git tag -a v1.0 -m "…" · git push origin v1.0
태그는 따로 push. 한 번 붙이면 옮기지 않기
git이 커밋과 이름표를 내부에서 어떻게 저장하는지, rebase가 왜 해시를 바꿀 수밖에 없는지까지 궁금하다면 Git 완전 정복에서 더 깊이 들어가 볼 수 있습니다.
시리즈를 마치며: 여덟 편 돌아보기
「Git, 이것만 알면 협업한다」가 이 보너스 편으로 정말 끝납니다. 한 편에 한 줄씩 돌아보면 이렇습니다.
8한 걸음 더 — rebase로 다듬고, cherry-pick으로 골라 오고, 과거를 안전하게 둘러보기
여덟 편을 관통하는 원칙은 7편에서 말한 그대로입니다. 바꾸기 전에 저장하고, 공유하기 전에 확인한다. 이번 편의 도구들은 역사를 다시 쓸 수 있을 만큼 강력하지만, 그 힘도 이 원칙 안에서 써야 안전합니다. 공유 전에는 마음껏 다듬고, 공유한 뒤에는 쌓아 올리세요. 그리고 뭔가 잘못됐다 싶을 때 떠올릴 한 단어는 언제나 reflog입니다.
민지는 오늘 디저트 메뉴 PR을 깔끔한 커밋 두 개로 올렸고, 도윤의 가격 수정은 main에 먼저 들어갔고, 사장님은 옛 카페라테 가격을 알게 됐습니다. 여러분의 저장소에도 읽기 좋은 역사가 쌓이길 바랍니다.