-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjgit.clj
245 lines (206 loc) · 9.62 KB
/
jgit.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
(ns clj-github-mock.impl.jgit
(:require [clj-github-mock.impl.base64 :as base64]
[clojure.set :as set]
[clojure.string :as string])
(:import [org.eclipse.jgit.internal.storage.dfs DfsRepositoryDescription InMemoryRepository]
[org.eclipse.jgit.lib AnyObjectId CommitBuilder Constants FileMode ObjectId
ObjectInserter ObjectReader PersonIdent Repository TreeFormatter]
[org.eclipse.jgit.revwalk RevCommit]
[org.eclipse.jgit.treewalk TreeWalk]))
(set! *warn-on-reflection* true)
(defn empty-repo []
(InMemoryRepository. (DfsRepositoryDescription.)))
(defn- new-inserter ^ObjectInserter [^Repository repo]
(-> repo (.getObjectDatabase) (.newInserter)))
(defmacro with-inserter [[inserter repo] & body]
`(let [~inserter (new-inserter ~repo)
result# (do ~@body)]
(.flush ~inserter)
result#))
(defn- new-reader [^Repository repo]
(-> repo (.getObjectDatabase) (.newReader)))
(defn- load-object [^ObjectReader reader ^ObjectId object-id]
(let [object-loader (.open reader object-id)]
(.getBytes object-loader)))
(defn- insert-blob [^ObjectInserter inserter {:keys [content encoding]}]
; https://docs.github.com/en/rest/git/blobs?apiVersion=2022-11-28#create-a-blob
(let [^bytes bs (if (= encoding "base64")
(base64/decode-str->bytes content)
(.getBytes ^String content "UTF-8"))]
(.insert inserter Constants/OBJ_BLOB bs)))
(defn create-blob! [repo blob]
(with-inserter [inserter repo]
(let [object-id (insert-blob inserter blob)]
{:sha (ObjectId/toString object-id)})))
(defn get-blob [repo sha]
; https://docs.github.com/en/rest/git/blobs?apiVersion=2022-11-28#get-a-blob
(let [content (load-object (new-reader repo) (ObjectId/fromString sha))]
{:content (base64/encode-bytes->str content)
:encoding "base64"}))
(def ^:private github-mode->file-mode {"100644" FileMode/REGULAR_FILE
"100755" FileMode/EXECUTABLE_FILE
"040000" FileMode/TREE
"160000" FileMode/GITLINK
"120000" FileMode/SYMLINK})
(def ^:private file-mode->github-mode (set/map-invert github-mode->file-mode))
(def ^:private file-mode->github-type {FileMode/REGULAR_FILE "blob"
FileMode/EXECUTABLE_FILE "blob"
FileMode/TREE "tree"
FileMode/GITLINK "commit"
FileMode/SYMLINK "blob"})
(defn- tree-walk-seq [^TreeWalk tree-walk]
(lazy-seq
(when (.next tree-walk)
(cons {:path (.getPathString tree-walk)
:mode (file-mode->github-mode (.getFileMode tree-walk))
:type (file-mode->github-type (.getFileMode tree-walk))
:sha (ObjectId/toString (.getObjectId tree-walk 0))}
(tree-walk-seq tree-walk)))))
(defn tree-walk [^Repository repo sha]
(doto (TreeWalk. repo)
(.reset (ObjectId/fromString sha))))
(defn split-path [path]
(string/split path #"/"))
(defn path-sha [^Repository repo base_tree ^String path]
(when base_tree
(when-let [tree-walk (TreeWalk/forPath repo path ^"[Lorg.eclipse.jgit.lib.AnyObjectId;" (into-array ObjectId [(ObjectId/fromString base_tree)]))]
(ObjectId/toString (.getObjectId tree-walk 0)))))
(defn leaf-item? [item]
(not (map? item)))
(defn tree-items->tree-map [tree-items]
(->> (reduce (fn [acc {:keys [path] :as item}]
(let [path-split (split-path path)
item-path (last path-split)]
(update-in acc path-split conj (assoc item :path item-path))))
{} tree-items)))
(defn tree-map->tree-items [tree-map repo base_tree]
(mapcat
(fn [[path item]]
(if-not (leaf-item? item)
(let [tree-sha (path-sha repo base_tree path)]
[{:path path
:type "tree"
:mode "040000"
:base_tree tree-sha
:content (if (map? item)
(tree-map->tree-items item repo tree-sha)
item)}])
item))
tree-map))
(declare insert-tree)
(defn content->object-id ^ObjectId [{:keys [type content base_tree] :as blob} repo inserter]
(case type
"blob" (insert-blob inserter blob)
"tree" (insert-tree repo inserter content base_tree)))
(defn append-tree-item [{:keys [^String path mode sha content] :as item} repo ^TreeFormatter tree-formatter inserter]
(when (or content sha)
(.append tree-formatter
path
^FileMode (github-mode->file-mode mode)
(if sha
(ObjectId/fromString sha)
(content->object-id item repo inserter)))))
(defn- merge-trees [base-tree new-tree]
(-> (merge (group-by :path base-tree)
(group-by :path new-tree))
vals
flatten))
(defn insert-tree [repo inserter tree base_tree]
(let [tree-formatter (TreeFormatter.)
tree' (if base_tree
(merge-trees (tree-walk-seq (tree-walk repo base_tree))
tree)
tree)]
(doseq [tree-item tree']
(append-tree-item tree-item repo tree-formatter inserter))
(.insertTo tree-formatter inserter)))
(defn get-tree [repo sha]
{:sha sha
:tree (into [] (tree-walk-seq (tree-walk repo sha)))})
(defn- concat-path [base-path path]
(if base-path (str base-path "/" path) path))
(defn- flatten-tree [repo base-sha base-path]
(let [{:keys [tree]} (get-tree repo base-sha)]
(mapcat (fn [{:keys [path type sha] :as tree-item}]
(if (= "tree" type)
(flatten-tree repo sha (concat-path base-path path))
[(-> (merge tree-item (get-blob repo (:sha tree-item)))
; NOTE: when reading the flattened tree, contents are always assumed to be a String
; (needed for backwards compatibility)
(update :content #(if (string/blank? %) % (base64/decode-str->str %)))
(assoc :encoding "utf-8")
(update :path (partial concat-path base-path))
(dissoc :sha))]))
tree)))
(defn get-flatten-tree [repo sha]
{:sha sha
:tree (into [] (flatten-tree repo sha nil))})
(defn create-tree! [repo {:keys [tree base_tree]}]
(let [final-tree (-> (tree-items->tree-map tree)
(tree-map->tree-items repo base_tree))]
(let [sha (with-inserter [inserter repo]
(ObjectId/toString (insert-tree repo inserter final-tree base_tree)))]
(get-tree repo sha))))
;; only for testing - start
(declare tree-content)
(defn- tree-item-content [repo {:keys [type sha]}]
(case type
"tree" (tree-content repo sha)
"blob" (get-blob repo sha)))
(defn tree-content [repo sha]
(let [tree (tree-walk-seq (tree-walk repo sha))]
(->> tree
(map (fn [{:keys [path] :as tree-item}]
[path (tree-item-content repo tree-item)]))
(into {}))))
;; only for testing - end
(defn get-commit [repo sha]
(let [bytes (load-object (new-reader repo) (ObjectId/fromString sha))
commit (RevCommit/parse bytes)]
{:sha sha
:message (.getFullMessage commit)
:tree {:sha (-> commit (.getTree) (.getId) (.getName))}
:parents (mapv #(hash-map :sha (ObjectId/toString (.getId ^RevCommit %))) (.getParents commit))}))
(defn create-commit! [repo {:keys [tree message parents]}]
(let [commit-id (with-inserter [inserter repo]
(let [commit-builder (doto (CommitBuilder.)
(.setMessage message)
(.setTreeId (ObjectId/fromString tree))
(.setAuthor (PersonIdent. "me" "[email protected]"))
(.setCommitter (PersonIdent. "me" "[email protected]"))
(.setParentIds ^"[Lorg.eclipse.jgit.lib.ObjectId;" (into-array ObjectId (map #(ObjectId/fromString %) parents))))]
(.insert inserter commit-builder)))]
(get-commit repo (ObjectId/toString commit-id))))
(defn get-reference [^Repository repo ^String ref-name]
(when-let [ref (.exactRef repo ref-name)]
{:ref ref-name
:object {:type "commit"
:sha (ObjectId/toString (.getObjectId ref))}}))
(defn create-reference! [^Repository repo {:keys [^String ref sha]}]
(let [ref-update (.updateRef repo ref)]
(doto ref-update
(.setNewObjectId (ObjectId/fromString sha))
(.update))
(get-reference repo ref)))
(defn delete-reference! [^Repository repo ^String ref]
(.delete
(doto (.updateRef repo ref) (.setForceUpdate true))))
(defn get-branch [^Repository repo ^String branch]
(when-let [ref (.findRef repo branch)]
(let [commit (get-commit repo (ObjectId/toString (.getObjectId ref)))]
{:name branch
:commit {:sha (:sha commit)
:commit (dissoc commit :sha)}})))
(defn get-content [repo sha path]
; https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
(let [reader (new-reader repo)
commit (RevCommit/parse (load-object reader (ObjectId/fromString sha)))
tree-id (-> commit (.getTree) (.getId))
tree-walk (TreeWalk/forPath ^ObjectReader reader ^String path ^"[Lorg.eclipse.jgit.lib.AnyObjectId;" (into-array AnyObjectId [tree-id]))
object-id (when tree-walk (.getObjectId tree-walk 0))]
(when object-id
(let [content (load-object reader object-id)]
{:type "file"
:path path
:encoding "base64"
:content (base64/encode-bytes->str content)}))))