본문 바로가기
빌드노트 검색

빌드노트

stash pop이 충돌했는데 stash가 그대로 남아 있다

stash pop이 충돌하면 stash는 남지만 일부 변경과 충돌 마커는 이미 작업 트리에 반영될 수 있다. status와 저장된 패치를 대조해 충돌을 해결하고, 결과를 확인한 뒤 해당 항목 하나만 제거하는 순서를 실험했다.

git stash pop이 충돌한 뒤 git stash list에 같은 항목이 남아 있으면, 아직 꺼내지 못한 작업이라고 생각하기 쉽다. pop은 적용 중 충돌이 나면 stash를 자동으로 삭제하지 않는다. 이때 작업 파일에는 충돌 마커와 일부 변경이 이미 들어와 있을 수 있다. 남은 항목을 다시 pop하기 전에 현재 파일과 보관된 내용을 따로 확인해야 한다. Git stash 공식 문서 (새 탭에서 열림)

충돌한 파일과 이미 돌아온 변경을 함께 읽는다

충돌이 난 저장소에서 다음 조회로 현재 상태를 확인한다. 첫 명령의 경로가 의도한 저장소인지 보고, 하위 폴더에서 시작했다면 출력된 루트로 이동한 뒤 나머지를 실행한다. 명령이 오류를 내면 빈 출력을 정상 상태로 해석하지 않는다. 아래 셸 표기는 PowerShell 7에서 확인했다.

git rev-parse --show-toplevel
git status --short
git diff --name-only --diff-filter=U

다음은 동작을 확인하려고 만든 독립 저장소의 실제 status 출력이다. 최초 커밋에서 message.txt는 message=base, note.txt는 note=base 한 줄이었다. 두 값을 각각 message=draft, note=draft로 고쳐 git stash push -m 'draft-message'로 보관했다. 이어 message.txt만 message=current로 바꿔 커밋한 뒤, 작업 트리가 깨끗한 상태에서 그 stash를 pop했다.

UU message.txt
M  note.txt

UU는 양쪽에서 수정한 파일의 충돌이 아직 해결되지 않았다는 뜻이다. 마지막 조회에는 message.txt만 나왔다. 반면 note.txt는 note=draft로 이미 돌아와 있었고, 첫 칸의 M은 그 변경이 스테이징돼 있음을 나타냈다. 충돌한 파일의 두 칸은 평소의 staged/unstaged 구분으로 읽지 않는다. Git status의 짧은 형식 (새 탭에서 열림)

Get-Content -LiteralPath message.txt로 파일을 읽자 다음 내용이 나왔다. 자기 작업에서는 파일명을 실제 충돌 경로로 바꾼다.

<<<<<<< Updated upstream
message=current
=======
message=draft
>>>>>>> Stashed changes

이 실험에서 위쪽은 pop 직전 현재 커밋의 값이고, 아래쪽은 stash에 저장했던 값이다. pop은 종료 코드 1과 The stash entry is kept in case you need it again.을 남겼다. 실패 뒤에도 파일은 적용 전 상태와 달랐다. PowerShell에서 pop 직후의 코드를 보려면 다른 외부 명령을 실행하기 전에 $LASTEXITCODE를 읽어야 한다. 이미 조회 명령을 실행했다면 예전 종료 코드를 얻으려고 pop을 반복하지 않는다.

같은 변경을 보관한 다른 독립 저장소에서는 중간에 message=current 커밋을 만들지 않고 pop했다. 이쪽은 종료 코드 0이었고, 두 파일에 draft 값이 돌아온 뒤 stash 목록이 비었다. 충돌 후 목록에 남았다는 사실만으로 아무 변경도 적용되지 않았다고 판단할 수 없다.

남은 stash를 기준으로 해결 결과를 확인한다

목록에서 방금 적용했던 항목을 고른 뒤, 그 항목에 저장된 변경을 읽는다. 아래 stash@{0}은 가장 최근 항목이다. 자기 목록에서 선택한 번호로 바꾸고, PowerShell에서는 작은따옴표를 유지한다.

git stash list
git stash show -p 'stash@{0}'

