diff --git a/CHANGES.md b/CHANGES.md index 1b730a0..a259183 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## Unreleased + +- Add support of dune formatter. (#3) + ## 0.1 (2019-02-19) -- Initial release. \ No newline at end of file +- Initial release. diff --git a/README.md b/README.md index 75f223a..ef4c2a6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ code formatters. `merge-fmt` currently only knows about the following formatters: - [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) for OCaml. - [refmt](https://github.com/facebook/reason) for reason. +- [dune](https://github.com/ocaml/dune) for dune. Note that supporting new code formatters is trivial. @@ -47,4 +48,4 @@ Install ------- ```sh $ opam pin add merge-fmt git@github.com:hhugo/merge-fmt.git -``` \ No newline at end of file +``` diff --git a/merge-fmt-help.txt b/merge-fmt-help.txt index b142c1f..c93030c 100644 --- a/merge-fmt-help.txt +++ b/merge-fmt-help.txt @@ -17,6 +17,9 @@ COMMANDS Register the [merge-fmt] mergetool in git OPTIONS + --dune=VAL + dune path + --echo Echo all commands. diff --git a/merge-fmt-mergetool-help.txt b/merge-fmt-mergetool-help.txt index 2ef1e7a..c85055a 100644 --- a/merge-fmt-mergetool-help.txt +++ b/merge-fmt-mergetool-help.txt @@ -9,6 +9,9 @@ OPTIONS --current= + --dune=VAL + dune path + --echo Echo all commands. diff --git a/src/fmters.ml b/src/fmters.ml index ef4f092..e190f4b 100644 --- a/src/fmters.ml +++ b/src/fmters.ml @@ -4,26 +4,62 @@ open Common type config = { ocamlformat_path : string option ; refmt_path : string option + ; dune_path : string option } -type t = string +type t = + | Inplace of string + | Stdout of string + +let transfer ic oc = + let b = Bytes.create 4096 in + let rec loop () = + match Stdlib.input ic b 0 (Bytes.length b) with + | 0 -> () + | l -> + Stdlib.output oc b 0 l; + loop () + in + loop () let ocamlformat ~bin ~name = - sprintf "%s -i %s" - (Option.value ~default:"ocamlformat" bin) - (Option.value_map ~default:"" ~f:(fun name -> " --name=" ^ name) name) + Inplace + (sprintf "%s -i %s" + (Option.value ~default:"ocamlformat" bin) + (Option.value_map ~default:"" ~f:(fun name -> " --name=" ^ name) name)) + +let refmt ~bin = + Inplace (sprintf "%s --inplace" (Option.value ~default:"refmt" bin)) -let refmt ~bin = sprintf "%s --inplace" (Option.value ~default:"refmt" bin) +let dune ~bin = + Stdout (sprintf "%s format-dune-file --" (Option.value ~default:"dune" bin)) let find ~config ~filename ~name = let filename = Option.value ~default:filename name in - match (Caml.Filename.extension filename, config) with - | (".ml" | ".mli"), { ocamlformat_path; _ } -> + match (filename, Caml.Filename.extension filename, config) with + | _, (".ml" | ".mli"), { ocamlformat_path; _ } -> Some (ocamlformat ~bin:ocamlformat_path ~name) - | (".re" | ".rei"), { refmt_path; _ } -> Some (refmt ~bin:refmt_path) + | _, (".re" | ".rei"), { refmt_path; _ } -> Some (refmt ~bin:refmt_path) + | ("dune" | "dune-project" | "dune-workspace"), "", { dune_path; _ } -> + Some (dune ~bin:dune_path) | _ -> None -let run t ~echo ~filename = system ~echo "%s %s" t filename +let run t ~echo ~filename = + match t with + | Inplace t -> system ~echo "%s %s" t filename + | Stdout t -> ( + let ic = open_process_in ~echo "%s %s" t filename in + let tmp_file, oc = Stdlib.Filename.open_temp_file "merge-fmt" "stdout" in + transfer ic oc; + Stdlib.close_out oc; + match Unix.close_process_in ic with + | WEXITED 0 -> + Stdlib.Sys.rename tmp_file filename; + Ok () + | WEXITED n -> + Stdlib.Printf.eprintf ">>> Exit with %d\n" n; + Error () + | WSIGNALED _ | WSTOPPED _ -> Error ()) module Flags = struct open Cmdliner @@ -36,9 +72,13 @@ module Flags = struct let doc = "refmt path" in Arg.(value & opt (some string) None & info [ "refmt" ] ~doc) + let dune_path = + let doc = "dune path" in + Arg.(value & opt (some string) None & info [ "dune" ] ~doc) + let t = Term.( - const (fun ocamlformat_path refmt_path -> - { ocamlformat_path; refmt_path }) - $ ocamlformat_path $ refmt_path) + const (fun ocamlformat_path refmt_path dune_path -> + { ocamlformat_path; refmt_path; dune_path }) + $ ocamlformat_path $ refmt_path $ dune_path) end diff --git a/src/resolve_cmd.ml b/src/resolve_cmd.ml index a8460b3..bd7f5e3 100644 --- a/src/resolve_cmd.ml +++ b/src/resolve_cmd.ml @@ -67,8 +67,10 @@ let show ~echo version versions = let create_tmp ~echo fn version versions = let content = show ~echo version versions in - let ext = Caml.Filename.extension fn - and base = Caml.Filename.chop_extension fn in + let ext = Caml.Filename.extension fn in + let base = + if String.equal ext "" then fn else Caml.Filename.chop_extension fn + in let fn' = sprintf "%s.%s%s" base (string_of_version version) ext in let oc = Out_channel.create fn' in Out_channel.output_string oc content; @@ -123,7 +125,9 @@ let resolve config echo () = ~f:(fun () -> git_add ~echo ~filename) |> (ignore : (unit, unit) Result.t -> unit); let n2 = conflict ~filename in - eprintf "Resolved %d/%d %s\n%!" (n1 - n2) n1 filename + if n2 > n1 + then eprintf "Resolved ?? %s\n%!" filename + else eprintf "Resolved %d/%d %s\n%!" (n1 - n2) n1 filename | None -> eprintf "Ignore %s (no formatter register)\n%!" filename) | Error reason -> eprintf "Ignore %s (%s)\n%!" filename reason); let all = ls ~echo () in diff --git a/test/merge_dune.ml b/test/merge_dune.ml new file mode 100644 index 0000000..5dbdecf --- /dev/null +++ b/test/merge_dune.ml @@ -0,0 +1,177 @@ +open! Base +open! Stdio +open! Common + +let%expect_test "default merge tool" = + within_temp_dir (fun () -> + git_init (); + + write "dune" {| +(executable +(name aaaa) +(libraries unix)) +|}; + git_commit "first commit"; + git_branch "branch1"; + + (* add public name *) + if false + then ( + write "dune" + {| +(executable +(name aaaa) +(libraries unix) +(public_name pppp)) +|}; + git_commit "second commit"); + + (* add library *) + write "dune" + {| +(executable +(name aaaa) +(libraries unix)) + +(library +(name liblib)) +|}; + git_commit "third commit"; + git_branch "branch2"; + + git_checkout "branch1"; + [%expect {| Switched to branch 'branch1' |}]; + (* change name to b, reformat *) + write "dune" {| +(executable + (name bbbb) + (libraries unix)) +|}; + git_commit "second commit (fork)"; + + write "dune" + {| +(alias + (name runtest) + (action + (diff rebase.diff rebase.diff.gen))) + +(executable + (name bbbb) + (libraries unix)) +|}; + + git_commit "third commit (fork)"; + git_branch "old_branch1"; + system "git rebase branch2 -q"; + [%expect + {| + Auto-merging dune + CONFLICT (content): Merge conflict in dune + error: could not apply 2e00bc1... second commit (fork) + hint: Resolve all conflicts manually, mark them as resolved with + hint: "git add/rm ", then run "git rebase --continue". + hint: You can instead skip this commit: run "git rebase --skip". + hint: To abort and get back to the state before "git rebase", run "git rebase --abort". + Could not apply 2e00bc1... second commit (fork) + Exit with 1 |}]; + print_file "dune"; + [%expect + {| + File dune + + (executable + <<<<<<< HEAD + (name aaaa) + (libraries unix)) + + (library + (name liblib)) + ======= + (name bbbb) + (libraries unix)) + >>>>>>> 2e00bc1 (second commit (fork)) |}]) + +let%expect_test "custom merge tool" = + within_temp_dir (fun () -> + git_init (); + system "%s setup-merge --merge-fmt-path %s --update" merge_fmt merge_fmt; + [%expect {||}]; + + write "dune" {| +(executable +(name aaaa) +(libraries unix)) +|}; + git_commit "first commit"; + git_branch "branch1"; + + (* add public name *) + if false + then ( + write "dune" + {| +(executable +(name aaaa) +(libraries unix) +(public_name pppp)) +|}; + git_commit "second commit"); + + (* add library *) + write "dune" + {| +(executable +(name aaaa) +(libraries unix)) + +(library +(name liblib)) +|}; + git_commit "third commit"; + git_branch "branch2"; + + git_checkout "branch1"; + [%expect {| Switched to branch 'branch1' |}]; + (* change name to b, reformat *) + write "dune" {| +(executable + (name bbbb) + (libraries unix)) +|}; + git_commit "second commit (fork)"; + + write "dune" + {| +(alias + (name runtest) + (action + (diff rebase.diff rebase.diff.gen))) + +(executable + (name bbbb) + (libraries unix)) +|}; + git_commit "third commit (fork)"; + git_branch "old_branch1"; + + system "git rebase branch2 -q"; + [%expect {| |}]; + system "%s" merge_fmt; + print_file "dune"; + [%expect + {| + Nothing to resolve + Exit with 1 + File dune + (alias + (name runtest) + (action + (diff rebase.diff rebase.diff.gen))) + + (executable + (name bbbb) + (libraries unix)) + + (library + (name liblib)) |}]) diff --git a/test/rebase.diff b/test/rebase.diff index 1884675..c9c9764 100644 --- a/test/rebase.diff +++ b/test/rebase.diff @@ -28,19 +28,20 @@ --- > } > >>>>>>> 6070a8f (second commit (fork)) |}]; -59,62c69 +59,62c69,70 < [%expect < {| < Ignore a.ml (not a 3-way merge) < Exit with 1 |}]; --- -> [%expect {| Resolved 1/1 b.ml |}]; -66,67c73 +> [%expect {| +> Resolved 1/1 b.ml |}]; +66,67c74 < DU File a.ml < --- > M File b.ml -69,71c75,78 +69,71c76,79 < { a : int option; < b : string; < c : float; @@ -49,7 +50,7 @@ > ; b : string > ; c : float > ; d : unit option -76,79c83,84 +76,79c84,85 < a.ml: needs merge < You must edit all merge conflicts and then < mark them as resolved using git add diff --git a/test/rebase_b.ml b/test/rebase_b.ml index c1fa608..e6ec9fc 100644 --- a/test/rebase_b.ml +++ b/test/rebase_b.ml @@ -66,7 +66,8 @@ type t = } >>>>>>> 6070a8f (second commit (fork)) |}]; resolve (); - [%expect {| Resolved 1/1 b.ml |}]; + [%expect {| + Resolved 1/1 b.ml |}]; print_status (); [%expect {| diff --git a/test/resolve1.ml b/test/resolve1.ml index ee3ce93..a039386 100644 --- a/test/resolve1.ml +++ b/test/resolve1.ml @@ -149,7 +149,8 @@ type t = | A | B of int |}]; resolve (); - [%expect {| Resolved 1/1 b.ml |}]; + [%expect {| + Resolved 1/1 b.ml |}]; print_status (); [%expect {| diff --git a/test/resolve2.ml b/test/resolve2.ml index fde9ec3..742b544 100644 --- a/test/resolve2.ml +++ b/test/resolve2.ml @@ -69,7 +69,8 @@ type t = } >>>>>>> ead71ee (second commit (fork)):a.ml |}]; resolve (); - [%expect {| Resolved 1/1 b.ml |}]; + [%expect {| + Resolved 1/1 b.ml |}]; print_status (); [%expect {|