-
Notifications
You must be signed in to change notification settings - Fork 1
Add optional git pre-commit hooks for cljfmt & clj-kondo #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cap10morgan
merged 1 commit into
main
from
01-16-Add_optional_git_pre-commit_hooks_for_cljfmt_clj-kondo
Jan 18, 2024
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{:paths ["bb"] | ||
:pods {clj-kondo/clj-kondo {:version "2023.12.15"}} | ||
:deps {dev.weavejester/cljfmt {:git/url "https://github.com/cap10morgan/cljfmt.git" | ||
:git/sha "acc6a7d1e5a7e3391419ac731bf8a2774b8c4a13"}} | ||
:tasks {git-hooks {:requires ([git-hooks :as gh]) | ||
:task (apply gh/hook *command-line-args*)}}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
(ns git-hooks | ||
(:require [babashka.fs :as fs] | ||
[clojure.edn :as edn] | ||
[clojure.string :as str] | ||
[lib.clj-kondo :as clj-kondo] | ||
[lib.cljfmt :as cljfmt] | ||
[lib.git :as git]) | ||
(:import (java.util Date))) | ||
|
||
;; originally inspired by https://blaster.ai/blog/posts/manage-git-hooks-w-babashka.html | ||
|
||
;; installation | ||
|
||
(defn hook-script | ||
[hook] | ||
(format "#!/bin/sh | ||
# Installed by babashka task on %s | ||
|
||
bb git-hooks %s" (Date.) hook)) | ||
|
||
(defn spit-hook | ||
[hook] | ||
(println "Installing git hook:" hook) | ||
(let [file (str ".git/hooks/" hook)] | ||
(spit file (hook-script hook)) | ||
(fs/set-posix-file-permissions file "rwx------") | ||
(assert (fs/executable? file)))) | ||
|
||
(defmulti install-hook (fn [& args] (-> args first keyword))) | ||
|
||
(defmethod install-hook :pre-commit | ||
[& _] | ||
(spit-hook "pre-commit")) | ||
|
||
(defmethod install-hook :default | ||
[& args] | ||
(println "Unknown git hook:" (first args))) | ||
|
||
;; checks | ||
|
||
(defn- check-enabled? | ||
[check] | ||
(let [skip-checks (System/getenv "SKIP_CHECKS") | ||
skip-checks-coll (when skip-checks | ||
(edn/read-string | ||
(if (str/starts-with? skip-checks "[") | ||
skip-checks | ||
(str "[" skip-checks "]")))) | ||
disabled-checks (into #{} (map keyword) skip-checks-coll)] | ||
(not (contains? disabled-checks check)))) | ||
|
||
(defmulti check (fn [& args] (-> args first keyword))) | ||
|
||
(defmethod check :cljfmt | ||
[_ staging-dir] | ||
(let [bad-files (cljfmt/check staging-dir)] | ||
(when (and (check-enabled? :cljfmt) (seq bad-files)) | ||
(println) | ||
(println (let [bfc (count bad-files)] | ||
(str bfc " " (if (= 1 bfc) "file is" "files are") | ||
" formatted incorrectly:\n" | ||
(str/join "\n" bad-files) | ||
"\n"))) | ||
(println "Run: cljfmt fix" (str/join " " bad-files)) | ||
(println " git add" (str/join " " bad-files)) | ||
(println) | ||
(System/exit 1)))) | ||
|
||
(defmethod check :clj-kondo | ||
[_ staging-dir] | ||
(let [bad-files (clj-kondo/lint staging-dir)] | ||
(when (and (check-enabled? :clj-kondo) (seq bad-files)) | ||
(println) | ||
(println "Fix errors and run: git add" (str/join " " bad-files)) | ||
(System/exit 2)))) | ||
|
||
;; hooks | ||
|
||
(defmulti hook (fn [& args] (-> args first keyword))) | ||
|
||
(defmethod hook :install | ||
[& _] | ||
(install-hook :pre-commit)) | ||
|
||
(defmethod hook :pre-commit | ||
[& _] | ||
(println "Running pre-commit hook") | ||
(when-let [files (git/changed-files)] | ||
(println "Found" (count files) "changed files\n") | ||
(let [staging-dir (git/staged-contents files)] | ||
(check :cljfmt staging-dir) | ||
(check :clj-kondo staging-dir)) | ||
(println) | ||
(println "🤘 Commit looks good! 🤘"))) | ||
|
||
(defmethod hook :default | ||
[& args] | ||
(println "Unknown git hook:" (first args))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
(ns lib.clj-kondo | ||
(:require [clojure.java.io :as io] | ||
[clojure.string :as str] | ||
[lib.path :as path] | ||
[pod.borkdude.clj-kondo :as clj-kondo])) | ||
|
||
(def linted-paths | ||
["src" "test" "bb" "build.clj"]) | ||
|
||
(defn lint | ||
"Runs clj-kondo --lint on all files (recursively) in dir. Returns a collection | ||
of any files that failed the check." | ||
[dir] | ||
(let [lint-paths (map #(str dir "/" %) | ||
(filter #(->> % (io/file dir) .exists) linted-paths)) | ||
result (clj-kondo/run! {:lint lint-paths}) | ||
{{:keys [error warning]} :summary} result] | ||
(if (and (zero? error) (zero? warning)) | ||
[] | ||
(let [->proj-path (partial path/tmp->project-rel | ||
(if (str/ends-with? dir "/") dir (str dir "/"))) | ||
bad-files (->> result :findings | ||
(map #(update % :filename ->proj-path)))] | ||
(println "clj-kondo lint:") | ||
(doseq [{:keys [filename level message row end-row col end-col] :as _bf} | ||
bad-files] | ||
(let [row-desc (if (not= row end-row) | ||
(str row "-" end-row) | ||
(str row)) | ||
col-desc (if (not= col end-col) | ||
(str col "-" end-col) | ||
(str col))] | ||
(println (str filename ":" row-desc ":" col-desc ":" level) | ||
"-" message))) | ||
(map :filename bad-files))))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
(ns lib.cljfmt | ||
(:require [cljfmt.lib :as fmt] | ||
[clojure.string :as str] | ||
[lib.path :as path])) | ||
|
||
(defn check | ||
"Runs cljfmt check on all files (recursively) in dir. Returns a collection of | ||
any files that failed the check." | ||
[dir] | ||
(let [cljfmt-opts {:paths [dir], :diff? false} | ||
{:keys [counts incorrect error] :as _result} (fmt/check cljfmt-opts)] | ||
#_(println "cljfmt results:" (pr-str result)) | ||
(if (and (zero? (:incorrect counts)) (zero? (:error counts))) | ||
[] | ||
(let [->proj-path (partial path/tmp->project-rel | ||
(if (str/ends-with? dir "/") | ||
dir | ||
(str dir "/")))] | ||
(concat (map (fn [[file]] (->proj-path file)) incorrect) | ||
(map (fn [[file]] (->proj-path file)) error)))))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
(ns lib.git | ||
(:require [babashka.fs :as fs] | ||
[clojure.java.shell :refer [sh]] | ||
[clojure.string :as str])) | ||
|
||
(defn changed-files | ||
"Returns the filenames of all changed files currently staged in git. | ||
NB: This is not the same as what will be committed. Git allows parts of files | ||
to be staged while other parts are left un-staged. So the content of these files | ||
in your local working copy won't always be the same as what would be committed | ||
from that index. You probably want to pass the return value of this fn to the | ||
`staged-contents` fn before running any hooks." | ||
[] | ||
(->> (sh "git" "diff" "--cached" "--name-only" "--diff-filter=ACM") | ||
:out | ||
str/split-lines | ||
(filter seq) | ||
seq)) | ||
|
||
(defn staged-contents | ||
"Returns the path to a temp dir with the staged contents of the files | ||
argument. This is necessary because git allows partial staging of files." | ||
[files] | ||
(let [tmp-dir (str (fs/create-temp-dir))] | ||
#_(println "staging dir:" tmp-dir) | ||
(sh "git" "checkout-index" (str "--prefix=" tmp-dir "/") "--stdin" | ||
:in (str/join "\n" files)) | ||
tmp-dir)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
(ns lib.path | ||
(:require [clojure.string :as str])) | ||
|
||
(defn tmp->project-rel | ||
"Takes a temp root dir and full temp path and removes the temp root prefix so | ||
that the returned path is relative to the project." | ||
[tmp-root tmp-path] | ||
(-> tmp-path | ||
str/join | ||
(str/replace "../" "") | ||
(str/replace #"^[^/]" "/") | ||
(str/replace-first tmp-root ""))) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I'd need to do for this to work going forwards, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, or perhaps more properly
bb run git-hooks install
but this should work too.