Jujutsu의 기본 개념, 변경 사항, revset, 충돌 해결, 작업 로그, 커밋 간 내용 이동을 Git 경험자를 대상으로 설명하는 튜토리얼입니다.
힌트: 이 튜토리얼은 기초를 정확하게 다루지만, 작성된 이후 Jujutsu에는 많은 새로운 기능이 추가되었습니다. 더 넓은 범위의 기능을 다루는 튜토리얼로는, 아직 완전히 끝나지는 않았지만, Steve Klabnik의 튜토리얼이 도움이 될 수 있습니다. Git 경험이 필요 없는 튜토리얼을 찾고 있다면 Jujutsu for everyone을 살펴보세요.
이 글은 독자가 Git에 익숙하다고 가정합니다.
아직 하지 않았다면, Jujutsu를 설치하고 구성했는지 확인하세요.
힌트: 이 튜토리얼에서 사용되는 대부분의 식별자는 직접 해보면 다르게 나타날 것입니다!
jj를 사용해 GitHub의 Hello-World 저장소를 복제하는 것부터 시작해 봅시다:
# "clone" 앞의 "git"에 주목하세요 (아직 네이티브 jj
# 저장소 복제는 지원되지 않습니다)
$ jj git clone https://github.com/octocat/Hello-World
Fetching into new repo in "/tmp/tmp.O1DWMiaKd4/Hello-World"
remote: Enumerating objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13 (from 1)
bookmark: master@origin [new] untracked
bookmark: octocat-patch-1@origin [new] untracked
bookmark: test@origin [new] untracked
Setting the revset alias `trunk()` to `master@origin`
Working copy (@) now at: kntqzsqt d7439b06 (empty) (no description set)
Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 1 files, modified 0 files, removed 0 files
$ cd Hello-World
이제 jj st (jj status의 줄임말)를 실행하면 다음과 비슷한 결과가 나옵니다:
$ jj st
The working copy has no changes.
Working copy (@) : kntqzsqt d7439b06 (empty) (no description set)
Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
이 출력은 새로운 개념들을 소개하므로 함께 살펴봅시다. Parent와 working copy라는 두 개의 커밋이 보입니다. 둘 다 두 개의 서로 다른 식별자, 즉 “change ID”와 “commit ID”로 식별됩니다.
예를 들어 parent commit은 change ID orrkosyo와 commit ID 7fd1a60b를 가집니다.
Git 사용자: commit ID/hash는 Git에서 익숙한 그것이며, 저장소를 Git 체크아웃에서
git log로 볼 때 나타나는 값과 일치해야 합니다. 하지만 change ID는 Jujutsu에만 있는 새로운 개념입니다.
위 출력에서 working copy 역시 commit ID(예시에서는 d7439b06)를 가진 실제 커밋이라는 것도 알 수 있습니다. working copy에서 변경을 하면, 다음 jj 명령에서 working-copy commit이 자동으로 amend됩니다.
Git 사용자: 이것은 working copy가 별도의 개념이고 아직 커밋이 아닌 Git와 비교했을 때 매우 큰 차이입니다.
변경 사항은 안정적인 식별자를 유지하면서 진화할 수 있는 커밋입니다(Gerrit의 Change-Id와 비슷합니다). 다시 말해, 어떤 변경 사항 안에서 파일을 수정해 새로운 커밋 해시가 생기더라도 change ID는 그대로 유지됩니다.
복제 작업이 자동으로 새로운 변경 사항을 하나 만든 것을 볼 수 있습니다:
Working copy (@) : kntqzsqt d7439b06 (empty) (no description set)
이 새로운 변경 사항은 ID kntqzsqt를 가지며, 현재는 비어 있고(parent와 비교했을 때 변경 없음) 설명도 없습니다.
저장소의 README 파일을 편집해서 “Hello” 대신 “Goodbye”라고 쓰고 싶다고 해봅시다. 무엇을 작업 중인지 잊지 않도록 먼저 변경 사항을 설명해 봅시다(커밋 메시지 추가):
# $EDITOR를 엽니다 (기본값은 `nano` 또는 `Notepad`일 수 있습니다).
# 에디터에 "Say goodbye" 같은 내용을 입력한 뒤 저장하고 닫으세요.
$ jj describe
Working copy (@) now at: kntqzsqt e427edcf (empty) Say goodbye
Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
이제 README를 수정합니다:
# 사용 중인 `sed` 변형에 맞게 필요하면 조정하세요
$ sed -i 's/Hello/Goodbye/' README
$ jj st
Working copy changes:
M README
Working copy (@) : kntqzsqt 5d39e19d Say goodbye
Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Git의 git add처럼 Jujutsu에 변경을 추가하라고 말할 필요가 없었다는 점에 주목하세요. 사실 새 파일을 추가하거나 기존 파일을 제거할 때도 따로 알려줄 필요가 없습니다. 어떤 경로를 추적 해제하려면 .gitignore에 추가한 뒤 jj file untrack <path>를 실행하세요.
또 현재 변경 사항(kntqzsqt)의 커밋 해시가 e427edcf에서 5d39e19d로 바뀌었다는 점도 주목하세요!
diff를 보려면 jj diff를 실행하세요:
$ jj diff --git # `--git` 플래그는 건너뛰어도 됩니다
diff --git a/README b/README
index 980a0d5f19..1ce3f81130 100644
--- a/README
+++ b/README
@@ -1,1 +1,1 @@
-Hello World!
+Goodbye World!
Jujutsu의 diff 형식은 현재 기본적으로 diff에 인라인 색상을 적용합니다(git diff --color-words와 비슷함). 그래서 이 튜토리얼에서는 읽기 쉽게 하기 위해 위에서 --git를 사용했습니다.
눈치챘을 수도 있듯이, 설명을 수정했을 때도 README를 수정했을 때도 working-copy commit의 ID는 바뀌었습니다. 그러나 parent commit은 그대로였습니다. working-copy commit에 대한 각 변경은 이전 버전을 amend합니다. 그렇다면 현재 변경 사항에 대한 amend를 끝내고 새 변경 사항 작업을 시작하고 싶을 때는 어떻게 할까요? 그럴 때 사용하는 것이 jj new입니다. 이 명령은 현재 working-copy commit 위에 새 커밋을 만듭니다. 새 커밋은 working-copy 변경 사항을 위한 것입니다.
그러니 이제 이 변경 사항 작업이 끝났다고 하고, 새 변경 사항을 만들어 봅시다:
$ jj new
Working copy (@) now at: mpqrykyp aef4df99 (empty) (no description set)
Parent commit (@-) : kntqzsqt 5d39e19d Say goodbye
$ jj st
The working copy has no changes.
Working copy (@) : mpqrykyp aef4df99 (empty) (no description set)
Parent commit (@-): kntqzsqt 5d39e19d Say goodbye
나중에 추가 변경이 필요하다고 깨달으면 working copy에서 수정한 뒤 jj squash를 실행하면 됩니다. 이 명령은 지정한 커밋의 변경 사항을 그 부모 커밋으로 squash(이동)합니다. 대부분의 명령과 마찬가지로 기본적으로는 working-copy commit에 작동합니다. working-copy commit에서 실행하면 git commit --amend와 매우 비슷하게 동작합니다.
또는 jj edit <commit>를 사용해 working copy에서 다시 어떤 커밋을 편집할 수도 있습니다. 그러면 이후 working copy에서의 변경은 그 커밋을 amend합니다. 새 변경 사항을 만들고 squash할지, 아니면 edit할지는 보통 그 변경이 얼마나 마무리되었는지에 따라 달라집니다. 거의 끝난 변경이라면 jj new를 사용해서 jj squash 전에 jj diff로 수정 사항을 쉽게 검토하는 편이 합리적입니다.
시간이 지나며 변경 사항이 어떻게 발전했는지 보려면 jj evolog를 사용해 현재 커밋에 기록된 각 변경을 볼 수 있습니다. 여기에는 working copy, 메시지, squash, rebase 등의 변경이 기록됩니다.
아마 git log에는 익숙할 것입니다. Jujutsu도 jj log 명령으로 매우 비슷한 기능을 제공합니다:
$ jj log
@ mpqrykyp martinvonz@google.com 2023-02-12 15:00:22 aef4df99
│ (empty) (no description set)
○ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
│ Say goodbye
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
@는 working-copy commit을 나타냅니다. 한 줄의 첫 번째 ID(예: 위의 “mpqrykyp”)는 change ID입니다. 두 번째 ID는 commit ID입니다. 개정을 인수로 받는 명령에는 어느 쪽 ID든 줄 수 있습니다. 우리는 일반적으로 커밋이 다시 작성되어도 그대로 유지되는 change ID를 선호합니다.
기본적으로 jj log는 로컬 커밋을 나열하고, 문맥을 위해 일부 원격 커밋을 추가합니다. ~는 그래프에 포함되지 않은 부모를 그 커밋이 가지고 있음을 나타냅니다. --revisions/-r 플래그를 사용하면 나열할 개정 집합을 다르게 선택할 수 있습니다. 이 플래그는 “revset”을 받는데, 이는 개정을 지정하기 위한 단순한 언어의 표현식입니다. 예를 들어 @는 working-copy commit을 가리키고, root()는 루트 커밋을 가리키며, bookmarks()는 북마크가 가리키는 모든 커밋(Git의 브랜치와 비슷함)을 가리킵니다. 표현식은 |로 합집합, &로 교집합, ~로 차집합을 만들 수 있습니다. 예를 들면:
$ jj log -r '@ | root() | bookmarks()'
@ mpqrykyp martinvonz@google.com 2023-02-12 15:00:22 aef4df99
│ (empty) (no description set)
~ (elided revisions)
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~ (elided revisions)
◆ zzzzzzzz root() 00000000
00000000 커밋(change ID zzzzzzzz)은 “루트 커밋”이라고 불리는 가상 커밋입니다. 모든 저장소의 루트 커밋입니다. revset의 root() 함수는 이것과 일치합니다.
부모(foo-), 자식(foo+), 조상(::foo), 자손(foo::), DAG 범위(foo::bar, git log --ancestry-path와 비슷함), 범위(foo..bar, Git와 동일함)를 구하는 연산자도 있습니다. 모든 revset 연산자와 함수는 revset 문서를 참고하세요.
힌트: 기본
jj log가 기대한 일부 커밋을 생략했다면, 언제든jj log -r ::(또는 같은 의미로jj log -r 'all()')를 실행해 모든 커밋을 볼 수 있습니다.
이제 Jujutsu가 병합 충돌을 어떻게 다루는지 살펴봅시다. 먼저 몇 개의 커밋을 만들어 보겠습니다. 변경 설명(커밋 메시지)을 바로 설정하기 위해 jj new에 --message/-m 옵션을 사용합니다.
# `master` 북마크에서 시작하는 커밋 체인을 만들기 시작합니다
$ jj new master -m A; echo a > file1
Working copy (@) now at: nuvyytnq 00a2aeed (empty) A
Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 0 files, modified 1 files, removed 0 files
$ jj new -m B1; echo b1 > file1
Working copy (@) now at: ovknlmro 967d9f9f (empty) B1
Parent commit (@-) : nuvyytnq 5dda2f09 A
$ jj new -m B2; echo b2 > file1
Working copy (@) now at: puqltutt 8ebeaffa (empty) B2
Parent commit (@-) : ovknlmro 7d7c6e6b B1
$ jj new -m C; echo c > file2
Working copy (@) now at: qzvqqupx 62a3c6d3 (empty) C
Parent commit (@-) : puqltutt daa6ffd5 B2
$ jj log
@ qzvqqupx martinvonz@google.com 2023-02-12 15:07:41 2370ddf3
│ C
○ puqltutt martinvonz@google.com 2023-02-12 15:07:33 daa6ffd5
│ B2
○ ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
│ B1
○ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│ A
│ ○ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯ Say goodbye
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
이제 몇 개의 커밋이 생겼고, A, B1, B2는 같은 파일을 수정하는 반면 C는 다른 파일을 수정합니다. 이제 B2를 직접 A 위로 rebase해 봅시다. B2의 change ID에 --source/-s 옵션을, A에는 --onto/-o 옵션을 사용합니다.
$ jj rebase -s puqltutt -o nuvyytnq # B2와 A에 해당하는 자신의 ID로 바꾸세요
Rebased 2 commits to destination
Working copy (@) now at: qzvqqupx 1978b534 (conflict) C
Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file1 2-sided conflict
New conflicts appeared in 2 commits:
qzvqqupx 1978b534 (conflict) C
puqltutt f7fb5943 (conflict) B2
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
jj new puqltutt
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
$ jj log
@ qzvqqupx martinvonz@google.com 2023-02-12 15:08:33 1978b534 (conflict)
│ C
× puqltutt martinvonz@google.com 2023-02-12 15:08:33 f7fb5943 (conflict)
│ B2
│ ○ ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
├─╯ B1
○ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│ A
│ ○ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯ Say goodbye
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
여기서 주목할 만한 점이 몇 가지 있습니다. 첫째, jj rebase 명령이 “Rebased 2 commits”라고 말했습니다. 이는 -s 옵션으로 B2 커밋을 rebase하라고 했고, 그 결과 자손도 함께 rebase되었기 때문입니다(이 경우 C 커밋). 둘째, B2가 B1과 같은 파일(그리고 같은 단어)을 수정했기 때문에, 출력에 보이듯 rebase 결과 충돌이 발생했습니다. 셋째, 이 충돌이 rebase의 성공적인 완료를 막지 않았고, C가 그 위로 rebase되는 것도 막지 않았습니다.
이제 B2의 충돌을 해결해 봅시다. B2 위에 새 커밋을 만들어 해결하겠습니다. 충돌이 해결되면 그 해결 내용을 충돌 상태의 B2에 squash할 것입니다. 예를 들어 다음과 같이 할 수 있습니다:
$ jj new puqltutt # B2에 해당하는 자신의 ID로 바꾸세요
Working copy (@) now at: zxoosnnp c7068d1c (conflict) (empty) (no description set)
Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
Added 0 files, modified 0 files, removed 1 files
Warning: There are unresolved conflicts at these paths:
file1 2-sided conflict
$ jj st
The working copy has no changes.
Working copy (@) : zxoosnnp c7068d1c (conflict) (empty) (no description set)
Parent commit (@-): puqltutt f7fb5943 (conflict) B2
Warning: There are unresolved conflicts at these paths:
file1 2-sided conflict
Hint: To resolve the conflicts, start by creating a commit on top of
the conflicted commit:
jj new puqltutt
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
$ cat file1
<<<<<<< conflict 1 of 1
%%%%%%% diff from: ovknlmro 7d7c6e6b "B1" (parents of rebased revision)
\\\\\\\ to: nuvyytnq 5dda2f09 "A" (rebase destination)
-b1
+a
+++++++ puqltutt daa6ffd5 "B2" (rebased revision)
b2
>>>>>>> conflict 1 of 1 ends
$ echo resolved > file1
$ jj st
Working copy changes:
M file1
Working copy (@) : zxoosnnp c2a31a06 (no description set)
Parent commit (@-): puqltutt f7fb5943 (conflict) B2
Hint: Conflict in parent commit has been resolved in working copy
$ jj squash
Rebased 1 descendant commits
Working copy (@) now at: ntxxqymr e3c279cc (empty) (no description set)
Parent commit (@-) : puqltutt 2c7a658e B2
Existing conflicts were resolved or abandoned from 2 commits.
$ jj log
@ ntxxqymr martinvonz@google.com 2023-02-12 19:34:09 e3c279cc
│ (empty) (no description set)
│ ○ qzvqqupx martinvonz@google.com 2023-02-12 19:34:09 b9da9d28
├─╯ C
○ puqltutt martinvonz@google.com 2023-02-12 19:34:09 2c7a658e
│ B2
│ ○ ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
├─╯ B1
○ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│ A
│ ○ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯ Say goodbye
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
C 커밋이 자동으로 해결된 B2 위로 다시 rebase되었고, C는 다른 파일만 수정했기 때문에 역시 해결되었다는 점에 주목하세요.
참고로, 이제 B1을 없애고 싶다면 다음을 실행할 수 있습니다.
jj abandon
ovknlmro
. 그러면 그 커밋은 log 출력에서 숨겨지고, 모든 자손은 그 부모 위로 rebase됩니다.
Jujutsu는 저장소에 가한 모든 변경의 기록을 “작업 로그(operation log)”라는 곳에 보관합니다. 여기에 상호작용하려면 jj op (jj operation의 줄임말) 계열 명령을 사용합니다. 작업 목록을 보려면 jj op log를 사용하세요:
$ jj op log
@ d3b77addea49 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 3 milliseconds
│ squash commits into f7fb5943a6b9460eb106dba2fac5cac1625c6f7a
│ args: jj squash
○ 6fc1873c1180 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 1 milliseconds
│ snapshot working copy
│ args: jj st
○ ed91f7bcc1fb martinvonz@vonz.svl.corp.google.com 6 minutes ago, lasted 1 milliseconds
│ new empty commit
│ args: jj new puqltutt
○ 367400773f87 martinvonz@vonz.svl.corp.google.com 12 minutes ago, lasted 3 milliseconds
│ rebase commit daa6ffd5a09a8a7d09a65796194e69b7ed0a566d and descendants
│ args: jj rebase -s puqltutt -o nuvyytnq
[many more lines]
가장 유용한 명령은 jj undo로, 마지막 작업을 되돌립니다.
$ jj undo
Reverted operation: d3b77addea49 (2025-05-12 00:27:27) squash commits into f7fb5943a6b9460eb106dba2fac5cac1625c6f7a
Working copy (@) now at: zxoosnnp 63874fe6 (no description set)
Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
New conflicts appeared in 2 commits:
qzvqqupx 1978b534 (conflict) C
puqltutt f7fb5943 (conflict) B2
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
jj new puqltutt
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
$ jj log
@ zxoosnnp martinvonz@google.com 2023-02-12 19:34:09 63874fe6
│ (no description set)
│ × qzvqqupx martinvonz@google.com 2023-02-12 15:08:33 1978b534 (conflict)
├─╯ C
× puqltutt martinvonz@google.com 2023-02-12 15:08:33 f7fb5943 (conflict)
│ B2
│ ○ ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
├─╯ B1
○ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│ A
│ ○ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯ Say goodbye
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
아마 보이듯이, 이것은 앞서 충돌 해결을 B2 커밋에 squash하기 위해 사용했던 jj squash 실행을 되돌린 것입니다. working copy도 함께 업데이트되었다는 점에 주목하세요.
이전 작업 직후의 저장소 상태를 볼 수도 있습니다. 예를 들어 jj rebase 작업 직후의 jj log 출력을 보고 싶다면 jj log --at-op=367400773f87를 시도하되, 자신의 jj op log에 나온 해시를 사용하세요.
이미 jj squash가 두 커밋의 변경을 하나로 합칠 수 있다는 것은 보았습니다. 기존 커밋의 내용을 바꾸기 위한 다른 명령도 여러 가지 있습니다.
이 명령들을 시험하려면 조금 더 복잡한 내용이 필요하므로, 몇 개의 커밋을 더 만들어 봅시다:
$ jj new master -m abc; printf 'a\nb\nc\n' > file
Working copy (@) now at: ztqrpvnw f94e49cf (empty) abc
Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 0 files, modified 0 files, removed 1 files
$ jj new -m ABC; printf 'A\nB\nc\n' > file
Working copy (@) now at: kwtuwqnm 6f30cd1f (empty) ABC
Parent commit (@-) : ztqrpvnw 51002261 abc
$ jj new -m ABCD; printf 'A\nB\nC\nD\n' > file
Working copy (@) now at: mrxqplyk a6749154 (empty) ABCD
Parent commit (@-) : kwtuwqnm 30aecc08 ABC
$ jj log -r master::@
@ mrxqplyk martinvonz@google.com 2023-02-12 19:38:21 b98c607b
│ ABCD
○ kwtuwqnm martinvonz@google.com 2023-02-12 19:38:12 30aecc08
│ ABC
○ ztqrpvnw martinvonz@google.com 2023-02-12 19:38:03 51002261
│ abc
◆ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│ (empty) Merge pull request #6 from Spaceghost/patch-1
~
두 번째 커밋에서 다른 글자들을 대문자로 바꿀 때 “c”를 대문자로 만드는 것을 “잊었다”고 해봅시다. 그리고 세 번째 커밋에서 “D”를 추가하면서 그것을 고쳤습니다. “c”를 대문자로 바꾸는 변경을 두 번째 커밋으로 옮기는 편이 더 깔끔할 것입니다. 이를 위해 세 번째 커밋에서 --interactive/-i 옵션과 함께 jj squash를 실행할 수 있습니다. jj squash는 한 커밋의 모든 변경을 부모로 이동한다는 점을 기억하세요. jj squash -i는 변경의 일부만 부모로 이동합니다. 이제 직접 해보세요:
$ jj squash -i
Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
Rebased 1 descendant commits
Working copy (@) now at: mrxqplyk 52a6c7fd ABCD
Parent commit (@-) : kwtuwqnm 643061ac ABC
그러면 “ABCD” 커밋의 변경 diff를 보여주는 내장 diff 에디터1가 열립니다. (+)를 클릭하거나 오른쪽 화살표 키를 눌러 파일을 펼친 뒤, 클릭하거나 스페이스바를 사용해 포함할 구간/줄을 선택하세요. 완료되면 c를 눌러 변경을 확정하고, 저장하지 않고 나가려면 q를 누르세요. 메뉴 항목을 마우스로 클릭해 더 많은 옵션을 볼 수도 있습니다(현재 키보드 탐색은 제한적입니다).
두 번째 커밋의 diff를 보면 이제 세 줄 모두가 대문자로 바뀐 것을 볼 수 있습니다:
$ jj diff -r @- --git
diff --git a/file b/file
index de980441c3..b1e67221af 100644
--- a/file
+++ b/file
@@ -1,3 +1,3 @@
-a
-b
-c
+A
+B
+C
자식 변경(이 경우 “ABCD”)은 jj squash 명령 이후에도 같은 내용 상태 를 유지합니다. 즉, 같은 단어를 건드리는 변경이라도 원하는 것을 부모 변경으로 이동시킬 수 있고, 그로 인해 충돌이 발생하지 않습니다.
이제 기존 커밋의 내용을 바꾸는 마지막 명령 하나를 더 살펴봅시다. 그 명령은 jj diffedit이며, 체크아웃하지 않고도 커밋의 변경을 편집할 수 있게 해줍니다.
$ jj diffedit -r @-
Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
Rebased 1 descendant commits
Working copy (@) now at: mrxqplyk 1c72cd50 (conflict) ABCD
Parent commit (@-) : kwtuwqnm 70985eaa ABC
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file 2-sided conflict
New conflicts appeared in 1 commits:
mrxqplyk 1c72cd50 (conflict) ABCD
Hint: To resolve the conflicts, start by creating a commit on top of
the conflicted commit:
jj new mrxqplyk
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
diff 에디터에서 화살표 키와 스페이스바를 사용해 마지막 줄을 제외한 모든 줄을 선택하세요. c를 눌러 변경을 저장하고 닫습니다. 이제 jj diff -r @-를 다시 실행해 다시 작성된 커밋을 확인할 수 있고, 마지막 줄을 삭제한 것이 보일 것입니다. 커밋의 내용 상태를 바꾸지 않았던 jj squash -i와 달리, jj diffedit는 보통 다른 상태를 만들며, 이는 자손 커밋에 충돌이 생길 수 있음을 의미합니다.
기존 커밋의 내용을 다시 쓰는 또 다른 명령은 jj split입니다. 이제 jj squash -i와 jj diffedit가 어떻게 동작하는지 보았으니, diff 안의 안내를 도움 삼아 jj split이 어떻게 작동하는지도 파악할 수 있을 것입니다.
jj squash -i --tool meld로 사용할 수 있고, 더 정교한 설정으로는jj
squash -i --tool meld-3
를 사용할 수도 있습니다. 기본값은 ui.diff-editor 옵션으로 설정할 수 있습니다. 해당 문서에는 실행 파일이 PATH에 없을 때 경로를 지정하는 방법도 설명되어 있습니다.↩