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