Skip to content
This page has been auto-translated and may contain errors.View in English

Git에서 변경 사항 및 커밋 취소하기

docs.scrimba.com

항상 취소해야 할 무언가가 있습니다. 의도하지 않은 편집을 입력했거나, 상황을 더 나쁘게 만든 수정을 시도했거나, 아무도 보지 않기를 원하는 것을 커밋했을 수도 있습니다. Git은 프로젝트의 거의 모든 버전 기록을 보관하므로 대부분의 실수는 순간에 느끼는 것보다 훨씬 더 되돌릴 수 있습니다. 어느 도구를 사용할지는 실수가 얼마나 멀리 진행되었는지에 따라 달라집니다: 아직 편집기에 있는지, 이미 스테이징되었는지, 이미 커밋되었는지, 또는 팀원이 볼 수 있는 곳에 이미 푸시되었는지. 이 장은 이 순서대로 각 단계를 설명합니다.

push와 pull이란 무엇인가요?

Push는 커밋을 팀원이 함께 작업하는 저장소의 공유 사본으로 보내고, pull은 그들의 커밋을 당신에게 가져옵니다. Remotes and GitHub에서 둘 다 자세히 다룹니다. 이 장에서 "푸시됨"은 커밋이 당신의 컴퓨터를 떠났다는 뜻입니다.

커밋하기 전에 변경 사항 삭제하기

커밋 루프에서 weather-appstyles.css를 다시 스타일링하고 있는데 새로운 레이아웃이 좋지 않은 아이디어인 것으로 판명되었다고 가정합시다. 스테이징하지 않았고, 커밋하지도 않았습니다. 파일을 마지막으로 본 그대로 돌려놓고 싶습니다. 정확히 그 작업을 위한 명령은 git restore입니다:

bash
$ git status
On branch main
Changes not staged for commit:
        modified:   styles.css

$ git restore styles.css

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

git restore <file>은 커밋하지 않은 편집을 버리고 파일을 마지막으로 저장된 상태로 돌려놓습니다. git add로 이미 변경 사항을 스테이징했었다면 마음을 바꾸면 일반 git restore은 스테이징 영역에 있는 파일이므로 건드리지 않을 것입니다. 먼저 언스테이징한 다음 복구합니다:

bash
$ git add styles.css
$ git restore --staged styles.css
$ git restore styles.css

두 개의 분리된 영역을 위한 두 개의 분리된 이동: git restore --staged는 파일을 스테이징 영역에서 꺼내고, git restore은 단독으로는 작업 디렉터리에서 편집을 버립니다. git restore은 커밋하지 않은 작업에만 도달하며, 변경 사항이 커밋되면 다른 명령이 작동합니다.

더 오래된 튜토리얼과 스크립트는 git restore <file>이 오늘 정확히 수행하는 작업에 git checkout -- <file>을 사용합니다. checkout은 "브랜치 전환"과 "파일 복구" 모두로 작동했으므로 혼동이 충분해서 Git은 둘을 switchrestore로 분할했습니다. 더 오래된 자료에서 여전히 checkout을 만날 수 있으며 인식할 수 있지만, 지금 작성하는 모든 것에서 restore을 사용하세요. git restore .로 현재 폴더의 모든 언스테이징된 변경 사항을 한 번에 버릴 수 있습니다. 한 번에 모든 파일을 지우기 때문에 실행하기 전에 일시 정지할 가치가 있습니다.

restore의 실행 취소는 작업 디렉터리에서 단호히 멈춥니다. 커밋은 Git의 가비지 컬렉션이 제거할 때까지 붙어있는 저장된 객체이므로 잘못된 커밋이 하드 리셋 후에도 reflog를 통해 복구 가능한 이유입니다. 커밋하지 않은 편집은 저장된 객체로 변환되지 않았습니다: restore는 파일의 마지막 저장된 버전을 당신의 편집 위로 확인하고 더 이상 파고들 수 있는 이전 상태가 저장되어 있지 않습니다. 이것이 자주 일찍 커밋하는 실질적인 경우입니다. 지저분한 커밋은 거의 항상 수정 가능합니다. 저장하지 않은 편집은 대부분 그렇지 않습니다.

