되돌리기
고친 사람 github-actions[bot]
되돌리기는 이미 해 놓은 변경을 취소해 그 전 상태로 돌려놓습니다. 돌아갈 상태를 어딘가에 남겨 둔 만큼만 돌아갈 수 있습니다. 버전관리에서는 지난 기록을 지우는 대신, 그 변경을 거꾸로 적용하는 새 기록을 하나 더 쌓는 쪽을 가리킵니다.
쉽고 빠른 이해
되돌리기는 이미 저지른 변경을 없던 일로 만들어 그 전 상태를 다시 꺼내 주는 일입니다. 방금 올린 커밋이 서비스를 망가뜨렸을 때 그 커밋이 한 일만 골라 빼는 것이 이 일입니다.
되돌릴 방법이 없으면 잘못을 알아채도 손으로 다시 짜는 수밖에 없습니다. 그동안 잘못된 상태가 계속 굴러갑니다.
어떻게 도나:
- 변경하기 전에 돌아갈 상태를 남겨 둡니다
- 무엇을 되돌릴지 고릅니다
- 그 변경을 거꾸로 적용하거나, 남겨 둔 그 전 상태를 꺼내 지금 것을 덮습니다
대가도 있습니다. 돌아갈 상태를 남기는 만큼 저장 공간과 기록이 늘어납니다. 그리고 바깥으로 이미 나간 일은 되돌려도 안 돌아옵니다. 보낸 메일과 빠져나간 결제가 그렇습니다.
어느 쪽으로 되돌릴지는 이렇게 고릅니다. 남이 이미 받아 간 변경이면 덧붙여 되돌리고, 나만 가진 것이면 아예 지워도 됩니다.
상세
되돌리기가 되려면 두 가지가 있어야 합니다. 돌아갈 상태와, 그 상태로 가는 길입니다. 둘 중 하나만 없어도 되돌리기는 말만 남습니다.
되돌릴 수단이 없으면 잘못을 알아채도 손으로 다시 짜는 수밖에 없습니다. 다시 짜는 동안 잘못된 상태가 계속 굴러갑니다. 다시 짜다가 또 틀릴 수도 있습니다.
어디서 되돌리든 골격은 같습니다. 변경하기 전에 돌아갈 곳을 남기고, 변경하고, 필요하면 남긴 곳으로 갑니다. 남긴 것을 지우면 그만큼 돌아갈 수 없게 됩니다.
골격은 같아도 무엇을 남겨 두느냐는 분야마다 다릅니다. 그래서 되돌릴 수 있는 범위도 같이 갈립니다.
같은 이름이 가리키는 세 분야
되돌리기는 일상어라 여러 분야가 같은 말을 씁니다. 가리키는 것이 분야마다 달라서 먼저 갈라 둡니다.
| 어디서 | 무엇을 되돌리나 | 남겨 두는 것 |
|---|---|---|
| 문서 편집기 | 방금 한 편집 한 걸음 | 편집 걸음의 목록 |
| 버전관리 | 이미 기록으로 남은 변경 묶음 | 변경 이력 전체 |
| 데이터베이스 · 배포 | 아직 확정 안 된 작업, 또는 방금 올린 판 | 확정 전 값 · 직전 판 |
셋째 줄은 롤백이라는 이름을 더 자주 씁니다. 이 항목은 앞의 둘을 다룹니다. 그중에서도 버전관리 쪽을 주로 봅니다.
거꾸로 하기와 사본으로 덮기
했던 일을 거꾸로 한 번 더 하거나, 그 전 상태의 사본을 꺼내 지금 것을 덮습니다. 되돌리는 길은 이 둘로 갈립니다.
거꾸로 하기는 그 변경 하나만 골라 지울 수 있습니다. 넣었으면 빼고, 지웠으면 다시 넣습니다. 대신 거꾸로 하는 방법을 변경 종류마다 따로 알고 있어야 합니다.
사본으로 덮기는 그 방법을 몰라도 됩니다. 남겨 둔 사본을 쓰면 끝나기 때문입니다. 대신 그 사본 이후의 변경이 같이 사라집니다. 중간 하나만 골라 되돌릴 수 없습니다.
변경 셋을 해 놓고 가운데 것을 되돌린다고 하면 어느 변경이 살아남는지가 이렇게 갈립니다.
flowchart TD
A["변경 A · 여기서 사본을 남겼다"] --> B["변경 B · 되돌리려는 것"]
B --> C["변경 C"]
subgraph R["거꾸로 하기"]
direction TD
R1["B 가 한 일만 거꾸로 한다"] --> R2["A 와 C 는 남는다"]
end
subgraph S["사본으로 덮기"]
direction TD
S1["A 때 남긴 사본을 꺼내 덮는다"] --> S2["B 와 C 가 함께 사라진다"]
end
C --> R1
C --> S1
아래 절들은 거꾸로 하는 쪽을 따라갑니다. 남긴 사본을 꺼내 덮는 쪽은 배포와 데이터베이스가 주로 씁니다. 그것이 앞에서 말한 롤백입니다.
버전관리가 기록을 지우지 않는 까닭
버전관리의 되돌리기는 거꾸로 하는 쪽입니다. 지난 커밋을 지우지 않습니다. 그 커밋이 한 일을 거꾸로 적용한 새 커밋을 맨 위에 하나 더 쌓습니다.
이렇게 하는 까닭은 이력이 나 혼자 것이 아니기 때문입니다. 푸시 로 올린 커밋은 이미 동료가 받아 갔습니다. 내가 그 커밋을 이력에서 빼면 동료의 이력과 내 이력이 어긋납니다. 다음에 주고받을 때 서로 맞출 수가 없습니다.
flowchart TD
A["커밋 A"] --> B["커밋 B · 기능을 넣었다"]
B --> C["커밋 C"]
C --> R["커밋 R · B 가 넣은 것을 뺀다"]
그림에서 커밋 B 는 남아 있습니다. 파일의 내용만 B 이전으로 돌아갔습니다. 이력에는 「넣었다」와 「뺐다」가 나란히 남습니다. 나중에 왜 뺐는지를 커밋 메시지에서 읽을 수 있는 것도 이 덕입니다.
덧붙이는 되돌리기와 이력을 고쳐 쓰는 되돌리기
앞 절의 거꾸로 하기가 버전관리에서는 새 커밋을 하나 더 쌓는 꼴로 나타납니다. 이것을 덧붙이는 되돌리기라고 부르겠습니다.
덧붙이지 않고 커밋을 아예 없애는 길도 있습니다. 아직 아무에게도 안 보낸 커밋이라면 이력에서 빼도 어긋날 상대가 없습니다.
없애는 길 하나는 커밋을 다시 쌓아 이력을 새로 만드는 것입니다. 이 방법을 리베이스라고 합니다.
다른 하나는 「여기까지가 최신이다」를 가리키는 표시를 앞 커밋으로 옮기는 것입니다. 표시가 지나친 커밋은 이력에서 떨어져 나갑니다.
둘 다 지난 커밋을 이력에서 지웁니다. 그래서 둘을 묶어 이력을 고쳐 쓰는 되돌리기라고 부르겠습니다.
남이 이미 받아 간 커밋을 이력에서 지우면 이력이 이렇게 갈립니다.
flowchart TD
A["커밋 A"]
subgraph K["동료가 받아 간 이력"]
direction TD
B["커밋 B"] --> C["커밋 C"]
end
subgraph M["내가 고쳐 쓴 이력"]
direction TD
CP["커밋 C′ · 커밋 B 가 빠졌다"]
end
A --> B
A --> CP
동료에게는 커밋 B 가 있습니다. 내 쪽에는 없습니다. 같은 커밋에서 갈라진 두 줄기라 이어 붙이려면 사람이 손을 대야 합니다.
두 길이 무엇을 남기고 무엇을 지우는지 나란히 놓으면 이렇습니다.
| 덧붙이는 되돌리기 | 이력을 고쳐 쓰는 되돌리기 | |
|---|---|---|
| 지난 커밋 | 남는다 | 사라진다 |
| 남에게 보낸 뒤 | 쓸 수 있다 | 쓰면 이력이 갈린다 |
| 되돌린 까닭 | 이력에 남는다 | 안 남는다 |
가르는 잣대는 남이 그 커밋을 이미 받아 갔느냐입니다. 받아 갔으면 덧붙이고, 안 받아 갔으면 고쳐 써도 됩니다.
되돌리기가 막히는 때
거꾸로 적용하는 일이 늘 되지는 않습니다. 되돌리려는 변경이 손댄 줄을 그 뒤의 변경이 또 손댔으면, 어느 쪽을 남길지 기계가 정할 수 없습니다. 이렇게 사람이 골라 줘야 하는 상태를 충돌이라고 합니다.
병합 커밋은 다른 까닭으로 까다롭습니다. 커밋은 저마다 바로 앞 커밋을 가리킵니다. 그 앞 커밋을 부모라고 합니다.
병합 커밋은 갈라졌던 줄기 둘을 합친 것이라 부모가 둘입니다. 「그 전 상태」도 둘이라 어느 줄기를 기준으로 삼을지 사람이 짚어 줘야 합니다.
flowchart TD
T1["줄기 1 의 끝 커밋"] -->|그 전 상태 후보| M["병합 커밋"]
T2["줄기 2 의 끝 커밋"] -->|그 전 상태 후보| M
되돌려도 안 돌아오는 바깥의 결과
되돌리기가 돌려놓는 것은 내가 들고 있는 상태뿐입니다. 그 변경이 바깥에 남긴 결과까지 따라 돌아오지는 않습니다.
보낸 메일은 상대 메일함에 남습니다. 승인된 결제는 계좌에서 이미 빠져나갔습니다. 코드를 이전 판으로 배포 해도 그사이 쌓인 데이터베이스 내용은 남아 있습니다.
그래서 못 되돌리는 일에는 짝이 되는 반대 작업을 따로 만들어 둡니다. 결제를 취소하는 환불이 그것입니다. 겉보기에는 되돌리기지만 새 작업이 하나 더 일어나는 것이라 기록도 둘이 남습니다.
되돌림이 어디까지 닿고 어디서 멈추는지를 그리면 이렇습니다.
flowchart TD
subgraph IN["내가 들고 있는 상태"]
direction TD
A["변경했다"] --> B["되돌렸다 · 그 전 상태로 온다"]
end
A --> P["바깥 · 결제가 승인됐다"]
B --> F["바깥 · 환불이라는 새 작업"]
P --> L["바깥에 남은 기록 둘"]
F --> L
편집기의 되돌리기를 떠받치는 스택
편집기 쪽 되돌리기는 훨씬 단순합니다. 편집 한 걸음마다 「무엇을 어떻게 바꿨는지」를 목록에 쌓아 둡니다. 되돌리라고 하면 맨 위 것부터 꺼내 거꾸로 적용합니다.
맨 위부터 꺼내는 이 목록이 스택입니다. 가장 나중에 넣은 것이 먼저 나오는 성질이 되돌리기 순서와 맞아떨어집니다.
꺼낸 걸음은 다시하기 목록으로 옮겨 둡니다. 그래야 되돌린 것을 한 번 더 되돌릴 수 있습니다. 다만 되돌린 뒤에 새로 편집하면 다시하기 목록은 버립니다. 갈라진 두 미래 중 하나를 고른 것이라 나머지 한쪽은 이어 붙일 데가 없습니다.
목록 둘이 마주 보는 모양은 이렇습니다.
flowchart TD
subgraph U["되돌리기 목록 · 맨 위가 가장 나중 걸음"]
direction TD
U3["넣음 강아지 · 맨 위"] --- U2["지움 고양이"] --- U1["그 앞 걸음들"]
end
subgraph RD["다시하기 목록"]
direction TD
R1["꺼낸 걸음이 여기로 온다"]
end
U3 -->|되돌리기| R1
R1 -->|다시하기| U3
R1 -->|되돌린 뒤 새로 편집하면| X["버린다"]
코드로 보면 쌓고 꺼내는 것이 전부입니다. 편집 걸음 둘을 쌓았다가 하나를 도로 꺼내면 이렇게 됩니다.
undo = []
undo.append(("지움", "고양이"))
undo.append(("넣음", "강아지"))
op = undo.pop() # ("넣음", "강아지")
len(undo) # 1
관련 항목
버전관리에서 되돌리기가 다루는 기록 단위
커밋 · 브랜치 · 병합 커밋 · HEAD · 작업 디렉터리 · 스테이징 영역 · 커밋 메시지
버전관리에서 되돌리기를 실행하는 명령
git revert · git reset · git restore · git checkout · 체리픽 · 스태시
되돌리기와 같은 뿌리에서 갈라진 이력 수정 방식
리베이스 · 스쿼시 · 강제 푸시 · 선형 이력 · 빨리 감기 병합
되돌리다 잃은 커밋을 되찾는 수단
레퍼로그 · 분리된 HEAD · 댕글링 커밋 · 가비지 컬렉션
시스템에서 변경을 취소하는 다른 이름
롤백 · 세이브포인트 · 트랜잭션 · 보상 트랜잭션 · 사가
되돌리기를 떠받치는 자료구조와 기록 방식
스택 · 스냅숏 · 커맨드 패턴 · 메멘토 패턴 · WAL · 이벤트 소싱
배포한 판을 이전으로 돌리는 운영 동작
배포 · 블루-그린 배포 · 카나리 배포 · 단계적 출시 · 기능 플래그
되돌리기가 막히거나 어긋나는 상황
충돌 · 충돌 해소 · 3방향 병합 · 부수 효과 · 멱등성
되돌리기를 제공하는 버전관리 도구
다른 이름: undo · revert · 언두 · 리버트