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