Product Promotion
0x5a.live
for different kinds of informations and explorations.
GitHub - clj-jgit/clj-jgit: Clojure wrapper around JGit
Clojure wrapper around JGit. Contribute to clj-jgit/clj-jgit development by creating an account on GitHub.
Visit SiteGitHub - clj-jgit/clj-jgit: Clojure wrapper around JGit
Clojure wrapper around JGit. Contribute to clj-jgit/clj-jgit development by creating an account on GitHub.
Powered by 0x5a.live ๐
clj-jgit
Clojure wrapper for using the JGit library to manipulate Git repositories in a "pure Java" fashion.
You can view latest auto-generated API documentation here: clj-jgit.github.io/clj-jgit.
Installation
Last stable version is available on Clojars.
Note: If you don't plan on signing commits you may exclude org.eclipse.jgit/org.eclipse.jgit.gpg.bc
, i.e. for lein:
:dependencies [[clj-jgit "1.x.x" :exclusions [org.eclipse.jgit/org.eclipse.jgit.gpg.bc]]]
Note: Java 11 or newer is required since clj-jgit-1.0.2
Quickstart Tutorial
This brief tutorial will show you how to:
- Clone a remote repository
- Create a local branch for your changes
- Checkout that branch, and
- Add and commit those changes
- Manage tags
;; First require or use the porcelain namespace, assuming a quick REPL session:
(use 'clj-jgit.porcelain)
;; Clone a repository into a folder of my choosing and keep the jgit repository object at my-repo
(def my-repo (git-clone "https://github.com/clj-jgit/clj-jgit.git" :dir "local-folder/clj-jgit"))
;=> #'user/my-repo
;; A bit redundant for a fresh repo, but always good to check the repo status
;; before making any changes
(git-status my-repo)
;=> {:added #{}, :changed #{}, :missing #{}, :modified #{}, :removed #{}, :untracked #{}}
;; List existing branches
(git-branch-list my-repo)
;=> ("master")
;; Create a new local branch to store our changes
(git-branch-create my-repo "my-branch")
;=> #<org.eclipse.jgit.internal.storage.file.RefDirectory$LooseUnpeeled, Name: refs/heads/my-branch, ...>
;; Prove to ourselves that it was created
(git-branch-list my-repo)
;=> ("master" "my-branch")
;; Check out our new branch
(git-checkout my-repo "my-branch")
;=> #<org.eclipse.jgit.internal.storage.file.RefDirectory$LooseUnpeeled, Name: refs/heads/my-branch, ...>
;; Now go off and make your changes.
;; For example, let's say we added a file "foo.txt" at the base of the project.
(git-status my-repo)
;=> {:added #{}, :changed #{}, :missing #{}, :modified #{}, :removed #{}, :untracked #{"foo.txt"}}
;; Add the file to the index
(git-add my-repo "foo.txt")
;=> #object[org.eclipse.jgit.dircache.DirCache 0x3d9e3c3a "org.eclipse.jgit.dircache.DirCache@3d9e3c3a"]
;; Check for status change
(git-status my-repo)
;=> {:added #{"foo.txt"}, :changed #{}, :missing #{}, :modified #{}, :removed #{}, :untracked #{}}
;; Now commit your changes, specifying author and committer if desired
(git-commit my-repo "Add file foo.txt" :committer {:name "Daniel Gregoire" :email "[email protected]"})
;=> #object[org.eclipse.jgit.revwalk.RevCommit 0x7283e79c "commit b6feeb3baab8fa0422390b2d2a737b1e21610401 -----p"]
;; Status clean
(git-status my-repo)
;=> {:added #{}, :changed #{}, :missing #{}, :modified #{}, :removed #{}, :untracked #{}}
(git-clean my-repo :dirs? true, :ignore? true)
;=> #{}
;; You can also diff any part of the history
(git-diff repo :old-commit "ab6fc2" :new-commit "HEAD^2")
#object[java.io.ByteArrayOutputStream 0x753bc044 "diff --git ...
;; Blame
(first (git-blame my-repo "README.md"))
;=> {:author {:name "Ilya Sabanin",
:email "[email protected]",
:timezone #<ZoneInfo ...>},
:commit #<RevCommit commit fabdf5cf4abb72231461177238349c21e23fa46a 1352414190 -----p>,
:committer {:name "Ilya Sabanin",
:email "[email protected]",
:timezone #<ZoneInfo ...>},
:line 66,
:source-path "README.md"}
;; Tagging
(git-tag-create my-repo "v2.0.0")
;=> #<org.eclipse.jgit.internal.storage.file.RefDirectory$LooseUnpeeled, Name: refs/tags/v2.0.0, ObjectId: ...
(git-tag-list my-repo)
;=> ("v0.0.1" "v0.0.2" "v0.0.3" "v0.8.10" "v2.0.0")
(git-tag-delete my-repo "v2.0.0")
;=> ["refs/tags/v2.0.0"]
(git-tag-list my-repo)
;=> ("v0.0.1" "v0.0.2" "v0.0.3" "v0.8.10")
Detailed Usage
Currently, this library leverages the "porcelain" API exposed by JGit, which allows the use of methods/functions similar to the ones available on the command-line when using Git for "basic" purposes. If enough interest arises, this tool will wrap more of the lower-level functions in the future, but for now it acts as a basic Java replacement for the command-line version of Git.
Cloning a Repository
(git-clone "url-to-repo" :dir "optional-local-folder")
Authentication
Per default JGit expects a standard SSH key setup in ~/.ssh
, things should just work unless your key is password
protected. For further configuration any command may be wrapped with the with-credentials
or with-identity
macro:
;; Use custom SSH key location with key pw, also accept all unknown server keys and add them to `known_hosts` file:
(with-identity {:name "my.key" :key-dir "/some/path" pw: "$ecRet" :trust-all? true}
(git-push my-repo :tags true))
;; Use user/pw auth instead of key based auth
(with-credentials {:login "someuser" :pw "$ecReT"}
(git-pull my-repo))
GPG signing of Commits
A working GPG key setup is required for signing:
$ gpg --gen-key
$ gpg --fingerprint
pub rsa4096 2019-12-01 [SC]
20AB A393 B992 6661 63BE 1346 5022 F878 A751 2BA8 <- the fingerprint
uid [ultimate] Foo Bar <[email protected]>
sub rsa4096 2018-12-01 [E]
GPG signing is used if:
- enabled per commit:
(git-commit ... :signing? true :signing-key "A7512BA8")
- git is configured via
commit.gpgSign = true
, i.e.:
(-> my-repo
git-config-load
(git-config-set "commit.gpgSign" true)
(git-config-set "user.signingKey" "A7512BA8")
git-config-save)
Note: You may use as few as 2 digits of the fingerprint as long the id is unique in your keyring, no spaces.
If GPG-signing a commit is requested but no GpgSigner is installed, an org.eclipse.jgit.api.errors.ServiceUnavailableException
will be thrown.
Loading an Existing Repository
In order to use most of the functions in JGit's API, you need to have a repository object to play with. Here are ways to load an existing repository:
;; Simples method is to point to the folder
(load-repo "path-to-git-repo-folder")
;; In order to remain consistent with JGit's default behavior,
;; you can also point directly at the .git folder of the target repo
(load-repo "path-to-git-repo-folder/.git")
This function throws a FileNotFoundException
if the directory in question does not exist.
Querying repository
This uses internal JGit API, so it may require some additional knowledge of JGit.
(ns clj-jgit.porcelain)
;; Log
(git-log my-repo :max-count 1)
;=> ({:id #object[org.eclipse.jgit.revwalk.RevCommit], :msg "fix git-commit docs, typo in readme", :author ...
;; Log for range
(git-log my-repo :since "5a7b97" :until "master^1")
;=> ...
;; This macro allows you to create a universal handler with name "repo"
(with-repo "/path/to/a/repo"
(git-log repo))
;; Notes Add
(git-notes-add my-repo "my note")
;=> (#object[org.eclipse.jgit.notes.Note 0x1237ddce "Note[3c14d1f8917761rc71db03170866055ef88b585f -> 10500018fca9b3425b50de67a7258a12cba0c076]"])
;; Notes Append
(git-notes-add my-repo "my appended note")
;=> (#object[org.eclipse.jgit.notes.Note 0x1237ddce "Note[3c14d1f8917761rc71db03170866055ef88b585f -> 10500018fca9b3425b50de67a7258a12cba0c076]"])
;; Notes List
(git-notes my-repo)
;=> (#object[org.eclipse.jgit.notes.Note 0x1237ddce "Note[3c14d1f8917761rc71db03170866055ef88b585f -> 10500018fca9b3425b50de67a7258a12cba0c076]"])
;; Notes Show
(git-notes-show my-repo)
;=> ["my note" "my appended note"]
(ns clj-jgit.querying)
;; Creates a RevWalk instance needed to traverse the commits for the repo.
;; Commits found through a RevWalk can be compared and used only with other
;; commits found with a same RevWalk instance.
(def rev-walk (new-rev-walk repo))
;; List of pairs of branch refs and RevCommits associated with them
(branch-list-with-heads repo rev-walk)
;=> ([#<org.eclipse.jgit.storage.file.RefDirectory$LooseUnpeeled, Name: refs/heads/master, ObjectId: 3b9c98bc151bb4920f9799cfa6c32c536ed64348>
#<RevCommit commit 3b9c98bc151bb4920f9799cfa6c32c536ed64348 1339922123 -----p>])
;; Find an ObjectId instance for a repo and given commit-ish, tree-ish or blob
(def commit-obj-id (resolve-object repo "38dd57264cf5c05fb77211c8347d1f16e4474623"))
;=> #<ObjectId AnyObjectId[38dd57264cf5c05fb77211c8347d1f16e4474623]>
;; Show all the branches where commit is present
(branches-for repo commit-obj-id)
;=> ("refs/heads/master")
;; List of all revision objects in the repository, for all branches
(rev-list repo)
;=> #<RevCommitList [commit 3b9c98bc151bb4920f9799cfa6c32c536ed64348 1339922123 ----sp, ... ]>
;; Gather information about specific commit
(commit-info repo (find-rev-commit repo rev-walk "38dd57264cf"))
; Returns
{:repo #<Git org.eclipse.jgit.api.Git@21d306ef>,
:changed_files [[".gitignore" :add]
["README.md" :add]
["project.clj" :add]
["src/clj_jgit/core.clj" :add]
["src/clj_jgit/util/print.clj" :add]
["test/clj_jgit/test/core.clj" :add]],
:raw #<RevCommit commit 38dd57264cf5c05fb77211c8347d1f16e4474623 1304645414 ----sp>,
:author "Daniel Gregoire",
:email "[email protected]",
:message "Initial commit",
:branches ("refs/heads/master"),
:merge false,
:time #<Date Fri May 06 09:30:14 KRAT 2011>,
:id "38dd57264cf5c05fb77211c8347d1f16e4474623"}
;; You can also combine this with Porcelain API, to get a list of all commits in a repo with detailed information
(with-repo "/path/to/repo.git"
(map #(commit-info repo %) (git-log repo)))
;; Branches lookup is an expensive operation, especially for repos with many branches.
;; commit-info spends most of it time trying to detect list of branches commit belongs to.
; If you don't require branches list in commit info, you can use:
(commit-info-without-branches repo rev-walk rev-commit)
; If you want branches list, but want it to work faster, you can generate commit map that turns
; commits and branches into a map for fast branch lookups. In real-life this can give 30x-100x speed
; up when you are traversing lists of commits, depending on amount of branches you have.
(let [rev-walk (new-rev-walk repo)
commit-map (build-commit-map repo rev-walk)
commits (git-log repo)]
(map (partial commit-info repo rev-walk commit-map) commits))
Diffing
JGit also has excellent diffing support that can be used without any Git context:
(ns clj-jgit.diff)
;; Diff two text files and write a diff-style patch to foo.patch
(-> (diff-string-formatted (slurp "foo_org.txt") (slurp "foo_new.txt")
:output-stream (clojure.java.io/output-stream "foo.patch") :context-lines 4)
.close)
See clj-jgit.diff
for more advanced use cases.
Contribute
If you want to contribute just fork the repository, work on the code, cover it with tests and submit a pull request through Github.
Any questions related to clj-jgit can be discussed in the Google Group.
Caveat Windows Users
Cygwin will cause this library to hang. Make sure to remove C:\cygwin\bin
from your PATH before attempting to use this library.
License
Copyright (C) 2019 FIXME
Distributed under the Eclipse Public License, the same as Clojure.
Made with โค๏ธ
to provide different kinds of informations and resources.