LainyZine: 프로그래머 가이드 🐣

git stash 사용법: 커밋하지 않고 변경사항 저장하는 방법

git stash는 변경사항을 임시로 저장할 수 있도록 도와주는 기능입니다.

Git 저장소에서 코드 작업을 하고 있는 상황을 가정해보겠습니다. 코드를 열심히 수정하던 중인데 아직 커밋은 하지 않았습니다. 갑작스럽게 핫픽스를 요청이 들어옵니다. 이전에는 이럴 때, 변경사항 전체를 하드 리셋하거나 😓, 저장소를 하나 더 클론 받아오거나 😅, 그것도 아니면 작업 내용을 보존하기 위해서 급하게 코드 전체를 커밋하곤 했습니다.

$ git add .
$ git commit -m'Hotfix 작업을 위한 임시 커밋(작업중)'

혼자 쓰는 브랜치에 임시 커밋을 해도 큰 문제는 없습니다만, 나중에 rebase하고 정리하려면 꽤나 귀찮은 일입니다. 또한 공용 브랜치에서 이런 식의 커밋을 푸시하는 건 당연히 좋은 않은 프렉티스트입니다. 이런 상황에서 git stash 명령어로 변경사항을 임시 저장했다가, 나중에 다시 꺼내올 수 있습니다. 이 글에서는 git stash의 기본적인 사용방법을 소개하고, 다양한 활용법과 옵션들에 대해서 소개합니다.

git stash 기초편: 임시로 변경사항 저장하고 되돌리기

git stash에서 꼭 알아야 하는 건 변경사항을 임시로 저장하는 git stash 명령어와 이렇게 저장한 임시 변경 사항을 꺼내오는 git stash pop 명령어입니다. pop 이라는 이름에서도 알 수 있지만, stash는 기본적으로 스택처럼 동작합니다. git stash로 변경사항을 쌓아놓고, pop으로 가장 최근에 저장한 변경사항을 꺼내고 그 내용은 삭제해버립니다.

간단한 예제로 살펴보겠습니다. 아래와 같이 README.mdindex.html 파일이 있는 Git 저장소를 생각해보겠습니다.

$ tree
.
├── README.md
└── index.html

이 Git 저장소의 파일들을 한참 편집중이었습니다만, 갑자기 다른 내용을 처리해야하는 상황입니다. 현재 Git 저장소의 상태는 다음과 같습니다. README.md 파일과 index.html은 둘 다 편집되었고, README.md 파일은 git add로 스테이지 단계에 들어가있습니다.

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   README.md

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:   index.html

git stash 명령어는 Git 저장소에 관리하고 있는 파일들을 대상으로 실행되며, 스테이지에 있는 내용과 아직 스테이지에 들어가지 않은 변경사항을 모두 저장해줍니다. 그럼 이 상태에서 git stash를 실행하고, git status로 상태를 확인해봅니다.

$ git stash
Saved working directory and index state WIP on master: 451c825 Add index.html

$ git status 
On branch master
nothing to commit, working tree clean

워킹 트리가 깨끗한 상태입니다. 즉, 최종 커밋으로부터 변경된 내용이 없다는 의미입니다. 이제 다른 작업을 진행하면 됩니다. 예를 들어 app.js 파일을 급하게 하나 추가하고 커밋했다고 가정해보겠습니다. 이제 프로젝트는 다음과 같습니다.

$ tree
.
├── README.md
├── app.js
└── index.html

이번에는 git stash pop으로 앞서 임시 저장한 내용을 가져오겠습니다.

# 먼저 변경사항이 없는 것을 확인합니다.
$ git status
On branch master
nothing to commit, working tree clean

$ git stash pop
On branch master
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:   README.md
    modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (1248c07e9784f15a5dfa8df78e50239fe083041f)

앞서 편집하고 있던 README.md 파일과 index.html 파일의 변경 사항이 워킹 트리에 다시 반영되었습니다. git status로 저장소의 상태를 확인해봅니다.

$ git status
On branch master
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:   README.md
    modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")

여기서 알 수 있는 중요한 점은 스테이지 상태까지 그대로 복원하지는 않는다는 점입니다(이건 뒤에서 다룹니다).

stash의 가장 중요한 기능은 커밋되지 않은 변경사항을 스택에 쌓아두고, 이걸 다시 꺼내오는 기능입니다. 다른 커밋이 추가되어있는 상태나 심지어 다른 브랜치에서 꺼내는 것도 가능합니다. 하지만 다른 변경사항과 충돌이 나는 경우는 merge 충돌과 마찬가지로 충돌을 풀어주어야합니다.

다시 한 번 pop을 해봅니다.

$ git stash pop
No stash entries found.

