···5353 | File_preview (rev, file) ->
5454 let command =
5555 if file != ""
5656- then [ "diff"; "--summary"; "-r"; rev; file ]
5656+ then Jj_cli.with_files [ "diff"; "--summary"; "-r"; rev ] [ file ]
5757 else [ "show"; "--summary"; "-r"; rev ]
5858 in
5959 let log = jj_no_log command in
···7272 let render_detail = function
7373 | File_preview (rev, file) ->
7474 let command =
7575- if file != "" then [ "diff"; "-r"; rev; file ] else [ "show"; "-r"; rev ]
7575+ if file != ""
7676+ then Jj_cli.with_files [ "diff"; "-r"; rev ] [ file ]
7777+ else [ "show"; "-r"; rev ]
7678 in
7779 let log = jj_no_log command in
7880 Control.yield ();
+16
jj_tui/lib/jj_cli.ml
···11+(** Utilities for constructing jj command-line argument lists.
22+33+ jj is invoked via [Unix.create_process_env] (direct argv), not through a
44+ shell. That means wrapping file paths in single-quotes would pass the
55+ quote characters as literal bytes to jj — wrong. The POSIX end-of-options
66+ sentinel [--] is the correct mechanism: it tells jj that every subsequent
77+ argument is a file path, not a flag. *)
88+99+(** [with_files base_args paths] appends [--] followed by every non-empty
1010+ string in [paths] to [base_args]. Empty strings are silently dropped so
1111+ callers need not pre-filter placeholder values. When all paths are empty
1212+ the list is returned unchanged (no spurious [--] emitted). *)
1313+let with_files base_args paths =
1414+ match List.filter (fun p -> p <> "") paths with
1515+ | [] -> base_args
1616+ | real_paths -> base_args @ [ "--" ] @ real_paths
+40
jj_tui/lib/jj_cli_tests.ml
···11+open Jj_cli
22+33+(* Display args separated by | so spaces inside an argument are visible and
44+ distinct from argument boundaries. *)
55+let show args = print_string (String.concat "|" args); print_newline ()
66+77+let%expect_test "with_files: empty path list yields no separator" =
88+ show (with_files [ "diff"; "-r"; "abc" ] []);
99+ [%expect {| diff|-r|abc |}]
1010+;;
1111+1212+let%expect_test "with_files: all-empty strings yield no separator" =
1313+ show (with_files [ "diff" ] [ ""; "" ]);
1414+ [%expect {| diff |}]
1515+;;
1616+1717+let%expect_test "with_files: single path" =
1818+ show (with_files [ "diff"; "-r"; "abc" ] [ "src/foo.ml" ]);
1919+ [%expect {| diff|-r|abc|--|src/foo.ml |}]
2020+;;
2121+2222+let%expect_test "with_files: multiple paths remain separate argv entries" =
2323+ show (with_files [ "restore"; "--to"; "abc" ] [ "a/b.ml"; "c/d.ml" ]);
2424+ [%expect {| restore|--to|abc|--|a/b.ml|c/d.ml |}]
2525+;;
2626+2727+let%expect_test "with_files: path with spaces is a single argv entry" =
2828+ show (with_files [ "diff" ] [ "my file.txt" ]);
2929+ [%expect {| diff|--|my file.txt |}]
3030+;;
3131+3232+let%expect_test "with_files: path with leading dash is protected by separator" =
3333+ show (with_files [ "diff" ] [ "-not-a-flag.txt" ]);
3434+ [%expect {| diff|--|-not-a-flag.txt |}]
3535+;;
3636+3737+let%expect_test "with_files: empty strings mixed with real paths are dropped" =
3838+ show (with_files [ "diff" ] [ ""; "real.ml"; "" ]);
3939+ [%expect {| diff|--|real.ml |}]
4040+;;