Juno커밋하지 않은 변경 사항 삭제하기git restore <file>은 커밋하지 않은 편집을 버리고 파일을 이전의 방식대로 돌려놓습니다. 이미 git add로 변경 사항을 스테이징했다면 먼저 git restore --staged <file>로 언스테이징하고, 편집이 없어지기를 원하면 파일을 복구하세요. 이는 아직 커밋하지 않은 변경 사항에서만 작동하므로 이미 저장한 실수를 구할 수 없습니다.
Juno커밋하지 않은 변경 사항 삭제하기git restore <file>git checkout -- <file>의 현대적 대체물이며, git restore .는 폴더의 모든 언스테이징된 변경 사항을 한 번에 지우므로 먼저 git status를 실행해서 무엇을 건드릴지 확인하세요. 더 오래된 답변과 스크립트에서 여전히 이 작업을 수행하는 checkout을 볼 수 있으며, 더 오래되고 더 많은 기능을 담은 이름으로 같은 개념입니다.
Juno커밋하지 않은 변경 사항 삭제하기restore는 절대 커밋을 만들거나 건드리지 않으며, 파일의 마지막 저장된 버전으로 작업 디렉터리를 덮어쓰기만 합니다. 이는 잘못된 경우 복구할 흔적을 남기지 않으므로 이 장의 거의 모든 다른 것과 달리입니다. 작고 자주 커밋하고, "어라"는 reset 또는 reflog가 수정할 수 있는 문제가 되거나 저장 지점으로 돌아갈 수 없는 변경이 됩니다.

git stash로 작업 보관하기

때로는 수정할 실수가 없고 단지 나쁜 타이밍이 있습니다. styles.css를 편집 중이고 팀원이 지금 당신을 다른 브랜치로 필요로 하지만, 반완성된 스타일링을 커밋할 준비가 되지 않았습니다. git stash는 변경 사항을 보관하고 깨끗한 작업 디렉터리를 제공하므로 나중에 물러나서 돌아올 수 있습니다.

bash
$ git status
On branch main
Changes not staged for commit:
        modified:   styles.css

$ git stash
Saved working directory and index state WIP on main: 8f3c7d1 Cache forecast responses in localStorage

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

styles.css에 대한 편집은 사라지지 않았으며, 보관되어 있습니다. 준비가 되면 git stash pop으로 돌려놓으세요:

bash
$ git stash pop
On branch main
Changes not staged for commit:
        modified:   styles.css

git stash는 아직 커밋할 준비가 되지 않은 변경 사항을 위한 것이며, 브랜치에 커밋을 생성하지 않습니다.

하나의 stash는 실제 일주일의 작업을 거의 다루지 못하므로 나머지 도구 키트를 알아두세요. git stash list는 보관한 모든 것을 가장 최근 것부터 먼저 보여줍니다. git stash pop은 상단 항목을 다시 적용하고 목록에서 제거합니다. git stash apply는 다시 적용하지만 목록에 남겨두며, 같은 보관된 변경 사항을 두 브랜치에 원하는 경우 유용합니다. 이후 list가 레이블이 없는 항목의 벽이 되지 않도록 방법을 입력할 때 stash에 이름을 붙이세요:

bash
$ git stash push -m "wip: forecast card layout"
$ git stash list
stash@{0}: On main: wip: forecast card layout

stash는 Git의 다른 모든 것과 동일한 커밋 메커니즘으로 만들어집니다. git stash는 스테이징되고 언스테이징된 상태를 커밋 객체 몇 개로 저장하고 특수 참조 refs/stash를 가리키므로 git stash list가 자체 작은 기록처럼 읽히는 이유입니다. reflog는 동일한 트릭을 사용하며, 커밋을 가리키는 참조이지만 브랜치가 도달하지 않습니다.