실험의 목록에는 stash@{0}: On main: draft-message가 남았다. show -p에는 message의 -message=base·+message=draft와 note의 -note=base·+note=draft가 나왔다. pop 전후에 항목의 커밋 식별자와 이 패치가 모두 같은 것도 확인했다. 보관본은 그대로이고, 해결할 상태는 현재 작업 트리에 남아 있었다.

여기서 show -p는 저장 당시 커밋을 기준으로 한 변경을 보여 준다. 현재 파일과 직접 비교한 출력은 아니다. 따라서 message의 draft 줄만 복사하면 현재 커밋의 current 값을 버릴 수 있다. 두 쪽에서 유지할 내용을 정해 파일을 편집한다. 이번 합성 예제에서는 두 값을 함께 남기기로 정하고 충돌 마커 전체를 다음 한 줄로 바꿨다.

message=current+draft

이 한 줄은 실험에서 정한 해결 결과다. 실제 코드에서는 두 변경을 어떻게 합칠지 직접 판단해야 한다. 파일을 고친 뒤에는 해당 경로만 add하고, 커밋에 들어갈 내용과 아직 스테이징하지 않은 변경을 확인한다.

git add -- message.txt
git status --short
git diff --cached
git diff

add가 성공한 뒤 status는 M message.txt, M note.txt였다. 앞의 충돌 경로 조회도 다시 실행하자 빈 출력이었다. git diff --cached에는 message의 -message=current·+message=current+draft와 note의 -note=base·+note=draft가 보였다. 원래 stash의 의도와 현재 커밋에서 유지하려던 값을 함께 대조한 것이다. 일반 git diff는 비어 있어, 이 실험에서는 스테이징 후 추가로 달라진 작업 파일이 없었다. 두 diff가 비교하는 대상 (새 탭에서 열림)

git add를 했다는 사실만으로 내용이 올바르게 합쳐졌다고 보장되지는 않는다. 충돌 표시가 사라졌는지와 별개로 diff의 실제 내용을 읽는다. 다른 충돌 파일이 있다면 각각 해결해야 한다. 이번 예제의 note처럼 이미 스테이징된 변경도 함께 검토한다.

해결이 끝나도 제거는 별도로 결정한다

충돌을 해결하고 add한 뒤에도 실험의 stash는 목록에 남아 있었다. 이 상태에서 pop을 다시 실행해 적용을 마무리할 필요는 없다. 보관본이 더 필요하다면 그대로 두고, 필요한 변경을 모두 확인했을 때 해당 항목을 지울지 결정한다.

제거 직전에는 앞의 list와 show -p를 다시 실행해 대상이 맞는지 확인한다. 새 stash를 추가하거나 다른 항목을 삭제하면 번호가 바뀔 수 있다. 이름이 같은 항목도 내용까지 읽어 구별한다. 아래는 실험에서 직접 만든 단일 항목이 여전히 stash@{0}임을 확인한 뒤 실행한 명령이다. 자기 작업의 번호로 바꾸며, 제거 여부를 결정하기 전에는 실행하지 않는다.

git stash drop 'stash@{0}'
$LASTEXITCODE
git stash list

drop은 종료 코드 0이었고, 목록은 비었다. 삭제 전후에 파일 내용과 스테이징된 diff를 비교했을 때는 바뀐 것이 없었다. 두 파일은 여전히 staged 상태였다. stash 항목의 제거와 작업 결과의 보존을 따로 확인한 셈이다. 전체 목록을 비우는 clear 대신 확인한 항목 하나만 대상으로 삼았다.

직접 실행한 환경은 Windows 11, Git 2.45.1.windows.1, PowerShell 7.6.6이다. 일반 추적 텍스트 두 파일을 사용했고 줄바꿈 자동 변환을 끄고 충돌 형식을 merge로 고정했다. 실행 버전에 대응하는 2.43.0 문서 (새 탭에서 열림)와 현재 문서에서 충돌 시 보존 계약을 확인했다. --index, untracked 파일, 바이너리·이름 변경 충돌, 다른 운영체제는 시험하지 않았다. 실제 소스의 동작 검증도 이 한 줄 합성 실험이 대신하지는 않는다.

← 목록으로 돌아가기