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