Git 실수 안전하게 되돌리기: revert, reset, checkout 설명
Git은 버전 관리의 강력한 도구로, 개발자가 변경 사항을 추적하고, 협업하며, 코드 히스토리를 효율적으로 관리할 수 있게 해줍니다. 하지만 숙련된 사용자조차 실수를 할 수 있으며, 이는 의도하지 않은 커밋, 잘못된 파일 수정 또는 작업 손실로 이어질 수 있습니다. 다행히 Git은 이러한 오류를 안전하게 되돌릴 수 있는 여러 명령어를 제공합니다. 이 가이드에서는 세 가지 필수 명령어인 git revert, git reset, git checkout을 살펴보고, 각 명령어의 고유한 목적, 사용 사례 및 Git 실수를 프로젝트 무결성을 손상시키지 않으면서 효과적으로 수정하는 방법을 설명합니다.
이 명령어들을 이해하는 것은 깔끔하고 관리 가능한 Git 히스토리를 유지하는 데 매우 중요합니다. 이 명령어들은 모두 변경 사항을 되돌리는 것과 관련이 있지만, 작동 방식이 다르고 리포지토리의 상태와 히스토리에 미치는 영향이 다양합니다. 상황에 맞는 올바른 명령어를 선택하는 것은 상당한 데이터 손실과 디버깅의 어려움을 피하는 데 도움이 될 수 있습니다.
핵심 개념 이해하기
명령어로 들어가기 전에 몇 가지 기본적인 Git 개념을 파악하는 것이 중요합니다.
- 작업 디렉토리 (Working Directory): 프로젝트 파일을 수정하는 로컬 파일 시스템입니다.
- 스테이징 영역 (Staging Area / Index): 파일을 수정한 후, 다음 커밋을 위해 스테이징 영역에
git add합니다. - 로컬 리포지토리 (Local Repository): Git이 프로젝트의 커밋 히스토리를 저장하는 곳입니다. 프로젝트 내의 숨겨진
.git디렉토리입니다. - 커밋 (Commit): 특정 시점의 프로젝트 스냅샷입니다. 각 커밋은 고유한 SHA-1 해시를 가집니다.
- HEAD: 일반적으로 현재 브랜치의 가장 최근 커밋을 가리키는 포인터입니다.
git revert: 변경 사항 안전하게 되돌리기
git revert는 특히 공유 리포지토리에서 커밋을 되돌리는 가장 안전한 방법입니다. 히스토리를 삭제하거나 다시 쓰는 대신, 이전 커밋에서 도입된 변경 사항을 되돌리는 새로운 커밋을 생성합니다.
작동 방식:
git revert <commit-hash>를 실행하면 Git은 지정된 커밋에서 이루어진 변경 사항을 분석하고, 반대되는 변경 사항을 적용하는 새 커밋을 생성합니다. 이 방식은 리포지토리의 히스토리를 보존하므로, 히스토리를 다시 쓰는 것이 협업자에게 문제를 일으킬 수 있는 공개 브랜치에 이상적입니다.
사용 사례:
- 원격 리포지토리에 이미 푸시된 잘못된 커밋 되돌리기.
- 문제를 일으킨 병합 커밋 수정.
- 이후 커밋에 영향을 주지 않고 특정 변경 사항을 안전하게 롤백.
예시:
다음과 같은 커밋 히스토리가 있다고 가정해 봅시다.
A -- B -- C -- D (main)
그리고 C 커밋에서 도입된 변경 사항을 되돌리고 싶다고 가정해 봅시다. 먼저 git log를 사용하여 C의 커밋 해시를 찾습니다.
git log --oneline
C 커밋의 해시가 abcdef1이라고 가정해 봅시다.
git revert abcdef1
Git은 새 revert 커밋의 커밋 메시지를 수정할 수 있도록 기본 편집기를 엽니다. 저장하고 닫은 후 히스토리는 다음과 같이 보일 것입니다.
A -- B -- C -- D -- E (main) <-- E는 C의 변경 사항을 되돌림
중요 고려 사항:
git revert는 항상 새 커밋을 추가합니다. 기존 커밋을 변경하지 않습니다.- revert 과정에서 충돌이 발생하는 경우(예: revert 커밋의 변경 사항이 이후 변경 사항과 겹치는 경우), Git은 일시 중지되며 수동으로 충돌을 해결한 후 revert를 커밋해야 합니다.
git reset: 히스토리 다시 쓰기
git reset은 브랜치 포인터를 이동시키고 작업 디렉토리와 스테이징 영역을 선택적으로 수정할 수 있는 더 강력한 명령어입니다. 주로 로컬 리포지토리의 변경 사항을 되돌리는 데 사용되며, 이미 공유된 커밋에 사용하면 위험할 수 있습니다.
작동 방식:
git reset은 HEAD 포인터를 다른 커밋으로 이동시킵니다. 작업 디렉토리와 스테이징 영역에 미치는 영향은 선택한 모드에 따라 달라집니다.
--soft:HEAD포인터를 이동시키지만 작업 디렉토리와 스테이징 영역은 그대로 둡니다. 되돌려진 커밋의 변경 사항은 작업 디렉토리에서 스테이징되지 않은 변경 사항으로 나타납니다.--mixed(기본값):HEAD포인터를 이동시키고 스테이징 영역을 재설정합니다. 되돌려진 커밋의 변경 사항은 스테이징되지 않은 상태로 작업 디렉토리에 나타납니다.--hard:HEAD포인터를 이동시키고, 스테이징 영역을 재설정하며, 되돌려진 커밋에 대한 작업 디렉토리의 모든 변경 사항을 삭제합니다. 이것이 가장 파괴적인 옵션이며 데이터 손실로 이어질 수 있습니다.
사용 사례:
- 파일 스테이징 취소 (
git reset HEAD <file>). - 푸시하기 전에 마지막 로컬 커밋 되돌리기.
- 공유하기 전에 지저분한 로컬 커밋 히스토리 정리.
예시:
-
파일 스테이징 취소:
실수로 파일을
git add한 경우.```bash
git add unwanted_file.txt
git status # unwanted_file.txt가 스테이징됨으로 표시git reset HEAD unwanted_file.txt
git status # unwanted_file.txt가 스테이징되지 않은 상태로 표시
```모든 변경 사항 스테이징 취소:
bash git reset -
마지막 커밋 되돌리기 (soft reset):
마지막 커밋을 되돌리고 싶지만 변경 사항을 유지하여 다르게 다시 커밋하고 싶을 때:
```bash
git reset --soft HEAD~1HEAD는 이제 마지막 커밋 이전 커밋을 가리킵니다.
마지막 커밋의 변경 사항은 이제 스테이징되었습니다.
```
-
마지막 커밋 되돌리기 (mixed reset - 기본값):
마지막 커밋을 되돌리고 변경 사항을 작업 디렉토리에서 스테이징되지 않은 상태로 사용하고 싶을 때:
```bash
git reset --mixed HEAD~1또는 간단히:
git reset HEAD~1
HEAD는 이제 마지막 커밋 이전 커밋을 가리킵니다.
마지막 커밋의 변경 사항은 이제 작업 디렉토리에서 스테이징되지 않은 상태입니다.
```
-
마지막 커밋 및 해당 변경 사항 모두 삭제 (hard reset):
경고: 이 작업은 변경 사항을 영구적으로 삭제합니다. 극도로 주의하여 사용하세요!
```bash
git reset --hard HEAD~1HEAD는 이제 마지막 커밋 이전 커밋을 가리킵니다.
마지막 커밋에 의해 도입된 모든 변경 사항이 사라집니다.
```
-
특정 커밋으로 재설정:
브랜치를 HEAD보다 이전 커밋(
commit_hash)으로 되돌리려면:```bash
git reset --hard commit_hash이 작업은 commit_hash 이후의 모든 커밋과 변경 사항을 삭제합니다.
```
중요 고려 사항:
git reset은 히스토리를 다시 씁니다. 이미 원격 리포지토리에 푸시된 커밋을reset하는 경우, 강제 푸시(git push -f)를 수행해야 하며, 이는 협업자에게 문제가 될 수 있습니다.--hard는 파괴적입니다.git reset --hard를 사용하기 전에 항상 커밋 히스토리와 작업 중인 파일을 다시 확인하세요.
git checkout: 파일 전환 및 복원
git checkout은 주로 브랜치 간 전환 및 파일을 이전 상태로 복원하는 데 사용됩니다. revert나 reset처럼 직접적으로 커밋을 되돌리지는 않지만, 의도하지 않은 파일 수정 사항을 수정하거나 과거 상태를 보는 데 필수적입니다.
작동 방식:
git checkout은 여러 방식으로 사용될 수 있습니다.
- 브랜치 전환:
git checkout <branch-name>은HEAD를 지정된 브랜치로 이동시키고 작업 디렉토리를 해당 브랜치의 최신 커밋과 일치하도록 업데이트합니다. - 브랜치 생성 및 전환:
git checkout -b <new-branch-name>은 새 브랜치를 생성하고 즉시 해당 브랜치로 전환합니다. - 로컬 파일 변경 사항 삭제:
git checkout -- <file>은 작업 디렉토리의 특정 파일을 마지막 커밋(또는 스테이징된 경우 인덱스)의 상태로 복원합니다. 원치 않는 수정을 삭제하는 데 유용합니다. - 과거 커밋 보기:
git checkout <commit-hash>는 특정 커밋을 체크아웃할 수 있게 합니다. 이는HEAD를 분리시켜 "detached HEAD" 상태로 만들어, 현재 브랜치를 변경하지 않고 해당 시점의 프로젝트를 검사할 수 있게 합니다.
사용 사례:
- 파일의 커밋되지 않은 변경 사항 삭제.
- 기능 브랜치와 메인 브랜치 간 전환.
- 특정 과거 커밋의 코드 검토.
예시:
-
파일 변경 사항 삭제:
my_file.txt에 일부 수정을 가했고 이를 삭제하고 싶을 때:```bash
my_file.txt에 일부 변경 사항 적용
git status # my_file.txt가 수정됨으로 표시
git checkout -- my_file.txt
git status # my_file.txt가 수정되지 않은 상태(마지막 커밋 상태)로 표시
``` -
특정 커밋 체크아웃 (detached HEAD):
프로젝트가
abcdef1커밋 시점에 어떻게 보였는지 확인하려면:```bash
git checkout abcdef1이제 'detached HEAD' 상태입니다.
HEAD는 직접 commit abcdef1을 가리킵니다.
이 시점부터의 히스토리를 보려면
git log를 사용하세요.브랜치(예: main)로 돌아가려면:
git checkout main
```
중요 고려 사항:
git checkout -- <file>을 사용할 때 해당 파일의 커밋되지 않은 변경 사항은 영구적으로 손실됩니다. 정말로 삭제해도 되는지 확인하세요.- detached HEAD 상태에서는 새로 만드는 커밋이 어떤 브랜치에도 속하지 않습니다. 이러한 변경 사항을 저장하려면 해당 상태에서 새 브랜치를 만드세요 (
git checkout -b new-feature-branch).
어떤 명령어를 언제 사용해야 할까?
-
git revert를 사용할 때:- 이미 공유된 원격 리포지토리에 푸시된 커밋을 되돌려야 할 때.
- 명확하고 변경 불가능한 히스토리를 유지하고 싶을 때.
- 이후 커밋에 직접적인 영향을 주지 않고 특정 변경 사항을 되돌리고 싶을 때.
-
git reset을 사용할 때:- 파일 스테이징을 취소해야 할 때.
- 공유하기 전에 하나 이상의 로컬 커밋을 되돌리고 싶을 때.
- 로컬 커밋 히스토리를 다시 작성하는 것에 익숙할 때 (예: 풀 리퀘스트 전에 정리).
- 히스토리 다시 쓰기의 위험을 이해할 때, 특히 나중에 푸시할 계획이 있다면.
-
git checkout을 사용할 때:- 작업 디렉토리의 특정 파일에 대한 커밋되지 않은 변경 사항을 삭제해야 할 때.
- 브랜치 간 전환하거나 프로젝트의 과거 상태를 볼 필요가 있을 때.
결론
git revert, git reset, git checkout을 능숙하게 사용하는 것은 효과적인 Git 사용의 기본입니다. 이 명령어들의 차이점을 이해하고 올바르게 사용하면 실수를 자신 있게 되돌리고, 커밋 히스토리를 관리하며, 프로젝트의 무결성을 보장할 수 있습니다. 히스토리를 다시 쓰는 명령어(예: git reset)를 사용하기 전에 변경 사항이 로컬인지 공유된 것인지 항상 고려하세요. 확신이 서지 않을 때는 공유 브랜치의 경우 git revert가 종종 더 안전한 선택입니다.