11import { log as logCmd } from "@array/core/commands/log" ;
2+ import type { ArrContext } from "@array/core/engine" ;
23import type { LogGraphData , PRInfo } from "@array/core/log-graph" ;
34import { COMMANDS } from "../registry" ;
45import {
@@ -7,24 +8,32 @@ import {
78 dim ,
89 formatChangeId ,
910 formatCommitId ,
10- formatDiffStats ,
1111 green ,
12+ hint ,
1213 magenta ,
1314 message ,
1415 red ,
1516 yellow ,
1617} from "../utils/output" ;
1718import { unwrap } from "../utils/run" ;
1819
19- export async function log ( ) : Promise < void > {
20- const data = unwrap ( await logCmd ( ) ) ;
20+ export async function log ( ctx : ArrContext ) : Promise < void > {
21+ const data = unwrap (
22+ await logCmd ( {
23+ engine : ctx . engine ,
24+ trunk : ctx . trunk ,
25+ } ) ,
26+ ) ;
2127
28+ // isEmpty means: on trunk with empty working copy and no tracked branches
29+ // In this case, show a simplified view
2230 if ( data . isEmpty ) {
23- message ( dim ( "No changes in stack" ) ) ;
31+ message ( `${ green ( "◉" ) } ${ ctx . trunk } ${ dim ( "(current)" ) } ` ) ;
32+ hint ( `Run ${ cyan ( "arr create" ) } to start a new stack` ) ;
2433 return ;
2534 }
2635
27- const output = renderLogGraph ( data ) ;
36+ const output = renderLogGraph ( data , ctx . trunk ) ;
2837 message ( output ) ;
2938
3039 if ( data . modifiedCount > 0 ) {
@@ -35,7 +44,7 @@ export async function log(): Promise<void> {
3544 }
3645}
3746
38- function renderLogGraph ( data : LogGraphData ) : string {
47+ function renderLogGraph ( data : LogGraphData , trunk : string ) : string {
3948 const output = data . rawOutput ;
4049
4150 // Process each line to handle placeholders
@@ -45,7 +54,8 @@ function renderLogGraph(data: LogGraphData): string {
4554 for ( const line of lines ) {
4655 let processed = line ;
4756
48- // {{LABEL:changeId|prefix|timestamp|description|conflict|wc|empty|immutable|localBookmarks|remoteBookmarks|added|removed|files}}
57+ // {{LABEL:changeId|prefix|timestamp|description|conflict|wc|empty|immutable|localBookmarks|remoteBookmarks}}
58+ // Note: jj outputs the graph marker (@, ○, ◆), we just output the label content
4959 processed = processed . replace ( / \{ \{ L A B E L : ( [ ^ } ] + ) \} \} / g, ( _ , content ) => {
5060 const parts = content . split ( "|" ) ;
5161 const [
@@ -54,20 +64,15 @@ function renderLogGraph(data: LogGraphData): string {
5464 timestamp ,
5565 description ,
5666 conflict ,
57- wc ,
67+ _wc ,
5868 empty ,
59- immutable ,
69+ _immutable ,
6070 localBookmarks ,
6171 _remoteBookmarks ,
62- added ,
63- removed ,
64- files ,
6572 ] = parts ;
6673
67- const isWorkingCopy = wc === "1" ;
6874 const hasConflicts = conflict === "1" ;
6975 const isEmpty = empty === "1" ;
70- const isImmutable = immutable === "1" ;
7176 const bookmarks = localBookmarks
7277 ? localBookmarks . split ( "," ) . filter ( Boolean )
7378 : [ ] ;
@@ -103,19 +108,7 @@ function renderLogGraph(data: LogGraphData): string {
103108 const badgeStr =
104109 badges . length > 0 ? ` ${ dim ( "(" ) } ${ badges . join ( ", " ) } ${ dim ( ")" ) } ` : "" ;
105110
106- // Diff stats
107- const filesChanged = Number ( files ) || 0 ;
108- const insertions = Number ( added ) || 0 ;
109- const deletions = Number ( removed ) || 0 ;
110- const statsStr =
111- filesChanged > 0
112- ? ` ${ formatDiffStats ( { filesChanged, insertions, deletions } ) } `
113- : "" ;
114-
115- // Marker
116- const marker = isWorkingCopy ? green ( "◉" ) : isImmutable ? "◆" : "○" ;
117-
118- return `${ marker } ${ label } ${ shortId } ${ statsStr } ${ badgeStr } ` ;
111+ return `${ label } ${ shortId } ${ badgeStr } ` ;
119112 } ) ;
120113
121114 // {{TIME:timestamp}}
@@ -126,7 +119,7 @@ function renderLogGraph(data: LogGraphData): string {
126119
127120 // {{HINT_EMPTY}}
128121 processed = processed . replace ( / \{ \{ H I N T _ E M P T Y \} \} / g, ( ) => {
129- return `${ dim ( "No changes yet " ) } \n ${ dim ( "Edit files, then:" ) } \n ${ arr ( COMMANDS . create ) } ${ dim ( '"message"' ) } ${ dim ( "to save as new change" ) } \n ${ arr ( COMMANDS . down ) } ${ dim ( "to edit the change below ") } ` ;
122+ return `${ dim ( "Run " ) } ${ arr ( COMMANDS . create ) } ${ dim ( '"message"' ) } ${ dim ( "to save as a change" ) } ` ;
130123 } ) ;
131124
132125 // {{HINT_UNCOMMITTED}}
@@ -175,10 +168,40 @@ function renderLogGraph(data: LogGraphData): string {
175168 } ,
176169 ) ;
177170
171+ // {{TRUNK:bookmark}} - trunk label (prefer actual trunk name if present)
172+ processed = processed . replace (
173+ / \{ \{ T R U N K : ( [ ^ } ] * ) \} \} / g,
174+ ( _ , bookmarksStr ) => {
175+ const bookmarks = bookmarksStr . split ( "," ) . filter ( Boolean ) ;
176+ // Prefer the actual trunk name if this commit has multiple bookmarks
177+ if ( bookmarks . includes ( trunk ) ) {
178+ return trunk ;
179+ }
180+ return bookmarks [ 0 ] || "trunk" ;
181+ } ,
182+ ) ;
183+
178184 processedLines . push ( processed ) ;
179185 }
180186
181- return processedLines . join ( "\n" ) ;
187+ let result = processedLines . join ( "\n" ) ;
188+
189+ // Replace jj's graph markers with styled versions
190+ // @ = working copy (green ◉)
191+ // ○ = mutable commit (◯)
192+ // ◆ = immutable commit (◯ - same as mutable, we don't distinguish)
193+ // × = conflict (red ×)
194+ // ~ = elided (│)
195+ result = result . replace ( / ^ ( @ ) ( \s + ) / gm, `${ green ( "◉" ) } $2` ) ;
196+ result = result . replace ( / ^ ( ○ ) ( \s + ) / gm, "◯$2" ) ;
197+ result = result . replace ( / ^ ( ◆ ) ( \s + ) / gm, "◯$2" ) ;
198+ result = result . replace ( / ^ ( × ) ( \s + ) / gm, `${ red ( "×" ) } $2` ) ;
199+ result = result . replace ( / ^ ( ~ ) ( \s + ) / gm, "│$2" ) ;
200+
201+ // Remove trailing newlines
202+ result = result . trimEnd ( ) ;
203+
204+ return result ;
182205}
183206
184207function formatPRLine ( prInfo : PRInfo , description : string ) : string {
0 commit comments