Unix

jj, jujutsu, git 대신 쓰기 좋은 버전 관리 시스템 + 내가 쓰는 사용법

ForceCore 2025. 12. 31. 11:40

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가 사라지게 된다.