commitly/commitly
2026-01-05 02:13:30 -05:00

117 lines
4.5 KiB
Clojure
Executable File

#!/usr/bin/env bb
(ns commitly.core
(:require [babashka.process :as process]
[babashka.fs :as fs]
[clojure.string :as str]))
(defn git-repo? [dir]
"Check if directory is a git repository"
(fs/exists? (fs/path dir ".git")))
(defn has-changes? [repo-path]
"Check if git repository has uncommitted changes"
(let [result (process/shell {:dir (str repo-path)
:out :string
:continue true}
"git status --porcelain")]
(not (str/blank? (:out result)))))
(defn find-subrepos [parent-dir]
"Find all git repositories in parent directory"
(->> (fs/list-dir parent-dir)
(filter fs/directory?)
(filter git-repo?)
(map str)))
(defn commit-changes [repo-path message]
"Commit all changes in repository with given message"
(try
(let [add-result (process/shell {:dir repo-path
:out :string
:err :string
:continue true}
"git add -A")
commit-result (process/shell {:dir repo-path
:out :string
:err :string
:continue true}
"git commit -m" message)]
(if (zero? (:exit commit-result))
{:success true :repo repo-path}
{:success false :repo repo-path :error (:err commit-result)}))
(catch Exception e
{:success false :repo repo-path :error (.getMessage e)})))
(defn push-changes [repo-path]
"Push committed changes to remote"
(try
(let [push-result (process/shell {:dir repo-path
:out :string
:err :string
:continue true}
"git push")]
(if (zero? (:exit push-result))
{:success true :repo repo-path}
{:success false :repo repo-path :error (:err push-result)}))
(catch Exception e
{:success false :repo repo-path :error (.getMessage e)})))
(defn commitly [commit-message & {:keys [push?]}]
"Main function: commit changes across all modified subrepos"
(let [current-dir (fs/cwd)
subrepos (find-subrepos current-dir)
modified-repos (filter has-changes? subrepos)]
(when (empty? modified-repos)
(println "No repositories with changes found.")
(System/exit 0))
(println (format "Found %d repositories with changes:" (count modified-repos)))
(doseq [repo modified-repos]
(println (format " - %s" (fs/file-name repo))))
(println "\nCommitting changes...")
(let [commit-results (map #(commit-changes % commit-message) modified-repos)]
(doseq [result commit-results]
(if (:success result)
(println (format "✓ %s" (fs/file-name (:repo result))))
(println (format "✗ %s: %s" (fs/file-name (:repo result)) (:error result)))))
(let [failed (filter #(not (:success %)) commit-results)]
(when (seq failed)
(println (format "\n%d repositories failed to commit" (count failed)))
(System/exit 1)))
(when push?
(println "\nPushing changes...")
(let [committed-repos (map :repo (filter :success commit-results))
push-results (map push-changes committed-repos)]
(doseq [result push-results]
(if (:success result)
(println (format "✓ %s" (fs/file-name (:repo result))))
(println (format "✗ %s: %s" (fs/file-name (:repo result)) (:error result)))))
(let [failed (filter #(not (:success %)) push-results)]
(when (seq failed)
(println (format "\n%d repositories failed to push" (count failed)))
(System/exit 1))))))))
;; CLI entry point
(defn -main [& args]
(if (empty? args)
(do
(println "Usage: commitly [-p] <commit-message>")
(println " -p Push changes after committing")
(System/exit 1))
(let [push? (some #(= "-p" %) args)
message-args (remove #(= "-p" %) args)
commit-message (str/join " " message-args)]
(when (str/blank? commit-message)
(println "Error: commit message cannot be empty")
(System/exit 1))
(commitly commit-message :push? push?))))
(when (= *file* (System/getProperty "babashka.file"))
(apply -main *command-line-args*))