이전에 작성했던 Git에 대한 기본개념과 사용법에 이어서 `git`을 실전에서 사용할 때, 일어나는 문제 상황과 그에 대한 해결책을 중심적으로 정리해보겠다.
커밋 되돌리기
개발을 하다보면 커밋 메시지에 오타가 있거나, 커밋에 어떤 파일을 빠뜨리는 등 이미 실행한 커밋을 수정해야하는 상황이 발생한다. 아직 원격에 푸쉬하기 전(내 로컬에만 있는 커밋)이라면 `git reset`을 사용해보자. 만약, 이미 원격에 푸쉬한 상황이라면 `git revert`를 사용하면 된다. 그리고 단순히 특정 커밋의 상태를 확인하거나 특정 버전의 파일이 필요할 때는 `git checkout`을 활용하면 된다.
git reset
`git reset`은 현재 브랜치의 HEAD를 지정한 커밋으로 이동시켜 과거의 특정 시점으로 완전히 되돌아갈 때 사용한다. 일반적으로 아직 원격으로 푸쉬하지 않은 로컬의 변경사항에 사용한다. 주요 옵션 3가지를 살펴보자.
git reset --soft <커밋 해쉬>
`--soft` 옵션은 커밋을 되돌리고, 변경된 파일들을 staging area에 그대로 두는 옵션이다. 즉, 작업 내용을 그대로 스테이징 상태로 두는 옵션이다.
git reset --mixed <커밋 해쉬>
# 또는
git reset <커밋 해쉬>
`--mixed` 옵션은 커밋을 되돌리고, 변경된 파일들을 staging area에서 내리는 옵션이다. 아무 옵션도 적지 않는다면, 이 `--mixed`가 기본값이 된다.
git reset --hard <커밋 해쉬>
`--hard` 옵션은 커밋을 되돌리고, 모든 변경 내용을 ⚠️영구적으로 삭제⚠️하는 옵션이다. 따라서 조심해서 사용해야한다!
HEAD와 ~
그럼 일반적으로 `git reset`을 사용할 때 뒤에 일일이 `<커밋 해쉬>`를 찾아서 입력할까? 사실 그렇게 쓰기보다 `HEAD`와 `~`를 활용하는 경우가 더 많은 것 같다.
git reset --soft HEAD~1
이 명령어는 마지막 커밋을 취소하고, 변경사항은 그대로 스테이징 상태로 두기 위한 명령어다. 그럼 `HEAD`와 물결 기호 `~`의 의미에 대해서 이해해야 한다. `HEAD`는 현재 브랜치의 가장 마지막 커밋을 가리키는 포인터로, "바로 지금 여기!"라는 뜻이다. 뒤에 같이 사용된 `~<숫자>`는 특정 커밋을 기준으로 과거로 몇 칸을 돌아갈지를 의미한다. `HEAD~2`는 `HEAD`로부터 2단계 이전의 커밋을 가리키는 것이다.
대표적인 활용 예시 상황은 다음과 같다.
마지막 커밋 메시지 수정하기
"feat: Add login funtion" 처럼 커밋 메시지에 오타가 포함되어 있는 경우
# 1. 마지막 커밋을 취소하고, 작업 내용은 그대로 스테이징 상태로 둠
git reset --soft HEAD~1
# 2. 올바른 메시지로 다시 커밋
git commit -m "feat: Add login function"
마지막 커밋에 빠뜨린 파일 추가하기
`login.js`와 `login.css`를 커밋해야 했는데, `login.css`를 빠뜨린 채로 `login.js`만 커밋한 경우
# 1. 마지막 커밋을 취소 (login.js는 여전히 스테이징 상태)
git reset --soft HEAD~1
# 2. 빠뜨렸던 파일을 스테이징에 추가
git add login.css
# 3. 두 파일을 함께 다시 커밋
git commit -m "feat: Add login feature"
너무 잘게 나눈 커밋들 하나로 합치기
# 1. 최근 3개의 커밋을 취소하고, 모든 변경 사항을 스테이징 상태로 만듦
git reset --soft HEAD~3
# 2. 하나의 의미있는 메시지로 다시 커밋
git commit -m "feat: Implement login feature"
이렇게 하면 지저분했던 3개의 커밋을 삭제하고, 깔끔한 커밋 하나만 남길 수 있다. 이를 `squash`한다고 부르는데, 이에 대해서는 뒤에서 다뤄보겠다.
git revert
`git revert`는 특정 커밋의 변경사항을 취소하는 새로운 커밋을 생성한다. 이렇게 하면 기존의 커밋 히스토리는 그대로 유지되기 때문에, 일반적으로 이미 원격에 푸쉬하여 다른 사람들과 공유된 커밋을 되돌릴 때 사용한다.
git revert <커밋 해쉬>
그렇다면 `reset`은 `revert`와 어떤 차이가 있을까? `reset`은 브랜치 포인터(HEAD)를 과거의 특정 커밋으로 이동시키는 것으로, 과거 커밋 이후의 히스토리를 삭제하거나 변경한다. 주로 로컬 변경사항에 대해 적용한다. 반면, `revert`는 과거의 변경사항을 취소하는 새로운 커밋을 추가하여 히스토리를 유지한다. 주로 원격에 푸쉬한 커밋을 되돌릴 때 사용한다.
협업 환경이라면 revert를 잘 활용해보자!
git checkout
`reset`이나 `revert`와는 다르게, `checkout`은 주로 특정 커밋의 상태를 단순히 확인하거나 저장소 전체를 과거의 특정 시점으로 탐험할 때 사용한다. checkout을 통해 특정 커밋 시점을 확인하는 중에 git log를 확인해보면 'detached HEAD' 상태가 된다. 이 상태에서 작업한 내용은 새로운 브랜치를 만들지 않는 한 유지되지 않는다.
# 예시: f9a3e3c 커밋 시점으로 되돌아가기
git checkout f9a3e3c
이렇게 해당 커밋 시점으로 이동하여 확인한 후
git checkout <원래 브랜치>
다시 작업하던 원래의 브랜치로 돌아오면 된다.
또는 특정 파일만 특정 시점으로 되돌릴 수도 있다.
# 특정 커밋 시점의 파일로 되돌리기
git checkout <커밋 해시> -- <파일 경로>
# 예시: 5d8a9f3 커밋 시점의 main.py 파일로 되돌리기
git checkout 5d8a9f3 -- src/main.py
언스테이징
`git add <파일 경로>`를 통해 변경사항을 스테이지로 보내는 걸 `스테이징`이라고 표현하는데, 반대로 스테이지에서 내리는 작업을 `언스테이징`이라고 볼 수 있다. 이 때 `git restore`를 활용한다.
git restore
`git restore`는 파일의 변경사항을 되돌리는데 사용되는 명령어이다. `git add`를 실행한 파일을 스테이지에서 내리고(unstage), 변경사항은 그대로 둔다.
# 특정 파일을 unstaged 상태로 되돌리기
git restore --staged <파일 경로>
# staged 상태의 모든 변경사항을 한번에 되돌리고 싶다면
git restore --staged .
이렇게 하면 VSCode의 Source Control 창에서 Staged Changes에 있던 파일이 Changes로 옮겨가는 것을 확인할 수 있다.
임시 저장
`feature/login`이라는 브랜치에서 로그인 기능을 열심히 개발하고 있다가 메인 페이지에 대한 수정 요청이 들어왔다고 가정해보자. 아직 내가 작업하던 게 끝나지 않아 커밋하기는 애매한 상황이라면 `git stash`를 통해 작업을 임시 저장하면 된다.
git stash
git stash는 현재 작업 내용을 커밋하지 않고 '잠시 숨겨두는' 임시 저장 기능이다. 먼저 현재 브랜치에서 작업하던 내용을 임시 저장해보자.
git stash
# 또는
git stash save "<작업 내용에 대한 메모>"
그리고 다른 브랜치로 이동하여 작업을 진행한다. 이동한 브랜치에서 필요한 작업을 완료했다면 `add`하고 커밋하자. 그리고 다시 원래 브랜치로 돌아가자.
# 다른 작업 브랜치로 이동
git checkout <다른 브랜치>
# 또는
git switch <다른 브랜치>
# ... 작업 수행 ...
# ... 작업 수행 ...
# ... 작업 수행 ...
git add .
git commit -m "급한 버그 수정"
# 원래 작업 브랜치로 이동
git checkout <원래 브랜치>
임시 저장했던 작업 내용 목록을 확인하자. (어떤 내용을 가져올지 결정하기 위해 필요하다.)
git stash list
그리고 임시 저장된 stash를 다시 적용해보자.
# 가장 최근에 저장한 stash를 적용하고 목록에서 삭제
git stash pop
# stash를 목록에 그대로 남겨두고 임시 저장 내용만 적용
git stash apply
# pop이나 apply 뒤에 stash@{번호}를 붙이면 특정 stash를 선택하여 적용 가능
git stash pop stash@{1}
git stash apply stash@{1}
stash를 삭제하려면
# 가장 최근의 stash (stash@{0})를 삭제
git stash drop
# 특정 스태시를 지정하여 삭제
git stash drop stash@{1}
'Programming > Git, Github' 카테고리의 다른 글
| [Git/Github] 다른 사람의 repo를 내 repo로 가져오기 (set-url) (2) | 2023.04.26 |
|---|---|
| [Git] git bash 에서 conda 명령어 사용 (0) | 2023.03.03 |
| Git 명령어: 기본적인 개념과 사용법 (1) | 2022.09.06 |