Ta kontroll over historikken med jj
I Jeg har slutta med git viste jeg fram noen av tingene med jj som gjør det til et genialt verktøy. I denne posten har jeg lyst til å demonstrere noen konkrete flyter som kan brukes for å rydde i historikken.
Rebasing
Dette eksempelet viser to uavhengige brancher fra main som henholdsvis løser issue 123 og issue 321.
❯ jj log
@ yzksnkxq glenn.bitar@amedia.no 2026-08-21 08:19:19 d6a82bec
│ (empty) (no description set)
│ ○ qxntlovw glenn.bitar@amedia.no 2026-08-21 08:06:45 316c361c
├─╯ Solve issue 321
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
La oss si at man ønsker en lineær historikk. Dette kan løses vha. rebasing:
❯ jj rebase -b q -o m # rebases the branch of change q onto change m
Rebased 1 commits to destination.
❯ jj log
@ yzksnkxq glenn.bitar@amedia.no 2026-08-21 08:19:19 d6a82bec
│ (empty) (no description set)
│ ○ qxntlovw glenn.bitar@amedia.no 2026-08-21 08:19:49 85d9188d
│ │ Solve issue 321
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Se operasjonen i et terminalopptak
I jj kan man henvise til en endring ved hjelp av den korteste unike change ID-en blant settet av synlige, mutable commits. Siden ingen andre commits begynner med
q, kan endringaqxntlovw(som egentlig også er kort forqxntlovwzwqrvlotnwtnmvtwomxtpoqp) henvises til med bokstavenq.
Vi valgte å rebase branchen som q er en del av.
Det vil si at dersom q hadde hatt en commit før og etter seg, ville den samme rebase-kommandoen rebaset tre commits oppå m:
❯ jj log
@ yzksnkxq glenn.bitar@amedia.no 2026-08-21 08:19:19 d6a82bec
│ (empty) (no description set)
│ ○ xupuyzyw glenn.bitar@amedia.no 2026-08-21 08:35:41 6a71efe5
│ │ (empty) After
│ ○ qxntlovw glenn.bitar@amedia.no 2026-08-21 08:35:32 1d3fb72c
│ │ Solve issue 321
│ ○ orlxlkos glenn.bitar@amedia.no 2026-08-21 08:35:32 68aead36
├─╯ (empty) Before
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
❯ jj rebase -b q -o m
Rebased 3 commits to destination.
❯ jj log
@ yzksnkxq glenn.bitar@amedia.no 2026-08-21 08:19:19 d6a82bec
│ (empty) (no description set)
│ ○ xupuyzyw glenn.bitar@amedia.no 2026-08-21 08:36:16 52668ba1
│ │ (empty) After
│ ○ qxntlovw glenn.bitar@amedia.no 2026-08-21 08:36:16 6b92b721
│ │ Solve issue 321
│ ○ orlxlkos glenn.bitar@amedia.no 2026-08-21 08:36:16 c6f13d23
│ │ (empty) Before
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Løse konflikter
I dette eksempelet vil jeg vise hvordan man kan løse konflikter mellom endringer i jj. Utgangspunktet er omtrent det samme som forrige eksempel, men med en ekstra commit oppå branchen som skal rebases:
@ lxpmopks glenn.bitar@amedia.no 2026-08-21 08:57:34 0fc02fcb
│ (empty) (no description set)
│ ○ wvuulrun glenn.bitar@amedia.no 2026-08-21 08:57:30 ef2b4c0c
│ │ Make an additional improvement
│ ○ qxntlovw glenn.bitar@amedia.no 2026-08-21 08:56:19 a3c62b36
├─╯ Solve issue 321
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Vi gjør den samme rebasinga:
❯ jj rebase -b q -o m
Rebased 2 commits to destination.
New conflicts appeared in 2 commits:
wvuulrun 7a44c361 (conflict) Make an additional improvement
qxntlovw f7d73c8e (conflict) Solve issue 321
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
jj new qxntlovw
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
@ lxpmopks glenn.bitar@amedia.no 2026-08-21 08:57:34 0fc02fcb
│ (empty) (no description set)
│ × wvuulrun glenn.bitar@amedia.no 2026-08-21 08:58:54 7a44c361 (conflict)
│ │ Make an additional improvement
│ × qxntlovw glenn.bitar@amedia.no 2026-08-21 08:58:54 f7d73c8e (conflict)
│ │ Solve issue 321
│ ○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
├─╯ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Rebasinga gjennomføres, men det er nå konflikter i commit q og i committen som kommer etter, w.
jj-CLI-et foreslår selv å lage en ny endring oppå den første konflikt-committen q, løse konflikten, for deretter å squashe tilbake til q.
❯ jj new q
Working copy (@) now at: qyrkyotx 88d43e51 (conflict) (empty) (no description set)
Parent commit (@-) : qxntlovw b8d62690 (conflict) Solve issue 321
Added 1 files, modified 0 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
A.txt 2-sided conflict
❯ jj resolve
Working copy (@) now at: qyrkyotx 8380efca (no description set)
Parent commit (@-) : qxntlovw b8d62690 (conflict) Solve issue 321
Added 0 files, modified 1 files, removed 0 files
❯ jj squash
Rebased 1 descendant commits.
Working copy (@) now at: wlqvvqlw 6b936fea (empty) (no description set)
Parent commit (@-) : qxntlovw 6ebcd14f Solve issue 321
Existing conflicts were resolved or abandoned from 2 commits.
❯ jj log
@ wlqvvqlw glenn.bitar@amedia.no 2026-08-21 09:08:11 6b936fea
│ (empty) (no description set)
│ ○ wvuulrun glenn.bitar@amedia.no 2026-08-21 09:08:11 ce03623b
├─╯ Make an additional improvement
○ qxntlovw glenn.bitar@amedia.no 2026-08-21 09:08:11 6ebcd14f
│ Solve issue 321
○ mszxrytz glenn.bitar@amedia.no 2026-08-21 08:06:13 9b2b7a9f
│ Solve issue 123
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Vi gjorde som foreslått og løste konflikten i en ny commit i TUI-et startet av jj resolve.
jj squash flytter endringa tilbake til parent-committen q, som var det første stedet en konflikt oppsto.
Dette løste problemet der, og alle child-committene arvet løsninga.
Siden det ikke var flere problemer er grafen nå uten konflikter.
Se operasjonen i et terminalopptak
Endre rekkefølgen av commits
Gjort en refaktorering, og deretter lagt til en test for å være sikker på at den var uten uønska konsekvens?
❯ jj log
@ ukqpwlzk glenn.bitar@amedia.no 2026-08-21 09:46:10 b83f3798
│ (empty) (no description set)
○ qxntlovw glenn.bitar@amedia.no 2026-08-21 09:46:08 916582b6
│ Add integration test
○ mszxrytz glenn.bitar@amedia.no 2026-08-21 09:44:22 7d325900
│ Refactor API
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
Kanskje du vil at testen skulle ha vært på plass før refaktoreringa i historikken?
❯ jj rebase -r q --before m
Rebased 1 commits to destination.
Rebased 2 descendant commits.
Working copy (@) now at: ukqpwlzk 49cf1d10 (empty) (no description set)
Parent commit (@-) : mszxrytz 65ce589c Refactor API
❯ jj log
@ ukqpwlzk glenn.bitar@amedia.no 2026-08-21 10:21:24 49cf1d10
│ (empty) (no description set)
○ mszxrytz glenn.bitar@amedia.no 2026-08-21 10:21:24 65ce589c
│ Refactor API
○ qxntlovw glenn.bitar@amedia.no 2026-08-21 10:21:24 f5e5fab2
│ Add integration test
○ pylvtwox glenn.bitar@amedia.no 2026-08-20 11:09:13 main 78fcd5d4
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
I virkeligheten ville det kanskje vært noen konflikter å løse, og da kan du jo prøve metoden i eksempelet over.
Se operasjonen i et terminalopptak
I tillegg til jj rebase, finnes også jj arrange, som starter et TUI hvor du interaktivt kan flytte på commits.
Se jj arrange i et terminalopptak
Kjør en kommando på et sett av commits
I jj kan man kjøre en kommando per commit i et sett av commits med jj run.
La oss si at du ønsker å sjekke at formateringa er riktig i alle commits i branchen du jobber med:
❯ jj log
@ nkpswpvo glenn.bitar@amedia.no 2026-08-21 10:50:10 feb9b981
│ (empty) (no description set)
○ qxntlovw glenn.bitar@amedia.no 2026-08-21 10:50:07 3c8622a8
│ Add integration test
○ pylvtwox glenn.bitar@amedia.no 2026-08-21 10:49:20 76c617ba
│ Add functionality
○ uypzopkr glenn.bitar@amedia.no 2026-08-20 11:08:21 main 72405d18
│ Add README
◆ zzzzzzzz root() 00000000
❯ jj diff -r main..
Added regular file file.py:
1: def a_function(A: str):
2: return ""
Added regular file test.py:
1: ABC = 'def'
Denne kommandoen kjører ruff format --check på hver commit etter main:
❯ jj run -r main.. -- ruff format --check
2 files already formatted
unformatted: File would be reformatted
--> test.py:1:7
|
- ABC = 'def'
1 + ABC = "def"
|
1 file would be reformatted, 2 files already formatted
Error: the command 'ruff format --check' failed with exit status: 1
Hint: Failed revision: qxntlovw 3c8622a8 Add integration test
Her rapporteres det at q, den andre committen i branchen feiler sjekken.
jj run kan også endre på commits:
❯ jj run -r main.. -- ruff format
2 files left unchanged
1 file reformatted, 2 files left unchanged
1 file reformatted, 2 files left unchanged
Rewrote 2 commits.
Working copy (@) now at: nkpswpvo d81e8a70 (empty) (no description set)
Parent commit (@-) : qxntlovw 50e1b262 Add integration test
Added 0 files, modified 1 files, removed 0 files
❯ jj run -r main.. -- ruff format --check
2 files already formatted
3 files already formatted
3 files already formatted
Nothing changed.
❯ jj diff -r main..
Added regular file file.py:
1: def a_function(A: str):
2: return ""
Added regular file test.py:
1: ABC = "def"
Og dermed ble filene formatert riktig i hver commit.
Se operasjonen i et terminalopptak
Har du lyst til å dele hvordan du jobber med jj, eller har du spørsmål til hvordan komme i gang? Ta det gjerne i #fag-jj! Ellers vil jeg også bruke muligheten til å anbefale Steve's Jujutsu Tutorial. Den forteller mer om hverdagslige arbeidsflyter, som f.eks. hvordan man forholder seg til jj's working copy dersom man er vant med git indexen, ev. noe som kalles The edit workflow, som snur rundt på det. Den dekker også hvordan man samarbeider via f.eks. GitHub og PRs vha. jj.