콘텐츠로 건너뛰기

Git 사용 방법 (변경 내용 되돌리기) (4편)

  • 기준

이 글은 Git 사용 방법 (브랜치와 Pull Request) (3편)에서 이어진다.

1편에서는 로컬 Git 저장소를 만들고 git add, git commit, git log, git diff 등의 기본 명령어를 사용하는 방법을 살펴봤다.

2편에서는 GitHub 원격 저장소와 연결하고 git push, git pull, git fetch, git clone을 사용해 로컬 저장소와 원격 저장소 사이에서 커밋을 주고받는 방법을 정리했다.

3편에서는 별도의 브랜치에서 작업하고 GitHub에 push한 뒤 Pull Request를 만들고, 변경 내용을 검토한 후 main에 merge하는 협업 흐름과 병합 충돌(merge conflict)을 해결하는 방법을 살펴봤다.

이번 4편에서는 Git을 사용하다 실수했을 때 변경 내용을 안전하게 되돌리는 방법을 정리한다.

Git에는 비슷해 보이는 git restore, git reset, git revert가 있다.

하지만 세 명령어는 같은 일을 하는 것이 아니다. 어디까지 작업했는지에 따라 사용해야 하는 명령어가 달라진다.

예를 들어 파일만 수정하고 아직 git add하지 않았다면 git restore가 적절할 수 있다.

실수로 git add했다면 git restore --staged로 스테이징만 취소할 수 있다.

아직 GitHub에 push하지 않은 커밋 자체를 다시 만들고 싶다면 git reset을 사용할 수 있다.

반대로 이미 GitHub에 push해서 다른 사람과 공유한 커밋이라면 기존 기록을 지우기보다 git revert로 되돌리는 것이 일반적으로 더 안전하다.

이번 글에서는 다음 내용을 중심으로 살펴본다.

  • Git에서 되돌리기 명령어가 여러 개인 이유
  • Working Directory, Staging Area, Commit의 차이
  • git restore로 수정 내용 취소하기
  • git restore --stagedgit add 취소하기
  • 특정 파일만 과거 커밋 상태로 가져오기
  • HEAD, HEAD~1, 커밋 해시의 의미
  • git reset --softgit reset --mixed의 차이
  • 위험한 git reset --hard
  • 이미 push한 커밋을 git revert로 되돌리기
  • revert 과정에서 충돌이 발생했을 때 해결하는 방법
  • 마지막 커밋을 수정하는 git commit --amend
  • git reflog로 사라진 것처럼 보이는 커밋 찾기
  • 상황별로 어떤 명령어를 선택해야 하는지

1. Git에서 "되돌리기"가 여러 종류인 이유

Git을 처음 사용할 때 가장 헷갈리는 부분 중 하나가 변경 내용을 되돌리는 방법이다.

파일을 잘못 수정했을 수도 있고,
실수로 git add했을 수도 있고,
잘못된 내용을 커밋했을 수도 있고,
이미 그 커밋을 GitHub에 push했을 수도 있다.

이 네 상황은 모두 "실수했다"는 점에서는 비슷하지만 Git 내부에서는 서로 다른 상태다.

따라서 같은 명령어로 모든 상황을 해결하려고 하면 오히려 문제가 생길 수 있다.

예를 들어 아직 커밋하지 않은 파일 하나의 수정 내용을 취소하려고 하는데 git reset --hard를 사용할 필요는 없다.

반대로 이미 여러 사람과 공유한 커밋을 git reset으로 되돌린 뒤 그 결과를 원격 저장소에 강제로 반영하려고 하면, 공유된 Git 이력을 다시 쓰게 될 수 있다.

가장 먼저 기억하면 좋은 기준은 다음과 같다.

git restore
파일이나 스테이징 상태를 되돌릴 때 사용한다.

git reset
이번 글에서 다루는 git reset <commit> 형태는 현재 브랜치가 가리키는 커밋 위치를 이동해 로컬 커밋 기록을 다시 구성할 때 사용한다.

git revert
기존 커밋을 삭제하지 않고, 그 커밋의 변경을 반대로 적용하는 새 커밋을 만들 때 사용한다.

간단히 정리하면 다음과 같다.

  • 파일 수정 실수 > git restore
  • git add 실수 > git restore --staged
  • 아직 push하지 않은 커밋 실수 > git reset
  • 이미 push하고 공유한 커밋 실수 > git revert

2. 먼저 이해해야 할 세 단계

restore, reset, revert의 차이를 이해하려면 Git에서 파일이 거치는 세 단계를 먼저 이해하는 것이 좋다.

