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