A git diff and merge tool for edn and clojure code. Semantic diffs for conflict free merges.
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"}}}
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
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...]
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
Initial funding for this project provided by ClojuristsTogether!
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.
-
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.
- 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
Copyright © 2024 Kurt Harriger
Distributed under the Eclipse Public License version 1.0.