1편에서 살펴본 기본 작업 흐름은 다음과 같았다.

Working Directory
       │
       │ git add
       ▼
Staging Area
       │
       │ git commit
       ▼
Commit

Working Directory는 현재 직접 파일을 수정하는 작업 공간이다.

Staging Area는 다음 커밋에 포함할 내용을 준비하는 공간이다. Git에서는 Index라고 부르기도 한다.

Commitgit commit을 실행해 Git 기록으로 저장한 상태다.

예를 들어 profile.txt를 수정했다고 하자.

아직 git add하지 않았다면 변경 내용은 Working Directory에만 있다.

Working Directory
profile.txt 수정됨

Staging Area
변화 없음

최근 Commit
변화 없음

그다음:
git add profile.txt
를 실행하면 수정 내용이 Staging Area에도 올라간다.

그리고:
git commit -m "프로필 수정"
을 실행하면 새로운 커밋이 만들어진다.

따라서 "무엇을 되돌리고 싶은가?"를 생각하기 전에 먼저: 현재 실수가 Working Directory에 있는지, Staging Area에 있는지, 이미 Commit이 되었는지를 확인하는 것이 중요하다.

3. 되돌리기 전에 먼저 상태 확인하기

Git에서 무언가를 되돌리기 전에 바로 명령어부터 입력하지 않는 것이 좋다.

먼저 현재 상태를 확인한다.
git status

수정 내용도 확인한다.
git diff

스테이징된 변경 내용을 확인하려면:
git diff --staged

커밋까지 만들어진 상태라면 최근 커밋도 확인한다.
git log --oneline

예를 들어 다음과 같이 나올 수 있다.

81331c1 프로필 문장 수정
21ae00a 프로필 설명 파일 추가
73bc112 Initial commit

특히 git reset이나 git revert를 사용할 때는 어떤 커밋을 대상으로 작업하려는지 먼저 확인하는 습관이 중요하다.

이번 글의 되돌리기 예제는 중요한 프로젝트가 아니라 별도의 연습 브랜치에서 실습하는 것을 권장한다.

예를 들어:

git switch main
git pull
git switch -c undo-practice
git push -u origin undo-practice

처럼 undo-practice라는 별도 브랜치를 만들어 연습할 수 있다.

각 절의 예제는 개념을 설명하기 위한 독립적인 상황으로 생각하면 된다.

4. 파일 수정 내용을 취소하기: git restore

먼저 가장 간단한 상황부터 살펴보자.

Git이 이미 추적하고 있는 profile.txt 파일이 있다고 하자.

파일을 수정했지만 아직 git add하지 않았다.

git status를 실행하면 다음과 비슷하게 표시될 수 있다.

On branch undo-practice
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   profile.txt
git status 실행 결과
git status 실행

수정한 내용을 확인한다.
git diff profile.txt

git diff profile.txt 실행 결과
git diff profile.txt 실행

그런데 수정한 내용이 잘못되어 현재 변경 내용을 버리고 싶다고 하자.

이때 다음 명령어를 사용할 수 있다.
git restore profile.txt

그러면 Working Directory의 profile.txt가 복원된다.

git restore profile.txt 후 txt 파일 실행 결과
git restore profile.txt 후 txt 파일 실행 결과

다만 여기서 중요한 점이 있다.

git restore profile.txt는 기본적으로 Staging Area(Index)에 있는 버전으로 Working Directory의 파일을 복원한다.

따라서 아직 git add한 변경이 없다면 Staging Area와 최근 커밋의 내용이 같은 경우가 많기 때문에 결과적으로 최근 커밋 상태로 돌아간 것처럼 보인다.

하지만 다음과 같은 상황에서는 다르다.

최근 Commit: 버전 A
Staging Area: 버전 B
Working Directory: 버전 C

이 상태에서 git restore profile.txt를 실행하면 Working Directory의 버전 C가 버전 B로 바뀐다.

즉 버전 A가 아니라 현재 Staging Area에 올라가 있는 버전 B로 돌아간다.

따라서 git restore를 사용할 때는 현재 스테이징 상태도 함께 확인하는 것이 좋다.

git status
git diff
git diff --staged

그리고 매우 중요한 점이 하나 더 있다.

git restore로 버린 커밋되지 않은 수정 내용은 Git에서 쉽게 복구할 수 없다.

명령어를 실행하기 전에 정말 버려도 되는 변경인지 확인해야 한다.

5. git add를 취소하기: git restore --staged

이번에는 파일 수정 내용 자체는 유지하되, 실수로 git add만 했다고 가정하자.