Juno작업 보관하기git stash는 커밋할 준비가 되지 않은 변경 사항을 보관하므로 작업 디렉터리가 깨끗해지고, git stash pop으로 나중에 돌려놓습니다. 아무것도 커밋되지 않고 아무것도 손실되지 않으며, 편집은 잠시 보관됩니다. 팀원이 지금 당신을 다른 브랜치로 필요할 때마다 이를 계속 사용합니다.
Juno작업 보관하기git stash list는 모든 보관된 변경 사항을 보여주고, pop은 가장 최근 것을 다시 적용하고 제거하며, apply는 제거하지 않고 다시 적용합니다. push -m으로 방법을 입력할 때 stash에 이름을 붙이세요. 일주일 후 레이블이 없는 항목으로 가득 찬 stash 목록은 당신을 포함한 누구도 도움이 되지 않습니다.
Juno작업 보관하기 stash는 refs/stash가 가리키는 몇 개의 커밋 객체이며, 어떤 브랜치에서도 떨어져 있으므로 자체 작은 기록처럼 작동합니다. 그것을 그렇게 보면 stash는 마법을 멈추고, 동일한 객체 모델이 동일한 트릭을 다시 수행합니다.

git revert로 커밋 취소하기

커밋 8f3c7d1 "Cache forecast responses in localStorage"가 잘못된 것으로 판명되고 이미 푸시되었으므로 팀원이 이미 당겨갔습니다. 그 커밋을 지금 다시 작성하면 누군가가 이미 가진 것을 변경하고, 그들의 사본과 당신의 사본이 다음에 당겨갈 때 불일치합니다. git revert는 이미 존재하는 것을 변경하지 않고 이를 해결합니다: 변경 사항의 반대를 적용하는 완전히 새로운 커밋을 만듭니다.

bash
$ git revert 8f3c7d1
[main 2b6f0d1] Revert "Cache forecast responses in localStorage"

역사는 여전히 원본과 그것을 취소하는 것을 순서대로 보여줍니다. 아무도의 사본도 다시 형성할 필요가 없으며, 팀원은 다른 커밋처럼 새로운 revert 커밋을 당겨갑니다.

git revert는 다른 사람이 이미 가진 커밋에서 항상 안전합니다. 왜냐하면 오래된 것을 변경하는 대신 새로운 커밋을 추가하기 때문입니다.

나중의 커밋과 겹치는 변경 사항을 되돌리면 충돌을 일으킬 수 있습니다. 같은 종류의 충돌을 병합 및 충돌로 다룹니다: Git은 파일을 <<<<<<<, =======, >>>>>>>로 표시하고 당신이 해결하기를 기다립니다. 표시를 수정하고, 파일을 git add하고, git revert --continue로 마칩니다.

bash
$ git revert 8f3c7d1
Auto-merging src/index.js
CONFLICT (content): Merge conflict in src/index.js

$ git add src/index.js
$ git revert --continue

Revert의 공유 역사에 대한 안전성은 공개 브랜치를 리베이스하지 않는 것 뒤의 동일한 개념에 있습니다: 다른 사람이 이미 당겨간 커밋을 다시 작성하면 그들의 사본과 당신의 사본을 동기화 해제하고, 나중에 누군가가 불일치를 강제로 지나가야 합니다. Revert는 모든 기존 커밋을 정확히 있는 그대로 남겨둡니다. 새로운 것만 추가하므로 당신의 사본과 조정할 필요가 없습니다.

Junorevert로 커밋 취소하기git revert <commit>은 원본을 지우는 대신 이를 역으로 돌리는 완전히 새로운 커밋을 추가함으로써 커밋을 취소합니다. 이것이 커밋이 이미 공유되면 안전한 선택이 되는 이유는 저장된 것이 변경되지 않기 때문입니다. 역사는 실수와 수정 모두의 기록을 유지하므로 Git을 더 오래 사용할수록 더 좋아합니다.
Junorevert로 커밋 취소하기 Revert는 나중 커밋이 같은 라인을 건드렸을 때 병합처럼 충돌할 수 있으며, 같은 방식으로 해결합니다: 표시를 수정하고, 파일을 add하고, git revert --continue를 합니다. 취소하려는 커밋이 이미 푸시되거나 공유될 때마다 이를 사용하세요.
Junorevert로 커밋 취소하기 Revert는 절대 기존 커밋을 건드리지 않으므로 다른 사람이 이미 당겨가는 브랜치와 잘 작동하는 취소 도구입니다. 나쁜 커밋이 당신 자신의 컴퓨터를 떠났을 때 이를 기본값으로 유지하세요.

git reset로 되돌리기: soft, mixed, hard

