terminal user interface to jujutsu. Focused on speed and clarity
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

node rendering improvement

+380 -47
+20 -5
jj_tui/bin/graph_view.ml
··· 139 139 |> Lwd.map2 (Lwd.get Vars.ui_state.revset) ~f:(fun revset _ -> 140 140 try 141 141 let max_commits = (Vars.config |> Lwd.peek).max_commits in 142 - let nodes, rev_ids = get_graph_nodes ?revset max_commits in 142 + let nodes, rev_ids, native_rows = 143 + get_graph_nodes_with_native_rows ?revset max_commits 144 + in 143 145 let state = 144 146 Render_jj_graph.{ depth = 0; columns = [||]; pending_joins = [] } 145 147 in ··· 181 183 else rev_ids 182 184 in 183 185 let rendered_rows = 184 - Render_jj_graph.render_nodes_structured 185 - state 186 - nodes 187 - ~node_attr:Commit_render.graph_node_attr 186 + if Vars.get_rebase_preview_active () 187 + then 188 + Render_jj_graph.render_nodes_structured 189 + state 190 + nodes 191 + ~node_attr:Commit_render.graph_node_attr 192 + else ( 193 + match native_rows with 194 + | Some rows -> 195 + rows 196 + | None -> 197 + [%log 198 + warn "Failed to align native jj graph rows; using synthetic renderer"]; 199 + Render_jj_graph.render_nodes_structured 200 + state 201 + nodes 202 + ~node_attr:Commit_render.graph_node_attr) 188 203 in 189 204 error_var $= None; 190 205 rendered_rows, rev_ids
+2 -3
jj_tui/lib/commit_render.ml
··· 59 59 let line1_parts = ref [] in 60 60 (* Render change_id with prefix highlighting *) 61 61 let change_id_prefix_attr, change_id_rest_attr = 62 - if node.hidden 62 + if node.hidden || node.conflict 63 63 then 64 - let duplicate_attr = fg white ++ st bold in 65 - duplicate_attr, duplicate_attr 64 + fg white ++ st bold, fg lightblack ++ bs 66 65 else if node.divergent 67 66 then fg red ++ st bold, fg red ++ bs 68 67 else fg magenta ++ st bold, fg lightblack ++ bs
+96
jj_tui/lib/commit_render_tests.ml
··· 217 217 |}] 218 218 ;; 219 219 220 + let%expect_test "render_hidden_commit_uses_white_prefix_gray_rest" = 221 + let node = 222 + make_test_node 223 + ~change_id_prefix:"upnslvuv" 224 + ~change_id_rest:"/2" 225 + ~commit_id_prefix:"4c63c987" 226 + ~commit_id_rest:"" 227 + ~description:"make bookmarks render origin if needed" 228 + ~bookmarks:[ "re-based@origin" ] 229 + ~hidden:true 230 + () 231 + in 232 + let line1 = render_commit_content node |> List.hd in 233 + line1 234 + |> image_to_ansi_string 235 + |> Parser.parse_ansi_escape_codes 236 + |> Result.get_ok 237 + |> List.iter (fun (attr, text) -> 238 + if String.trim text <> "" 239 + then ( 240 + AnsiReverse.Internal.print_attr attr; 241 + Printf.printf "Text: %S\n" text)); 242 + [%expect 243 + {| 244 + attr: 245 + \e[0m<\e[0;37;1mATTR\e[0m\e[K\e[0m>\e[0m 246 + Text: "upnslvuv" 247 + attr: 248 + \e[0m<\e[0;90mATTR\e[0m\e[K\e[0m>\e[0m 249 + Text: "/2" 250 + attr: 251 + \e[0m<\e[0;33mATTR\e[0m\e[K\e[0m>\e[0m 252 + Text: " test@example.com" 253 + attr: 254 + \e[0m<\e[0;36mATTR\e[0m\e[K\e[0m>\e[0m 255 + Text: " 2024-01-01" 256 + attr: 257 + \e[0m<\e[0;35mATTR\e[0m\e[K\e[0m>\e[0m 258 + Text: " re-based@origin" 259 + attr: 260 + \e[0m<\e[0;34;1mATTR\e[0m\e[K\e[0m>\e[0m 261 + Text: " 4c63c987" 262 + attr: 263 + \e[0m<\e[0;90mATTR\e[0m\e[K\e[0m>\e[0m 264 + Text: " (hidden)" 265 + |}] 266 + ;; 267 + 220 268 let%expect_test "render_divergent_commit" = 221 269 let node = 222 270 make_test_node ··· 255 303 {| 256 304 lqzzqwqx/0 test@example.com 2024-01-01 main?? main@git 5ab39974 (conflict) (divergent) 257 305 disable worker mode 306 + |}] 307 + ;; 308 + 309 + let%expect_test "render_conflict_commit_uses_white_prefix_gray_rest" = 310 + let node = 311 + make_test_node 312 + ~change_id_prefix:"lqzzqwqx" 313 + ~change_id_rest:"/0" 314 + ~commit_id_prefix:"5ab39974" 315 + ~commit_id_rest:"" 316 + ~description:"disable worker mode" 317 + ~bookmarks:[ "main??"; "main@git" ] 318 + ~conflict:true 319 + () 320 + in 321 + let line1 = render_commit_content node |> List.hd in 322 + line1 323 + |> image_to_ansi_string 324 + |> Parser.parse_ansi_escape_codes 325 + |> Result.get_ok 326 + |> List.iter (fun (attr, text) -> 327 + if String.trim text <> "" 328 + then ( 329 + AnsiReverse.Internal.print_attr attr; 330 + Printf.printf "Text: %S\n" text)); 331 + [%expect 332 + {| 333 + attr: 334 + \e[0m<\e[0;37;1mATTR\e[0m\e[K\e[0m>\e[0m 335 + Text: "lqzzqwqx" 336 + attr: 337 + \e[0m<\e[0;90mATTR\e[0m\e[K\e[0m>\e[0m 338 + Text: "/0" 339 + attr: 340 + \e[0m<\e[0;33mATTR\e[0m\e[K\e[0m>\e[0m 341 + Text: " test@example.com" 342 + attr: 343 + \e[0m<\e[0;36mATTR\e[0m\e[K\e[0m>\e[0m 344 + Text: " 2024-01-01" 345 + attr: 346 + \e[0m<\e[0;35mATTR\e[0m\e[K\e[0m>\e[0m 347 + Text: " main?? main@git" 348 + attr: 349 + \e[0m<\e[0;34;1mATTR\e[0m\e[K\e[0m>\e[0m 350 + Text: " 5ab39974" 351 + attr: 352 + \e[0m<\e[0;31mATTR\e[0m\e[K\e[0m>\e[0m 353 + Text: " (conflict)" 258 354 |}] 259 355 ;; 260 356
+25 -15
jj_tui/lib/jj_json.ml
··· 28 28 ; divergent : bool 29 29 ; conflict : bool 30 30 ; empty : bool 31 - ; bookmarks : string list 31 + ; local_bookmarks : string list 32 + ; remote_bookmarks : string list 33 + ; tags : string list 32 34 ; author : jj_author 33 35 ; change_id_prefix : string 34 36 ; change_id_rest : string ··· 51 53 ++ ',"divergent":' ++ json(divergent) 52 54 ++ ',"conflict":' ++ json(conflict) 53 55 ++ ',"empty":' ++ json(empty) 54 - ++ ',"bookmarks":[' 55 - ++ bookmarks 56 - .map(|b| 57 - json( 58 - stringify( 59 - if( 60 - b.remote(), 61 - b.name() ++ "@" ++ b.remote(), 62 - if(b.tracked() && !b.synced(), b.name() ++ "*", b.name()) 63 - ) 64 - ) 65 - ) 66 - ) 56 + ++ ',"local_bookmarks":[' 57 + ++ local_bookmarks 58 + .map(|b| json(stringify(if(!b.synced(), b.name() ++ "*", b.name())))) 67 59 .join(",") 68 60 ++ ']' 61 + ++ ',"remote_bookmarks":[' 62 + ++ remote_bookmarks 63 + .map(|b| json(stringify(b.name() ++ "@" ++ b.remote()))) 64 + .join(",") 65 + ++ ']' 66 + ++ ',"tags":[' 67 + ++ tags.map(|t| json(t.name())).join(",") 68 + ++ ']' 69 69 ++ ',"author":{"email":' ++ json(author.email().local()) ++ ',"timestamp":' ++ json(author.timestamp().local().format("%Y-%m-%d %H:%M:%S")) ++ '}' 70 70 ++ ',"change_id_prefix":' ++ json(change_id.shortest(8).prefix()) 71 71 ++ ',"change_id_rest":' ++ json(change_id.shortest(8).rest()) ··· 122 122 2. Second pass: Process in reverse order, look up parents from Hashtbl, update nodes 123 123 *) 124 124 let commits_to_nodes (commits : jj_commit list) : Render_jj_graph.node list = 125 + let display_refs (jj_commit : jj_commit) = 126 + (* Local bookmarks occupy the visible commit row; otherwise keep the remote-only 127 + label. Tags are appended after refs to match jj's short header output. *) 128 + let primary_refs = 129 + if jj_commit.local_bookmarks <> [] 130 + then jj_commit.local_bookmarks 131 + else jj_commit.remote_bookmarks 132 + in 133 + primary_refs @ jj_commit.tags 134 + in 125 135 (* First pass: create all nodes without parents and populate hashtable *) 126 136 let node_tbl : (string, Render_jj_graph.node) Hashtbl.t = 127 137 Hashtbl.create (List.length commits) ··· 138 148 ; change_id = jj_commit.change_id 139 149 ; commit_id = jj_commit.commit_id 140 150 ; description = jj_commit.description 141 - ; bookmarks = jj_commit.bookmarks 151 + ; bookmarks = display_refs jj_commit 142 152 ; author_email = jj_commit.author.email 143 153 ; author_timestamp = jj_commit.author.timestamp 144 154 ; empty = jj_commit.empty
+47 -24
jj_tui/lib/jj_json_tests.ml
··· 2 2 3 3 let%expect_test "parse_valid_jsonl" = 4 4 let input = 5 - {|{"commit_id":"abc123","parents":[],"change_id":"xyz","description":"First commit","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"abc","commit_id_rest":"123"} 6 - {"commit_id":"def456","parents":["abc123"],"change_id":"uvw","description":"Second commit","working_copy":true,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":["main"],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"uv","change_id_rest":"w","commit_id_prefix":"def","commit_id_rest":"456"}|} 5 + {|{"commit_id":"abc123","parents":[],"change_id":"xyz","description":"First commit","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"abc","commit_id_rest":"123"} 6 + {"commit_id":"def456","parents":["abc123"],"change_id":"uvw","description":"Second commit","working_copy":true,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":["main"],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"uv","change_id_rest":"w","commit_id_prefix":"def","commit_id_rest":"456"}|} 7 7 in 8 8 (match parse_jj_log_output input with 9 9 | Ok commits -> ··· 27 27 28 28 let%expect_test "parse_root_commit" = 29 29 let input = 30 - {|{"commit_id":"root","parents":[],"change_id":"xyz","description":"Root","working_copy":false,"immutable":true,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"ro","commit_id_rest":"ot"}|} 30 + {|{"commit_id":"root","parents":[],"change_id":"xyz","description":"Root","working_copy":false,"immutable":true,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"ro","commit_id_rest":"ot"}|} 31 31 in 32 32 (match parse_jj_log_output input with 33 33 | Ok commits -> ··· 43 43 44 44 let%expect_test "commits_to_nodes_parent_linking" = 45 45 let input = 46 - {|{"commit_id":"parent","parents":[],"change_id":"p","description":"Parent","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"p","change_id_rest":"","commit_id_prefix":"par","commit_id_rest":"ent"} 47 - {"commit_id":"child","parents":["parent"],"change_id":"c","description":"Child","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"","commit_id_prefix":"chi","commit_id_rest":"ld"}|} 46 + {|{"commit_id":"parent","parents":[],"change_id":"p","description":"Parent","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"p","change_id_rest":"","commit_id_prefix":"par","commit_id_rest":"ent"} 47 + {"commit_id":"child","parents":["parent"],"change_id":"c","description":"Child","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"","commit_id_prefix":"chi","commit_id_rest":"ld"}|} 48 48 in 49 49 (match parse_jj_log_output input with 50 50 | Ok commits -> ··· 70 70 71 71 let%expect_test "parse_multiple_parents" = 72 72 let input = 73 - {|{"commit_id":"parent1","parents":[],"change_id":"p1","description":"Parent 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"p","change_id_rest":"1","commit_id_prefix":"par","commit_id_rest":"ent1"} 74 - {"commit_id":"parent2","parents":[],"change_id":"p2","description":"Parent 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"p","change_id_rest":"2","commit_id_prefix":"par","commit_id_rest":"ent2"} 75 - {"commit_id":"merge","parents":["parent1","parent2"],"change_id":"m","description":"Merge commit","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"m","change_id_rest":"","commit_id_prefix":"mer","commit_id_rest":"ge"}|} 73 + {|{"commit_id":"parent1","parents":[],"change_id":"p1","description":"Parent 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"p","change_id_rest":"1","commit_id_prefix":"par","commit_id_rest":"ent1"} 74 + {"commit_id":"parent2","parents":[],"change_id":"p2","description":"Parent 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"p","change_id_rest":"2","commit_id_prefix":"par","commit_id_rest":"ent2"} 75 + {"commit_id":"merge","parents":["parent1","parent2"],"change_id":"m","description":"Merge commit","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"m","change_id_rest":"","commit_id_prefix":"mer","commit_id_rest":"ge"}|} 76 76 in 77 77 (match parse_jj_log_output input with 78 78 | Ok commits -> ··· 98 98 |}] 99 99 ;; 100 100 101 - let%expect_test "parse_commit_with_bookmarks" = 101 + let%expect_test "parse_commit_refs" = 102 102 let input = 103 - {|{"commit_id":"abc123","parents":[],"change_id":"xyz","description":"Commit with bookmarks","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":["main","feature"],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"abc","commit_id_rest":"123"}|} 103 + {|{"commit_id":"abc123","parents":[],"change_id":"xyz","description":"Commit with refs","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":["main*"],"remote_bookmarks":["main@origin"],"tags":["v0.18"],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"abc","commit_id_rest":"123"}|} 104 104 in 105 105 (match parse_jj_log_output input with 106 106 | Ok commits -> 107 107 let (c : jj_commit) = List.hd commits in 108 108 Printf.printf 109 - "Commit: %s, Bookmarks: [%s]\n" 109 + "Commit: %s, Local: [%s], Remote: [%s], Tags: [%s]\n" 110 110 c.commit_id 111 - (String.concat ";" c.bookmarks) 111 + (String.concat ";" c.local_bookmarks) 112 + (String.concat ";" c.remote_bookmarks) 113 + (String.concat ";" c.tags) 112 114 | Error msg -> 113 115 Printf.printf "Error: %s\n" msg); 114 116 [%expect 115 117 {| 116 - Commit: abc123, Bookmarks: [main;feature] 117 - |}] 118 + Commit: abc123, Local: [main*], Remote: [main@origin], Tags: [v0.18] 119 + |}] 118 120 ;; 119 121 120 122 let%expect_test "parse_wip_commit" = 121 123 let input = 122 - {|{"commit_id":"wip123","parents":[],"change_id":"xyz","description":"wip: work in progress","working_copy":true,"immutable":false,"wip":true,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"wip","commit_id_rest":"123"}|} 124 + {|{"commit_id":"wip123","parents":[],"change_id":"xyz","description":"wip: work in progress","working_copy":true,"immutable":false,"wip":true,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"wip","commit_id_rest":"123"}|} 123 125 in 124 126 (match parse_jj_log_output input with 125 127 | Ok commits -> ··· 165 167 166 168 let%expect_test "commits_to_nodes_preserves_order" = 167 169 let input = 168 - {|{"commit_id":"first","parents":[],"change_id":"f","description":"First","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"f","change_id_rest":"","commit_id_prefix":"fir","commit_id_rest":"st"} 169 - {"commit_id":"second","parents":["first"],"change_id":"s","description":"Second","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"s","change_id_rest":"","commit_id_prefix":"sec","commit_id_rest":"ond"} 170 - {"commit_id":"third","parents":["second"],"change_id":"t","description":"Third","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"t","change_id_rest":"","commit_id_prefix":"thi","commit_id_rest":"rd"}|} 170 + {|{"commit_id":"first","parents":[],"change_id":"f","description":"First","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"f","change_id_rest":"","commit_id_prefix":"fir","commit_id_rest":"st"} 171 + {"commit_id":"second","parents":["first"],"change_id":"s","description":"Second","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"s","change_id_rest":"","commit_id_prefix":"sec","commit_id_rest":"ond"} 172 + {"commit_id":"third","parents":["second"],"change_id":"t","description":"Third","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"t","change_id_rest":"","commit_id_prefix":"thi","commit_id_rest":"rd"}|} 171 173 in 172 174 (match parse_jj_log_output input with 173 175 | Ok commits -> ··· 186 188 187 189 let%expect_test "commits_to_nodes_copies_fields" = 188 190 let input = 189 - {|{"commit_id":"test","parents":[],"change_id":"xyz","description":"Test commit","working_copy":true,"immutable":true,"wip":true,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"te","commit_id_rest":"st"}|} 191 + {|{"commit_id":"test","parents":[],"change_id":"xyz","description":"Test commit","working_copy":true,"immutable":true,"wip":true,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"te","commit_id_rest":"st"}|} 190 192 in 191 193 (match parse_jj_log_output input with 192 194 | Ok commits -> ··· 210 212 211 213 let%expect_test "commits_to_nodes_missing_parent_creates_elided" = 212 214 let input = 213 - {|{"commit_id":"child","parents":["missing_parent"],"change_id":"c","description":"Child with missing parent","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"","commit_id_prefix":"chi","commit_id_rest":"ld"}|} 215 + {|{"commit_id":"child","parents":["missing_parent"],"change_id":"c","description":"Child with missing parent","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"","commit_id_prefix":"chi","commit_id_rest":"ld"}|} 214 216 in 215 217 (match parse_jj_log_output input with 216 218 | Ok commits -> ··· 237 239 238 240 let%expect_test "commits_to_nodes_multiple_children_same_missing_parent" = 239 241 let input = 240 - {|{"commit_id":"child1","parents":["missing_parent"],"change_id":"c1","description":"Child 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"1","commit_id_prefix":"chi","commit_id_rest":"ld1"} 241 - {"commit_id":"child2","parents":["missing_parent"],"change_id":"c2","description":"Child 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"c","change_id_rest":"2","commit_id_prefix":"chi","commit_id_rest":"ld2"}|} 242 + {|{"commit_id":"child1","parents":["missing_parent"],"change_id":"c1","description":"Child 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"1","commit_id_prefix":"chi","commit_id_rest":"ld1"} 243 + {"commit_id":"child2","parents":["missing_parent"],"change_id":"c2","description":"Child 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"c","change_id_rest":"2","commit_id_prefix":"chi","commit_id_rest":"ld2"}|} 242 244 in 243 245 (match parse_jj_log_output input with 244 246 | Ok commits -> ··· 264 266 265 267 let%expect_test "commits_to_nodes_same_missing_parent_physical_equality" = 266 268 let input = 267 - {|{"commit_id":"child1","parents":["missing_parent"],"change_id":"c1","description":"Child 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"1","commit_id_prefix":"chi","commit_id_rest":"ld1"} 268 - {"commit_id":"child2","parents":["missing_parent"],"change_id":"c2","description":"Child 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"bookmarks":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"c","change_id_rest":"2","commit_id_prefix":"chi","commit_id_rest":"ld2"}|} 269 + {|{"commit_id":"child1","parents":["missing_parent"],"change_id":"c1","description":"Child 1","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"c","change_id_rest":"1","commit_id_prefix":"chi","commit_id_rest":"ld1"} 270 + {"commit_id":"child2","parents":["missing_parent"],"change_id":"c2","description":"Child 2","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":[],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-03"},"change_id_prefix":"c","change_id_rest":"2","commit_id_prefix":"chi","commit_id_rest":"ld2"}|} 269 271 in 270 272 (match parse_jj_log_output input with 271 273 | Ok commits -> ··· 288 290 Same parent object (physical equality): true 289 291 |}] 290 292 ;; 293 + 294 + let%expect_test "commits_to_nodes_prefers_local_refs_and_appends_tags" = 295 + let input = 296 + {|{"commit_id":"local","parents":[],"change_id":"xyz","description":"Visible commit","working_copy":false,"immutable":false,"wip":false,"hidden":false,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":["main*"],"remote_bookmarks":["main@origin"],"tags":["v0.18"],"author":{"email":"test@example.com","timestamp":"2024-01-01"},"change_id_prefix":"xy","change_id_rest":"z","commit_id_prefix":"abc","commit_id_rest":"123"} 297 + {"commit_id":"remote","parents":[],"change_id":"uvw","description":"Hidden remote","working_copy":false,"immutable":false,"wip":false,"hidden":true,"divergent":false,"conflict":false,"empty":false,"local_bookmarks":[],"remote_bookmarks":["main@origin"],"tags":[],"author":{"email":"test@example.com","timestamp":"2024-01-02"},"change_id_prefix":"uv","change_id_rest":"w","commit_id_prefix":"def","commit_id_rest":"456"}|} 298 + in 299 + (match parse_jj_log_output input with 300 + | Ok commits -> 301 + let nodes = commits_to_nodes commits in 302 + let visible = List.nth nodes 0 in 303 + let hidden = List.nth nodes 1 in 304 + Printf.printf "Visible refs: [%s]\n" (String.concat ";" visible.bookmarks); 305 + Printf.printf "Hidden refs: [%s]\n" (String.concat ";" hidden.bookmarks) 306 + | Error msg -> 307 + Printf.printf "Error: %s\n" msg); 308 + [%expect 309 + {| 310 + Visible refs: [main*;v0.18] 311 + Hidden refs: [main@origin] 312 + |}] 313 + ;;
+190
jj_tui/lib/process_wrappers.ml
··· 32 32 let remove_ansi str = str |> Re.replace_string ~by:"" ansi_regex 33 33 34 34 let count_ansi str = str |> Re.all ansi_regex |> List.length 35 + let node_row_marker = "@@NODE@@" 36 + let info_row_marker = "@@INFO@@" 35 37 36 38 let find_selectable_from_graph limit str = 37 39 (* Matches a single revision in the format specificied by the graph template *) ··· 121 123 struct 122 124 open Process 123 125 126 + type native_graph_group = { 127 + node_row : Render_jj_graph.graph_row_output 128 + ; continuation_rows : Render_jj_graph.graph_row_output list 129 + } 130 + 124 131 (* Currently hard-coded. Soon it'l be settable in config *) 125 132 let base_graph_template = 126 133 {|if(root, ··· 146 153 ^ {|++"$$--END--$$"++""|} 147 154 ;; 148 155 156 + let native_graph_template = 157 + Printf.sprintf 158 + {|label(if(current_working_copy, "working_copy"), "%s") ++ "\n" ++ "%s"|} 159 + node_row_marker 160 + info_row_marker 161 + ;; 162 + 163 + let native_graph_output ?revset limit = 164 + let args = [ "log"; "-T"; native_graph_template; "--limit"; string_of_int limit ] in 165 + let args = match revset with Some r -> args @ [ "-r"; r ] | None -> args in 166 + jj_no_log args ~color:false 167 + ;; 168 + 169 + let line_before_marker line marker = 170 + let marker_len = String.length marker in 171 + match String.index_opt line '@' with 172 + | None -> 173 + None 174 + | Some _ -> 175 + let rec search start = 176 + if start + marker_len > String.length line 177 + then None 178 + else if String.sub line start marker_len = marker 179 + then Some (String.sub line 0 start) 180 + else search (start + 1) 181 + in 182 + search 0 183 + ;; 184 + 185 + let make_graph_row_output ~graph_chars ~row_type () = 186 + let open Notty in 187 + Render_jj_graph. 188 + { 189 + graph_chars 190 + ; graph_image = I.string A.empty graph_chars 191 + ; node = Render_jj_graph.make_elided_node () 192 + ; row_type 193 + } 194 + ;; 195 + 196 + let parse_native_graph_groups output = 197 + let lines = String.split_on_char '\n' output in 198 + let flush_group current_group acc = 199 + match current_group with Some g -> g :: acc | None -> acc 200 + in 201 + let groups_rev, current_group = 202 + List.fold_left 203 + (fun (acc, current_group) line -> 204 + match line_before_marker line node_row_marker with 205 + | Some graph_chars -> 206 + let acc = flush_group current_group acc in 207 + let node_row = 208 + make_graph_row_output ~graph_chars ~row_type:Render_jj_graph.NodeRow () 209 + in 210 + acc, Some { node_row; continuation_rows = [] } 211 + | None -> 212 + let graph_chars = remove_ansi line in 213 + if graph_chars = "" 214 + then acc, current_group 215 + else ( 216 + let row_type = 217 + match line_before_marker line info_row_marker with 218 + | Some _ -> 219 + Render_jj_graph.PadRow 220 + | None -> 221 + Render_jj_graph.classify_row_type graph_chars 222 + in 223 + let graph_chars = 224 + match line_before_marker line info_row_marker with 225 + | Some chars -> 226 + chars 227 + | None -> 228 + graph_chars 229 + in 230 + match current_group with 231 + | Some group -> 232 + let row = make_graph_row_output ~graph_chars ~row_type () in 233 + ( acc 234 + , Some 235 + { group with continuation_rows = group.continuation_rows @ [ row ] } 236 + ) 237 + | None -> 238 + let node_row = make_graph_row_output ~graph_chars ~row_type () in 239 + acc, Some { node_row; continuation_rows = [] })) 240 + ([], None) 241 + lines 242 + in 243 + List.rev (flush_group current_group groups_rev) 244 + ;; 245 + 246 + let attach_nodes_to_native_groups ~(nodes : Render_jj_graph.node list) groups = 247 + if List.length groups <> List.length nodes 248 + then None 249 + else ( 250 + let rows_rev = 251 + List.fold_left2 252 + (fun acc group node -> 253 + let node_row : Render_jj_graph.graph_row_output = 254 + { group.node_row with node } 255 + in 256 + let continuation_rows = 257 + List.map 258 + (fun (row : Render_jj_graph.graph_row_output) -> { row with node }) 259 + group.continuation_rows 260 + in 261 + List.rev_append (List.rev (node_row :: continuation_rows)) acc) 262 + [] 263 + groups 264 + nodes 265 + in 266 + Some (List.rev rows_rev)) 267 + ;; 268 + 149 269 let get_graph_info node_template revset_arg limit = 150 270 let output = 151 271 jj_no_log ··· 202 322 in 203 323 nodes, rev_ids 204 324 ;; 325 + 326 + let get_native_graph_rows ?revset limit nodes = 327 + let output = native_graph_output ?revset limit in 328 + output |> parse_native_graph_groups |> attach_nodes_to_native_groups ~nodes 329 + ;; 330 + 331 + let get_graph_nodes_with_native_rows ?revset limit = 332 + (* Keep native graph rows and JSON metadata in lockstep. If the parser loses 333 + alignment, fall back to the synthetic renderer instead of showing broken rows. *) 334 + Flock.join_after @@ fun _ -> 335 + let commits_promise = 336 + Flock.fork_as_promise @@ fun () -> get_graph_json ?revset limit 337 + in 338 + let native_promise = 339 + Flock.fork_as_promise @@ fun () -> native_graph_output ?revset limit 340 + in 341 + let commits = Promise.await commits_promise in 342 + let nodes = Jj_json.commits_to_nodes commits in 343 + let rev_ids = 344 + commits 345 + |> List.map (fun (c : Jj_json.jj_commit) -> 346 + if c.divergent || c.hidden then Duplicate c.commit_id else Unique c.change_id) 347 + |> Array.of_list 348 + in 349 + let native_rows = 350 + Promise.await native_promise 351 + |> parse_native_graph_groups 352 + |> attach_nodes_to_native_groups ~nodes 353 + in 354 + nodes, rev_ids, native_rows 355 + ;; 205 356 end 206 357 207 358 (*========Tests======*) ··· 334 485 count |> string_of_int |> print_endline; 335 486 [%expect {|2|}] 336 487 ;; 488 + 489 + module Test_native_graph = Make (struct 490 + let jj_no_log ?get_stderr:_ ?snapshot:_ ?color:_ _ = failwith "unused" 491 + end) 492 + 493 + let%expect_test "parse_native_graph_groups_preserves_elision_continuity" = 494 + let output = 495 + {|◆ @@NODE@@ 496 + │ @@INFO@@ 497 + ~ (elided revisions) 498 + │ ○ @@NODE@@ 499 + ├─╯ @@INFO@@ 500 + ~|} 501 + in 502 + let groups = 503 + Test_native_graph.parse_native_graph_groups output 504 + |> List.map (fun (group : Test_native_graph.native_graph_group) -> 505 + group.node_row.Render_jj_graph.graph_chars 506 + :: List.map 507 + (fun (row : Render_jj_graph.graph_row_output) -> row.graph_chars) 508 + group.continuation_rows) 509 + in 510 + List.iteri 511 + (fun i rows -> 512 + Printf.printf "Group %d\n" i; 513 + List.iter (fun row -> Printf.printf " %S\n" row) rows) 514 + groups; 515 + [%expect 516 + {| 517 + Group 0 518 + "\226\151\134 " 519 + "\226\148\130 " 520 + "~ (elided revisions)" 521 + Group 1 522 + "\226\148\130 \226\151\139 " 523 + "\226\148\156\226\148\128\226\149\175 " 524 + "~" 525 + |}] 526 + ;;