init commitly
This commit is contained in:
commit
08e503f968
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# commitly
|
||||
|
||||
A CLI tool for making commits to many subrepos after a distributed change.
|
||||
|
||||
## Overview
|
||||
|
||||
`commitly` is a Babashka/Clojure CLI tool designed for monorepo workflows where multiple independent git repositories coexist. When you make changes that span multiple subrepos (e.g., updating documentation, shared configuration, or architecture changes), `commitly` automates the process of committing those changes across all affected repositories.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatically detects which subrepos have uncommitted changes
|
||||
- Creates commits with a single message across all modified subrepos
|
||||
- Reports commit status for each subrepo
|
||||
- Written in Babashka for fast startup and easy distribution
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Commit changes across all modified subrepos
|
||||
./commitly "Your commit message here"
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Babashka](https://babashka.org/) installed and available on PATH
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Make the script executable
|
||||
chmod +x commitly
|
||||
|
||||
# Optionally, symlink to a directory in your PATH
|
||||
ln -s $(pwd)/commitly /usr/local/bin/commitly
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Scans parent directory for git repositories
|
||||
2. Checks each repository for uncommitted changes (staged or unstaged)
|
||||
3. Commits changes in each modified repository with the provided message
|
||||
4. Reports success/failure for each repository
|
||||
|
||||
## Use Case
|
||||
|
||||
Perfect for monorepos containing multiple independent services (each with their own `.git` directory) where cross-cutting changes need to be committed atomically with consistent commit messages.
|
||||
7
bb.edn
Normal file
7
bb.edn
Normal file
@ -0,0 +1,7 @@
|
||||
{:paths ["src"]
|
||||
:deps {}
|
||||
:tasks
|
||||
{:requires ([babashka.fs :as fs])
|
||||
|
||||
run {:doc "Run commitly"
|
||||
:task (load-file "commitly")}}}
|
||||
116
commitly
Executable file
116
commitly
Executable file
@ -0,0 +1,116 @@
|
||||
#!/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*))
|
||||
Loading…
x
Reference in New Issue
Block a user