커밋을 취소하는 세 번째 방법이 있습니다. git reset이며, revert와 다르게 작동합니다: 오래된 것을 취소하는 새로운 커밋을 추가하는 대신 reset은 현재 브랜치를 완전히 다른 커밋을 가리키도록 이동합니다. 이는 아무도 본 적이 없는 커밋에 빠르고 깔끔하며 공유된 커밋에 위험합니다.

Reset은 위의 restore와 revert의 더 광범위하고 사용 오류가 더 쉬운 친척입니다. 대부분의 일상적인 수정에서는 이를 필요로 하지 않으며, restore, revert, stash가 대부분의 "도와줘, 이것을 취소해"라는 순간을 다룹니다. 그러나 다른 사람의 지침에서 이를 볼 것이므로 여기 요약이 있습니다: reset은 당신의 브랜치를 역사에서 뒤로 이동하고, 그 모드 중 하나인 --hard는 확인 없이 커밋되지 않은 작업을 버립니다. 어딘가에서 복사하는 명령이 git reset --hard를 포함하면 실행하기 전에 무엇을 하는지 읽어보세요.

git reset <commit>은 브랜치를 그 커밋을 가리키도록 이동하고, --soft, --mixed, --hard는 얼마나 더 많은 것이 함께 이동하는지 결정합니다. 세 개의 레이어를 상상해보세요: 커밋 역사, 브랜치 포인터가 앉아있는 곳, 스테이징 영역, 커밋을 위해 줄을 선 것, 작업 디렉터리, 디스크의 파일. 아래의 짧은 해시는 git log --onelineHistory로 다루는 것과 같은 방식으로 읽으며, 대상 HEAD~1은 "HEAD 이전 한 커밋"을 의미하며, "가장 최신 커밋 취소"를 말하는 표준 방법입니다.

bash
$ git log --oneline
8f3c7d1 Cache forecast responses in localStorage
4f2a1c9 Fix temperature conversion in Celsius-to-Fahrenheit formula
5f3d8b2 Add five-day forecast to the dashboard
1a2b3c4 Set up project structure

$ git reset --soft HEAD~1

--soft는 브랜치 포인터를 한 커밋 뒤로 이동하고 거기서 멈춥니다. 스테이징 영역과 작업 디렉터리는 건드리지 않으므로 취소된 커밋의 모든 것이 스테이징 상태로 앉아 당신이 원하는 대로 다시 커밋하기를 기다립니다.

bash
$ git reset --mixed HEAD~1

--mixed는 플래그를 남기면 실행되는 것입니다. 브랜치 포인터를 뒤로 이동하고 스테이징 영역도 지우므로 취소된 커밋의 변경 사항이 작업 디렉터리에 언스테이징된 편집으로 돌아옵니다. 아무것도 손실되지 않으며, 커밋하기 전에 다시 git add를 실행하세요.

bash
$ git reset --hard HEAD~1

git reset --hard는 브랜치 포인터를 뒤로 이동하고 작업 디렉터리를 일치시키도록 덮어씁니다. 따라서 커밋되지 않은 작업과 취소된 커밋 모두 한 단계에서 사라지며, 당신이 확실한지 묻지 않습니다. 이것이 실행하기 전에 일시 정지할 모드입니다.

이는 또한 커밋 루프에서 git commit --amend가 무엇을 하고 있었는지 설명합니다: amend는 reset --soft HEAD~1 다음에 바로 새 커밋과 가깝습니다. 이것이 새로운 커밋을 그것 위에 쌓지 않고 마지막 커밋을 대체하는 이유입니다.

Reset은 포인터를 이동하기만 하고, --hard 모드에서는 작업 디렉터리를 일치시킵니다. 절대 커밋 객체를 직접 삭제하지 않습니다. reset --hard가 남긴 커밋은 여전히 Git의 객체 데이터베이스에 앉아 있으며, 어떤 브랜치에서도 도달할 수 없으므로 다시 도달할 수 있는 방법이 있다는 점이 중요합니다: git reflog, 다음. 그러나 공유 브랜치에서 브랜치를 뒤로 이동하고 푸시하는 것은 팀원이 이미 가진 커밋을 강제 푸시하는 것을 의미하므로 다음 당겨갈 때 그들의 사본이 다시 작성된 역사와 불일치합니다. 이것이 reset이 로컬 정리 도구로 유지되는 실질적인 이유이며 revert가 이미 당신의 컴퓨터를 떠난 모든 것을 다룹니다.

