Git reflog란?
git reflog
는 로컬 저장소에서 HEAD의 업데이트를 기록을 출력합니다. 업데이트의 내용은 저장소 디렉토리의 .git/logs/refs/heads/.
혹은 .git/logs/HEAD
에 기록되며 git reflog는 이 내용을 출력합니다.
git reflog 사용법(명령어)
모든 브랜치의 reflog를 보고싶다면 아래와 같이 호출하면 됩니다.
$ git reflog
위의 명령어는 아래의 명령어가 생략된 것입니다. 모든 브랜치의 HEAD 참조 기록을 보여줍니다.
$ git reflog show HEAD
만약, 특정 브랜치의 reflog만 보고싶다면 아래와 같이 호출하면 됩니다.
$ git reflog [show] "branch name"
git reflog가 알려주는 정보는?
커밋 5개를 찍고 git reflog
명령어를 실행해보겠습니다.
reflog의 한줄이 나타내는 정보들은 아래와 같습니다.
commit id(hash)
4053b90
은 HEAD가 가리키고 있는 커밋 아이디(해쉬)를 나타냅니다.
HEAD가 가리키고 있는 브랜치
(HEAD -> {branch name})
는 HEAD가 어떤 브랜치를 가리키고 있는지를 나타냅니다.
최신의 HEAD가 참조하는 포인터가 변경된 작업으로 부터 몇번째 전의 작업인지
HEAD@{n}
은 가장 최근의 HEAD의 참조가 변경된 작업으로 부터 몇번째 전의 작업인지를 알려줍니다.
HEAD@{0}은 가장 최근에(방금)한 작업입니다.
HEAD@{2}는 가장 최근에한 작업으로부터 2번째 전의 작업입니다.
즉, 숫자가 작을 수록 가장 최근에 했던 작업임을 나타냅니다.
어떤 git 명령어를 통해 작업을 했는지
어떤 git 명령어를 통해 참조 포인터가 변경되었는지의 정보를 나타냅니다
git commit
git checkout
git reset
git merge
git rebase
git cherrypick
등을 통해 HEAD의 포인터 변경이 일어날 수 있습니다.
아래 스크린샷을 보시면 이해가 더 빠를 것 같습니다.
커밋 메시지
커밋 메시지
를 출력해줍니다.
.git/logs/HEAD 혹은 .git/logs/refs/heads/{branch name}
git reflog는 .git/logs/refs/heads/{branch name}
혹은 .git/logs/HEAD
파일의 내용을 출력해줍니다.
.git/logs/refs/heads/{branch name}
는 로컬 특정 브랜치의 HEAD 참조의 변경 기록을 나타내줍니다.
.git/logs/HEAD
는 모든 로컬 브랜치의 HEAD 참조의 변경 기록을 나타냅니다.
git reset이란?
git reflog를 통해 복원하는 방법을 배우기 전에 git reset
의 원리에 대해 알아야 합니다.
또한, git reset을 알아보기 전에 위에서 계속 언급되었던 HEAD
에 대해 정확히 알아야합니다.
git HEAD
GIT
에서의 HEAD
는 현재 checkout된 브랜치의 최신 커밋에 위치합니다.
하지만, 실제로는 커밋을 직접 가리키는 것이 아니라, 해당 브랜치를 가리키는것이고, 자동으로 최신 커밋을 사용하는 것일 뿐입니다.
만약 HEAD의 위치가 최신 커밋의 위치(브랜치)가 아니라면 우리가 잘 아는 'detached HEAD' 상태가 되는 것이죠.
그럼 git reset의 동작 원리는?
git reset
은 현재 작업위치인 HEAD의 포인터를 특정위치로 변경하는 것입니다.
각각의 커밋은 이전 커밋과 연결됩니다. 그래서 git reset을 통해 HEAD의 위치를 변경해주었기 때문에 HEAD보다 뒤에있는 커밋들은 연결이 사라지게 되고 히스토리에서 삭제된 것 처럼 보이게 되는 것입니다.
그림을 통해 보도록 하겠습니다.
일반적으로 위의 그림과 같이 HEAD가 브랜치의 최신 커밋을 가리키고 있을 겁니다.
git reset을 통해 HEAD의 위치를 아래 그림과 같이 바꿔주게 되면, HEAD가 바뀌었으므로 HEAD 커밋 뒤에있는 커밋에 대한 참조가 사라지게 되고, 뒤의 커밋은 히스토리에서 삭제된 것 처럼 보이는 것입니다.
이렇게 git reset으로 없어진 커밋은 삭제된 것이 아니라, '고아(orphans)' 상태가 된 것일 뿐이므로 git reflog를 통해서는 확인할 수 있습니다.
'고아'상태란 해당 커밋에 직접적으로 접근할 수 있는 경로가 없어짐을 의미합니다. 이러한 커밋들은 commit Id만 알고있다면 복원할 수 있습니다.
Git은 일반적으로 30일마다 가비지 컬렉터를 실행하여 이러한 고아 커밋을 영구적으로 삭제합니다.
그러므로, 삭제한지 얼마 지나지 않았고, git reflog
를 통해 지워진 커밋의 commit Id를 알게 된다면 git reset --hard 명령어를 통해 HEAD의 위치를 바꿔줌으로써 커밋을 복구할 수 있는 것입니다.
git reset --hard로 삭제된 커밋 되돌리기
위에서 git reflog
와 git reset
의 원리에 대해 알아보았습니다.
이제 git reflog
를 이용하여 git reset --hard로 삭제된 커밋을 복구하는 방법을 알아보겠습니다.
아래 그래프처럼 c5 까지 커밋을 찍고 시작을 하겠습니다.
위의 히스토리를 git log --oneline를 실행해보면 아래와 같은 log가 출력됩니다.
그럼 git reset --hard를 이용하여 HEAD의 위치를 c3까지 옮기도록 하겠습니다.
지워진 커밋들은 log에서는 볼 수 없지만 git reflog를 통해서는 볼 수 있습니다.
git reflog를 통해 지워진 커밋들의 정보들을 볼 수 있고, git reset을 활용한 다양한 방법으로 지워진 커밋들을 복구 할 수 있습니다. 이제 지워졌던 커밋들을 복구해보도록 하겠습니다.
1. git reset --hard {commit Id}로 복구하기
위의 예제에서 c5 커밋으로 복구해주기 위해서는 git reset --hard {commit Id}
명령어로 HEAD가 c5였던 커밋의 commit Id를 넣어주면 됩니다.
git reflog 명렁어를 통해서 아래처럼 reset으로 커밋이 삭제되었다가 복구된 내역을 볼 수 있습니다.
2. git reset --hard {HEAD{n}} 으로 복구하기
HEAD@{n}은 가장 최근의 HEAD의 참조가 변경된 작업으로 부터 몇번째 전의 작업인지를 알려준다고 했습니다.
이 HEAD@{n}을 이용해서도 git reset --hard HEAD@{n}
으로 복구할 수 있습니다.
브랜치의 HEAD가 c5였었던 log가 HEAD@{1}이므로
git reset --hard HEAD@{1} 명령어를 입력하면 c5커밋으로 복구가 됩니다.
3. git reset --hard ORIG_HEAD로 복구하기
깃은 위험한 명령을 수행하기 전에 HEAD가 가리키고 있는 commit Id를 .git/ORIG_HEAD에 기록합니다.
그러므로, git reset --hard ORIG_HEAD
를 실행하면 방금 실행했던 명령을 취소 할 수 있습니다.
References
https://www.atlassian.com/git/tutorials/rewriting-history/git-reflog
https://opentutorials.org/module/2676/15304