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