4. 되돌리기는 대상을 먼저 정한다
해설글
작성 · 3분 읽기 · 조회수 —
이전으로 돌아간다는 한 문장
Restore 노트는 다음 문장으로 시작한다.
이전 커밋으로 돌아가는 명령어
그 아래에는 restore, revert, reset --hard가 함께 적혀 있다. 모두 과거 상태와 관련 있지만 같은 대상을 되돌리지는 않는다. 파일 하나를 바꾸려는지, staging만 취소하려는지, branch 끝을 옮기려는지에 따라 결과가 달라진다.
이번에는 note.txt를 version 1, 2, 3으로 차례로 commit한 저장소를 여섯 벌 복제했다. 같은 시작 상태에서 명령 하나씩만 바꿔 HEAD, index, working tree를 비교했다. Git 공식 문서도 세 명령을 이렇게 나눈다. restore는 working tree나 index의 파일을 복원하고, reset은 branch tip을 옮기며, revert는 과거 변경의 반대를 기록한 새 commit을 만든다. (Reset, restore and revert)
파일과 staging을 나누는 restore
노트에는 두 사용법이 구분돼 있다.
git restore 파일명
git restore --staged 파일명
version 3을 commit한 뒤 working tree의 파일을 unfinished로 바꾸고 git restore note.txt를 실행했다. 파일은 다시 다음 내용이 됐고 status에는 변경이 남지 않았다.
version 3
source를 생략한 restore는 working tree 파일을 index의 내용으로 맞춘다. 아직 commit하지 않은 unfinished는 사라졌지만 HEAD나 branch history는 움직이지 않았다. 특정 commit의 파일을 가져오려면 노트에 적힌 것처럼 git restore --source <commit> note.txt로 source를 지정할 수 있다. (git-restore)
다른 복제본에서는 staged edit를 add한 뒤 git restore --staged note.txt를 실행했다.
M note.txt
index의 변경만 빠지고 working tree의 수정은 남았다. 두 restore는 똑같이 파일 경로를 받지만 기본 목적지가 다르다. --staged가 없으면 working tree, 있으면 index다.
branch를 옮기는 reset의 세 단계
노트에는 git reset --hard 커밋아이디를 “해당 커밋 아이디 시점으로 모든 걸 되돌려준다”라고 적었다. 여기서 “모든 것”을 나누기 위해 version 3에서 HEAD~1, 즉 version 2로 reset했다.
--soft의 결과는 다음과 같았다.
M note.txt
HEAD만 version 2로 옮기고 index와 working tree에는 version 3이 남았다. 그래서 version 2와 비교한 변경이 staged 상태가 됐다.
기본 모드인 --mixed의 결과는 이랬다.
M note.txt
HEAD와 index가 version 2로 맞춰졌고 working tree만 version 3을 유지했다. 같은 변경이 unstaged로 보인다.
--hard를 시험하기 전에는 working tree를 uncommitted and lost로 바꿨다. 실행 후 파일은 다음 한 줄로 덮였고 status는 깨끗했다.
version 2
hard는 HEAD, index, working tree를 대상 commit에 맞춘다. commit하지 않은 tracked 파일 변경도 잃을 수 있으므로 “과거로 돌아가기” 전에 가장 먼저 현재 작업을 버려도 되는지 확인해야 한다. 공식 문서는 대상 commit에 없는 tracked 파일을 제거하고, tracked 경로를 쓰는 데 방해되는 untracked 파일도 삭제될 수 있다고 경고한다. (git-reset)
과거를 지우지 않고 반대 변경을 남기는 revert
노트의 revert 설명은 reset과 다른 생각을 이미 담고 있다.
해당 커밋에서 변경된 내용들을 없는 내용으로 새로운 커밋을 한다. 해당 커밋 이후에 추가된 커밋내용은 보존된다
version 3 commit에 git revert --no-edit HEAD를 실행한 실제 log는 다음과 같았다.
fb38577 Revert "version 3"
92a1762 version 3
241ce02 version 2
파일 내용은 version 2가 됐지만 version 3 commit은 history에 그대로 남았다. 그 위에 효과를 반대로 적용한 새 commit이 추가됐다. 이미 공유한 history에서 무엇을 취소했는지 남겨야 할 때 reset과 다른 선택지가 되는 이유다. (git-revert)
노트에는 “깃은 커밋을 삭제할 수 없음”이라고도 적혀 있다. 더 정확하게는 reset으로 branch가 가리키는 끝을 과거로 옮겨 commit을 현재 branch history에서 제외할 수 있다. 그 객체가 즉시 사라지는 것은 아니며 reflog에 남아 다시 찾을 가능성도 있지만, 계속 보존된다고 기대해서는 안 된다. 중요한 구분은 삭제 가능 여부 한 문장보다 history를 다시 쓰는가, 반대 변경을 새 history로 남기는가다.
현재의 판단 순서는 대상부터 정하는 것이다. 파일이나 index를 복원하면 restore, branch tip과 세 영역의 상태를 다시 맞추면 reset, 기존 commit을 보존하면서 효과를 취소하면 revert다. 특히 hard reset은 이름 그대로 돌이키기 어려운 working tree 변경까지 덮으므로 상태 확인 없이 사용하지 않는다.
여섯 복제본의 결과는 04-restore.sh에서 확인할 수 있다. hard reset을 포함한 모든 동작은 새 임시 디렉터리에서만 실행된다.