예를 들어:
git add profile.txt
를 실행했다.

git status에서는 다음과 비슷하게 표시된다.

On branch undo-practice
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   profile.txt
git status 실행 결과
git status 실행

그런데 아직 이 파일을 다음 커밋에 넣고 싶지 않다고 하자.

이때:
git restore --staged profile.txt
를 사용할 수 있다.

이 명령어는 profile.txt를 Staging Area에서 내린다.

중요한 점은 파일의 수정 내용 자체는 삭제하지 않는다는 것이다.

실행 전 상태는 아래와 같다
Working Directory: 수정 내용 있음
Staging Area: 수정 내용 있음

git restore --staged profile.txt를 실행하면 아래와 같이 된다.
Working Directory: 수정 내용 있음
Staging Area: 수정 내용 없음

다시 git status를 확인하면 다음과 비슷하게 표시된다.

On branch undo-practice
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   profile.txt
git restore --staged profile.txt 실행 후 git status 결과

git restore --stagedgit add의 효과만 취소하는 용도로 이해하면 쉽다.

그리고 그 수정 내용까지 완전히 버리고 싶다면 그 다음에:
git restore profile.txt
를 실행할 수 있다.

전체 흐름은 다음과 같다.

# 실수로 스테이징
git add profile.txt

# 스테이징만 취소
git restore --staged profile.txt

# 수정 내용 자체도 버리고 싶을 경우
git restore profile.txt

두 명령어는 역할이 다르므로 구분해서 기억해야 한다.

6. 특정 파일만 과거 커밋 상태로 되돌리기

git restore는 현재 Staging Area의 파일로 되돌리는 것뿐 아니라 특정 과거 커밋에서 파일을 가져올 수도 있다.

먼저 커밋 기록을 확인한다.
git log --oneline

예:

397a3b0 프로필 문장 수정
1cd5fd2 프로필 설명 파일 추가
7b1777b (origin/main, origin/HEAD, main) Merge branch 'main' of https://github.com/****/git-practice

현재 profile.txt만 바로 이전 커밋 상태로 가져오고 싶다고 하자.

다음과 같이 사용할 수 있다.

git restore --source=HEAD~1 profile.txt

또는 특정 커밋 해시를 직접 지정할 수도 있다.

git restore --source=1cd5fd2 profile.txt

이 명령어는 브랜치 전체를 과거로 이동하는 것이 아니다.

지정한 과거 커밋에서 profile.txt의 내용을 가져와 현재 Working Directory에 적용하는 것이다.

따라서 다른 파일이나 현재 브랜치의 커밋 위치는 그대로 유지된다.

변경 결과를 확인한다.
git diff profile.txt

D:\git-practice>git diff profile.txt
diff --git a/profile.txt b/profile.txt
index 71f8c8b..c5a7f9f 100644
--- a/profile.txt
+++ b/profile.txt
@@ -1 +1 @@
-프로필 페이지 수정 연습 (리뷰 의견 반영) - 프로필 설명 파일 추가 - 프로필 문장 수정
+프로필 페이지 수정 연습 (리뷰 의견 반영) - 프로필 설명 파일 추가
git restore --source=HEAD~1 profile.txt 실행 후 git diff profile.txt 결과
git diff profile.txt 실행

원하는 상태라면 일반적인 수정 작업처럼 다시 커밋할 수 있다.

git add profile.txt
git commit -m "profile.txt 이전 버전으로 복원"

이 방법은 프로젝트 전체의 커밋 기록을 되돌리는 것이 아니라 특정 파일만 예전 내용으로 복원하고 싶을 때 유용하다.

7. HEAD와 HEAD~1 이해하기

git reset을 배우기 전에 HEAD의 의미를 이해해야 한다.

Git에서 HEAD는 일반적으로 현재 체크아웃한 브랜치의 현재 커밋을 가리킨다.

예를 들어 커밋 기록이 다음과 같다고 하자.

A---B---C
        ↑
       HEAD

현재 브랜치가 C 커밋을 가리키고 있다면 HEAD도 C를 가리킨다.

HEAD~1은 일반적인 선형 커밋 기록에서 HEAD보다 한 단계 이전 커밋을 의미한다.

A---B---C
    ↑   ↑
 HEAD~1 HEAD

따라서:
git reset --soft HEAD~1
이라고 하면 현재 브랜치의 끝을 한 커밋 이전으로 이동한다는 뜻이다.

두 커밋 이전은:
HEAD~2
처럼 표현할 수 있다.

하지만 실수로 다른 커밋을 대상으로 작업하는 것을 막으려면 먼저 다음 명령어로 실제 기록을 확인하는 것이 좋다.

git log --oneline

그리고 필요하다면 HEAD~1 대신 커밋 해시를 직접 사용할 수도 있다.

8. push 전 커밋을 취소하고 스테이징은 유지하기: reset --soft

이번에는 파일 수정과 git add, git commit까지 끝났는데 커밋을 다시 만들고 싶은 상황을 생각해 보자.

예를 들어:

A---B---C
        ↑
       HEAD

에서 C가 방금 만든 잘못된 커밋이라고 하자.

아직 이 커밋을 GitHub에 push하지 않았다.

C 커밋 자체는 취소하되 변경 내용은 그대로 다음 커밋에 넣을 준비가 된 상태로 유지하고 싶다면 git reset --soft를 사용할 수 있다.

git reset --soft HEAD~1

실행 후에는:

A---B
    ↑
   HEAD

로 현재 브랜치가 B를 가리키게 된다.

하지만 C에서 변경했던 파일 내용은 사라지지 않는다.

또한 해당 변경 내용은 Staging Area에 그대로 유지된다.

따라서: git status를 실행하면 변경 내용이 Changes to be committed: 아래에 표시될 수 있다.

git reset --soft HEAD~1 실행 후 git status 결과
git status 실행

--soft는 다음과 같이 생각하면 쉽다.

커밋만 취소
    ↓
변경 내용 유지
    ↓
스테이징도 유지

커밋 메시지를 바꾸거나 일부 내용을 추가한 뒤 다시 커밋할 수 있다.

git commit -m "수정된 커밋 메시지"

git reset --soft는 아직 다른 사람과 공유하지 않은 로컬 커밋을 다시 구성할 때 사용하는 것이 좋다.

9. push 전 커밋과 스테이징을 취소하기: reset --mixed

이번에도 가장 최근의 C 커밋을 취소한다고 하자.

하지만 이번에는 커밋뿐 아니라 git add까지 취소해서 파일을 다시 하나씩 확인하고 싶다.

이때:

git reset --mixed HEAD~1

을 사용할 수 있다.

또는 --mixed가 기본값이므로 다음처럼 써도 같다.

git reset HEAD~1

실행하면 현재 브랜치는 한 커밋 이전으로 이동한다.

그리고 변경 내용은 Working Directory에는 남아 있지만 Staging Area에서는 내려온다.

Unstaged changes after reset:
M       profile.txt
git reset --mixed HEAD~1 실행 결과
git reset --mixed HEAD~1 실행

즉:

커밋 취소
     ↓
스테이징 취소
     ↓
파일 수정 내용은 유지

이다.

git status에서는 다음과 비슷하게 볼 수 있다.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   profile.txt

이제 다시 변경 내용을 확인한다.

git diff
git diff 실행 결과
git diff 실행

필요한 파일만 스테이징할 수도 있다.

git add profile.txt

그리고 다시 커밋한다.

git commit -m "수정한 커밋"

--soft--mixed의 차이를 정리하면 다음과 같다.

git reset --soft HEAD~1
• 커밋 취소
• 스테이징 유지
• 파일 수정 유지

git reset --mixed HEAD~1
• 커밋 취소
• 스테이징 취소
• 파일 수정 유지

따라서 초보자 입장에서는 커밋을 취소하되 작업한 파일을 잃고 싶지 않다면 --soft 또는 --mixed를 먼저 생각하는 것이 안전하다.

10. 주의해서 사용해야 하는 git reset --hard

git reset에서 가장 주의해야 하는 옵션이 --hard다.

git reset --hard HEAD~1

--hard는 현재 브랜치의 커밋 위치만 이동하는 것이 아니다.

Staging Area와 Working Directory까지 지정한 커밋 상태에 맞춘다.

즉:
• 커밋 위치 되돌림
• 스테이징 상태 되돌림
• Working Directory도 대상 커밋 상태로 되돌림

와 비슷하게 이해할 수 있다.

예를 들어:

A---B---C
        ↑
       HEAD

에서: git reset --hard HEAD~1을 실행하면 브랜치는 B로 이동하고, 작업 파일도 B 커밋의 상태에 맞춰진다.

따라서 C에 있던 변경 내용뿐 아니라 Working Directory에 따로 작업 중이던 tracked 파일의 커밋되지 않은 수정 내용까지 덮어쓸 수 있다.

또한 --hard는 Working Directory를 대상 커밋 상태에 맞추는 과정에서 untracked 파일에도 영향을 줄 수 있으므로, untracked 파일이 항상 보존된다고 생각해서는 안 된다.

git reset --hard는 이 글에서 다루는 명령어 중 특히 주의해서 사용해야 한다.

실행하기 전에는 최소한 다음을 확인하는 것이 좋다.

git status
git log --oneline

그리고 다음 질문을 스스로 확인한다.

  • 현재 올바른 브랜치에 있는가?
  • 어떤 커밋으로 이동하려는가?
  • 커밋하지 않은 수정 내용이 남아 있지 않은가?
  • 정말 Working Directory의 변경 내용까지 버려도 되는가?

특히 중요한 점은 다음이다.

커밋된 내용이라면 나중에 git reflog 등을 통해 다시 찾을 가능성이 있지만, Git에 한 번도 커밋되지 않은 수정 내용을 reset --hard로 덮어쓴 경우 Git만으로 복구하기 어려울 수 있다.

따라서 단순히 파일 하나의 수정 내용을 취소하거나 git add만 취소하려는 목적으로 git reset --hard를 사용하는 것은 권장하지 않는다.

그런 상황에서는 앞에서 살펴본: git restore 파일명
또는: git restore --staged 파일명
처럼 필요한 범위만 되돌리는 것이 좋다.

11. 이미 push한 커밋 되돌리기: git revert

이번 글에서 다루는 git reset <commit> 형태는 현재 브랜치가 가리키는 커밋을 이동해 해당 브랜치의 이력을 다시 구성한다.

아직 나만 사용하는 로컬 커밋이라면 큰 문제가 없을 수 있다.

하지만 이미 GitHub에 push했고 다른 사람이 해당 커밋을 내려받았을 수도 있다면 이야기가 달라진다.

이런 상황에서는 기존 커밋을 기록에서 제거하려고 하기보다 git revert를 사용하는 것이 일반적으로 더 안전하다.

git revert는 기존 커밋을 삭제하지 않는다.

대신 해당 커밋의 변경 내용을 반대로 적용하는 새로운 커밋을 만든다.

예를 들어:

A---B---C
        ↑
   잘못된 커밋

이 있다고 하자.

C를 revert하면:

A---B---C---R
            ↑
      C를 되돌리는 새 커밋

처럼 된다.

C라는 기록은 그대로 남아 있다.

대신 R이라는 새 커밋이 C의 변경 효과를 취소한다.

git revert를 실행하기 전에는 git status로 커밋하지 않은 변경 사항이 없는지 확인하는 것이 좋다. 일반적인 git revert는 Working Directory가 깨끗한 상태에서 실행하는 것이 기본이다.

먼저 커밋을 확인한다.

git status
git log --oneline

다음과 같이 출력된다.

bf51ab3 (HEAD -> undo-practice) 잘못된 프로필 수정
386abe1 프로필 설명 파일 추가
git revert 실행 전 git log --oneline 결과
git log --oneline 실행

이번 실습에서는 이 잘못된 커밋을 먼저 GitHub에 push한 뒤 되돌리는 상황을 가정한다.

git push

가장 최근 커밋을 되돌리려면

git revert HEAD

를 사용할 수 있다.

특정 커밋을 지정하려면:

git revert bf51ab3

처럼 사용할 수 있다.

여기서는 부모(parent)가 하나인 일반적인 커밋을 되돌리는 경우를 기준으로 설명한다. merge commit을 revert하려면 mainline parent를 지정하는 -m <parent-number> 옵션이 필요하다. parent 번호는 1부터 시작한다. 여기서 git revert-m은 커밋 메시지를 뜻하는 옵션이 아니라 --mainline을 의미한다. parent 선택에 따라 결과가 달라질 수 있으므로, 초보 단계에서는 merge commit을 임의로 revert하지 말고 먼저 커밋 구조를 확인하는 것이 좋다.

기본 설정에서는 revert 커밋 메시지를 확인하거나 수정하기 위해 Git에 설정된 텍스트 편집기가 열릴 수 있다. 환경에 따라 Vim/vi, Visual Studio Code 등의 편집기가 사용될 수 있다.

기본 메시지를 그대로 사용하고 싶다면 다음처럼 사용할 수도 있다.

git revert --no-edit bf51ab3

완료 후 기록을 확인한다.

git log --oneline

그러면 다음과 비슷한 새 커밋이 만들어진 것을 볼 수 있다.

0791b97 (HEAD -> undo-practice) Revert "잘못된 프로필 수정"
bf51ab3 잘못된 프로필 수정
386abe1 프로필 설명 파일 추가
git log --oneline 실행 결과
git log --oneline 실행

GitHub에도 반영하려면 일반적으로 다시 push한다.

git push

이 방법은 기존 커밋 기록을 지우지 않으므로 협업 프로젝트에서 "누가 어떤 변경을 했고, 왜 다시 취소했는가"라는 기록도 남길 수 있다.

따라서 초보 단계에서는 다음 원칙을 기억하면 좋다.

아직 push하지 않은 개인 커밋 > 필요에 따라 git reset

이미 push했거나 다른 사람이 사용했을 가능성이 있는 커밋 > git revert를 우선 고려

12. git revert 중 충돌이 발생했을 때

git revert도 항상 자동으로 성공하는 것은 아니다.

예를 들어 오래된 커밋을 되돌리려고 하는데 그 이후 같은 파일의 같은 부분이 여러 번 수정되었다면 Git이 어떤 상태로 만들어야 할지 자동으로 결정하지 못할 수 있다.

이 경우 3편에서 살펴본 merge conflict와 비슷한 충돌이 발생할 수 있다.

먼저 상태를 확인한다.

git status

충돌 파일을 직접 열어 원하는 최종 내용으로 수정한다.

수정이 끝나면:

git add 파일명

으로 해결된 파일을 스테이징한다.

그다음:

git revert --continue

를 실행한다.

그러면 중단되었던 revert 작업이 계속 진행된다.

반대로 revert 작업 자체를 취소하고 처음 상태로 돌아가고 싶다면:

git revert --abort

를 사용할 수 있다.

정리하면 다음과 같다.

# revert 실행
git revert 커밋해시
# 충돌 발생 시 상태 확인
git status
# 충돌 파일 수정 후
git add 파일명
# revert 계속
git revert --continue

작업을 취소하려면:

git revert --abort

3편에서 merge conflict를 직접 해결해봤다면 구조는 상당히 비슷하다.

다만 merge 중인 상태인지 revert 중인 상태인지에 따라 마지막 명령어가 다르다.

merge 충돌 해결
git merge --continue

revert 충돌 해결
git revert --continue

13. 마지막 커밋만 수정하기: git commit --amend

마지막 커밋에 작은 실수를 한 경우 항상 git reset을 사용할 필요는 없다.

예를 들어 가장 최근 커밋 메시지를 실수로 "프로필 수저"라고 작성했다고 하자.

1a887bd (HEAD -> undo-practice) 프로필 수저
git log --oneline 실행 결과
git log --oneline 실행

실제로는: "프로필 수정"이라고 작성하고 싶었다.

아직 push하지 않은 가장 최근 커밋이라면 다음과 같이 수정할 수 있다.

커밋 메시지만 수정하려는 경우에는 먼저 git status로 의도하지 않은 변경 사항이 Staging Area에 올라가 있지 않은지 확인하는 것이 좋다.

git commit --amend -m "프로필 수정"

--amend는 기존 마지막 커밋을 직접 수정하는 것처럼 보이지만, 실제로는 수정된 내용을 가진 새 커밋으로 마지막 커밋을 교체한다.

git commit --amend -m "프로필 수정" 실행 후 git log --oneline 실행 결과
git commit --amend -m "프로필 수정" 실행 후 git log --oneline 실행

따라서 커밋 해시도 달라진다.

파일 하나를 빠뜨리고 커밋한 경우에도 사용할 수 있다.

예를 들어 profile2.txt를 커밋에 포함하는 것을 깜빡했다고 하자.

먼저 파일을 스테이징한다.

git add profile2.txt

기존 커밋 메시지를 유지하면서 마지막 커밋에 포함하려면:

git commit --amend --no-edit

를 사용할 수 있다.

정리하면:

커밋 메시지만 수정:

git commit --amend -m "새 커밋 메시지"

빠뜨린 파일 추가:

git add 파일명
git commit --amend --no-edit

다만 git commit --amend 역시 기존 마지막 커밋을 다른 커밋으로 교체하기 때문에 커밋 기록이 바뀐다.

따라서 이미 다른 사람과 공유한 커밋에서는 무심코 사용하지 않는 것이 좋다.

초보 단계에서는 아직 push하지 않은 마지막 커밋을 고칠 때 사용하는 것으로 기억하면 안전하다.

14. reset 후 커밋을 다시 찾아야 할 때: git reflog

git reset을 사용한 뒤 "방금 전 커밋이 사라졌다"고 당황할 수 있다.

예를 들어 원래 다음과 같았다고 하자.

A---B---C

그런데 실수로:
git reset --hard HEAD~1
을 실행해 현재 브랜치가 B로 이동했다.

git log에서는 C가 더 이상 보이지 않을 수 있다.

이때 바로 모든 것이 영구적으로 사라졌다고 생각할 필요는 없다.

Git은 로컬 저장소에서 HEAD와 브랜치가 이동했던 기록을 reflog에 남긴다.

다음 명령어로 확인할 수 있다.

git reflog

아래와 같이 나타날 수 있다.

f9d51ab (HEAD -> undo-practice) HEAD@{0}: reset: moving to HEAD~1
800a619 HEAD@{1}: commit: 프로필 수정
git reset --hard HEAD~1 후 git reflog 실행 결과
git reflog 실행

여기서 800a619이 다시 찾고 싶은 커밋이라고 하자.

기존 브랜치를 바로 다시 움직이기보다 먼저 복구용 브랜치를 만드는 안전한 방법을 사용할 수 있다.

git switch -c recovery-branch 800a619

그러면 해당 커밋을 가리키는 recovery-branch라는 새 브랜치가 만들어진다.

내용을 확인한 뒤 필요한 작업을 진행할 수 있다.

git log --oneline
git status

git reflog는 매우 유용하지만 몇 가지 제한도 있다.

reflog는 현재 로컬 저장소의 참조 이동 기록이다.

다른 사람의 PC와 자동으로 공유되는 Git 기록이 아니다.

또한 reflog 항목은 영구 보관을 위한 기록도 아니다. 따라서 reflog를 백업처럼 생각해서는 안 된다.

그리고 가장 중요한 점은 다음이다.

Git에 한 번도 커밋되지 않은 수정 내용을 git restoregit reset --hard로 버린 경우, reflog가 그 파일 내용을 복구해주는 것은 아니다.

reflog는 주로 이미 존재했던 커밋이나 브랜치 위치를 다시 찾는 데 유용하다.

15. restore, reset, revert 중 무엇을 사용해야 할까?

지금까지 내용을 상황별로 정리해 보자.

파일을 수정했는데 아직 git add하지 않았다.
수정 내용을 버리고 싶다면:
git restore 파일명

주의: 커밋하지 않은 변경 내용이 사라질 수 있으므로 실행 전 git diff로 확인한다.

실수로 git add했다.
파일 수정은 유지하고 스테이징만 취소:
git restore --staged 파일명

특정 파일만 예전 커밋 버전으로 가져오고 싶다.
git restore --source=커밋 파일명
예:
git restore --source=HEAD~1 profile.txt

가장 최근 로컬 커밋을 취소하지만 스테이징 상태는 유지하고 싶다.
git reset --soft HEAD~1

가장 최근 로컬 커밋과 git add를 취소하고 파일 수정은 유지하고 싶다.
git reset HEAD~1
또는:
git reset --mixed HEAD~1

커밋, 스테이징, Working Directory의 변경까지 버리고 싶다.
git reset --hard HEAD~1
주의해서 사용해야 한다. 특히 커밋하지 않은 tracked 파일의 변경 내용까지 사라질 수 있다.

이미 GitHub에 push한 커밋을 안전하게 취소하고 싶다.
git revert 커밋해시
기존 커밋은 유지하고 되돌리는 새 커밋을 만든다.

마지막 커밋 메시지만 잘못 작성했다.
아직 공유하지 않은 커밋이라면:
git commit --amend -m "새 커밋 메시지"

reset 후 이전 커밋이 보이지 않는다.
git reflog
찾은 커밋에 복구용 브랜치를 생성:
git switch -c recovery-branch 커밋해시

결정 흐름을 간단히 표현하면 다음과 같다.

아직 commit 전인가?
│
├─ Yes
│   │
│   ├─ 파일 수정만 취소
│   │      → git restore
│   │
│   └─ git add만 취소
│          → git restore --staged
│
└─ No
    │
    ├─ 아직 push하지 않았는가?
    │   │
    │   ├─ 커밋만 다시 만들기
    │   │      → git reset --soft
    │   │
    │   ├─ 커밋 + staging 다시 하기
    │   │      → git reset --mixed
    │   │
    │   └─ 마지막 commit만 수정
    │          → git commit --amend
    │
    └─ 이미 push하거나 공유했는가?
            → git revert

초보 단계에서 가장 중요한 원칙은 다음과 같다.

필요한 범위보다 더 큰 범위를 되돌리지 않는다.

파일 하나의 문제라면 파일 하나만 되돌리고, 스테이징만 잘못했다면 스테이징만 취소하고, 공유된 커밋이라면 기록을 삭제하기보다 되돌리는 새 커밋을 만드는 식으로 작업하는 것이 좋다.

16. 전체 명령어 요약

이번 글에서는 Git 작업 중 실수했을 때 변경 내용을 되돌리는 방법을 살펴봤다.

가장 먼저 현재 상태를 확인한다.

git status
git diff
git diff --staged
git log --oneline

Working Directory의 파일 수정 취소:

git restore 파일명

스테이징 취소:

git restore --staged 파일명

특정 파일을 과거 커밋 상태로 가져오기:

git restore --source=HEAD~1 파일명

또는:

git restore --source=커밋해시 파일명

최근 로컬 커밋 취소 + 스테이징 유지:

git reset --soft HEAD~1

최근 로컬 커밋 취소 + 스테이징 취소 + 파일 수정 유지:

git reset HEAD~1

또는:

git reset --mixed HEAD~1

커밋과 작업 파일까지 지정 커밋 상태로 되돌리기:

git reset --hard HEAD~1

--hard는 Working Directory의 변경 내용을 버릴 수 있으므로 특히 주의한다.

이미 공유한 커밋 되돌리기:

git revert 커밋해시

기본 revert 커밋 메시지를 그대로 사용:

git revert --no-edit 커밋해시

revert 충돌 해결 후 계속:

git add 파일명
git revert --continue

진행 중인 revert 취소:

git revert --abort

마지막 커밋 메시지 변경:

git commit --amend -m "새 커밋 메시지"

빠뜨린 파일을 마지막 커밋에 추가:

git add 파일명
git commit --amend --no-edit

최근 HEAD와 브랜치 이동 기록 확인:

git reflog

reflog에서 찾은 커밋에 복구용 브랜치 생성:

git switch -c recovery-branch 커밋해시

각 명령어의 역할을 정리하면 다음과 같다.

상황: Working Directory의 수정 내용 취소
명령어: git restore 파일명
의미: 파일을 기본적으로 Staging Area의 내용으로 복원한다.

상황: 스테이징 취소
명령어: git restore --staged 파일명
의미: 파일 내용은 유지하면서 Staging Area에서 내린다.

상황: 과거 파일 복원
명령어: git restore --source=커밋 파일명
의미: 특정 과거 커밋에서 파일 내용을 가져와 Working Directory에 적용한다.

상황: 로컬 커밋 취소 + 스테이징 유지
명령어: git reset --soft HEAD~1
의미: 브랜치의 끝을 이전 커밋으로 이동하지만 Working Directory와 Staging Area(Index)는 그대로 유지한다.

상황: 로컬 커밋 취소 + 스테이징 취소
명령어: git reset --mixed HEAD~1
의미: 브랜치의 끝을 이전 커밋으로 이동하고 Staging Area(Index)를 대상 커밋 상태에 맞추지만 Working Directory는 유지한다.

상황: 브랜치의 커밋 위치와 파일 상태까지 되돌리기
명령어: git reset --hard HEAD~1
의미: 브랜치의 끝을 이전 커밋으로 이동하고 Staging Area와 Working Directory를 대상 커밋 상태에 맞춘다. 커밋되지 않은 작업을 잃을 수 있으므로 주의한다.

상황: 공유된 커밋 되돌리기
명령어: git revert 커밋해시
의미: 기존 기록을 삭제하지 않고 해당 커밋의 변경을 취소하는 새로운 커밋을 만든다.

상황: 마지막 로컬 커밋 수정
명령어: git commit --amend
의미: 마지막 커밋을 수정된 새 커밋으로 교체한다.

상황: 이전 HEAD 위치 확인
명령어: git reflog
의미: 로컬 저장소에서 HEAD와 참조가 이동한 기록을 확인한다.

정리하면 이번 4편에서 가장 중요한 것은 명령어 자체를 외우는 것이 아니다.

현재 변경 내용이 Git의 어느 단계까지 진행되었는지를 먼저 확인한 뒤, 필요한 범위만 되돌리는 것이 핵심이다.

Working Directory
      ↓
   restore
Staging Area
      ↓
restore --staged
공유 전 Commit
      ↓
    reset
공유된 Commit
      ↓
    revert

특히 git reset --hard처럼 Working Directory까지 변경하는 명령어는 실행하기 전에 반드시 git statusgit log --oneline으로 현재 상태를 확인하는 습관을 들이는 것이 좋다.

다음 글에서는 작업 도중 다른 브랜치로 이동하거나 급하게 다른 작업을 해야 할 때 현재 수정 내용을 커밋하지 않고 임시로 보관하는 git stash의 사용 방법을 정리할 예정이다.

Join the conversation

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다