Skip to content

Instantly share code, notes, and snippets.

@Jared314
Last active December 22, 2015 04:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jared314/6418917 to your computer and use it in GitHub Desktop.
Save Jared314/6418917 to your computer and use it in GitHub Desktop.
Improving the Clojure-Git Interface with a Nice Facade
(ns gitter.core
(:require [clj-jgit.porcelain :as c]
[clj-jgit.querying :as q]
[clj-jgit.internal :as i]
[clojure.string :as string]
[gitter.clj-jgit.porcelain :as helpers])
(:import [clojure.lang ISeq Associative IFn MapEntry IObj]
[org.eclipse.jgit.api Git]
[org.eclipse.jgit.revwalk RevWalk]))
(defn gen-metadata [^Git repo]
(let [path (-> repo .getRepository .getDirectory .getParent)
branch (c/git-branch-current repo)
branch-names (map #(string/replace (.getName %) #"^refs/heads/" "") (c/git-branch-list repo))
attached (c/git-branch-attached? repo)]
{::path path
::branch branch
::branches branch-names
::attached-head attached}))
(deftype Repository [^Git repo ^RevWalk walker metadata]
Associative
(count [this] -1) ;; TODO: Not yet
(seq [this] (seq walker))
(cons [this o]
;; TODO: if walker first is not the current HEAD, create branch from current first
(helpers/git-add-commit repo o)
(Repository. repo (i/new-rev-walk repo) metadata))
(empty [_] nil) ;; TODO: Not applicable or branch?
(equiv [_ o]
(and (instance? Repository o)
(= repo (.repo o))))
(containsKey [_ key] (not (nil? (i/resolve-object key repo))))
(entryAt [this k] (MapEntry. k (.valAt this k)))
(assoc [this _ _] this) ;; Not applicable
(valAt [this key] (.valAt this key nil))
(valAt [this key notFound] (if (.containsKey this key)
(q/find-rev-commit repo walker key)
notFound))
IFn
(invoke [this arg1] (.valAt this arg1 nil))
(invoke [this arg1 arg2] (.valAt this arg1 arg2))
IObj
(withMeta [_ meta] (Repository. repo walker (merge metadata meta)))
(meta [_] (merge (gen-metadata repo) metadata))
Object
(toString [this] (let [m (.meta this)] (str "Path: " (::path m) " Branch: " (::branch m)))))
(defn get-repo [path]
(c/with-repo path (Repository. repo rev-walk nil)))
(ns gitter.core-test
(:require [clojure.test :refer :all]
[gitter.core :refer :all]
[gitter.clj-jgit.porcelain :refer :all])
(:import [java.util Date]))
(def test-repo-path "/Users/user1/Desktop/tester1")
(deftest read-repo-test
(let [x (get-repo test-repo-path)]
(testing "Reading from the repository"
;; Checking for a commit by id
(is (contains? x "44cf78fa233d4ef44314099762ab77543471720a"))
(is (contains? x "HEAD"))
;; Fetching a commit
(is (= (get x "44cf78fa233d4ef443")
(x "44cf78fa233d4ef443")))
(is (= (get x "HEAD")
(x "HEAD")))
;; Fetching a commit with a notFound parameter
(is (= :fail
(get x "" :fail)
(x "" :fail)))
;; The repository has the following default metadata
(is (= {:gitter.core/path test-repo-path, :gitter.core/branches '("master"), :gitter.core/branch "master" :gitter.core/attached-head true}
(meta x)))
;; Assoc does nothing, and returns the repository
(is (= x
(assoc x "" 1)))
;;(seq? x) ;; TODO:
;;(first x) ;; TODO:
)))
(deftest write-repo-test
(testing "Writing to the repository"
(let [x (get-repo test-repo-path)
c {:message "test commit"
:author {:name "guy 1234" :email "guy1234@gmail.com"}
:changes {"stuff.txt" (str "stuff contents " (Date.))
"stuff4.txt" (str "stuff4 contents " (Date.))}}]
(is (= x
(conj x c))))))
(ns gitter.clj-jgit.porcelain
(:require [clj-jgit.util :as util]
[clj-jgit.internal :refer :all]
[clj-jgit.porcelain :as c])
(:import [org.eclipse.jgit.api Git StatusCommand Status StashCreateCommand StashApplyCommand]
[org.eclipse.jgit.lib ObjectId]
[gitter TreeIterator]))
(defn git-create-stash [^Git repo]
(-> repo
.stashCreate
.call))
(defn git-apply-stash [^Git repo ^String key]
(-> repo
.stashApply
(.setStashRef key)
.call))
(defn git-list-stash [^Git repo]
(-> repo
.stashList
.call))
(defn git-drop-stash [^Git repo ^String key]
(let [stashes (git-list-stash repo)
target (first (filter #(= key (second %))
(map-indexed #(vector %1 (.getName %2)) stashes)))] ;; TODO: cleanup
(when-not (nil? target)
(-> repo
.stashDrop
(.setStashRef (first target))
.call))))
(defn git-pop-stash [^Git repo ^String key]
(git-apply-stash repo key)
(git-drop-stash repo key))
(defn head? [^Git repo k]
(let [r (.getRepository repo)
head (.getRef r "HEAD")]
(and (not (nil? head))
(not (nil? (.getObjectId head)))
(= head
(.getRef r k)))))
;; TODO: There has to be a better way!
(defn with-preserved-staging [repo f]
(let [has-staged-changes (not-every? empty? (vals (c/git-status repo :added :changed :removed)))
stashid (if has-staged-changes (ObjectId/toString (.getId (git-create-stash repo))))
result (f)]
(when has-staged-changes (git-pop-stash repo stashid))
result))
(defn git-add-commit [repo {:keys [message author committer changes]
:or {message "" author {:name "" :email ""} committer nil changes {}}}]
(let [prefixed-author {:author-name (:name author) :author-email (:email author)}
c (merge author committer)
prefixed-committer {:committer-name (:name c) :committer-email (:email c)}]
(with-preserved-staging repo
#(doto repo
(c/git-add "." false (TreeIterator. (.getRepository repo) changes))
(c/git-commit message prefixed-author prefixed-committer)))))
(defproject gitter "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]
[clj-jgit "0.3.9"]]
:aot [gitter.TreeIterator])
(ns gitter.TreeIterator
(:import [org.eclipse.jgit.lib Repository FileMode]
[org.eclipse.jgit.treewalk WorkingTreeIterator WorkingTreeIterator$Entry WorkingTreeOptions]
[java.io ByteArrayInputStream])
(:gen-class :extends org.eclipse.jgit.treewalk.WorkingTreeIterator
:init init2
:post-init postinit
:state state
:constructors {[org.eclipse.jgit.lib.Repository Object] [org.eclipse.jgit.treewalk.WorkingTreeOptions]
[org.eclipse.jgit.lib.Repository Object org.eclipse.jgit.treewalk.WorkingTreeIterator] [org.eclipse.jgit.treewalk.WorkingTreeIterator]}))
(gen-interface :name gittree.IHasValue
:methods [[getValue [] Object]])
(defn toEntry
([[k v]] (toEntry k v))
([k v]
(let [node-name (if (keyword? k) (name k) (str k))
node-mode (if (coll? v) FileMode/TREE FileMode/REGULAR_FILE)
node-modified 0
node-value (if (string? v) (.getBytes v) (byte-array 0))
node-length (count node-value)]
(proxy [WorkingTreeIterator$Entry gittree.IHasValue] []
(getMode [] node-mode)
(getName [] node-name)
(getLength [] node-length)
(getLastModified [] node-modified)
(openInputStream [] (ByteArrayInputStream. node-value))
(getValue [] v)))))
(defn -init2
([^Repository repo data]
(let [options (-> repo (.getConfig) (.get WorkingTreeOptions/KEY))]
[[options] {:repo repo}]))
([^Repository repo data ^WorkingTreeIterator itr]
[[itr] {:repo repo}]))
(defn -postinit
([this repo data] (-postinit this repo data nil))
([this repo data _]
(let [entries (if (map? data) (map toEntry data) (map-indexed toEntry data))]
(.initRootIterator this repo)
(.init this (into-array WorkingTreeIterator$Entry entries)))))
(defn -createSubtreeIterator [this reader idBuffer]
(let [repo (:repo (.state this))]
(gitter.TreeIterator. repo (.getValue (.current this)) this)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment