Git에서의 되돌리기 옵션

Git은 변경 사항을 되돌리는 옵션을 제공합니다. 변경 사항을 되돌리는 방법은 변경 사항이 스테이징되지 않았는지, 스테이징되었는지, 커밋되었는지 또는 푸시되었는지에 따라 다릅니다.

변경 사항을 되돌릴 수 있는 경우

표준 Git 워크플로우에서:

  1. 파일을 생성하거나 편집합니다. 파일은 스테이징되지 않은 상태로 시작됩니다. 새로운 경우 Git에서 아직 추적되지 않습니다.
  2. 파일을 로컬 저장소에 추가합니다(git add), 이렇게 하면 파일이 스테이징된 상태가 됩니다.
  3. 파일을 로컬 저장소에 커밋합니다(git commit).
  4. 다른 개발자들과 파일을 공유할 수 있으며, 이는 원격 저장소에 커밋하여 이루어집니다(git push).

이 워크플로우의 모든 단계에서 변경 사항을 되돌릴 수 있습니다:

로컬 변경사항 되돌리기

원격 저장소에 변경 사항을 푸시하기 전까지 Git에서 만든 변경 사항은 로컬 개발 환경에만 존재합니다.

스테이징되지 않은 로컬 변경사항 되돌리기

변경을 만들었지만 아직 스테이징하지 않았다면, 작업을 되돌릴 수 있습니다.

  1. 파일이 스테이징되지 않았는지(git add <file>를 사용하지 않았는지) git status 명령으로 확인합니다:

    $ git status
    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 checkout -- <file>..." to discard changes in working directory)
    
        modified:   <file>
    no changes added to commit (use "git add" and/or "git commit -a")
    
  2. 옵션을 선택하고 변경 사항을 되돌립니다:

    • 로컬 변경 사항 덮어쓰기:

      git checkout -- <file>
      
    • 나중에 재사용할 수 있도록 로컬 변경 사항 저장:

      git stash
      
    • 모든 파일의 로컬 변경 사항 영구적으로 삭제:

      git reset --hard
      

스테이징된 로컬 변경 사항 되돌리기

파일을 스테이징했다면, 이를 되돌릴 수 있습니다.

  1. 파일이 스테이징된지(git add <file>를 사용했는지) git status 명령으로 확인합니다:

    $ git status
    On branch main
    Your branch is up-to-date with 'origin/main'.
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
    
      new file:   <file>
    
  2. 옵션을 선택하고 변경 사항을 되돌립니다:

    • 파일을 스테이징 해제하고 변경 사항 유지:

      git restore --staged <file>
      
    • 모든 변경을 스테이징 해제하고 변경 사항 유지:

      git reset
      
    • 파일을 현재 커밋(HEAD)의 상태로 스테이징 해제:

      git reset HEAD <file>
      
    • 모든 로컬 변경 사항을 삭제하고 나중을 위해 저장:

      git stash
      
    • 모든 것을 영구적으로 삭제:

      git reset --hard
      

빠르게 로컬 변경 저장

다른 브랜치로 변경하려면 git stash를 사용할 수 있습니다.

  1. 작업을 저장하려는 브랜치에서 git stash를 입력합니다.
  2. 다른 브랜치로 전환합니다(git checkout <branchname>).
  3. 커밋하고, 푸시하고, 테스트합니다.
  4. 변경을 계속하려는 브랜치로 돌아갑니다.
  5. 이전에 보관한 커밋을 모두 나열하려면 git stash list를 사용합니다.

    stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
    stash@{1}: On master: 9cc0589... Add git-stash
    
  6. git stash의 버전 실행:

    • git stash pop을 사용하여 이전에 저장한 변경 사항을 다시 적용하고 보관 목록에서 제거합니다.
    • git stash apply을 사용하여 이전에 저장한 변경 사항을 다시 적용하지만 보관 목록에 유지합니다.

커밋된 로컬 변경 사항 되돌리기

로컬 저장소에 커밋을 하면 (git commit), Git은 변경 사항을 기록합니다. 아직 원격 저장소에 푸시하지 않았으므로 변경 사항은 공개되지 않았으며(또는 다른 개발자와 공유되지 않았습니다) 이 시점에서 변경 사항을 되돌릴 수 있습니다.

히스토리를 변경하지 않고 스테이징된 로컬 변경 사항 되돌리기

커밋을 되돌릴 수 있지만 커밋 히스토리를 유지할 수 있습니다.

