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