jj라는 것이 나왔는데 인기가 좀 생기고 있다고하여 사용해봤다. 이제 그냥 git을 쓰려면 좀 불편하다고 느끼기에 이르렀다.
Mercurial등은 git 사용자들과 협업하기 힘든데, jj는 git 위에서 돌아가는 물건이므로 나 혼자 쓰기에도 용이하다. Git branchless는 그렇지 못했는데.
설치: 바이너리 파일 한 개라, 패키지로 제공되지 않는 리눅스 시스템에도 설치가 쉽다.
단점: jujutsu 라고 검색하면 주술회전이 아직 우세하게 결과로 뜨기 때문에 자료 검색을 하기 좀 귀찮다.
1. 상태 보기 / 커밋간 이동하기
| Command | Description |
|--------------------------|-------------------------------------------------------|
| jj / jj log | View commit log |
| jj log -r <revset> | View specific revisions (e.g., master::@, develop..@) |
| jj log -n 20 | Limit log output |
| jj diff / jj diff <file> | Show changes |
| jj diff -r <rev> | Show changes in a specific revision |
| jj st | Show status |
여기까지는 git하고 비슷하다.
2. Creating & Editing Changes
| Command | Description |
|-------------------------|-------------------------------------|
| jj new / jj new <rev> | Create new change (on top of rev) |
| jj new -A <rev> | Create new change after rev |
| jj new -B <rev> | Create new change before rev |
| jj edit / jj e <rev> | Edit an existing change |
| jj desc / jj desc <rev> | Set commit description |
| jj commit | Finalize working copy into a commit |
jj new는 특정 revision (git에서는 커밋) 에서부터 branch를 해서 작업을 시작하는 것이다.
jj edit은 아예 즉정 커밋을 수정하는 것이다. 마치 시간여행처럼. 이 작업을 깃에서 하려면 branch를 한 다음에 rebase-squash를 하는 것이다. 아니면 새로 conflict를 일으키지 않을만한 커밋을 만든 다음 rebase를 해도 되고. jj없이 git으로 하려면 짜증나고 귀찮은 작업이다.
jj desc - 커밋 메시지만 수정한다.
jj commit - 깃의 커밋하고 같지만... git add에 해당하는 것 없이 커밋하면 .gitignore에 서술된 파일들을 제외한 모든 파일이 커밋에 딸려간다는 점이 다르다. 그리고 이게 jj와 git의 큰 차이점인데, 현재 working copy도 jj한테는 description이 입력되지 않은 commit으로 간주된다는 것이다. 그래서 커밋을 하지 않은 경우라도 다른 revision을 edit하고 나서도 원래 작업으로 돌아올 수 있는 것이다.
* 그리고 현재 상태 자체가 암묵적 커밋이라서, git stash에 해당하는 명령어가 없다! 그냥 커밋취급 해서 rebase하든 abandon (drop) 하든 stash보다 다루기 오히려 쉽다고 생각한다. git에서 stash 한게 conflict나면 commit conflict에 비해 좀 손보기 어려웠던 듯.
그러면, github에 올리고 싶지 않는 변경사항을 (나만의 설정파일들이라든지, .vscode/launch.json이라든지) 어떻게 해야 하는가? 깃에서는 add하지 않고 커밋하지 않으면 그만이었는데, jj에서는 커밋을 하고, 해당 커밋을 후술할 rebase로 이리저리 옮겨다니면서 남들에게는 공유되지 않게 하면 된다.
3. Reorganizing History (Heavy Use!)
| Command | Description |
|---------------------------------------|---------------------------------------|
| jj rebase -r <rev> -d <dest> | Move a single revision to destination |
| jj rebase -r <rev> -B <before> | Insert revision before another |
| jj rebase -r <rev> -A <after> | Insert revision after another |
| jj rebase -b <rev> -d <dest> | Move revision and descendants |
| jj squash / jj squash -r <rev> | Squash into parent |
| jj squash --from <range> --into <rev> | Squash a range into a target |
| jj split / jj split -r <rev> | Split a commit interactively |
| jj abandon <rev> | Delete a commit |
jj rebase는 git rebase와 좀 비슷하긴 한데 리비전들의 순서를 옮기기만 해준다.
jj squash는 이거는 인접한 두 커밋을 한 개로 병합하는 것이다. git rebase -i에서 squash로 지정하는 것과 같은데... 어차피 이렇게 설명해도 깃에서 squash까지 하는 사람들은 잘 없을거니까...
jj split - 이거는 squash의 반대 작업으로, 리비전 한 개의 변경사항을 두 개로 쪼개는 것이다. 언제 가장 많이 쓰이는가? 위에서 현재 작업중인 상태는 desc가 없는 커밋이라고 했다. git처럼 내가 커밋해서 desc달고 싶은 변경사항만 다른 리비전으로 만들어낼 때 유용하다.
jj abandon - 현재까지 작업하던 암묵적 현재커밋 날릴 때 유용하고, 과거의 임시 리비전 없앨 때도 유용하다.
4. Bookmarks (Branches)
| Command | Description |
|--------------------------------------------|----------------------------|
| jj b set <name> -r <rev> | Point bookmark to revision |
| jj b set <name> -r <rev> --allow-backwards | Move bookmark backward |
| jj b delete <name> | Delete bookmark |
| jj b list / jj b list -a | List bookmarks |
| jj b track <name>@origin | Track remote bookmark |
| jj bookmark track <name>@origin | Same as above (full form) |
git으로 치면 브랜치.
jj b set master -r @- 를 가장 많이 쓴다: 지금 암묵적 커밋은 @인데, -를 붙이면 바로 그 아래 커밋, @--는 아래아래 커밋이다. @--는 @-커밋이 남들과 공유하고싶지 않은 커밋일 때 유용함. 그리고 남들과 공유하고싶지 않은 커밋을 빼돌리려면
jj rebase -r XX -B @ 이런식으로 하면 됨. xx라는 리비전을 @의 직전으로 가져오란 것.
5. File Management
| Command | Description |
|------------------------|----------------------------------|
| jj restore <file> | Restore file to parent's version |
| jj file untrack <file> | Stop tracking file |
| jj file track <file> | Start tracking file |
git restore랑 비슷. 헌데 jj file track/untrack은 .gitignore에 등록되지 않으면 무의미함... 먼저 등록하고 언트랙해야 @커밋에서 제외된다.
6. Git Integration
| Command | Description |
|--------------------------------|-------------------------|
| jj git push | Push to remote |
| jj git push -b <bookmark> | Push specific bookmark |
| jj git push --allow-new | Push new bookmark |
| jj git fetch | Fetch from remote |
| jj git fetch --all-remotes | Fetch from all remotes |
| jj git remote add <name> <url> | Add remote |
| jj git remote remove <name> | Remove remote |
| jj git init --colocate | Init colocated with git |
깃헙과 상호작용하려면 필요한 명령어들이다.
7. Undo & Recovery
| Command | Description |
|-----------------------|---------------------------|
| jj undo | Undo last operation |
| jj op log | View operation history |
| jj op restore <op-id> | Restore to previous state |
| jj revert -r <rev> | Create a reverse commit |
jj undo야말로 git에서 jj로 갈아탈 이유를 만들어주는 가장 쓸모있는 명령어이다. 깃 작업하다보면 실수해서 커밋을 날린다든지하는 불상사가 발생해서 땅을 치고 후회하기도 하는데... jj는 실행취소하면 그만이다...
8. Configuration
| Command | Description |
|----------------------------------|----------------------------|
| jj config set --user <key> <val> | Set user config |
| jj config set --repo <key> <val> | Set repo config |
| jj config edit --repo | Edit repo config in editor |
이런게 있다.
9. Conflict Resolution
| Command | Description |
|-------------------------------|---------------------|
| jj resolve | Resolve conflicts |
| jj resolve --tool meld <file> | Use meld to resolve |
이것도 이런게 있긴한데 vscode에서 하는게 좋음.
하는 방법은... jj에 설명이 나오는데 여기 설명하자면.
jj rebase 를 하다보면 conflict가 생길수도 있다. 그러면 jj new XX (XX는 컨플릭 난 가장 과거의 리비전) 를 통해 새로 빈 리비전을 만들어 낸 뒤, vscode등에서 conflict 제거작업을 수행한다. 여기까진 깃과 같다. 그 후 `jj squash` 명령어를 내리면 conflict 제거를 하는 diff들이 XX 리비전 안으로 "들어가면서" conflict가 사라지게 된다.