이 예시에서는 A,B,C,D,E라는 다섯개의 커밋이 순서대로 커밋되었다고 가정합니다: A-B-C-D-E. 되돌리고자 하는 커밋은 B입니다.

  1. 되돌릴 커밋의 커밋 SHA를 찾습니다. 커밋 로그를 확인하려면 git log를 입력합니다.
  2. 옵션을 선택하고 변경 사항을 되돌립니다:

    • 커밋 B에서 도입된 추가 및 삭제 변경 사항을 교체할 경우:

      git revert <commit-B-SHA>
      
    • 커밋 B에서 특정 파일 또는 디렉터리의 변경 사항을 되돌리지만 스테이징된 상태로 유지할 경우:

      git checkout <commit-B-SHA> <file>
      
    • 커밋 B에서 특정 파일 또는 디렉터리의 변경 사항을 되돌리지만 스테이징되지 않도록 유지할 경우:

      git reset <commit-B-SHA> <file>
      

다중 커밋한 변경 내용 되돌리기

여러 커밋에서 복구할 수 있습니다. 예를 들어, 기능 브랜치에서 A-B-C-D를 커밋했고, 이후에 CD가 잘못되었다는 것을 깨달았다고 가정해보겠습니다.

다중 잘못된 커밋에서 복구하려면:

  1. 마지막 올바른 커밋인 B를 확인하세요.

    git checkout <commit-B-SHA>
    
  2. 새 브랜치를 만드세요.

    git checkout -b new-path-of-feature
    
  3. 변경 사항을 추가하고 푸시하고 커밋하세요.

이제 커밋은 A-B-C-D-E입니다.

또는 GitLab을 사용하여 해당 커밋을 새 병합 요청에 사용할 수 있습니다. 단일 커밋을 cherry-pick하여 병합 요청에 넣을 수 있습니다.

참고: 또 다른 해결책은 B로 되돌리고 E를 커밋하는 것입니다. 그러나 이 방법은 A-B-E라는 결과로 이어지며, 다른 개발자들이 로컬로 가지고 있는 것과 충돌할 수 있습니다.

히스토리 수정이 포함된 스테이징된 로컬 변경 내용 되돌리기

다음 작업은 Git 히스토리를 다시 작성합니다.

특정 커밋 삭제

특정 커밋을 삭제할 수 있습니다. 예를 들어, A-B-C-D와 같이 커밋한 경우 B 커밋을 삭제하려는 경우입니다.

  1. 현재 커밋 D에서 B까지의 범위를 재베이스하세요.

    git rebase -i A
    

    커밋 목록이 편집기에서 표시됩니다.

  2. 커밋 B 앞에 pickdrop으로 바꿉니다.
  3. 다른 모든 커밋에 대해서는 기본값 pick을 유지합니다.
  4. 편집기를 저장하고 종료하세요.

특정 커밋 수정

특정 커밋을 수정할 수 있습니다. 예를 들어, A-B-C-D와 같이 커밋한 경우 B에서 소개된 내용을 수정하려는 경우입니다.

  1. 현재 커밋 D에서 B까지의 범위를 재베이스하세요.

    git rebase -i A
    

    커밋 목록이 편집기에서 표시됩니다.

  2. 커밋 B 앞에 pickedit으로 바꿉니다.
  3. 다른 모든 커밋에 대해서는 기본값 pick을 유지합니다.
  4. 편집기를 저장하고 종료하세요.
  5. 편집기에서 파일을 열고 편집한 후 변경 사항을 커밋하세요.

    git commit -a
    

되돌리기의 취소

이전 로컬 커밋을 호출할 수 있습니다. 그러나 모든 이전 커밋이 사용 가능한 것은 아니며, 왜냐하면 Git이 정기적으로 브랜치나 태그에 의해 도달할 수 없는 커밋을 정리하기 때문입니다.

저장소 이력을 확인하고 이전 커밋을 추적하려면 git reflog show를 실행하세요. 예를 들어:

$ git reflog show

# 예시 출력:
b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
eb37e74 HEAD@{6}: rebase -i (pick): Commit C
97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
...
88f1867 HEAD@{12}: commit: Commit D
97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
97436c6 HEAD@{14}: checkout: moving from master to 97436c6
05cc326 HEAD@{15}: commit: Commit C
6e43d59 HEAD@{16}: commit: Commit B

이 출력은 저장소 이력을 보여주며 다음을 포함합니다:

  • 커밋 SHA.
  • 커밋이 발생한 HEAD 변경 작업 수 (HEAD@{12}는 12번의 HEAD 변경 작업 전에 이루어진 것입니다).
  • 취해진 동작, 예를 들어: 커밋, 재베이스, 병합.
  • HEAD를 변경한 동작에 대한 설명.