집어넣은 내용(한 개)을 모두 pop했기 때문에 더 이상한 가져올 게 없다는 메시지가 출력됩니다. 즉, pop 명령어는 변경사항을 꺼내오고 쌓여있는 내용을 삭제까지 한 번에 수행합니다.

이 정도만 알아도 git stash를 충분히 활용할 수 있습니다.

git stash 명령어 톺아보기

간단한 시나리오로 git stash 명령어에 대해서 살펴보았습니다만, git stash는 더 다양한 기능들을 가지고 있습니다. 여기서부터는 git stash 명령어에 대해 더 자세히 알아봅니다.

git stash하고 임시 저장된 상태를 확인하기

앞서 자세히 확인해보지는 안았지만, git stash를 하면 변경사항이 스택에 쌓입니다. 스택이라는 것은, 하나 이상의 임시 변경 사항을 담아둘 수 있다는 의미입니다. 임시 저장한 내용의 목록을 확인하거나, 변경 사항을 확인해볼 수 있습니다. 다시 git stash를 해보겠습니다.

$ git stash -m 'app.js 작업을 위한 임시 저장'
Saved working directory and index state WIP on master: 451c825 Add index.html

git stash는 옵션 없이 사용할 수도 있습니다만, 커밋과 마찬가지로 -m 옵션을 사용하면 변경사항에 메시지를 붙여둘 수 있습니다.

git stash list 서브 커맨드로 현재 저장소에 임시 저장된 전체 목록을 확인할 수 있습니다.

$ git stash list
stash@{0}: On master: app.js 작업을 위한 임시 저장

여기서 stash@{0}이 이 임시 변경사항의 임시 이름이 됩니다. 뒤에서 살펴보겠습지만, stash를 쌓을 때마다 새로 쌓은 내용이 {0} 인덱스를 가지기 때문에 다수의 임시 변경사항을 가지고 작업할 때는 작업 전에 꼭 인덱스를 다시 확인해야합니다.

이 임시 변경사항(stash@{0})의 내용을 확인해보겠습니다. git stash show 명령어를 사용합니다.

$ git stash show stash@{0}
commit a686394953c63316913361cc2b7afb5d0bd0384b (refs/stash)
Merge: e1bbd0c 3e38de1
Author: LainyZine(lainyzine.com@gmail.com)
Date:   Thu Apr 29 09:18:48 2021 +0900

    On master: app.js 작업을 위한 임시 저장

diff --cc README.md
index e69de29,e69de29..087b7d1
--- a/README.md
+++ b/README.md
@@@ -1,0 -1,0 +1,3 @@@
.....
@@@ -1,5 -1,5 +1,5 @@@
.....

git diff와 같은 형식으로 stash된 내용을 확인할 수 있습니다.

여기서 app.js를 수정하고, 이 내용도 stash로 임시 저장해보겠습니다. 이번에는 git stash save 명령어를 사용합니다.

$ git stash save
Saved working directory and index state WIP on master: e1bbd0c Add app.js

git stashgit stash save의 축약형입니다. -m를 붙이지 않으면 현재 상태를 의미하는 메시지가 자동으로 붙여집니다.

다시 git stash list로 임시 저장된 목록을 확인해보겠습니다.

$ git stash list
stash@{0}: WIP on master: e1bbd0c Add app.js
stash@{1}: On master: app.js 작업을 위한 임시 저장

이제 임시 저장된 내용이 2개가 되었습니다. 여기서 주목할 점은 마지막에 stash한 내용이 가장 위에 있고, 인덱스가 {0}이 되었다는 점입니다.

git stash pop: apply와 drop 명령어의 조합

이번에는 git stash pop에 대해서 좀 더 알아보겠습니다. git stash pop을 인자 없이 실행하면 인덱스가 stash@{0}인 변경사항(가장 최근에 stash된 내용)에 대해 다음 두 가지 작업을 수행합니다.

즉, pop 작업은 apply와 drop으로 나눠서 생각해볼 수 있습니다. apply를 명시적으로 실행하면 stash@{0}의 내용을 꺼내오지만, 삭제하지는 않습니다.

$ git stash apply
On branch master
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:   app.js

$ git stash list
stash@{0}: WIP on master: e1bbd0c Add app.js
stash@{1}: On master: app.js 작업을 위한 임시 저장

꺼내온 내용이 여전히 남아있는 것을 확인할 수 있습니다. 이 상태에서 drop을 실행하면 stash@{0}을 삭제합니다.

