만약 여러개의 연속된 커밋들을 되돌리면서 그 중에 몇개의 커밋은 유지하고 싶은 경우, 아래와 같은 작업을 수행한다:
git rebase -i <rebase를 시작할 기준 커밋 SHA>
-i 옵션은 rebase 를 "대화형 (interactive) 모드" 로 동작하게 해 준다. rebase 수행에 따른 실제 커밋을 replay 적용하기 직전에, 앞으로 replay 될 각각의 커밋들에 대해 수행할 작업을 신택할 수 있도록 잠시 rebase 작업을 멈추게 된다.
rebase -i 수행시 아래와 같이 적용될 커밋들과 각 수행할 작업 목록을 편집할 수 있도록 사용자의 기본 편집기에 표시하여 줄 것이다:
커밋을 제거하고 싶다면, 간단히 해당 라인을 편집기에서 삭제하기만 하면 된다. 위 그림에 표시된 예제에서, 잘못된 커밋들을 프로젝트에서 제거하고자 한다면 1 번 라인과 3-4 번 라인을 삭제하면 된다. 만약 두개의 커밋을 하나로 합치고자 한다면, squash 나 fixup
명령을 아래와 같이 기입하여 준다.
git revert 를 이용하면 기존 커밋들을 되돌릴 수 있으며, 이는 특히 원격 저장소에 해당 커밋들이 이미 push 된 경우 더욱 유용하다.
이 작업은 기존 커밋의 효과를 되돌리는 새로운 커밋을 생성하게 되며, 이를 통해 history 를 덮어쓰지 않고도 안전하게 원격 저장소에 되돌린 결과물을 push 할 수 있게 된다.
git push --force 을 통해 history 를 덮어쓰는 행위는 해당 저장소를 사용하는 다른 사용자들로부터 항의를 받을 수 있는 위험한 행위이므로 절대 이러한 작업을 수행해서는 안된다.
만약, 방금 push 한 커밋에 버그가 있는 것을 발견하여 해당 커밋을 바로 되돌리고 싶다면, 아래와 같이 수행하도록 한다:
git revert HEAD~1
git push
이 작업 이후, 로컬에서 위 revert 커밋을 다시 revert 한 다음, 문제점을 수정한 후에 정상 동작하는 코드를 push 하도록 한다:
git revert HEAD~1
수정 .. 수정 .. 수정 ..
git add -A .
git commit -m "Update error code"
git push
만약 revert 하고자 하는 커밋이 history 상에서 이후에 추가된 다른 커밋들에 뒤덮여 있다면 (HEAD~1 로 지정할 수 없다면), 커밋 해시값을 지정해서 revert 를 수행할 수도 있다. Git 은 해당 커밋을 되돌리는 counter-commit 을 생성해 줄 것이고, 이를 원격 저장소에 안전하게 push 할 수 있다.
만약 merge 작업을 수행하긴 하였지만 원격 저장소에 아직 push 를 하지 않은 상태라면 아래에 소개된 일반적인 커밋 되돌리기와 유사한 과정을 통해 merge 작업을 되돌릴 수 있다.
reset 을 이용한 방법은 merge 커밋 자체와 해당 커밋 이후 브랜치에 추가된 모든 커밋들을 되돌리기에 가장 간단한 방법이다.
그러나, 이렇게 reset 을 하려면 되돌아갈 대상 커밋의 SHA 해시값을 알아야 하는데, git log 의 결과는 merge 한 두 브랜치 모두의 커밋 목록을 보여주기에 되돌아가고자 하는 정확한 커밋을 알아내기가 어려울 수 있다. 만약 잘못된 커밋을 지정하여 reset 을 수행한다면 (예: 되돌아가고자 하는 브랜치가 아닌 merge 의 대상이 되었던 브랜치의 커밋을 지정한다던가) 이전에 커밋하였던 작업 내역이 유실될 수 있다.
git reset --hard <작업 중이던 브랜치의 마지막 커밋>
혹은, 만약 되돌리고자 하는 merge 커밋 자체가 가장 최근에 추가된 커밋이라면:
git reset HEAD~
revert 를 이용하는 방법은 이미 커밋된 어떠한 작업도 무효화하지 않는다는 점에서 더욱 안전한 방법이나, 이후에 해당 브랜치와의 merge 를 다시 수행하고 싶다면 revert 한 커밋을 다시 revert 해야 한다는 점에서 추가적인 작업이 더 필요한 방법이기도 하다 (아래 섹션을 참고하라).
원격 저장소에 push 완료된 merge 작업 되돌리기
아래와 같이 'add-gremlins' 라는 새로운 feature 를 추가하는 상황을 고려해보자
이 시점에서는 시스템에 문제를 일으켰던 feature 가 원격 저장소에서 제외되어 다른 개발자들의 시스템이 다시 정상 동작하기 시작할 것이다.
이제 add-gremlins feature 의 문제를 해결한 후 다시 merge 를 수행하기 위해서는, 위 revert 작업을 되돌릴 필요가 있다.
git checkout feature/add-gremlins
...
# 문제 해결을 위한 커밋을 이 시점에 추가한다.
git checkout master
...
git revert e443799
...
git merge feature/add-gremlins
...
# 문제 수정으로 인해 새로이 발생한 merge conflict 을 해결한다
...
git commit # merge 작업을 커밋한다
...
git push
이 과정을 마치면 해당 feature 가 성공적으로 다시 추가되었을 것이다. 그러나, 이러한 버그들이 주로
merge conflict 으로 인해 발생하는 경우가 많다는 점을 고려하면, 아래와 같이 feature 브랜치에서 master 를 merge 한 후 문제를 해결하는 작업 순서를 따르는 것이 더 유용할 때가 있다.
git checkout feature/add-gremlins
...
# master 브랜치를 merge 한 후 바로 이전 revert 작업을 revert 한다.# 이 작업을 통해 이전에 master 에 문제가 생겼던 상태와 동일한 상태를 만들수 있다.
git merge master
...
git revert e443799
...
# 이제 발생한 문제를 해결한다 (여러 커밋이 이 시점에서 추가될 수 있다)
git checkout master
...
# revert 작업을 revert 하는 것은 위에서 수행 하였기에 이 시점에서는 필요하지 않다
git merge feature/add-gremlins
...
# 문제 수정으로 인해 새로이 발생한 merge conflict 을 해결한다
git commit #commit the merge
...
git push
rebase 수행시 작업이 원하는 대로 완료되지 않았을 때, rebase 이전의 커밋으로 되돌려서 작업을 다시 시도해 보고 싶을 수 있다.
이런 경우 reflog 를 이용하는 것이 편리하다 (reflog 는 최근 90일간의 사용자가 수행한 로컬 작업 내역을 기록하고 있다 - 이 설정은 변경 가능하다):
$ git reflog
4a5cbb3 HEAD@{0}: rebase finished: returning to refs/heads/foo
4a5cbb3 HEAD@{1}: rebase: fixed such and such
904f7f0 HEAD@{2}: rebase: checkout upstream/master
3cbe20a HEAD@{3}: commit: fixed such and such
...
reflog 기록으로부터 rebase 직전의 커밋이 HEAD@{3} (해시값을 참조하는 것도 가능하다) 임을 확인 가능하다:
git checkout HEAD@{3}
이렇게 rebase 직전의 커밋으로 돌아간 후, 새로운 브랜치 생성 / 예전 브랜치 삭제 / rebase 재수행 등의 작업을 할 수 있다.
reflog 를 이용하여 아래와 같이 기록상의 특정 지점으로 직접 초기화를 수행할 수도 있으나, 이러한 작업이 실제 사용자가 원하는 작업인지 100% 확신할때만 수행하도록 한다:
git reset --hard HEAD@{3}
위 명령어는 사용자의 현재 Git tree 내용물을 파라미터로 주어진 지점의 내용물과 동일하게 재설정한다 ("7.2: 변경 사항을 되돌리기" 참고).
이러한 방법은 특정 브랜치를 다른 브랜치에 rebase 했을때 어떻게 동작하는지 확인을 위해 임시로 작업을 완료하고 나서, 실제 결과물은 계속 유지하고 싶지 않을때 사용할 수 있다.
현재 디렉토리 내의 모든 파일 및 서브 디렉토리들에 대해 재귀적(recursively) 으로 모든 작업본들의 변경사항을 되돌리기:
git checkout -- .
변경사항 중 일부분만을 되돌리기 위해서는 --patch 옵션을 이용한다. 각각의 hunk 단위의 변경 사항들마다 사용자는 해당 변경사항을 되돌릴지 말지 결정할 수 있다.
git checkout --patch -- dir
index 에 추가된 변경 사항들을 되돌리기:
git reset --hard
--hard 옵션 없이 위 명령어를 수행할 경우에는 soft reset 을 수행할 것이다.
아직 원격 저장소에 push 하지 않은 로컬 커밋들에 대해서는 soft reset 을 사용할 수 있을 것이다. 이러한 방법으로 커밋들과 파일들에 대한 재작업을 수행할 수 있다.
git reset HEAD~2
위 예제 수행시, 현재 작업본에 해당하는 파일들을 유지한 채 사용자의 마지막 두개의 커밋을 되감기(unwind) 할 것이다. 이 상태에서 사용자는 추가적인 수정사항들과 커밋을 생성할 수 있다.
주의: 여기서 소개된 모든 조작법은 (soft reset 은 제외) 사용자의 변경사항들을 완전히 삭제할 것이다. 보다 안전한 작업을 위해서는 상황에 따라 git stash -p 혹은 git stash 등을 사용하도록 한다. 이후에 stash pop 을 통해 되돌리기 작업을 취소하거나 stash drop 을 이용하여 되돌리기 한 내역을 완전히 삭제할 수 있다.
이전 커밋으로 작업 환경을 돌리고 싶다면, 우선 git log 명령을 이용해 커밋의 해시값을 확인한다.
일시적으로 작업 환경을 해당 커밋으로 올리고 싶다면, 현재 HEAD를 아래와 같이 detach 시킨다:
git checkout 789abcd
위 명령어는 사용자의 작업 환경을 789abcd 커밋에 위치시켜 줄 것이다. 이제 사용자가 이 예전 커밋 위에 새로운 커밋을 추가하더라도, 기존 HEAD 가 가리키던 브랜치에는 아무런 영향을 미치지 않는다. 이렇게 추가된 새로운 수정 사항들은 branch 나 checkout -b 등의 Git 명령을 통해 정식 브랜치로 만들어질 수 있다.
현재의 변경 사항들을 유지하면서도 작업 환경을 예전 커밋으로 되돌리기를 원한다면, 아래와 같이 수행한다:
git reset --soft 789abcd
가장 마지막 커밋을 되돌리고 싶다면, 아래와 같이 수행한다:
git reset --soft HEAD~
특정 커밋 이후의 모든 변경사항들을 완전히 무효화하길 원한다면, 아래와 같이 수행한다:
git reset --hard 789abcd
마찬가지로, 이전 커밋 이후로의 모든 변경사항들을 완전히 무효화하길 원한다면, 아래와 같이 수행한다:
git reset --hard HEAD~
주의: 위와 같이 무효화시킨 커밋들은 reflog 와 reset 을 이용하여 복구를 할 수 있지만, 커밋되지 않은 변경사항들은 복구가 아예 되지 않는다.
안전한 작업을 위해서는 git reset --hard 보다는 git stash; git reset 을 이용하는 것을 권장한다.
iconv 는 서로 다른 인코딩간의 변환을 처리해주는 프로그램이다.
그런 후, 해당 변환이 필요한 저장소의 루트 디렉토리에 .gitattributes 파일을 새로 생성하거나 혹은 기존 파일을 편집하여 아래 내용을 추가한다. 물론 ~/.gitattributes 파일을 수정할 수도 있다.
*.strings diff=utf16
위 설정은 .strings 으로 끝나는 파일들에 대해 git diff 를 수행하기 전에 utf16 에 대한 인코딩 변환을 해줄것이다.
이러한 변환 처리는 텍스트로 변환 가능한 다른 파일 종류에 대해서도 적용할 수 있다.
예를 들어 바이너리 형식의 plist 파일 처리를 위해서는 .gitconfig 파일에 아래와 같은 항목을 추가하고,