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 package org.eclipse.jgit.transport;
46
47 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
48 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
49 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
50 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
51
52 import java.io.File;
53 import java.io.IOException;
54 import java.io.OutputStreamWriter;
55 import java.io.Writer;
56 import java.text.MessageFormat;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.concurrent.TimeUnit;
66
67 import org.eclipse.jgit.errors.MissingObjectException;
68 import org.eclipse.jgit.errors.NotSupportedException;
69 import org.eclipse.jgit.errors.TransportException;
70 import org.eclipse.jgit.internal.JGitText;
71 import org.eclipse.jgit.internal.storage.file.LockFile;
72 import org.eclipse.jgit.internal.storage.file.PackLock;
73 import org.eclipse.jgit.lib.BatchRefUpdate;
74 import org.eclipse.jgit.lib.BatchingProgressMonitor;
75 import org.eclipse.jgit.lib.Constants;
76 import org.eclipse.jgit.lib.ObjectId;
77 import org.eclipse.jgit.lib.ProgressMonitor;
78 import org.eclipse.jgit.lib.Ref;
79 import org.eclipse.jgit.lib.RefDatabase;
80 import org.eclipse.jgit.revwalk.ObjectWalk;
81 import org.eclipse.jgit.revwalk.RevWalk;
82
83 class FetchProcess {
84
85 private final Transport transport;
86
87
88 private final Collection<RefSpec> toFetch;
89
90
91 private final HashMap<ObjectId, Ref> askFor = new HashMap<>();
92
93
94 private final HashSet<ObjectId> have = new HashSet<>();
95
96
97 private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<>();
98
99
100 private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<>();
101
102 private final ArrayList<PackLock> packLocks = new ArrayList<>();
103
104 private FetchConnection conn;
105
106 private Map<String, Ref> localRefs;
107
108 FetchProcess(final Transport t, final Collection<RefSpec> f) {
109 transport = t;
110 toFetch = f;
111 }
112
113 void execute(final ProgressMonitor monitor, final FetchResult result)
114 throws NotSupportedException, TransportException {
115 askFor.clear();
116 localUpdates.clear();
117 fetchHeadUpdates.clear();
118 packLocks.clear();
119 localRefs = null;
120
121 try {
122 executeImp(monitor, result);
123 } finally {
124 try {
125 for (final PackLock lock : packLocks)
126 lock.unlock();
127 } catch (IOException e) {
128 throw new TransportException(e.getMessage(), e);
129 }
130 }
131 }
132
133 private void executeImp(final ProgressMonitor monitor,
134 final FetchResult result) throws NotSupportedException,
135 TransportException {
136 conn = transport.openFetch();
137 try {
138 result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
139 result.peerUserAgent = conn.getPeerUserAgent();
140 final Set<Ref> matched = new HashSet<>();
141 for (final RefSpec spec : toFetch) {
142 if (spec.getSource() == null)
143 throw new TransportException(MessageFormat.format(
144 JGitText.get().sourceRefNotSpecifiedForRefspec, spec));
145
146 if (spec.isWildcard())
147 expandWildcard(spec, matched);
148 else
149 expandSingle(spec, matched);
150 }
151
152 Collection<Ref> additionalTags = Collections.<Ref> emptyList();
153 final TagOpt tagopt = transport.getTagOpt();
154 if (tagopt == TagOpt.AUTO_FOLLOW)
155 additionalTags = expandAutoFollowTags();
156 else if (tagopt == TagOpt.FETCH_TAGS)
157 expandFetchTags();
158
159 final boolean includedTags;
160 if (!askFor.isEmpty() && !askForIsComplete()) {
161 fetchObjects(monitor);
162 includedTags = conn.didFetchIncludeTags();
163
164
165
166
167 closeConnection(result);
168 } else {
169 includedTags = false;
170 }
171
172 if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
173
174
175
176 have.addAll(askFor.keySet());
177 askFor.clear();
178 for (final Ref r : additionalTags) {
179 ObjectId id = r.getPeeledObjectId();
180 if (id == null)
181 id = r.getObjectId();
182 if (transport.local.hasObject(id))
183 wantTag(r);
184 }
185
186 if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) {
187 reopenConnection();
188 if (!askFor.isEmpty())
189 fetchObjects(monitor);
190 }
191 }
192 } finally {
193 closeConnection(result);
194 }
195
196 BatchRefUpdate batch = transport.local.getRefDatabase()
197 .newBatchUpdate()
198 .setAllowNonFastForwards(true)
199 .setRefLogMessage("fetch", true);
200 try (final RevWalk walk = new RevWalk(transport.local)) {
201 if (monitor instanceof BatchingProgressMonitor) {
202 ((BatchingProgressMonitor) monitor).setDelayStart(
203 250, TimeUnit.MILLISECONDS);
204 }
205 if (transport.isRemoveDeletedRefs())
206 deleteStaleTrackingRefs(result, batch);
207 for (TrackingRefUpdate u : localUpdates) {
208 result.add(u);
209 batch.addCommand(u.asReceiveCommand());
210 }
211 for (ReceiveCommand cmd : batch.getCommands()) {
212 cmd.updateType(walk);
213 if (cmd.getType() == UPDATE_NONFASTFORWARD
214 && cmd instanceof TrackingRefUpdate.Command
215 && !((TrackingRefUpdate.Command) cmd).canForceUpdate())
216 cmd.setResult(REJECTED_NONFASTFORWARD);
217 }
218 if (transport.isDryRun()) {
219 for (ReceiveCommand cmd : batch.getCommands()) {
220 if (cmd.getResult() == NOT_ATTEMPTED)
221 cmd.setResult(OK);
222 }
223 } else
224 batch.execute(walk, monitor);
225 } catch (IOException err) {
226 throw new TransportException(MessageFormat.format(
227 JGitText.get().failureUpdatingTrackingRef,
228 getFirstFailedRefName(batch), err.getMessage()), err);
229 }
230
231 if (!fetchHeadUpdates.isEmpty()) {
232 try {
233 updateFETCH_HEAD(result);
234 } catch (IOException err) {
235 throw new TransportException(MessageFormat.format(
236 JGitText.get().failureUpdatingFETCH_HEAD, err.getMessage()), err);
237 }
238 }
239 }
240
241 private void fetchObjects(final ProgressMonitor monitor)
242 throws TransportException {
243 try {
244 conn.setPackLockMessage("jgit fetch " + transport.uri);
245 conn.fetch(monitor, askFor.values(), have);
246 } finally {
247 packLocks.addAll(conn.getPackLocks());
248 }
249 if (transport.isCheckFetchedObjects()
250 && !conn.didFetchTestConnectivity() && !askForIsComplete())
251 throw new TransportException(transport.getURI(),
252 JGitText.get().peerDidNotSupplyACompleteObjectGraph);
253 }
254
255 private void closeConnection(final FetchResult result) {
256 if (conn != null) {
257 conn.close();
258 result.addMessages(conn.getMessages());
259 conn = null;
260 }
261 }
262
263 private void reopenConnection() throws NotSupportedException,
264 TransportException {
265 if (conn != null)
266 return;
267
268 conn = transport.openFetch();
269
270
271
272
273
274
275
276
277
278 final HashMap<ObjectId, Ref> avail = new HashMap<>();
279 for (final Ref r : conn.getRefs())
280 avail.put(r.getObjectId(), r);
281
282 final Collection<Ref> wants = new ArrayList<>(askFor.values());
283 askFor.clear();
284 for (final Ref want : wants) {
285 final Ref newRef = avail.get(want.getObjectId());
286 if (newRef != null) {
287 askFor.put(newRef.getObjectId(), newRef);
288 } else {
289 removeFetchHeadRecord(want.getObjectId());
290 removeTrackingRefUpdate(want.getObjectId());
291 }
292 }
293 }
294
295 private void removeTrackingRefUpdate(final ObjectId want) {
296 final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
297 while (i.hasNext()) {
298 final TrackingRefUpdate u = i.next();
299 if (u.getNewObjectId().equals(want))
300 i.remove();
301 }
302 }
303
304 private void removeFetchHeadRecord(final ObjectId want) {
305 final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
306 while (i.hasNext()) {
307 final FetchHeadRecord fh = i.next();
308 if (fh.newValue.equals(want))
309 i.remove();
310 }
311 }
312
313 private void updateFETCH_HEAD(final FetchResult result) throws IOException {
314 File meta = transport.local.getDirectory();
315 if (meta == null)
316 return;
317 final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD"));
318 try {
319 if (lock.lock()) {
320 final Writer w = new OutputStreamWriter(lock.getOutputStream());
321 try {
322 for (final FetchHeadRecord h : fetchHeadUpdates) {
323 h.write(w);
324 result.add(h);
325 }
326 } finally {
327 w.close();
328 }
329 lock.commit();
330 }
331 } finally {
332 lock.unlock();
333 }
334 }
335
336 private boolean askForIsComplete() throws TransportException {
337 try {
338 try (final ObjectWalk ow = new ObjectWalk(transport.local)) {
339 for (final ObjectId want : askFor.keySet())
340 ow.markStart(ow.parseAny(want));
341 for (final Ref ref : localRefs().values())
342 ow.markUninteresting(ow.parseAny(ref.getObjectId()));
343 ow.checkConnectivity();
344 }
345 return true;
346 } catch (MissingObjectException e) {
347 return false;
348 } catch (IOException e) {
349 throw new TransportException(JGitText.get().unableToCheckConnectivity, e);
350 }
351 }
352
353 private void expandWildcard(final RefSpec spec, final Set<Ref> matched)
354 throws TransportException {
355 for (final Ref src : conn.getRefs()) {
356 if (spec.matchSource(src) && matched.add(src))
357 want(src, spec.expandFromSource(src));
358 }
359 }
360
361 private void expandSingle(final RefSpec spec, final Set<Ref> matched)
362 throws TransportException {
363 final Ref src = conn.getRef(spec.getSource());
364 if (src == null) {
365 throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, spec.getSource()));
366 }
367 if (matched.add(src))
368 want(src, spec);
369 }
370
371 private Collection<Ref> expandAutoFollowTags() throws TransportException {
372 final Collection<Ref> additionalTags = new ArrayList<>();
373 final Map<String, Ref> haveRefs = localRefs();
374 for (final Ref r : conn.getRefs()) {
375 if (!isTag(r))
376 continue;
377
378 Ref local = haveRefs.get(r.getName());
379 if (local != null)
380
381
382 continue;
383
384 ObjectId obj = r.getPeeledObjectId();
385 if (obj == null)
386 obj = r.getObjectId();
387
388 if (askFor.containsKey(obj) || transport.local.hasObject(obj))
389 wantTag(r);
390 else
391 additionalTags.add(r);
392 }
393 return additionalTags;
394 }
395
396 private void expandFetchTags() throws TransportException {
397 final Map<String, Ref> haveRefs = localRefs();
398 for (final Ref r : conn.getRefs()) {
399 if (!isTag(r)) {
400 continue;
401 }
402 ObjectId id = r.getObjectId();
403 if (id == null) {
404 continue;
405 }
406 final Ref local = haveRefs.get(r.getName());
407 if (local == null || !id.equals(local.getObjectId())) {
408 wantTag(r);
409 }
410 }
411 }
412
413 private void wantTag(final Ref r) throws TransportException {
414 want(r, new RefSpec().setSource(r.getName())
415 .setDestination(r.getName()).setForceUpdate(true));
416 }
417
418 private void want(final Ref src, final RefSpec spec)
419 throws TransportException {
420 final ObjectId newId = src.getObjectId();
421 if (newId == null) {
422 throw new NullPointerException(MessageFormat.format(
423 JGitText.get().transportProvidedRefWithNoObjectId,
424 src.getName()));
425 }
426 if (spec.getDestination() != null) {
427 final TrackingRefUpdate tru = createUpdate(spec, newId);
428 if (newId.equals(tru.getOldObjectId()))
429 return;
430 localUpdates.add(tru);
431 }
432
433 askFor.put(newId, src);
434
435 final FetchHeadRecord fhr = new FetchHeadRecord();
436 fhr.newValue = newId;
437 fhr.notForMerge = spec.getDestination() != null;
438 fhr.sourceName = src.getName();
439 fhr.sourceURI = transport.getURI();
440 fetchHeadUpdates.add(fhr);
441 }
442
443 private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId)
444 throws TransportException {
445 Ref ref = localRefs().get(spec.getDestination());
446 ObjectId oldId = ref != null && ref.getObjectId() != null
447 ? ref.getObjectId()
448 : ObjectId.zeroId();
449 return new TrackingRefUpdate(
450 spec.isForceUpdate(),
451 spec.getSource(),
452 spec.getDestination(),
453 oldId,
454 newId);
455 }
456
457 private Map<String, Ref> localRefs() throws TransportException {
458 if (localRefs == null) {
459 try {
460 localRefs = transport.local.getRefDatabase()
461 .getRefs(RefDatabase.ALL);
462 } catch (IOException err) {
463 throw new TransportException(JGitText.get().cannotListRefs, err);
464 }
465 }
466 return localRefs;
467 }
468
469 private void deleteStaleTrackingRefs(FetchResult result,
470 BatchRefUpdate batch) throws IOException {
471 for (final Ref ref : localRefs().values()) {
472 final String refname = ref.getName();
473 for (final RefSpec spec : toFetch) {
474 if (spec.matchDestination(refname)) {
475 final RefSpec s = spec.expandFromDestination(refname);
476 if (result.getAdvertisedRef(s.getSource()) == null) {
477 deleteTrackingRef(result, batch, s, ref);
478 }
479 }
480 }
481 }
482 }
483
484 private void deleteTrackingRef(final FetchResult result,
485 final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) {
486 if (localRef.getObjectId() == null)
487 return;
488 TrackingRefUpdate update = new TrackingRefUpdate(
489 true,
490 spec.getSource(),
491 localRef.getName(),
492 localRef.getObjectId(),
493 ObjectId.zeroId());
494 result.add(update);
495 batch.addCommand(update.asReceiveCommand());
496 }
497
498 private static boolean isTag(final Ref r) {
499 return isTag(r.getName());
500 }
501
502 private static boolean isTag(final String name) {
503 return name.startsWith(Constants.R_TAGS);
504 }
505
506 private static String getFirstFailedRefName(BatchRefUpdate batch) {
507 for (ReceiveCommand cmd : batch.getCommands()) {
508 if (cmd.getResult() != ReceiveCommand.Result.OK)
509 return cmd.getRefName();
510 }
511 return "";
512 }
513 }