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 package org.eclipse.jgit.lfs;
44
45 import static java.nio.charset.StandardCharsets.UTF_8;
46 import static org.eclipse.jgit.lfs.Protocol.OPERATION_UPLOAD;
47 import static org.eclipse.jgit.lfs.internal.LfsConnectionFactory.toRequest;
48 import static org.eclipse.jgit.transport.http.HttpConnection.HTTP_OK;
49 import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
50 import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;
51
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.InputStreamReader;
55 import java.io.OutputStream;
56 import java.io.PrintStream;
57 import java.nio.file.Files;
58 import java.nio.file.Path;
59 import java.text.MessageFormat;
60 import java.util.Collection;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.TreeSet;
66
67 import org.eclipse.jgit.api.errors.AbortedByHookException;
68 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
69 import org.eclipse.jgit.errors.MissingObjectException;
70 import org.eclipse.jgit.hooks.PrePushHook;
71 import org.eclipse.jgit.lfs.Protocol.ObjectInfo;
72 import org.eclipse.jgit.lfs.errors.CorruptMediaFile;
73 import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
74 import org.eclipse.jgit.lfs.internal.LfsText;
75 import org.eclipse.jgit.lib.AnyObjectId;
76 import org.eclipse.jgit.lib.Constants;
77 import org.eclipse.jgit.lib.ObjectId;
78 import org.eclipse.jgit.lib.ObjectReader;
79 import org.eclipse.jgit.lib.Ref;
80 import org.eclipse.jgit.lib.RefDatabase;
81 import org.eclipse.jgit.lib.Repository;
82 import org.eclipse.jgit.revwalk.ObjectWalk;
83 import org.eclipse.jgit.revwalk.RevObject;
84 import org.eclipse.jgit.transport.RemoteRefUpdate;
85 import org.eclipse.jgit.transport.http.HttpConnection;
86
87 import com.google.gson.Gson;
88 import com.google.gson.stream.JsonReader;
89
90
91
92
93
94
95 public class LfsPrePushHook extends PrePushHook {
96
97 private static final String EMPTY = "";
98 private Collection<RemoteRefUpdate> refs;
99
100
101
102
103
104
105
106 public LfsPrePushHook(Repository repo, PrintStream outputStream) {
107 super(repo, outputStream);
108 }
109
110 @Override
111 public void setRefs(Collection<RemoteRefUpdate> toRefs) {
112 this.refs = toRefs;
113 }
114
115 @Override
116 public String call() throws IOException, AbortedByHookException {
117 Set<LfsPointer> toPush = findObjectsToPush();
118 if (toPush.isEmpty()) {
119 return EMPTY;
120 }
121 HttpConnection api = LfsConnectionFactory.getLfsConnection(
122 getRepository(), METHOD_POST, OPERATION_UPLOAD);
123 Map<String, LfsPointer> oid2ptr = requestBatchUpload(api, toPush);
124 uploadContents(api, oid2ptr);
125 return EMPTY;
126
127 }
128
129 private Set<LfsPointer> findObjectsToPush() throws IOException,
130 MissingObjectException, IncorrectObjectTypeException {
131 Set<LfsPointer> toPush = new TreeSet<>();
132
133 try (ObjectWalktWalk.html#ObjectWalk">ObjectWalk walk = new ObjectWalk(getRepository())) {
134 for (RemoteRefUpdate up : refs) {
135 walk.setRewriteParents(false);
136 excludeRemoteRefs(walk);
137 walk.markStart(walk.parseCommit(up.getNewObjectId()));
138 while (walk.next() != null) {
139
140 }
141 findLfsPointers(toPush, walk);
142 }
143 }
144 return toPush;
145 }
146
147 private static void findLfsPointers(Set<LfsPointer> toPush, ObjectWalk walk)
148 throws MissingObjectException, IncorrectObjectTypeException,
149 IOException {
150 RevObject obj;
151 ObjectReader r = walk.getObjectReader();
152 while ((obj = walk.nextObject()) != null) {
153 if (obj.getType() == Constants.OBJ_BLOB
154 && getObjectSize(r, obj) < LfsPointer.SIZE_THRESHOLD) {
155 LfsPointer ptr = loadLfsPointer(r, obj);
156 if (ptr != null) {
157 toPush.add(ptr);
158 }
159 }
160 }
161 }
162
163 private static long getObjectSize(ObjectReader r, RevObject obj)
164 throws IOException {
165 return r.getObjectSize(obj.getId(), Constants.OBJ_BLOB);
166 }
167
168 private static LfsPointer loadLfsPointer(ObjectReader r, AnyObjectId obj)
169 throws IOException {
170 try (InputStream is = r.open(obj, Constants.OBJ_BLOB).openStream()) {
171 return LfsPointer.parseLfsPointer(is);
172 }
173 }
174
175 private void excludeRemoteRefs(ObjectWalk walk) throws IOException {
176 RefDatabase refDatabase = getRepository().getRefDatabase();
177 List<Ref> remoteRefs = refDatabase.getRefsByPrefix(remote());
178 for (Ref r : remoteRefs) {
179 ObjectId oid = r.getPeeledObjectId();
180 if (oid == null) {
181 oid = r.getObjectId();
182 }
183 if (oid == null) {
184
185 continue;
186 }
187 RevObject o = walk.parseAny(oid);
188 if (o.getType() == Constants.OBJ_COMMIT
189 || o.getType() == Constants.OBJ_TAG) {
190 walk.markUninteresting(o);
191 }
192 }
193 }
194
195 private String remote() {
196 String remoteName = getRemoteName() == null
197 ? Constants.DEFAULT_REMOTE_NAME
198 : getRemoteName();
199 return Constants.R_REMOTES + remoteName;
200 }
201
202 private Map<String, LfsPointer> requestBatchUpload(HttpConnection api,
203 Set<LfsPointer> toPush) throws IOException {
204 LfsPointer[] res = toPush.toArray(new LfsPointer[0]);
205 Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
206 for (LfsPointer p : res) {
207 oidStr2ptr.put(p.getOid().name(), p);
208 }
209 Gson gson = Protocol.gson();
210 api.getOutputStream().write(
211 gson.toJson(toRequest(OPERATION_UPLOAD, res)).getBytes(UTF_8));
212 int responseCode = api.getResponseCode();
213 if (responseCode != HTTP_OK) {
214 throw new IOException(
215 MessageFormat.format(LfsText.get().serverFailure,
216 api.getURL(), Integer.valueOf(responseCode)));
217 }
218 return oidStr2ptr;
219 }
220
221 private void uploadContents(HttpConnection api,
222 Map<String, LfsPointer> oid2ptr) throws IOException {
223 try (JsonReader reader = new JsonReader(
224 new InputStreamReader(api.getInputStream(), UTF_8))) {
225 for (Protocol.ObjectInfo o : parseObjects(reader)) {
226 if (o.actions == null) {
227 continue;
228 }
229 LfsPointer ptr = oid2ptr.get(o.oid);
230 if (ptr == null) {
231
232 continue;
233 }
234 Protocol.Action uploadAction = o.actions.get(OPERATION_UPLOAD);
235 if (uploadAction == null || uploadAction.href == null) {
236 continue;
237 }
238
239 Lfs lfs = new Lfs(getRepository());
240 Path path = lfs.getMediaFile(ptr.getOid());
241 if (!Files.exists(path)) {
242 throw new IOException(MessageFormat
243 .format(LfsText.get().missingLocalObject, path));
244 }
245 uploadFile(o, uploadAction, path);
246 }
247 }
248 }
249
250 private List<ObjectInfo> parseObjects(JsonReader reader) {
251 Gson gson = new Gson();
252 Protocol.Response resp = gson.fromJson(reader, Protocol.Response.class);
253 return resp.objects;
254 }
255
256 private void uploadFile(Protocol.ObjectInfo o,
257 Protocol.Action uploadAction, Path path)
258 throws IOException, CorruptMediaFile {
259 HttpConnection contentServer = LfsConnectionFactory
260 .getLfsContentConnection(getRepository(), uploadAction,
261 METHOD_PUT);
262 contentServer.setDoOutput(true);
263 try (OutputStream out = contentServer
264 .getOutputStream()) {
265 long size = Files.copy(path, out);
266 if (size != o.size) {
267 throw new CorruptMediaFile(path, o.size, size);
268 }
269 }
270 int responseCode = contentServer.getResponseCode();
271 if (responseCode != HTTP_OK) {
272 throw new IOException(MessageFormat.format(
273 LfsText.get().serverFailure, contentServer.getURL(),
274 Integer.valueOf(responseCode)));
275 }
276 }
277 }