히스토리 변경 없이 원격 변경 내용 되돌리기

원격 저장소의 변경 내용을 되돌리려면, 되돌리고 싶은 변경 사항이 있는 새로운 커밋을 만들 수 있습니다. 이 과정을 따르면 히스토리를 보존하고 명확한 타임라인과 개발 구조를 제공합니다. 그러나 다른 개발자들이 작업의 기본으로 사용하는 브랜치에 병합된 경우에만 이 절차가 필요합니다.

분기를 유지하기 위해 되돌림 사용

특정 커밋 B에서 도입된 변경 사항을 되돌리려면:

git revert B

히스토리를 변경하면서 원격 변경 내용 되돌리기

원격 변경 내용을 되돌리고 히스토리를 변경할 수 있습니다.

업데이트된 히스토리로도 이전 커밋은 커밋 SHA로 여전히 액세스할 수 있습니다. 이는 모든 분리된 커밋의 자동 정리가 이루어지거나 수동으로 정리가 실행될 때까지, 또는 여전히 그것을 가리키는 참조가 있는 경우 오래된 커밋이 제거되지 않을 수 있습니다.

히스토리 수정이 원격 브랜치에 문제를 일으키는 경우

히스토리를 변경하는 것이 허용되는 경우

다른 개발자들이 사용할 수 있는 공용 브랜치나 브랜치에서는 히스토리를 변경해서는 안 됩니다.

GitLab와 같은 대규모 오픈 소스 저장소에 기여할 때, 여러분은 여러 커밋을 하나로 압축할 수 있습니다.

기능 브랜치의 커밋을 대상 브랜치에서 하나의 커밋으로 압축하여 병합하려면 git merge --squash를 사용하세요.

참고: 기본 브랜치나 공유 브랜치의 커밋 히스토리를 수정해서는 안 됩니다.

히스토리를 변경하는 방법

병합 요청의 기능 브랜치는 공용 브랜치이며 다른 개발자들이 사용할 수 있습니다. 그러나 프로젝트 규칙에 따라 리뷰가 완료된 후 대상 브랜치에서 표시된 커밋 수를 줄이기 위해 git rebase를 사용해야 할 수도 있습니다.

git rebase -i를 사용하여 히스토리를 수정, 압축, 삭제할 수 있습니다.

#
# 명령어:
# p, pick = 커밋 사용
# r, reword = 커밋 사용하지만 커밋 메시지 편집
# e, edit = 커밋 사용하지만 수정 중지
# s, squash = 커밋 사용하지만 이전 커밋과 통합
# f, fixup = "squash"과 유사하지만 해당 커밋 로그 메시지 삭제
# x, exec = 셸을 사용하여 명령어 실행
# d, drop = 커밋 제거
#
# 이러한 행은 재배치될 수 있습니다. 위에서 아래로 실행됩니다.
#
# 한 줄을 제거하면 해당 커밋이 손실됩니다.
#
# 그러나 모든 것을 제거하면 리베이스가 중단됩니다.
#
# 빈 커밋은 주석 처리됩니다.

참고: 리베이스를 중단하기로 결정하면 편집기를 닫지 마십시오. 대신 모든 주석이 제거된 행을 제거하고 저장하세요.

공유된 원격 브랜치에서 git rebase를 사용할 때 주의하세요. 원격 저장소로 푸시하기 전에 로컬에서 실험하세요.

# 커밋 ID부터 HEAD(현재 커밋)까지 히스토리 수정
git rebase -i 커밋 ID

커밋에서 민감한 정보 삭제

과거 커밋에서 민감한 정보를 삭제할 수 있습니다. 그러나 이 과정에서 히스토리가 수정됩니다.

특정 필터로 히스토리를 다시 작성하려면 git filter-branch를 실행하세요.

전체적으로 파일을 제거하려면 다음을 사용하세요.

git filter-branch --tree-filter 'rm 파일이름' HEAD

git filter-branch 명령은 대형 저장소에서 느릴 수 있습니다. Git 명령을 더 빠르게 실행할 수 있는 도구가 있습니다. 이러한 도구들은 git filter-branch와 같은 기능을 제공하지 않고 특정 사용 사례에 집중하기 때문에 더 빠릅니다.

저장소 히스토리와 GitLab 저장소에서 파일을 삭제하는 자세한 정보는 저장소 크기 축소를 참조하세요.