Junogit reset로 되돌리기 대부분의 일상적인 수정에서 git reset을 사용하지 않을 것이며, restore, revert, stash는 이미 거의 모든 것을 다룹니다. 여전히 이것의 형태를 알아두세요: reset은 당신의 브랜치를 이전 커밋으로 이동하고, --hard 모드는 확인 없이 작업 디렉터리를 일치시키기 위해 지웁니다. reset --hard를 포함하는 복사하는 명령을 실제로 주의해서 다루세요.
Junogit reset로 되돌리기git reset --soft는 취소된 커밋의 변경 사항을 스테이징 상태로 유지하고, --mixed(기본값)는 언스테이징하지만 편집을 유지하고, --hard는 편집을 완전히 버립니다. commit --amend는 기본적으로 soft reset 바로 뒤에 새 커밋이므로 마지막 커밋을 그것 위에 쌓지 않고 대체합니다.
Junogit reset로 되돌리기 Reset은 포인터를 이동하기만 하고, --hard로는 일치하도록 작업 디렉터리를 이동하며, 절대 커밋 객체 자체를 삭제하지 않습니다. 그 객체는 reflog와 가비지 컬렉션이 그것을 따라잡을 때까지 데이터베이스에 유지됩니다. 다시 작성된 브랜치를 푸시하고 팀원의 다음 당겨갈 때 더 이상 그들과 일치하지 않는 역사와 충돌합니다.

revert 또는 reset 선택하기

어떤 도구를 사용할지 결정하는 규칙: 다른 누군가가 이미 이 커밋을 당겨갔나요? 예라면 revert. 커밋이 당신의 컴퓨터에만 존재하고, 푸시하지 않은 브랜치에만 있다면 reset은 괜찮으며 종종 더 깔끔한 수정입니다. 확실하지 않을 때, revert는 항상 안전한 선택입니다. 두 경우 모두에서 작동하기 때문입니다.

"다른 누군가가 당겨갔나"는 정말로 "이 커밋이 다른 누군가가 가진 참조에서 도달 가능한가"를 의미하며, 실제로는 푸시된 브랜치에 있는지 여부를 의미합니다. 5분 전에 만들고 절대 푸시하지 않은 로컬 브랜치의 커밋은 reset을 위한 개방 영역입니다. 푸시하는 순간, 그것을 공유하는 것처럼 다루세요: 로컬로 리셋하고 강제 푸시하는 것은 그 브랜치를 건드리는 다른 누구와 먼저 조정하는 것을 의미합니다.

Junorevert 또는 reset 선택하기 커밋을 취소하기 전에 한 가지 질문을 하세요: 다른 누군가가 이미 이것을 당겨갔나요? 예라면 revert에 도달하세요. 항상 안전합니다. 커밋이 당신의 컴퓨터에만 존재하면 reset도 괜찮습니다. 어느 것이 적용되는지 확실하지 않을 때, revert는 더 안전한 기본값입니다.
Junorevert 또는 reset 선택하기 커밋이 푸시되고 누군가가 가질 수 있으면 revert에 도달하세요. 순전히 당신에게 로컬이고 푸시되지 않은 동안 reset은 괜찮으며 종종 더 깔끔한 수정이므로 로그에 앉아 있는 "revert of a revert" 항목을 남기지 않습니다.
Junorevert 또는 reset 선택하기 실제 테스트는 도달 가능성입니다: 이 커밋이 다른 누군가가 당겨간 참조에 앉아 있나요? 로컬이고 푸시되지 않으면 reset이 개방 영역입니다. 푸시되고 공유되면 리셋하는 것을 먼저 조정할 것으로 다루세요. 왜냐하면 나중의 강제 푸시가 팀원이 이미 가진 것과 충돌하기 때문입니다.

git reflog로 커밋 복구하기

