Mercurial
고친 사람 github-actions[bot]
Mercurial 은 파일이 바뀌어 온 이력을 남기고 여럿이 각자 고친 것을 하나로 합쳐 주는 프로그램입니다.
각자 자기 컴퓨터에 이력 전체를 한 벌씩 가집니다. 명령은 짧게 hg 로 칩니다.
한 번 남긴 이력은 되도록 고치지 않도록 만들어졌습니다.
쉽고 빠른 이해
Mercurial 은 코드 폴더가 바뀌어 온 기록을 남기는 도구입니다. 파일을 고치고 hg commit 을
치면 그 순간의 모습이 이력에 한 건 쌓입니다.
이게 없으면 폴더 복사본만 쌓입니다. 누가 무엇을 왜 바꿨는지는 남지 않습니다. 이력이 각자의 컴퓨터에 있어서 서버에 닿지 않아도 옛 모습을 꺼내 볼 수 있습니다.
돌아가는 방식은 이렇습니다.
- 폴더에서 파일을 고칩니다
- 커밋하면 고친 파일이 전부 한 기록으로 묶입니다
- 동료와 기록을 주고받고, 서로 갈라진 기록은 하나로 합칩니다
대가가 있습니다. 코드를 맡아 주는 서비스 대부분이 Git 만 받아서 저장소를 둘 곳을 따로 찾아야 합니다. 이미 남긴 기록을 크게 고치는 명령은 처음에 꺼져 있어서 직접 켜야 합니다. 그래서 주로 이미 Mercurial 로 쌓인 저장소를 이어 갈 때 씁니다.
상세
Mercurial 은 버전관리를 하는 도구입니다. 버전관리는 파일을 언제 누가 어떻게 바꿨는지 남겨 두는 일입니다. 필요하면 예전 모습으로 되돌립니다. 이런 도구가 없으면 폴더 복사본만 날짜별로 쌓입니다. 누가 무엇을 왜 바꿨는지는 남지 않습니다.
이 절은 Mercurial 이 기록을 쌓는 단위와 그 기록을 디스크에 담는 모양을 먼저 봅니다. 이어서 기록을 주고받고, 갈라진 기록을 합치고, 갈래를 나누는 법을 명령과 그림으로 봅니다. 끝으로 이 도구가 무엇을 안 하기로 했는지를 봅니다.
이력을 각자 한 벌씩 가지는 분산 버전관리
파일과 그 파일이 바뀐 기록을 함께 담은 폴더를 저장소라고 부릅니다. 이 폴더가 있어야 지난 기록을 꺼내 보고 되돌릴 수 있습니다.
버전관리 도구는 이 저장소를 어디에 두느냐로 갈립니다. 서버 한 곳에만 두는 방식이 중앙집중형 버전관리입니다. Subversion 이 그런 도구입니다.
Mercurial 은 그 반대인 분산 버전관리 도구입니다. 사람마다 저장소를 온전히 한 벌씩 가집니다. Git 도 분산 버전관리 도구입니다.
남의 저장소를 이력까지 전부 복사해 오는 일을 클론이라고 합니다. 명령은 hg clone 입니다.
복사가 끝나면 옛 기록을 보는 일도 새 기록을 남기는 일도 네트워크를 타지 않고 내 컴퓨터에서
끝납니다.
저장소 안에서 내가 편집기로 열어 고치는 파일들이 놓인 폴더를 작업 디렉터리라고 합니다.
기록은 이 폴더 맨 위의 .hg 폴더에 들어 있습니다. 파일을 아무리 고쳐도 커밋하기 전까지는
.hg 안의 기록이 바뀌지 않습니다.
명령 이름 hg 는 수은의 원소 기호에서 따왔습니다. 수은은 영어로 mercury 입니다. 제품 이름과
뿌리가 같습니다.
변경집합과 그 이름 두 개
고친 것을 저장소에 한 번 기록하는 일을 커밋이라고 합니다. Mercurial 은 커밋으로 남은 기록 한 건을 변경집합이라고 부릅니다. 영어로는 changeset 입니다.
변경집합 하나에는 바뀐 파일, 작성자, 시각, 설명 메시지가 담깁니다. 바로 앞 변경집합의 이름도 함께 담깁니다. 이 앞 변경집합을 부모라고 합니다. 부모를 따라 거슬러 올라가면 처음 기록까지 이어집니다.
변경집합에는 이름이 둘 붙습니다. 첫째는 리비전 번호입니다. 그 저장소에 기록이 들어온 차례대로 0, 1, 2 하고 붙는 정수입니다.
둘째는 해시입니다. 내용을 넣으면 그 내용만의 값을 돌려주는 계산을 해시 함수라고 합니다. 변경집합의 내용을 이 계산에 넣어 나온 16진수 값이 그 변경집합의 해시입니다. 내용이 같으면 어느 컴퓨터에서 계산해도 같은 값이 나옵니다.
아래 두 명령은 지금 작업 디렉터리가 딛고 있는 변경집합의 두 이름을 찍습니다.
hg id -n # 5
hg id -i # 1a2b3c4d5e6f
두 줄은 같은 변경집합을 가리킵니다. 리비전 번호는 짧아서 혼자 쓸 때 편합니다. 그런데 이 번호는 내 저장소 안에서만 통합니다. 기록이 들어온 차례가 저장소마다 달라서, 같은 변경집합이 동료 저장소에서는 7번일 수 있습니다. 그래서 동료에게 기록을 가리킬 때는 해시를 씁니다.
커밋 앞에 고르는 구역이 없는 구조
Git 은 고친 것 가운데 이번 기록에 담을 것을 먼저 골라 올려 두는 구역을 둡니다. 이 구역을 스테이징 영역이라고 부릅니다. Mercurial 에는 이 구역이 없습니다.
hg commit 은 추적 중인 파일 가운데 고친 것을 전부 한 변경집합에 담습니다. 추적 중인 파일은
hg add 로 한 번 등록해 둔 파일입니다. 등록하지 않은 파일은 커밋에 안 들어갑니다.
hg add login.py # 추적을 시작한다
hg commit -m "수정" # 고친 것 전부
hg commit login.py # 이 파일만
첫 줄로 등록하고 둘째 줄로 고친 것 전부를 기록합니다. 셋째 줄처럼 파일 이름을 적으면 그
파일만 담깁니다. -i 옵션을 붙이면 파일 안에서 붙어서 고친 줄 묶음을 하나씩 보며 담을지 고를
수도 있습니다.
명령 하나로 기록이 끝나서 칠 명령이 적습니다. 대신 한 번에 고친 두 가지 일을 다른 기록으로 나누려면 커밋할 때마다 직접 골라야 합니다.
기록을 디스크에 담는 세 층
.hg 폴더 안의 기록은 세 층으로 이어집니다. 맨 위는 변경집합을 차례로 적은 변경 로그입니다.
영어로 changelog 라고 부릅니다.
가운데 층에는 매니페스트가 변경집합마다 하나씩 쌓입니다. 매니페스트는 그 시점에 어떤 파일이 어느 판으로 있었는지를 적은 목록입니다. 변경집합 하나는 자기 매니페스트 하나를 가리킵니다.
맨 아래는 파일 로그입니다. 파일마다 하나씩 있고, 그 파일 하나가 거쳐 온 판들이 쌓입니다. 매니페스트에 적힌 판은 이 파일 로그 안의 판을 가리킵니다.
flowchart TD
C["변경 로그 · 변경집합을 차례로 적는다"] --> M["매니페스트 · 변경집합마다 하나"]
M --> F1["파일 로그 · login.py"]
M --> F2["파일 로그 · user.py"]
M --> F3["파일 로그 · 나머지 파일마다 하나씩"]
변경집합 하나를 꺼내면 위에서 아래로 따라 내려가 그 시점의 파일들을 모읍니다. 파일 로그가 파일마다 있어서 파일 하나의 이력만 볼 때는 그 파일 로그 하나만 읽으면 됩니다.
세 층은 모두 revlog 라는 같은 형식으로 저장됩니다. revlog 는 revision log 를 줄인 이름입니다. 한 대상이 거쳐 온 판들을 차례로 담는 저장 형식입니다.
revlog 는 새 판을 전부 다시 적지 않습니다. 앞 판과 달라진 부분만 적습니다. 이렇게 차이만 적은 조각을 델타라고 합니다.
차이만 이어 붙이면 저장소가 덜 불어납니다. 그런데 차이가 길게 이어지면 옛 판 하나를 꺼낼 때 조각을 많이 되짚어야 합니다. 그래서 revlog 는 사이사이에 판 전체를 한 번씩 적어 둡니다. 꺼낼 때는 가장 가까운 전체 판에서 시작해 차이 몇 개만 더하면 됩니다.
받아 오기와 작업 디렉터리 갱신
내 저장소가 아닌 다른 곳에 있는 저장소를 원격 저장소라고 합니다. 팀이 함께 쓰려고 서버에 둔 저장소가 흔한 예입니다. 기록을 주고받는 상대가 이 원격 저장소입니다.
원격 저장소에 올라온 변경집합을 내 저장소로 가져오는 동작이 풀입니다. 명령은 hg pull
입니다. 반대로 내 변경집합을 올리는 동작은 푸시입니다. 명령은 hg push 입니다.
hg pull 은 변경집합을 저장소에만 넣습니다. 작업 디렉터리의 파일은 바뀌지 않습니다. 파일을 새
변경집합의 모습으로 바꾸는 일은 hg update 가 합니다. 둘을 한 번에 하려면 hg pull -u 를
칩니다.
sequenceDiagram
participant 원격 as 원격 저장소
participant 내것 as 내 저장소
participant 작업 as 작업 디렉터리
원격->>내것: hg pull · 변경집합이 들어온다
Note over 작업: 파일은 아직 옛 모습이다
내것->>작업: hg update · 파일이 새 모습이 된다
내것->>원격: hg push · 내 변경집합을 올린다
받아 오기를 두 명령으로 나눠서 받아 온 기록을 먼저 살펴보고 파일을 바꿀지 정할 수 있습니다.
이름이 같아서 헷갈리는 대목이 있습니다. Git 의 git pull 은 받아 온 뒤 합치기까지 합니다.
hg pull 과 같은 일은 Git 에서 git fetch 가 합니다. 이 동작을 페치라고 부릅니다.
원격 저장소의 주소는 저장소 설정 파일 .hg/hgrc 의 [paths] 절에 이름을 붙여 적습니다.
hg pull 이나 hg push 를 주소 없이 치면 default 라는 이름의 주소로 갑니다. 클론하면 복사해
온 주소가 default 로 적힙니다.
헤드가 둘이 되면 하는 병합
헤드는 자식이 아직 없는 변경집합입니다. 부모를 따라 이어진 기록 한 갈래의 맨 끝입니다. 한 줄로만 기록하면 헤드는 하나입니다.
나와 동료가 같은 부모에서 각자 커밋하면 기록이 두 갈래로 갈립니다. 이 공통 부모 아래로 두
변경집합이 각각 붙습니다. 동료의 기록을 풀로 받아 오면 내 저장소에 헤드가 둘 생깁니다. 내 쪽에
새 기록이 없었다면 헤드는 하나라서 hg update 만 하면 됩니다.
두 헤드를 하나로 잇는 일이 병합입니다. hg merge 가 두 갈래의 변경을 작업 디렉터리에 합쳐
놓습니다. 이어서 hg commit 이 그 결과를 기록합니다. 병합은 커밋까지 쳐야 끝납니다.
flowchart TD
A["공통 부모"] --> B["내 변경집합"]
A --> C["동료 변경집합"]
B --> M["병합 변경집합 · 부모 둘"]
C --> M
이렇게 남은 병합 변경집합은 부모를 둘 가집니다. 부모가 둘인 기록을 병합 커밋이라고 합니다. 이력을 보면 두 갈래가 언제 만났는지 알 수 있습니다.
두 갈래가 같은 파일의 같은 줄을 다르게 고쳤으면 도구가 어느 쪽을 택할지 정하지 못합니다. 그때는 사람이 골라 고친 뒤 커밋합니다. 이 일을 충돌 해소라고 합니다.
hg push 는 원격 저장소에 새 헤드를 만들게 되면 거절합니다. 원격 저장소의 기록을 먼저 받아 와
내 쪽에서 합친 뒤 올리라는 뜻입니다. 억지로 올리는 --force 옵션도 있습니다. 그러면 원격
저장소에 헤드가 둘 남아 다음 사람이 합쳐야 합니다.
갈래를 나누는 세 방법
브랜치는 이력을 갈래로 나눠 두는 것입니다. 앞 절에서 헤드가 둘로 갈린 두 갈래도 브랜치의 한 모양입니다. 갈래를 나눠 두면 한 작업이 다른 작업을 건드리지 않습니다. Mercurial 에는 갈래를 나누는 방법이 셋 있습니다.
첫째는 이름 있는 브랜치입니다. hg branch 이름 을 치고 커밋하면 그 이름이 변경집합에
속성으로 적힙니다. 이름을 안 주면 모든 변경집합은 default 브랜치에 속합니다. 앞 소절의
주소 이름 default 와는 별개입니다.
변경집합에 적힌 브랜치 이름은 이력에 영구히 남습니다. 다 쓴 브랜치도 지우지 못하고 닫힘으로 표시만 합니다. 그래서 출시 판처럼 오래 이어 가는 갈래에 씁니다.
둘째는 북마크입니다. 북마크는 변경집합 하나를 가리키는 움직이는 이름표입니다. 그 북마크 위에서 커밋하면 이름표가 새 변경집합으로 따라 옮겨 갑니다. 이력에는 안 적혀서 다 쓰면 지울 수 있습니다. Git 의 브랜치가 이 방식으로 움직입니다.
셋째는 이름 없이 갈라지는 것입니다. 옛 변경집합으로 hg update 한 뒤 커밋하면 이름 없는 새
헤드가 생깁니다. 앞 절에서 동료와 각자 커밋해 생긴 두 헤드도 이 모양입니다. 잠깐 시험해 보고
버릴 작업에 씁니다.
아래 표는 세 방법을 이름이 적히는 곳과 다 쓴 뒤 지울 수 있는지로 가릅니다.
| 방법 | 이름이 적히는 곳 | 다 쓴 뒤 지우기 |
|---|---|---|
| 이름 있는 브랜치 | 변경집합 안 | ✗ 닫기만 |
| 북마크 | 변경집합 밖의 이름표 | ✓ |
| 이름 없는 헤드 | 없음 | 해당 없음 |
가르는 기준은 이름이 이력에 남느냐입니다. 남겨야 할 갈래는 이름 있는 브랜치로, 짧게 쓰고 치울 갈래는 북마크로 나눕니다.
이력을 고치지 않게 둔 기본값
이미 남긴 변경집합을 고치는 일을 이력 재작성이라고 합니다. 기록의 순서를 바꾸거나, 여러 건을 하나로 합치거나, 다른 갈래 위로 옮겨 붙이는 일입니다. 이력은 깔끔해집니다. 하지만 남이 이미 받아 간 기록을 바꾸면 그 사람의 저장소와 어긋납니다.
Mercurial 은 이 위험을 막도록 기본값을 골랐습니다. 이력을 크게 고치는 명령은 처음 설치했을 때 꺼져 있습니다. 쓰려면 설정 파일에서 확장으로 켜야 합니다.
확장은 Mercurial 에 명령을 더하는 부품입니다. Mercurial 은 대부분 파이썬으로 짜여 있습니다. 확장도 파이썬 모듈 하나로 붙습니다. 자주 쓰는 확장은 Mercurial 안에 이미 들어 있어서 설정 파일에 이름만 적으면 켜집니다.
[extensions]
rebase =
histedit =
위 설정은 함께 들어 있는 확장 둘을 켭니다. = 뒤를 비워 두면 함께 들어 있는 확장을 쓴다는
뜻입니다.
rebase 확장은 리베이스를 합니다. 리베이스는 변경집합을 다른 갈래 위로 옮겨 붙이는
일입니다. histedit 확장은 기록의 순서를 바꾸거나 여럿을 하나로 합칩니다.
마지막 커밋 하나만 고칠 때는 확장이 없어도 됩니다. hg commit --amend 가 그 일을 합니다.
확장을 켜도 아무 기록이나 고쳐지지는 않습니다. Mercurial 은 변경집합마다 단계(phase)를 붙여 두고, 단계를 보고 고쳐도 되는지 가립니다.
| 단계 | 뜻 | 고칠 수 있나 |
|---|---|---|
| public | 남에게 나간 기록 | ✗ |
| draft | 내 저장소에만 있는 기록 | ✓ |
| secret | 푸시해도 안 나가는 기록 | ✓ |
새로 커밋한 변경집합은 draft 로 시작합니다. 푸시가 끝나면 public 으로 바뀝니다. 그 뒤에 고치려 들면 Mercurial 이 거절합니다. 남이 받아 갔을 수 있는 기록을 바꾸지 못하게 막는 장치입니다.
얻는 것은 공유한 이력이 흔들리지 않는다는 점입니다. 잃는 것은 편의입니다. 기록을 정리하고 싶은 사람은 확장을 켜는 수고를 한 번 더 들입니다.
쓰는 때와 쓰지 않는 때
이 절은 Mercurial 을 고르는 때와 안 고르는 때를 저장소를 맡길 곳과 옮기는 수고로 가립니다.
Mercurial 은 주로 이미 Mercurial 로 쌓인 저장소를 이어 가는 팀이 씁니다. 옮기지 않으면 지난 기록과 해시를 손대지 않고 계속 쓸 수 있습니다. 옮기는 수고는 이 절 끝에서 봅니다.
새 프로젝트의 버전관리는 대개 Git 으로 시작합니다. 코드를 맡아 주는 GitHub 와 GitLab 은 Git 저장소만 받습니다. Mercurial 저장소를 받던 Bitbucket 도 그 지원을 거둬들였습니다.
그래서 팀이 Mercurial 을 쓰려면 저장소를 둘 서버를 직접 차리거나 Mercurial 을 받는 Heptapod 같은 몇 안 되는 서비스를 찾아야 합니다. 팀원 대부분이 Git 에 익어 있다면 명령을 새로 익히는 품도 듭니다.
이미 Mercurial 로 쌓인 저장소를 Git 으로 옮길 때도 따질 것이 있습니다. 두 도구는 해시를 계산하는 방식이 달라서 옮기면 모든 기록의 이름이 바뀝니다. 기록을 해시로 가리키던 문서나 이슈의 링크는 새 이름으로 다시 이어 줘야 합니다.
관련 항목
Mercurial 이 속하는 상위 분류
같은 역할을 두고 겨루는 버전관리 도구
Git · Subversion · Perforce · CVS · Fossil · Bazaar · Darcs · Sapling
파일이 기록되기까지 거치는 구역
작업 디렉터리 · 스테이징 영역 · 저장소 · 원격 저장소
Mercurial 이 기록을 담는 단위와 형식
변경집합 · 커밋 · 리비전 번호 · 해시 함수 · 매니페스트 · revlog · 델타
저장소 사이에서 기록을 주고받는 동작
갈라진 이력을 나누고 합치는 방법
브랜치 · 이름 있는 브랜치 · Mercurial 북마크 · 헤드 · 병합 · 병합 커밋 · 3방향 병합 · 충돌 해소
이미 남긴 이력을 고치는 방법
이력 재작성 · 리베이스 · 스쿼시 · 체리픽 · 되돌리기
저장소를 맡아 주는 호스팅 서비스
다른 이름: 머큐리얼 · hg · mercurial