···22open Nottui
33open Eio.Std
44open Lwd_infix
55+open Jj_tui.Process
5667type cmd_args = string list
77-88-type rev_id = {
99- change_id : string
1010- ; commit_id : string
1111-}
1212-1313-type 'a maybe_unique =
1414- | Unique of 'a
1515- | Duplicate of 'a
168179type ui_state_t = {
1810 view :
+7-6
jj_tui/bin/graph_view.ml
···11module Make (Vars : Global_vars.Vars) = struct
22 open Lwd_infix
33 open Vars
44- open Jj_process.Make (Vars)
54 open Notty
65 open Jj_tui
76 open Nottui
87 open! Jj_tui.Util
98 open Jj_commands.Make (Vars)
109 open Jj_widgets.Make (Vars)
1010+ module Process =Jj_process.Make (Vars)
1111+ open Process
1212+ open Jj_tui.Process_wrappers.Make(Process)
11131214 let branch_select_prompt get_branch_list name func =
1315 Selection_prompt
···7173 let rev = Vars.get_selected_rev () in
7274 let source_msg, dest_msg = get_messages rev (rev ^ "-") in
7375 let new_msg =
7474- [ dest_msg;source_msg ] |> String.concat_non_empty "\n"
7676+ [ dest_msg; source_msg ] |> String.concat_non_empty "\n"
7577 in
7678 jj [ "squash"; "--quiet"; "-r"; rev; "-m"; new_msg ] |> ignore)
7779 }
···8688 (fun rev ->
8789 let src_msg, dest_msg = get_messages rev target in
8890 let new_msg =
8989- [ dest_msg;src_msg ] |> String.concat_non_empty "\n"
9191+ [ dest_msg; src_msg ] |> String.concat_non_empty "\n"
9092 in
9193 Cmd
9294 [
···134136 }
135137 ; {
136138 key = 'D'
137137- ; cmd =
138138- Dynamic_r (fun rev -> Cmd_I[ "describe"; "-r"; rev; ])
139139+ ; cmd = Dynamic_r (fun rev -> Cmd_I [ "describe"; "-r"; rev ])
139140 ; description = "Describe this revision using an editor"
140141 }
141142 ; {
···210211 ]
211212 in
212213 let log =
213213- jj_no_log ~get_stderr:true [ "git"; "push"; "--dry-run" ]
214214+ jj_no_log ~get_stderr:true [ "git"; "push"; "--dry-run" ]
214215 |> AnsiReverse.colored_string
215216 |> Ui.atom
216217 |> Lwd.pure
-237
jj_tui/bin/jj_widgets.ml
···1111 open Global_vars
1212 open Jj_process.Make (Vars)
13131414- exception FoundStart
1515- exception FoundFiller
1616-1717- let make_uchar str =
1818- let a = String.get_utf_8_uchar str 0 in
1919- if a |> Uchar.utf_decode_is_valid
2020- then a |> Uchar.utf_decode_uchar
2121- else failwith "not a unicode string"
2222- ;;
2323-2424- let elieded_symbol = make_uchar "◌"
2525- let rev_symbol = make_uchar "◉"
2626- let merge_symbol = make_uchar "◆"
2727-2828- let is_whitespace_char (code_point : int) : bool =
2929- match code_point with
3030- | 0x0009 (* Tab *)
3131- | 0x000A (* Line Feed *)
3232- | 0x000B (* Vertical Tab *)
3333- | 0x000C (* Form Feed *)
3434- | 0x000D (* Carriage Return *)
3535- | 0x0020 (* Space *)
3636- | 0x0085 (* Next Line *)
3737- | 0x00A0 (* No-Break Space *)
3838- | 0x1680 (* Ogham Space Mark *)
3939- | 0x2000 (* En Quad *)
4040- | 0x2001 (* Em Quad *)
4141- | 0x2002 (* En Space *)
4242- | 0x2003 (* Em Space *)
4343- | 0x2004 (* Three-Per-Em Space *)
4444- | 0x2005 (* Four-Per-Em Space *)
4545- | 0x2006 (* Six-Per-Em Space *)
4646- | 0x2007 (* Figure Space *)
4747- | 0x2008 (* Punctuation Space *)
4848- | 0x2009 (* Thin Space *)
4949- | 0x200A (* Hair Space *)
5050- | 0x2028 (* Line Separator *)
5151- | 0x2029 (* Paragraph Separator *)
5252- | 0x202F (* Narrow No-Break Space *)
5353- | 0x205F (* Medium Mathematical Space *)
5454- | 0x3000 (* Ideographic Space *) ->
5555- true
5656- | _ ->
5757- false
5858- ;;
5959-6060- let is_graph_start_char char =
6161- let i = Uchar.to_int char in
6262- (*chars like these: ├─╮*)
6363- let is_pipe = i > 0x2500 && i < 0x259f in
6464- let is_whitespace = is_whitespace_char i in
6565- is_pipe || is_whitespace
6666- ;;
6767-6868- let test_data =
6969- {|◉ yzquvpvl eli.jambu@gmail.com 2024-05-23 15:04:24 3565237c
7070-├─╮ merger
7171-◉ │ wttqrrwo eli.jambu@gmail.com 2024-05-23 14:36:43 ui-update* 7e46fdef
7272-│ │ border_box working
7373-│ │ ◉ skwqmzmt eli.jambu@gmail.com 2024-05-25 01:07:57 7e358c79
7474-│ ├─╯ test old size scaling
7575-│ ◉ nuptyuws eli.jambu@gmail.com 2024-05-22 18:58:31 master 7b156964
7676-│ │ Update README.md
7777-│ ◌ (elided revisions)
7878-│ │ ◉ kmslutyl eli.jambu@gmail.com 2024-05-22 18:07:36 7b10ea4f conflict
7979-│ │ │ (no description set)
8080-│ │ ◉ nqyzyups eli.jambu@gmail.com 2024-05-22 18:07:36 519c664f conflict
8181-│ ├─╯ progress
8282-│ ◉ tutrxvzs eli.jambu@gmail.com 2024-05-22 18:07:36 af8620df
8383-│ │ flakes working
8484-│ ◌ (elided revisions)
8585-│ │ ◉ vlkxvssz eli.jambu@gmail.com 2024-05-15 19:40:28 79fb16f1
8686-│ ├─╯ (no description set)
8787-│ ◉ vtkpsqlr eli.jambu@gmail.com 2024-05-15 16:44:09 1974cc7d
8888-│ │ switch to pkgsStatic
8989-│ ◌ (elided revisions)
9090-│ │ ◉ zpvnoqkm eli.jambu@gmail.com 2024-05-15 09:47:41 c9f95816
9191-├───╯ (no description set)
9292-◉ │ unspmqrw eli.jambu@gmail.com 2024-05-15 09:47:41 83aafe3c
9393-├─╯ update to nottui
9494-│ ◉ smvuxtrv eli.jambu@gmail.com 2024-05-15 09:47:41 d0ce4665
9595-├─╯ (empty) bup
9696-◉ yqytskyk eli.jambu@gmail.com 2024-05-15 09:47:41 0a89ce77
9797-│ test reorganise
9898-◌ (elided revisions)
9999-│ ◉ qpqzkuss eli.jambu@gmail.com 2024-05-15 09:46:18 fdd16b26 conflict
100100-│ │ (no description set)
101101-│ ◉ xpqmtrmp eli.jambu@gmail.com 2024-05-15 09:46:18 555a5355
102102-├─╯ remove old nix file
103103-◉ zxpskuop eli.jambu@gmail.com 2024-05-15 09:46:18 dac0f8bb
104104-│ Update README.md|}
105105- ;;
106106-107107- let root_test = {|◆ zzzzzzzz root() 00000000|}
108108-109109- let rec list_to_pairs lst =
110110- match lst with
111111- | [] | [ _ ] ->
112112- [] (* If list is empty or has only one element, return empty list *)
113113- | x :: y :: rest ->
114114- (x, y) :: list_to_pairs rest
115115- ;;
116116-117117- let rec pairwise f ~f_last lst =
118118- match lst with
119119- | [ single ] ->
120120- f_last single
121121- | x :: y :: rest ->
122122- ((x, y) |> f) :: pairwise f ~f_last rest
123123- | [] ->
124124- [] (* If list is empty or has only one element, return empty list *)
125125- ;;
126126-127127- (*
128128- 1. Make the graph
129129- *)
130130- let is_line_filler line =
131131- if line |> Base.String.is_substring ~substring:"root()"
132132- then raise FoundStart
133133- else
134134- line
135135- (* We will iterate through skipping any chars like pipes and whitespace untill we find either:
136136- a) A rev start char,which would make the line a rev.
137137- b) Nothing, which would make the the line filler
138138- *)
139139- |> String.iteri (fun i char ->
140140- let uchar = String.get_utf_8_uchar line i |> Uchar.utf_decode_uchar in
141141- (*I've removed the part that tries to precisely skip all the start chars. this is becasue it gets all stuffed up by the terminal escape codes
142142- FIXME currently this will get stuffed up if a line has that rev symbol in it
143143- *)
144144- if uchar |> Uchar.equal merge_symbol
145145- || uchar |> Uchar.equal rev_symbol
146146- || char == '@'
147147- then raise FoundStart)
148148- ;;
149149-150150- (** Function to tag duplicated items in a list *)
151151- let tag_duplicates lst =
152152- (* Create a frequency map to count occurrences of each element *)
153153- let freq_map =
154154- List.fold_left
155155- (fun acc { change_id; _ } ->
156156- let count = try List.assoc change_id acc with Not_found -> 0 in
157157- (change_id, count + 1) :: List.remove_assoc change_id acc)
158158- []
159159- lst
160160- in
161161- (* Tag each item in the list based on the frequency map *)
162162- List.map
163163- (fun ({ change_id; _ } as x) ->
164164- if List.assoc change_id freq_map > 1 then Duplicate x else Unique x)
165165- lst
166166- ;;
167167-168168- (**Returns a list of revs with both the change_id and commit_id*)
169169- let get_revs ?revset () =
170170- let revset_arg = match revset with Some revset -> [ "-r"; revset ] | None -> [] in
171171- jj_no_log
172172- ~color:false
173173- ([ "log"; "-T"; {|"|"++change_id++"|"++commit_id++"\n"|} ] @ revset_arg)
174174- |> String.split_on_char '\n'
175175- |> List.filter_map (fun x ->
176176- let items = x |> String.split_on_char '|' in
177177- match items with
178178- | [ _graph; change_id; commit_id ] ->
179179- Some { change_id; commit_id }
180180- | _ ->
181181- None)
182182- |> tag_duplicates
183183- |> Array.of_list
184184- ;;
185185-186186- let find_selectable_from_graph str =
187187- let selectable_count = ref 0 in
188188- let processLine new_list previous_line this_line =
189189- match previous_line with
190190- | Some previous_line ->
191191- selectable_count := !selectable_count + 1;
192192- `Selectable (String.concat "\n" [ previous_line; this_line ]) :: new_list, None
193193- | None ->
194194- (try
195195- is_line_filler this_line;
196196- `Filler this_line :: new_list, None
197197- with
198198- | FoundStart ->
199199- new_list, Some this_line
200200- | FoundFiller ->
201201- `Filler this_line :: new_list, None)
202202- in
203203- let graph =
204204- str
205205- |> String.split_on_char '\n'
206206- (* filter out any lines that contain *)
207207- |> Base.List.fold ~init:([], None) ~f:(fun (new_list, previous) x ->
208208- (*there is generally a final newline and we should just skip that *)
209209- if String.length x = 0
210210- then new_list, previous
211211- else if String.length x <= 1
212212- then `Filler x :: new_list, None
213213- else processLine new_list previous x)
214214- (*the root() commit only has one line and will always be last, so we will try to process the final line*)
215215- |> (fun (list, final_line) ->
216216- match final_line with
217217- | Some line ->
218218- selectable_count := !selectable_count + 1;
219219- `Selectable line :: list
220220- | None ->
221221- list)
222222- |> List.rev
223223- |> Array.of_list
224224- in
225225- !selectable_count, graph
226226- ;;
227227-228228- (* let test= *)
229229- (* let count,graph=find_selectable_from_graph root_test *)
230230- (* in *)
231231- (* if count<=0 then failwith "no process root" *)
232232- (* ;; *)
233233-234234- (** returns the graph and a list of revs within that graph*)
235235- let graph_and_revs ?revset () =
236236- let selectable_count, graph =
237237- let revset_arg = match revset with Some revset -> [ "-r"; revset ] | None -> [] in
238238- let output = jj_no_log ([ "log" ] @ revset_arg) in
239239- output |> find_selectable_from_graph
240240- in
241241- let revs = get_revs ?revset () in
242242- (*The graph should never have selectable items that don't also have a rev*)
243243-244244- (* TODO: remove this becasue it's just for debugging*)
245245- let revs_len = revs |> Array.length in
246246- if selectable_count <> revs_len
247247- then failwith (Printf.sprintf "selectable:%d revs:%d" selectable_count revs_len);
248248- graph, revs
249249- ;;
250250-25114 (*We use this sepcial char as the seperator because it seems very unlikely anyone will ever use it in a branch name:
25215 \u{1c}
25316 *)
+9
jj_tui/lib/process.ml
···11+22+type rev_id = {
33+ change_id : string
44+ ; commit_id : string
55+}
66+77+type 'a maybe_unique =
88+ | Unique of 'a
99+ | Duplicate of 'a
+313
jj_tui/lib/process_wrappers.ml
···11+(** Collection of JJ specific widgets*)
22+33+open Notty
44+open Nottui
55+open Lwd_infix
66+open! Util
77+open Process
88+99+exception FoundStart
1010+exception FoundFiller
1111+1212+let make_uchar str =
1313+ let a = String.get_utf_8_uchar str 0 in
1414+ if a |> Uchar.utf_decode_is_valid
1515+ then a |> Uchar.utf_decode_uchar
1616+ else failwith "not a unicode string"
1717+;;
1818+1919+let elieded_symbol = make_uchar "◌"
2020+let elieded_symbol_alt = make_uchar "○"
2121+let rev_symbol = make_uchar "◉"
2222+let merge_symbol = make_uchar "◆"
2323+2424+let is_whitespace_char (code_point : int) : bool =
2525+ match code_point with
2626+ | 0x0009 (* Tab *)
2727+ | 0x000A (* Line Feed *)
2828+ | 0x000B (* Vertical Tab *)
2929+ | 0x000C (* Form Feed *)
3030+ | 0x000D (* Carriage Return *)
3131+ | 0x0020 (* Space *)
3232+ | 0x0085 (* Next Line *)
3333+ | 0x00A0 (* No-Break Space *)
3434+ | 0x1680 (* Ogham Space Mark *)
3535+ | 0x2000 (* En Quad *)
3636+ | 0x2001 (* Em Quad *)
3737+ | 0x2002 (* En Space *)
3838+ | 0x2003 (* Em Space *)
3939+ | 0x2004 (* Three-Per-Em Space *)
4040+ | 0x2005 (* Four-Per-Em Space *)
4141+ | 0x2006 (* Six-Per-Em Space *)
4242+ | 0x2007 (* Figure Space *)
4343+ | 0x2008 (* Punctuation Space *)
4444+ | 0x2009 (* Thin Space *)
4545+ | 0x200A (* Hair Space *)
4646+ | 0x2028 (* Line Separator *)
4747+ | 0x2029 (* Paragraph Separator *)
4848+ | 0x202F (* Narrow No-Break Space *)
4949+ | 0x205F (* Medium Mathematical Space *)
5050+ | 0x3000 (* Ideographic Space *) ->
5151+ true
5252+ | _ ->
5353+ false
5454+;;
5555+5656+let is_graph_start_char char =
5757+ let i = Uchar.to_int char in
5858+ (*chars like these: ├─╮*)
5959+ let is_pipe = i > 0x2500 && i < 0x259f in
6060+ let is_whitespace = is_whitespace_char i in
6161+ is_pipe || is_whitespace
6262+;;
6363+6464+let rec list_to_pairs lst =
6565+ match lst with
6666+ | [] | [ _ ] ->
6767+ [] (* If list is empty or has only one element, return empty list *)
6868+ | x :: y :: rest ->
6969+ (x, y) :: list_to_pairs rest
7070+;;
7171+7272+let rec pairwise f ~f_last lst =
7373+ match lst with
7474+ | [ single ] ->
7575+ f_last single
7676+ | x :: y :: rest ->
7777+ ((x, y) |> f) :: pairwise f ~f_last rest
7878+ | [] ->
7979+ [] (* If list is empty or has only one element, return empty list *)
8080+;;
8181+8282+(*
8383+ 1. Make the graph
8484+*)
8585+let is_line_filler line =
8686+ if line |> Base.String.is_substring ~substring:"root()"
8787+ then raise FoundStart
8888+ else
8989+ line
9090+ (* We will iterate through skipping any chars like pipes and whitespace untill we find either:
9191+ a) A rev start char,which would make the line a rev.
9292+ b) Nothing, which would make the the line filler
9393+ *)
9494+ |> String.iteri (fun i char ->
9595+ let uchar = String.get_utf_8_uchar line i |> Uchar.utf_decode_uchar in
9696+ (*I've removed the part that tries to precisely skip all the start chars. this is becasue it gets all stuffed up by the terminal escape codes
9797+ FIXME currently this will get stuffed up if a line has that rev symbol in it
9898+ *)
9999+ (* TODO: Overhaul the default graph template to include a special token at the end of selectable revisions *)
100100+ if (uchar |> Uchar.equal merge_symbol
101101+ || uchar |> Uchar.equal rev_symbol
102102+ || uchar |> Uchar.equal elieded_symbol
103103+ || uchar |> Uchar.equal elieded_symbol_alt
104104+ || char == '@')
105105+ && not (line |> Base.String.is_substring ~substring:"(elided revisions)")
106106+ then raise FoundStart)
107107+;;
108108+109109+(** Function to tag duplicated items in a list *)
110110+let tag_duplicates lst =
111111+ (* Create a frequency map to count occurrences of each element *)
112112+ let freq_map =
113113+ List.fold_left
114114+ (fun acc { change_id; _ } ->
115115+ let count = try List.assoc change_id acc with Not_found -> 0 in
116116+ (change_id, count + 1) :: List.remove_assoc change_id acc)
117117+ []
118118+ lst
119119+ in
120120+ (* Tag each item in the list based on the frequency map *)
121121+ List.map
122122+ (fun ({ change_id; _ } as x) ->
123123+ if List.assoc change_id freq_map > 1 then Duplicate x else Unique x)
124124+ lst
125125+;;
126126+127127+let find_selectable_from_graph str =
128128+ let selectable_count = ref 0 in
129129+ let processLine new_list previous_line this_line =
130130+ match previous_line with
131131+ | Some previous_line ->
132132+ selectable_count := !selectable_count + 1;
133133+ `Selectable (String.concat "\n" [ previous_line; this_line ]) :: new_list, None
134134+ | None ->
135135+ (try
136136+ is_line_filler this_line;
137137+ `Filler this_line :: new_list, None
138138+ with
139139+ | FoundStart ->
140140+ new_list, Some this_line
141141+ | FoundFiller ->
142142+ `Filler this_line :: new_list, None)
143143+ in
144144+ let graph =
145145+ str
146146+ |> String.split_on_char '\n'
147147+ (* filter out any lines that contain *)
148148+ |> Base.List.fold ~init:([], None) ~f:(fun (new_list, previous) x ->
149149+ (*there is generally a final newline and we should just skip that *)
150150+ if String.length x = 0
151151+ then new_list, previous
152152+ else if String.length x <= 1
153153+ then `Filler x :: new_list, None
154154+ else processLine new_list previous x)
155155+ (*the root() commit only has one line and will always be last, so we will try to process the final line*)
156156+ |> (fun (list, final_line) ->
157157+ match final_line with
158158+ | Some line ->
159159+ selectable_count := !selectable_count + 1;
160160+ `Selectable line :: list
161161+ | None ->
162162+ list)
163163+ |> List.rev
164164+ |> Array.of_list
165165+ in
166166+ !selectable_count, graph
167167+;;
168168+169169+(** retrieve revs from jj log of jj_tui*)
170170+let revs_from_log log =
171171+ log
172172+ |> String.split_on_char '\n'
173173+ |> List.filter_map (fun x ->
174174+ let items = x |> String.split_on_char '|' in
175175+ match items with
176176+ | [ _graph; change_id; commit_id ] ->
177177+ Some { change_id; commit_id }
178178+ | _ ->
179179+ None)
180180+ |> tag_duplicates
181181+ |> Array.of_list
182182+;;
183183+184184+module Make (Process : sig
185185+ val jj_no_log :
186186+ ?get_stderr:bool
187187+ -> ?snapshot:bool
188188+ -> ?color:bool
189189+ -> string list
190190+ -> string
191191+ end) =
192192+struct
193193+ open Process
194194+195195+ (**Returns a list of revs with both the change_id and commit_id*)
196196+ let get_revs ?revset () =
197197+ let revset_arg = match revset with Some revset -> [ "-r"; revset ] | None -> [] in
198198+ jj_no_log
199199+ ~color:false
200200+ ([ "log"; "-T"; {|"|"++change_id++"|"++commit_id++"\n"|} ] @ revset_arg)
201201+ |> revs_from_log
202202+ ;;
203203+204204+ (* let test= *)
205205+ (* let count,graph=find_selectable_from_graph root_test *)
206206+ (* in *)
207207+ (* if count<=0 then failwith "no process root" *)
208208+ (* ;; *)
209209+210210+ (** returns the graph and a list of revs within that graph*)
211211+ let graph_and_revs ?revset () =
212212+ let selectable_count, graph =
213213+ let revset_arg = match revset with Some revset -> [ "-r"; revset ] | None -> [] in
214214+ let output = jj_no_log ([ "log" ] @ revset_arg) in
215215+ output |> find_selectable_from_graph
216216+ in
217217+ let revs = get_revs ?revset () in
218218+ (*The graph should never have selectable items that don't also have a rev*)
219219+220220+ (* TODO: remove this becasue it's just for debugging*)
221221+ let revs_len = revs |> Array.length in
222222+ if selectable_count <> revs_len
223223+ then failwith (Printf.sprintf "selectable:%d revs:%d" selectable_count revs_len);
224224+ graph, revs
225225+ ;;
226226+end
227227+228228+(*========Tests======*)
229229+230230+let test_data =
231231+ {|◉ yzquvpvl eli.jambu@gmail.com 2024-05-23 15:04:24 3565237c
232232+├─╮ merger
233233+◉ │ wttqrrwo eli.jambu@gmail.com 2024-05-23 14:36:43 ui-update* 7e46fdef
234234+│ │ border_box working
235235+│ │ ◉ skwqmzmt eli.jambu@gmail.com 2024-05-25 01:07:57 7e358c79
236236+│ ├─╯ test old size scaling
237237+│ ◉ nuptyuws eli.jambu@gmail.com 2024-05-22 18:58:31 master 7b156964
238238+│ │ Update README.md
239239+│ ◌ (elided revisions)
240240+│ │ ◉ kmslutyl eli.jambu@gmail.com 2024-05-22 18:07:36 7b10ea4f conflict
241241+│ │ │ (no description set)
242242+│ │ ◉ nqyzyups eli.jambu@gmail.com 2024-05-22 18:07:36 519c664f conflict
243243+│ ├─╯ progress
244244+│ ◉ tutrxvzs eli.jambu@gmail.com 2024-05-22 18:07:36 af8620df
245245+│ │ flakes working
246246+│ ◌ (elided revisions)
247247+│ │ ◉ vlkxvssz eli.jambu@gmail.com 2024-05-15 19:40:28 79fb16f1
248248+│ ├─╯ (no description set)
249249+│ ◉ vtkpsqlr eli.jambu@gmail.com 2024-05-15 16:44:09 1974cc7d
250250+│ │ switch to pkgsStatic
251251+│ ◌ (elided revisions)
252252+│ │ ◉ zpvnoqkm eli.jambu@gmail.com 2024-05-15 09:47:41 c9f95816
253253+├───╯ (no description set)
254254+◉ │ unspmqrw eli.jambu@gmail.com 2024-05-15 09:47:41 83aafe3c
255255+├─╯ update to nottui
256256+│ ◉ smvuxtrv eli.jambu@gmail.com 2024-05-15 09:47:41 d0ce4665
257257+├─╯ (empty) bup
258258+◉ yqytskyk eli.jambu@gmail.com 2024-05-15 09:47:41 0a89ce77
259259+│ test reorganise
260260+◌ (elided revisions)
261261+│ ◉ qpqzkuss eli.jambu@gmail.com 2024-05-15 09:46:18 fdd16b26 conflict
262262+│ │ (no description set)
263263+│ ◉ xpqmtrmp eli.jambu@gmail.com 2024-05-15 09:46:18 555a5355
264264+├─╯ remove old nix file
265265+◉ zxpskuop eli.jambu@gmail.com 2024-05-15 09:46:18 dac0f8bb
266266+│ Update README.md|}
267267+;;
268268+269269+let root_test = {|◆ zzzzzzzz root() 00000000|}
270270+271271+let test_data_2 =
272272+ {|@ qpnrvwyl ethanboxx 13 seconds ago 48829167
273273+│ (no description set)
274274+◆ pqvkrmkw ethanboxx 3 months ago main v0.1.10 HEAD@git 931019c4
275275+│ (empty) Merge pull request #63 from eopb/release-plz-2024-05-11T09-14-47Z
276276+~ (elided revisions)
277277+│ ○ xnyvmlur ethanboxx 5 months ago push-znvwmrtqnnlq 0deaa0aa
278278+├─┘ feat: impl `Deref` and `DerefMut` without exposing `Secret`
279279+◆ kkzuqwxo ethanboxx 5 months ago 9aa340cc
280280+│ (empty) Merge pull request #56 from eopb/push-smksztlxprww
281281+~|}
282282+;;
283283+284284+let%expect_test "revs_graph_parsing" =
285285+ let selectable, graph = find_selectable_from_graph test_data_2 in
286286+ graph
287287+ |> Array.iter (fun x ->
288288+ match x with
289289+ | `Filler x ->
290290+ "F:" |> print_endline;
291291+ x |> print_endline
292292+ | `Selectable x ->
293293+ "S:" |> print_endline;
294294+ x |> print_endline);
295295+ [%expect
296296+ {|
297297+ S:
298298+ @ qpnrvwyl ethanboxx 13 seconds ago 48829167
299299+ │ (no description set)
300300+ S:
301301+ ◆ pqvkrmkw ethanboxx 3 months ago main v0.1.10 HEAD@git 931019c4
302302+ │ (empty) Merge pull request #63 from eopb/release-plz-2024-05-11T09-14-47Z
303303+ F:
304304+ ~ (elided revisions)
305305+ S:
306306+ │ ○ xnyvmlur ethanboxx 5 months ago push-znvwmrtqnnlq 0deaa0aa
307307+ ├─┘ feat: impl `Deref` and `DerefMut` without exposing `Secret`
308308+ S:
309309+ ◆ kkzuqwxo ethanboxx 5 months ago 9aa340cc
310310+ │ (empty) Merge pull request #56 from eopb/push-smksztlxprww
311311+ F:
312312+ ~ |}]
313313+;;