reset --hard, 지저분한 리베이스, 또는 실수로 브랜치 삭제는 커밋이 완전히 사라진 것처럼 보일 수 있습니다. 대부분의 경우 그렇지 않습니다. Git은 당신의 HEAD와 브랜치가 가리킨 모든 곳의 로컬 로그를 유지하며, **reflog**라고 불리며, 그 로그는 거의 항상 손실된 것처럼 보이는 커밋으로 돌아가는 방법입니다.

이를 자주 필요로 하지는 않을 것이지만 여기 요약이 있습니다: Git은 사라진 것처럼 보이는 순간 거의 아무것도 버리지 않습니다. reset --hard를 실행하고 나중에 그 커밋이 필요했다는 것을 깨닫는다면, 아마 그것을 되돌릴 수 있는 방법이 있을 것입니다. 이것은 더 깊이 들어갈 준비가 될 때마다 다룹니다.

짧은 버전: git reflogHEAD의 최근 위치를 짧은 해시로 나열하며, 그 해시 중 하나로 git reset --hard를 할 수 있어 그 정확한 상태로 돌아갑니다. 두 번째 역사처럼 읽습니다: HEAD가 있던 모든 장소. 브랜치가 더 이상 가리키지 않는 커밋을 포함합니다.

더 이상 브랜치, 태그, HEAD가 가리키지 않는 커밋은 **도달 불가능**합니다: 저장소에 떠있으며, 여전히 저장되어 있으며, 그것으로 돌아가는 레이블이 없습니다. reflog는 당신의 컴퓨터에서 HEAD가 이동한 곳의 시간순 목록이며, 이 브랜치 전환, 그 리셋, 그 리베이스, 각 항목은 HEAD@{2}와 같은 짧은 참조로 핵심입니다. 실수 직전의 항목을 찾고 브랜치를 다시 가리키세요:

bash
$ git reflog
2b6f0d1 HEAD@{0}: reset: moving to HEAD~1
8f3c7d1 HEAD@{1}: commit: Cache forecast responses in localStorage
4f2a1c9 HEAD@{2}: commit: Fix temperature conversion in Celsius-to-Fahrenheit formula

$ git reset --hard 8f3c7d1

이것은 reset --hard가 삭제한 것처럼 보이는 커밋이며, 복구되었습니다. 알아야 할 두 가지 제한이 있습니다. 첫째, reflog는 당신 자신의 컴퓨터에만 존재하며, 푸시와 당겨가기는 절대 건드리지 않으므로 팀원이 그들의 컴퓨터에서 손실한 커밋을 복구할 수 없습니다. 둘째, 영원히 지속되지 않습니다: Git은 결국 가비지 컬렉션을 실행하고 몇 주의 기본 기간을 지난 도달 불가능한 커밋을 지우므로 안전망은 프로젝트의 전체 수명이 아닌 최근 실수를 다룹니다.

Juno손실된 커밋 복구하기reset --hard를 실행하고 너무 늦게 그 커밋이 필요했다는 것을 깨닫는다면, 아마 복구 가능할 것입니다. Git은 reflog라고 불리는 로컬 기록을 유지하며 당신의 작업이 가리킨 모든 곳이며, 그것은 보통 사라진 것처럼 보이는 커밋을 되돌릴 수 있기에 충분합니다. 이것은 깊은 다이브 영역이지만 거기에 있다는 것을 아는 것은 안심입니다.
Juno손실된 커밋 복구하기git reflogHEAD의 최근 위치를 reset --hard할 수 있는 짧은 해시로 나열하므로 너무 멀리 간 reset은 보통 복구 가능합니다. 그러나 당신 자신의 컴퓨터에만 존재하므로 팀원이 그들의 것에서 손실한 커밋을 구할 수 없습니다.
Juno손실된 커밋 복구하기 reflog는 모든 HEAD가 있던 곳의 로컬 시간순 기록이며, 이것이 reset --hard가 지운 것처럼 보이는 커밋이 올바른 HEAD@{n} 항목을 가리키는 브랜치를 가리키게 하여 다시 나타나는 방법입니다. 결국 가비지 컬렉션으로 나이가 들고, 당신 자신의 최근 실수만 다룹니다. 커밋을 잃은 팀원은 자신의 컴퓨터에서 동일한 복구를 실행해야 합니다.