Skip to content

kurtharriger/clj-mergetool

Repository files navigation

clj-mergetool

A git diff and merge tool for edn and clojure code. Semantic diffs for conflict free merges.

Overview

A git diff and merge tool for edn and clojure code. Semantic diffs for conflict free merges.

For example given the following deps.edn

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/core.async {:mvn/version "1.3.610"}}}

One user adds a dependency:

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/core.async {:mvn/version "1.3.610"}
        clj-http {:mvn/version "3.11.0"}}}

Another user upgrades a dependency:

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
<<<<<<< HEAD
        org.clojure/core.async {:mvn/version "1.3.612"}}}
||||||| add178e
        org.clojure/core.async {:mvn/version "1.3.610"}}}
=======
        org.clojure/core.async {:mvn/version "1.3.610"}
        clj-http {:mvn/version "3.11.0"}}}
>>>>>>> right

Git line diff does not recognize these as logically independant changes. Also manually merging this change requires extra attention to the closing brackets.

What does clj mergetool do differently?

clj mergetool diffs the data structures rather then the lines. (currently using editscript here, but this is likely to change)

The left diff might be represented as a replacement of the value at a given path (eg assoc-in)

[[[:deps org.clojure/core.async :mvn/version] :r "1.3.612"]]

The right diff adds a new entry

[[[:deps clj-http] :+ {:mvn/version "3.11.0"}]]

Combined:

[[[:deps org.clojure/core.async :mvn/version] :r "1.3.612"] [[:deps clj-http] :+ {:mvn/version "3.11.0"}]]

The resulting patch is conflict free:

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/core.async {:mvn/version "1.3.612"}
        clj-http {:mvn/version "3.11.0"}}}

Installation

git clone  --recurse-submodules --depth 1 https://github.com/kurtharriger/clj-mergetool.git clj-mergetool
cd clj-mergetool
clj -T:build install

Note: If you have GraalVM installed (when native-image is in your PATH), install will attempt to build a native-image instead of an executable jar file which has much faster startup time than running jar file.

I use sdkman to manage multiple versions of java.

curl -s "https://get.sdkman.io" | bash
sdk install java 22-graal
sdk use java 22-graal
clj -T:build install

Usage

When you encounter a git conflict you can invoke this tool to attempt to automatically resolve the conflicts. This tool will fetch the ancestor current and other version from the git index and attempt to remerge them. If files is not specified all unmerged files in the index will be remerged.

clj-mergetool remerge [files...]

Git mergetool

Although not yet recommended, this tool can be automatically invoked by git merge.

The merge tool needs to be configured in both .git/config or ~/.gitconfig and .gitattributes or ~/.gitattributes.

.gitattributes is typically added to source control however .git/config unfortunatly cannot be included in source control

git will use default behavior without warning if mergetool is refrenced in .gitattributes but not installed in .git/config


git config --local "merge.clj-mergetool.driver" "clj-mergetool mergetool %O %A %B"

cat <<END >> .gitattributes
*.clj merge=clj-mergetool
*.cljs merge=clj-mergetool
*.edn merge=clj-mergetool
END

See also https://github.com/Praqma/git-merge-driver/blob/master/.gitconfig

Status

Initial funding for this project provided by ClojuristsTogether!

Share your merge conflicts

I would love to get any example merge conflicts you have encountered in practice on open source code that I can use in my unit tests during development.

Please open an issue with an example or any other feedback you may have.

Known Issues

  • limited testing on real examples (See above)

  • missing and/or duplicate whitespace before insert/replace point

  • Untested on Windows

    Probably works, but assumes linux conventions (eg installs to ~/.local/bin) Let me know if you have issues.

Roadmap

  • explore alternative diff representation
  • improve conflict detection
  • simplify installation with prebuilt binaries
  • improve documentation
  • improve diff visualization
  • explore detection and respresentation of higher order refactorings, such as: rename symbol, align forms, sort keys

License

Copyright © 2024 Kurt Harriger

Distributed under the Eclipse Public License version 1.0.