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.

enable preview mode (5.2 codex)

+563 -34
+91 -1
jj_tui/bin/global_vars.ml
··· 10 10 11 11 open Jj_tui.Key_map 12 12 13 + type rebase_preview_mode = 14 + [ `Insert_before 15 + | `Insert_after 16 + | `Add_after 17 + ] 18 + 13 19 type ui_state_t = { 14 20 view : 15 21 [ `Main (**Normal Mode*) ··· 18 24 (* | `Prompt of string * [ `Cmd of cmd_args | `Cmd_I of cmd_args ] *) 19 25 ] 20 26 Lwd.var 21 - ; input : [ `Normal | `Mode of Nottui.Ui.key -> Ui.may_handle ] Lwd.var 27 + ; input : 28 + [ `Normal 29 + | `Mode of Nottui.Ui.key -> Ui.may_handle 30 + | `Mode_no_popup of Nottui.Ui.key -> Ui.may_handle 31 + ] 32 + Lwd.var 22 33 ; show_popup : (Nottui.ui Lwd.t * string) option Lwd.var 23 34 ; show_prompt : W.Overlay.text_prompt_data option Lwd.var 24 35 (* ; show_graph_selection_prompt : *) ··· 33 44 ; jj_change_files : (string * string) list Lwd.var 34 45 ; hovered_revision : string maybe_unique Lwd.var 35 46 ; selected_revisions : string maybe_unique list Lwd.var 47 + ; rebase_preview_active : bool Lwd.var 48 + ; rebase_preview_mode : rebase_preview_mode Lwd.var 49 + ; rebase_preview_targets : string list Lwd.var 50 + ; rebase_preview_sources : string list Lwd.var 51 + ; rebase_preview_invalid : string option Lwd.var 36 52 ; revset : string option Lwd.var 37 53 ; trigger_update : unit Lwd.var 38 54 ; reset_selection : unit Signal.t ··· 63 79 val get_selected_revs_lwd : unit -> string list Lwd.t 64 80 val get_active_revs : unit -> string list 65 81 val get_active_revs_lwd : unit -> string list Lwd.t 82 + val get_rebase_preview_active : unit -> bool 83 + val get_rebase_preview_mode : unit -> rebase_preview_mode 84 + val get_rebase_preview_mode_lwd : unit -> rebase_preview_mode Lwd.t 85 + val get_rebase_preview_targets : unit -> string list 86 + val get_rebase_preview_sources : unit -> string list 87 + val set_rebase_preview_active : bool -> unit 88 + val set_rebase_preview_targets : string list -> unit 89 + val set_rebase_preview_sources : string list -> unit 90 + val set_rebase_preview_invalid : string option -> unit 91 + val clear_rebase_preview : unit -> unit 92 + val cycle_rebase_preview_mode : unit -> unit 93 + val rebase_preview_label : unit -> string 66 94 val config : Config.t Lwd.var 67 95 val show_popup : (Nottui.ui Lwd.t * string) option -> unit 68 96 val set_loading : string option -> unit ··· 80 108 ; jj_change_files = Lwd.var [] 81 109 ; hovered_revision = Lwd.var (Unique "@") 82 110 ; selected_revisions = Lwd.var [ Unique "@" ] 111 + ; rebase_preview_active = Lwd.var false 112 + ; rebase_preview_mode = Lwd.var `Insert_after 113 + ; rebase_preview_targets = Lwd.var [] 114 + ; rebase_preview_sources = Lwd.var [] 115 + ; rebase_preview_invalid = Lwd.var None 83 116 ; revset = Lwd.var None 84 117 ; graph_revs = Lwd.var [||] 85 118 ; input = Lwd.var `Normal ··· 138 171 if selected |> List.length == 0 139 172 then [ hovered |> get_unique_id ] 140 173 else selected |> List.map get_unique_id 174 + ;; 175 + 176 + let get_rebase_preview_active () = Lwd.peek ui_state.rebase_preview_active 177 + 178 + let get_rebase_preview_mode () = Lwd.peek ui_state.rebase_preview_mode 179 + 180 + let get_rebase_preview_mode_lwd () = Lwd.get ui_state.rebase_preview_mode 181 + 182 + let get_rebase_preview_targets () = Lwd.peek ui_state.rebase_preview_targets 183 + 184 + let get_rebase_preview_sources () = Lwd.peek ui_state.rebase_preview_sources 185 + 186 + let set_rebase_preview_active active = 187 + Lwd.set ui_state.rebase_preview_active active 188 + ;; 189 + 190 + let set_rebase_preview_targets targets = 191 + Lwd.set ui_state.rebase_preview_targets targets 192 + ;; 193 + 194 + let set_rebase_preview_sources sources = 195 + Lwd.set ui_state.rebase_preview_sources sources 196 + ;; 197 + 198 + let set_rebase_preview_invalid msg = Lwd.set ui_state.rebase_preview_invalid msg 199 + 200 + let clear_rebase_preview () = 201 + ui_state.rebase_preview_active $= false; 202 + ui_state.rebase_preview_targets $= []; 203 + ui_state.rebase_preview_sources $= []; 204 + ui_state.rebase_preview_invalid $= None 205 + ;; 206 + 207 + let cycle_rebase_preview_mode () = 208 + let next = 209 + match Lwd.peek ui_state.rebase_preview_mode with 210 + | `Insert_before -> 211 + `Insert_after 212 + | `Insert_after -> 213 + `Add_after 214 + | `Add_after -> 215 + `Insert_before 216 + in 217 + Lwd.set ui_state.rebase_preview_mode next 218 + ;; 219 + 220 + let rebase_preview_label () = 221 + let mode_str = 222 + match Lwd.peek ui_state.rebase_preview_mode with 223 + | `Insert_before -> 224 + "insert-before" 225 + | `Insert_after -> 226 + "insert-after" 227 + | `Add_after -> 228 + "add-after" 229 + in 230 + Printf.sprintf "Preview: %s" mode_str 141 231 ;; 142 232 143 233 let show_popup popup =
+25
jj_tui/bin/graph_commands.ml
··· 309 309 ; make_cmd = (fun () -> Dynamic_r (fun rev -> Cmd_I [ "resolve"; "-r"; rev ])) 310 310 } 311 311 ; { 312 + id = "rebase_preview_toggle" 313 + ; sorting_key = 19.5 314 + ; description = "Toggle rebase preview mode" 315 + ; make_cmd = 316 + (fun () -> 317 + Fun 318 + (fun _ -> 319 + if Vars.get_rebase_preview_active () 320 + then ( 321 + Vars.clear_rebase_preview (); 322 + ui_state.input $= `Normal; 323 + Vars.ui_state.trigger_update $= ()) 324 + else ( 325 + Vars.set_rebase_preview_active true; 326 + Vars.set_rebase_preview_sources (Vars.get_active_revs ()); 327 + let targets = 328 + let selected = Vars.get_selected_revs () in 329 + if List.length selected = 0 then [ Vars.get_hovered_rev () ] else selected 330 + in 331 + Vars.set_rebase_preview_targets targets; 332 + Vars.set_rebase_preview_invalid None; 333 + ui_state.input $= `Mode_no_popup (fun _ -> `Unhandled); 334 + Vars.ui_state.trigger_update $= ()))) 335 + } 336 + ; { 312 337 id = "rebase_single" 313 338 ; sorting_key = 20.0 314 339 ; description = "Rebase single revision "
+114 -30
jj_tui/bin/graph_view.ml
··· 132 132 x |> Ui.resize ~mw:10000 ~sw:1 |> Lwd.pure |> W.Box.box ~pad_w:0 ~pad_h:0) 133 133 |> Option.value ~default:(Ui.empty |> Lwd.pure) 134 134 in 135 + let mode_indicator = 136 + let$ active = Lwd.get Vars.ui_state.rebase_preview_active 137 + and$ mode = Lwd.get Vars.ui_state.rebase_preview_mode 138 + and$ invalid = Lwd.get Vars.ui_state.rebase_preview_invalid in 139 + if not active 140 + then Ui.empty 141 + else ( 142 + let mode_str = 143 + match mode with 144 + | `Insert_before -> 145 + "insert-before" 146 + | `Insert_after -> 147 + "insert-after" 148 + | `Add_after -> 149 + "add-after" 150 + in 151 + let base = "Preview: " ^ mode_str in 152 + let label = 153 + match invalid with 154 + | None -> 155 + base 156 + | Some msg -> 157 + base ^ " - " ^ msg 158 + in 159 + W.string label) 160 + in 135 161 let items = 136 162 let$ rendered_rows, rev_ids = 137 163 (*TODO I think this ads a slight delay to everything becasue it makes things need to be renedered twice. maybe I could try getting rid of it*) ··· 144 170 let state = 145 171 Render_jj_graph.{ depth = 0; columns = [||]; pending_joins = [] } 146 172 in 147 - let rendered_rows = Render_jj_graph.render_nodes_structured state nodes ~node_attr:(Commit_render.graph_node_attr) in 173 + let node_id_map = 174 + List.map2 175 + (fun node rev_id -> node.Render_jj_graph.commit_id, rev_id) 176 + nodes 177 + (Array.to_list rev_ids) 178 + |> List.to_seq 179 + |> Hashtbl.of_seq 180 + in 181 + let nodes, invalid = 182 + if Vars.get_rebase_preview_active () 183 + then 184 + Render_jj_graph.apply_rebase_preview 185 + ~mode:(Vars.get_rebase_preview_mode ()) 186 + ~sources:(Vars.get_rebase_preview_sources ()) 187 + ~targets:(Vars.get_rebase_preview_targets ()) 188 + nodes 189 + else nodes, None 190 + in 191 + let current_invalid = Lwd.peek Vars.ui_state.rebase_preview_invalid in 192 + if current_invalid <> invalid then Vars.set_rebase_preview_invalid invalid; 193 + let rev_ids = 194 + if Vars.get_rebase_preview_active () 195 + then 196 + nodes 197 + |> List.filter (fun node -> not node.Render_jj_graph.is_preview) 198 + |> List.filter_map (fun node -> 199 + Hashtbl.find_opt node_id_map node.Render_jj_graph.commit_id) 200 + |> Array.of_list 201 + else rev_ids 202 + in 203 + let rendered_rows = 204 + Render_jj_graph.render_nodes_structured 205 + state 206 + nodes 207 + ~node_attr:(Commit_render.graph_node_attr) 208 + in 148 209 error_var $= None; 149 210 rendered_rows, rev_ids 150 211 with ··· 168 229 match rendered_group with 169 230 | [] -> 170 231 [] 171 - | (_first_row, first_img) :: rest_rows -> 172 - let id = rev_ids.(!selectable_idx) in 173 - let selectable_ui = W.Lists.selectable_item (first_img |> Ui.atom) in 174 - let data = 175 - W.Lists. 176 - { 177 - ui = selectable_ui 178 - ; id = id |> Global_vars.get_unique_id |> String.hash 179 - ; data = rev_ids.(!selectable_idx) 180 - } 181 - in 182 - (* Add to our selectable array *) 183 - Array.set selectable_items !selectable_idx data; 184 - selectable_idx := !selectable_idx + 1; 185 - let first_item = W.Lists.(Selectable data) in 186 - (* All other rows in the group become fillers *) 187 - let filler_items = 232 + | (first_row, first_img) :: rest_rows -> 233 + if first_row.node.is_preview 234 + then 188 235 List.map 189 236 (fun (_row, img) -> W.Lists.(Filler (img |> Ui.atom |> Lwd.pure))) 190 - rest_rows 191 - in 192 - first_item :: filler_items) 237 + ((first_row, first_img) :: rest_rows) 238 + else ( 239 + let id = rev_ids.(!selectable_idx) in 240 + let selectable_ui = W.Lists.selectable_item (first_img |> Ui.atom) in 241 + let data = 242 + W.Lists. 243 + { 244 + ui = selectable_ui 245 + ; id = id |> Global_vars.get_unique_id |> String.hash 246 + ; data = rev_ids.(!selectable_idx) 247 + } 248 + in 249 + (* Add to our selectable array *) 250 + Array.set selectable_items !selectable_idx data; 251 + selectable_idx := !selectable_idx + 1; 252 + let first_item = W.Lists.(Selectable data) in 253 + (* All other rows in the group become fillers *) 254 + let filler_items = 255 + List.map 256 + (fun (_row, img) -> W.Lists.(Filler (img |> Ui.atom |> Lwd.pure))) 257 + rest_rows 258 + in 259 + first_item :: filler_items)) 193 260 |> Array.of_list 194 261 in 195 262 items ··· 209 276 |> W.Lists.multi_selection_list_exclusions 210 277 ~reset_selections:Vars.ui_state.reset_selection 211 278 ~on_selection_change:(fun ~hovered ~selected -> 212 - (*Respond to change in selected revision*) 213 - Lwd.set Vars.ui_state.hovered_revision hovered; 214 - Lwd.set Vars.ui_state.selected_revisions selected; 215 - (*If the files are focused we shouldn't send this*) 216 - (if Focus.peek_has_focus focus 217 - then Show_view.(push_status (Graph_preview (Vars.get_hovered_rev ())))); 218 - [%log debug "Hovered revision: '%s'" (Global_vars.get_unique_id hovered)]; 219 - Global_funcs.update_views_async ()) 279 + let prev_hovered = Lwd.peek Vars.ui_state.hovered_revision in 280 + let prev_selected = Lwd.peek Vars.ui_state.selected_revisions in 281 + if prev_hovered <> hovered || prev_selected <> selected 282 + then ( 283 + (*Respond to change in selected revision*) 284 + Lwd.set Vars.ui_state.hovered_revision hovered; 285 + Lwd.set Vars.ui_state.selected_revisions selected; 286 + if Vars.get_rebase_preview_active () 287 + then ( 288 + let targets = 289 + if List.length selected = 0 290 + then [ hovered |> Global_vars.get_unique_id ] 291 + else selected |> List.map Global_vars.get_unique_id 292 + in 293 + let current_targets = Vars.get_rebase_preview_targets () in 294 + if targets <> current_targets 295 + then ( 296 + Vars.set_rebase_preview_targets targets; 297 + Vars.ui_state.trigger_update $= ())) 298 + else ( 299 + (*If the files are focused we shouldn't send this*) 300 + if Focus.peek_has_focus focus 301 + then Show_view.(push_status (Graph_preview (Vars.get_hovered_rev ()))); 302 + [%log debug "Hovered revision: '%s'" (Global_vars.get_unique_id hovered)]; 303 + Global_funcs.update_views_async ()))) 220 304 ~custom_handler:(fun ~selected ~selectable_items key -> handleKeys key) 221 305 in 222 306 let final_ui = ··· 228 312 and$ error = Lwd.get error_var in 229 313 match error with Some e -> e |> Ui.keyboard_area handleKeys | None -> list_ui 230 314 in 231 - W.vbox [ revset_ui; final_ui ] 315 + W.vbox [ revset_ui; mode_indicator; final_ui ] 232 316 ;; 233 317 end
+7
jj_tui/bin/jj_commands.ml
··· 460 460 match Lwd.peek ui_state.input with 461 461 | `Mode mode -> 462 462 mode 463 + | `Mode_no_popup mode -> 464 + (fun key -> 465 + match mode key with 466 + | `Unhandled -> 467 + command_input ~is_sub:false command_mapping key 468 + | handled -> 469 + handled) 463 470 | `Normal -> 464 471 command_input ~is_sub:false command_mapping 465 472 ;;
+15 -1
jj_tui/bin/jj_ui.ml
··· 79 79 (* | `Arrow _, [ `Ctrl ] *) 80 80 (* | `Arrow _, [ `Meta ] *) 81 81 | `Tab, [] -> 82 - `Handled 82 + if Vars.get_rebase_preview_active () 83 + then ( 84 + Vars.cycle_rebase_preview_mode (); 85 + Vars.set_rebase_preview_invalid None; 86 + Vars.ui_state.trigger_update $= (); 87 + `Handled) 88 + else `Handled 83 89 | `Tab, [ `Meta ] | `Tab, [ `Meta; `Shift ] -> 84 90 `Handled 85 91 | _ -> ··· 94 100 (match event with 95 101 | `Escape, [] -> 96 102 show_popup None; 103 + ui_state.input $= `Normal; 104 + `Handled 105 + | _ -> 106 + `Unhandled) 107 + | `Mode_no_popup _, _, _ -> 108 + (match event with 109 + | `Escape, [] -> 110 + if Vars.get_rebase_preview_active () then Vars.clear_rebase_preview (); 97 111 ui_state.input $= `Normal; 98 112 `Handled 99 113 | _ ->
+1
jj_tui/lib/key_map.ml
··· 294 294 cmd "r" "rebase_single" 295 295 ; cmd "s" "rebase_with_descendants" 296 296 ; cmd "b" "rebase_with_bookmark" 297 + ; cmd "p" "rebase_preview_toggle" 297 298 ] 298 299 ; sub 299 300 "g"
+206 -2
jj_tui/lib/render_jj_graph.ml
··· 97 97 (** Create a preview node with a label. 98 98 Preview nodes are used to visualize where commits would land during 99 99 rebase/move operations. *) 100 - let make_preview_node ~label ?target_commit_id () : node = 100 + let make_preview_node ~label ?description ?target_commit_id () : node = 101 + let description = Option.value description ~default:label in 101 102 { 102 103 parents = [] 103 104 ; creation_time = Int64.zero ··· 111 112 Printf.sprintf "preview:%s:%s" label id 112 113 | None -> 113 114 Printf.sprintf "preview:%s" label) 114 - ; description = label 115 + ; description 115 116 ; bookmarks = [] 116 117 ; author_email = "" 117 118 ; author_timestamp = "" ··· 124 125 ; commit_id_prefix = "" 125 126 ; commit_id_rest = "" 126 127 } 128 + ;; 129 + 130 + type preview_mode = 131 + [ `Insert_before 132 + | `Insert_after 133 + | `Add_after 134 + ] 135 + 136 + module StringSet = Set.Make (String) 137 + 138 + let node_matches_rev (n : node) rev = n.change_id = rev || n.commit_id = rev 139 + 140 + let resolve_revs (nodes : node list) (revs : string list) : string list = 141 + revs 142 + |> List.concat_map (fun rev -> 143 + nodes 144 + |> List.filter (fun n -> node_matches_rev n rev) 145 + |> List.map (fun n -> n.commit_id)) 146 + |> List.sort_uniq String.compare 147 + ;; 148 + 149 + let build_parent_map (nodes : node list) = 150 + let map = Hashtbl.create (List.length nodes) in 151 + List.iter 152 + (fun n -> 153 + Hashtbl.replace map n.commit_id (List.map (fun p -> p.commit_id) n.parents)) 154 + nodes; 155 + map 156 + ;; 157 + 158 + let build_children_map parent_map = 159 + let children = Hashtbl.create (Hashtbl.length parent_map) in 160 + Hashtbl.iter 161 + (fun child_id parent_ids -> 162 + List.iter 163 + (fun parent_id -> 164 + let existing = Option.value (Hashtbl.find_opt children parent_id) ~default:[] in 165 + Hashtbl.replace children parent_id (child_id :: existing)) 166 + parent_ids) 167 + parent_map; 168 + children 169 + ;; 170 + 171 + let build_ancestors parent_map = 172 + let cache = Hashtbl.create (Hashtbl.length parent_map) in 173 + let rec ancestors id = 174 + match Hashtbl.find_opt cache id with 175 + | Some result -> 176 + result 177 + | None -> 178 + let parents = Option.value (Hashtbl.find_opt parent_map id) ~default:[] in 179 + let result = 180 + List.fold_left 181 + (fun acc parent_id -> 182 + let acc = StringSet.add parent_id acc in 183 + StringSet.union acc (ancestors parent_id)) 184 + StringSet.empty 185 + parents 186 + in 187 + Hashtbl.replace cache id result; 188 + result 189 + in 190 + ancestors 191 + ;; 192 + 193 + let preview_description sources = 194 + match sources with 195 + | [ rev ] -> 196 + Printf.sprintf "preview: %s" rev 197 + | _ -> 198 + Printf.sprintf "preview: %d commits" (List.length sources) 199 + ;; 200 + 201 + let apply_rebase_preview 202 + ~(mode : preview_mode) 203 + ~(sources : string list) 204 + ~(targets : string list) 205 + (nodes : node list) : node list * string option 206 + = 207 + if sources = [] || targets = [] then nodes, None 208 + else ( 209 + let source_ids = resolve_revs nodes sources in 210 + let target_ids = resolve_revs nodes targets in 211 + if source_ids = [] || target_ids = [] 212 + then nodes, None 213 + else ( 214 + let parent_map_all = build_parent_map nodes in 215 + let ancestors_of = build_ancestors parent_map_all in 216 + let removed_set = StringSet.of_list source_ids in 217 + let nodes_filtered = 218 + nodes |> List.filter (fun n -> not (StringSet.mem n.commit_id removed_set)) 219 + in 220 + let parent_map = Hashtbl.create (List.length nodes_filtered) in 221 + List.iter 222 + (fun n -> 223 + let parents = 224 + n.parents 225 + |> List.map (fun p -> p.commit_id) 226 + |> List.filter (fun id -> not (StringSet.mem id removed_set)) 227 + in 228 + Hashtbl.replace parent_map n.commit_id parents) 229 + nodes_filtered; 230 + let children_map = build_children_map parent_map in 231 + let invalid = ref None in 232 + let preview_by_target = Hashtbl.create (List.length target_ids) in 233 + let base_nodes = 234 + Hashtbl.create (List.length nodes_filtered + List.length target_ids) 235 + in 236 + List.iter (fun n -> Hashtbl.replace base_nodes n.commit_id n) nodes_filtered; 237 + let add_preview_for_target target_id = 238 + if not (Hashtbl.mem parent_map target_id) 239 + then () 240 + else ( 241 + let invalid_target = 242 + List.exists 243 + (fun source_id -> 244 + if source_id = target_id 245 + then true 246 + else ( 247 + let source_ancestors = ancestors_of source_id in 248 + let target_ancestors = ancestors_of target_id in 249 + match mode with 250 + | `Insert_before -> 251 + StringSet.mem target_id source_ancestors 252 + | `Insert_after | `Add_after -> 253 + StringSet.mem source_id target_ancestors)) 254 + source_ids 255 + in 256 + if invalid_target 257 + then invalid := Some "Preview blocked: cycle detected" 258 + else ( 259 + let preview_id = Printf.sprintf "preview:%s" target_id in 260 + if not (Hashtbl.mem preview_by_target target_id) 261 + then ( 262 + let label = "preview" in 263 + let description = preview_description sources in 264 + let preview_node = 265 + make_preview_node ~label ~description ~target_commit_id:target_id () 266 + in 267 + Hashtbl.replace base_nodes preview_id preview_node; 268 + Hashtbl.replace preview_by_target target_id preview_id; 269 + match mode with 270 + | `Insert_before -> 271 + let parents = Option.value (Hashtbl.find_opt parent_map target_id) ~default:[] in 272 + Hashtbl.replace parent_map preview_id parents; 273 + Hashtbl.replace parent_map target_id [ preview_id ] 274 + | `Insert_after -> 275 + Hashtbl.replace parent_map preview_id [ target_id ]; 276 + let children = 277 + Option.value (Hashtbl.find_opt children_map target_id) ~default:[] 278 + in 279 + List.iter 280 + (fun child_id -> 281 + let child_parents = 282 + Option.value (Hashtbl.find_opt parent_map child_id) ~default:[] 283 + in 284 + let updated = 285 + List.map 286 + (fun parent_id -> 287 + if parent_id = target_id then preview_id else parent_id) 288 + child_parents 289 + in 290 + Hashtbl.replace parent_map child_id updated) 291 + children 292 + | `Add_after -> 293 + Hashtbl.replace parent_map preview_id [ target_id ]))) 294 + in 295 + List.iter add_preview_for_target target_ids; 296 + (* Order must follow topological log order: children appear before parents. *) 297 + let preview_before = match mode with `Insert_after | `Add_after -> true | _ -> false in 298 + let preview_after = match mode with `Insert_before -> true | _ -> false in 299 + let ordered_ids_rev = 300 + List.fold_left 301 + (fun acc n -> 302 + let id = n.commit_id in 303 + match Hashtbl.find_opt preview_by_target id with 304 + | Some preview_id when preview_before -> 305 + id :: preview_id :: acc 306 + | Some preview_id when preview_after -> 307 + preview_id :: id :: acc 308 + | Some _ -> 309 + id :: acc 310 + | None -> 311 + id :: acc) 312 + [] 313 + nodes_filtered 314 + in 315 + let ordered_ids = List.rev ordered_ids_rev in 316 + let final_nodes = Hashtbl.create (List.length ordered_ids) in 317 + let rec build_node id = 318 + match Hashtbl.find_opt final_nodes id with 319 + | Some node -> 320 + node 321 + | None -> 322 + let base = Hashtbl.find base_nodes id in 323 + let parent_ids = Option.value (Hashtbl.find_opt parent_map id) ~default:[] in 324 + let parents = List.map build_node parent_ids in 325 + let node = { base with parents } in 326 + Hashtbl.replace final_nodes id node; 327 + node 328 + in 329 + let nodes = List.map build_node ordered_ids in 330 + nodes, !invalid)) 127 331 ;; 128 332 129 333 (** Insert a preview node after the specified commit.
+104
jj_tui/lib/render_jj_graph_tests.ml
··· 1 1 open Render_jj_graph 2 2 3 + let make_node ?(parents = []) commit_id : node = 4 + { 5 + parents 6 + ; creation_time = 0L 7 + ; working_copy = false 8 + ; immutable = false 9 + ; wip = false 10 + ; change_id = commit_id 11 + ; commit_id 12 + ; description = commit_id 13 + ; bookmarks = [] 14 + ; author_email = "" 15 + ; author_timestamp = "" 16 + ; empty = false 17 + ; hidden = false 18 + ; divergent = false 19 + ; is_preview = false 20 + ; change_id_prefix = "" 21 + ; change_id_rest = "" 22 + ; commit_id_prefix = "" 23 + ; commit_id_rest = "" 24 + } 25 + ;; 26 + 27 + let find_node_exn nodes commit_id = 28 + nodes |> List.find (fun n -> n.commit_id = commit_id) 29 + ;; 30 + 31 + let find_preview_exn nodes = 32 + nodes |> List.find (fun n -> n.is_preview) 33 + ;; 34 + 35 + let parent_ids node = node.parents |> List.map (fun p -> p.commit_id) 36 + ;; 37 + 38 + let%expect_test "apply_rebase_preview_insert_before" = 39 + let c = make_node "c" in 40 + let b = make_node ~parents:[ c ] "b" in 41 + let a = make_node ~parents:[ b ] "a" in 42 + let nodes, invalid = 43 + apply_rebase_preview ~mode:`Insert_before ~sources:[ "c" ] ~targets:[ "b" ] [ a; b; c ] 44 + in 45 + let preview = find_preview_exn nodes in 46 + let b = find_node_exn nodes "b" in 47 + print_endline (Option.value invalid ~default:"ok"); 48 + parent_ids preview |> String.concat "," |> print_endline; 49 + parent_ids b |> String.concat "," |> print_endline; 50 + [%expect 51 + {| 52 + ok 53 + 54 + preview:preview:b 55 + |}] 56 + ;; 57 + 58 + let%expect_test "apply_rebase_preview_insert_after" = 59 + let c = make_node "c" in 60 + let b = make_node ~parents:[ c ] "b" in 61 + let a = make_node ~parents:[ b ] "a" in 62 + let nodes, invalid = 63 + apply_rebase_preview ~mode:`Insert_after ~sources:[ "a" ] ~targets:[ "b" ] [ a; b; c ] 64 + in 65 + let preview = find_preview_exn nodes in 66 + let has_a = nodes |> List.exists (fun n -> n.commit_id = "a") in 67 + print_endline (Option.value invalid ~default:"ok"); 68 + parent_ids preview |> String.concat "," |> print_endline; 69 + print_endline (string_of_bool has_a); 70 + [%expect 71 + {| 72 + ok 73 + b 74 + false 75 + |}] 76 + ;; 77 + 78 + let%expect_test "apply_rebase_preview_removes_sources" = 79 + let c = make_node "c" in 80 + let b = make_node ~parents:[ c ] "b" in 81 + let a = make_node ~parents:[ b ] "a" in 82 + let nodes, _invalid = 83 + apply_rebase_preview ~mode:`Add_after ~sources:[ "a" ] ~targets:[ "b" ] [ a; b; c ] 84 + in 85 + let ids = nodes |> List.map (fun n -> n.commit_id) |> String.concat "," in 86 + print_endline ids; 87 + [%expect {| preview:preview:b,b,c |}] 88 + ;; 89 + 90 + let%expect_test "apply_rebase_preview_invalid_cycle" = 91 + let c = make_node "c" in 92 + let b = make_node ~parents:[ c ] "b" in 93 + let a = make_node ~parents:[ b ] "a" in 94 + let nodes, invalid = 95 + apply_rebase_preview ~mode:`Insert_before ~sources:[ "a" ] ~targets:[ "b" ] [ a; b; c ] 96 + in 97 + let preview_count = nodes |> List.filter (fun n -> n.is_preview) |> List.length in 98 + print_endline (Option.value invalid ~default:"ok"); 99 + print_endline (string_of_int preview_count); 100 + [%expect 101 + {| 102 + Preview blocked: cycle detected 103 + 0 104 + |}] 105 + ;; 106 + 3 107 let%expect_test "recreate_target_graph" = 4 108 (* Graph: a (working copy) has parents [c; d] where c->b->d 5 109 First parent (c) gets node's column (0), second parent (d) branches to column 1.