$ git stash drop
Dropped refs/stash@{0} (716b4e839729443a935b7e15b6398c5cee152e49

$ git stash list
stash@{0}: On master: app.js 작업을 위한 임시 저장

stash@{0}이 삭제되고, stash@{1}stash@{0}로 변경된 것을 확인할 수 있습니다.

git stash pop, git stash apply, git stash drop 명령이의 기본 인자가 stash@{0}라는 것을 기억해주세요. 인자를 변경하는 것도 가능합니다.

아래 예제에서는 명시적으로 stash@{1}의 변경사항을 꺼내고 삭제합니다. applydropgit stash pop stash@{1} 명령어 하나로 실행해도 결과는 같습니다.

$ git stash
Saved working directory and index state WIP on master: e1bbd0c Add app.js

$ git stash list
stash@{0}: WIP on master: e1bbd0c Add app.js
stash@{1}: On master: app.js 작업을 위한 임시 저장

$ git stash apply stash@{1}
On branch master
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:   README.md
    modified:   index.html

$ git stash drop stash@{1}
Dropped stash@{1} (a686394953c63316913361cc2b7afb5d0bd0384b)

git stash pop의 index 옵션: 스테이지 상태까지 같이 복원하기

앞선 예제에서 git stash pop을 하는 경우 git add한 스테이지 상태는 복원이 되지 않았습니다. git stash pop이나 git stas apply 명령어를 사용할 때 --index 옵션을 붙이면 스테이지 상태까지 같이 복원 됩니다.

아래 예제에서는 git addapp.js를 스테이지 상태로 만들고, 이 상태를 stash하고 --index 옵션을 붙여 pop한 상태를 확인해봅니다.

$ git status
On branch master
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:   app.js

$ git add app.js
$ git stash
$ git status
On branch master
nothing to commit, working tree clean

$ git stash pop --index
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   app.js

Dropped refs/stash@{0} (7e830776d5734398a8ec4e4b11112d2222670418)

git stash branch: 임시 저장된 변경사항을 반영한 브랜치 새로 만들기

새로운 브랜치를 만들고 stash된 내용을 pop합니다. pop으로 동작하기 때문에 임시 저장된 내용을 꺼낸 다음 삭제합니다.

$ git stash branch new-branch
Switched to a new branch 'new-branch'
On branch new-branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   app.js

Dropped refs/stash@{0} (4cc6adfc15c83fecd5a8280b6b9a74d2db8b75a6)

git stash clear: 모든 임시 변경사항(stash)을 삭제

clear 서브 커맨드를 사용하면 stash 스택에 저장된 모든 임시 변경 사항을 한 번에 삭제해버릴 수 있습니다.

$ git stash clear
$ git stash list

모든 stash가 삭제되어서 list 명령어를 실행해도 아무것도 출력되지 않습니다.

git stash 사용 팁

git stash 명령어를 사용할 때 알아두면 유용한 팁들을 소개합니다.

git stash apply로 꺼낸 임시 변경사항의 내용을 되돌리기

다른 작업을 진행하다가 git stash apply로 이전에 저장해온 변경사항을 꺼내올 수 있습니다. 하지만 인덱스를 잘못 지정하거나, 불필요한 내용이 포함되어있어서, 정확히 꺼내온 내용만 다시 되돌리고 싶을 수 있습니다. 이 때는 아래 명령어로 apply한 stash 변경사항만 되돌릴 수 있습니다.

# 가장 최근 stash를 되돌리는 경우
$ git stash show -p | git apply --reverse

# 특정 stash를 되돌리는 경우
$ git stash show -p [STASH_NAME] | git apply --reverse

이는 apply한 stash 내용을 기반으로 되돌리는 방법이기 때문에 pop이나 drop한 경우에는 사용할 수 없습니다.

stash한 내용을 포함해 현재 HEAD의 모든 변경 사항을 되돌려야 하는 경우에는, 아래 명령어를 사용해도 무방합니다.

$ git checkout -f

참고: git - How to reverse apply a stash? - Stack Overflow

팁: 스테이징 단계의 변경사항은 포함하지 않고 임시 저장하기

git stashgit add 명령어를 실행해 스테이지로 옮긴 파일이건 아니건 모두 임시 저장해줍니다. -k(--keep-index) 옵션을 사용하면 스테이징 단계에 있는 파일은 제외하고 임시 저장해줍니다.

$ git stash -k

팁: Git 저장소에서 관리되지 않는 새로 추가한 파일도 같이 임시 저장하기

git stash는 기본적으로 Git 저장소에서 관리하는 파일만을 임시 저장해줍니다.

git stash 명령어를 실행할 때 -u(--include-untracked) 옵션을 사용하면 아직 Git 저장소에서 관리하지 않는 파일들(Untracked files)도 함께 임시 저장해줍니다.

$ git stash -u

추천 문서

LainyZine은 쿠팡 파트너스 활동에 따른 수수료를 제공받습니다.