Switch jgit library to the EDL (3-clause BSD)
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / transport / FetchProcess.java
blobafaf9e25b9969f5201ab1481fa530146e2a74ce8
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.OutputStreamWriter;
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.Map;
52 import java.util.Set;
54 import org.spearce.jgit.errors.MissingObjectException;
55 import org.spearce.jgit.errors.NotSupportedException;
56 import org.spearce.jgit.errors.TransportException;
57 import org.spearce.jgit.lib.Constants;
58 import org.spearce.jgit.lib.LockFile;
59 import org.spearce.jgit.lib.ObjectId;
60 import org.spearce.jgit.lib.ProgressMonitor;
61 import org.spearce.jgit.lib.Ref;
62 import org.spearce.jgit.revwalk.ObjectWalk;
63 import org.spearce.jgit.revwalk.RevWalk;
65 class FetchProcess {
66 /** Transport we will fetch over. */
67 private final Transport transport;
69 /** List of things we want to fetch from the remote repository. */
70 private final Collection<RefSpec> toFetch;
72 /** How to handle annotated tags, if any are advertised. */
73 private final TagOpt tagopt;
75 /** Set of refs we will actually wind up asking to obtain. */
76 private final HashMap<ObjectId, Ref> askFor = new HashMap<ObjectId, Ref>();
78 /** Updates to local tracking branches (if any). */
79 private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<TrackingRefUpdate>();
81 /** Records to be recorded into FETCH_HEAD. */
82 private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<FetchHeadRecord>();
84 private FetchConnection conn;
86 FetchProcess(final Transport t, final Collection<RefSpec> f, final TagOpt o) {
87 transport = t;
88 toFetch = f;
89 tagopt = o;
92 void execute(final ProgressMonitor monitor, final FetchResult result)
93 throws NotSupportedException, TransportException {
94 askFor.clear();
95 localUpdates.clear();
96 fetchHeadUpdates.clear();
98 conn = transport.openFetch();
99 try {
100 result.setAdvertisedRefs(conn.getCachedRefs());
101 final Set<Ref> matched = new HashSet<Ref>();
102 for (final RefSpec spec : toFetch) {
103 if (spec.isWildcard())
104 expandWildcard(spec, matched);
105 else
106 expandSingle(spec, matched);
109 Collection<Ref> additionalTags = Collections.<Ref> emptyList();
110 if (tagopt == TagOpt.AUTO_FOLLOW)
111 additionalTags = expandAutoFollowTags();
112 else if (tagopt == TagOpt.FETCH_TAGS)
113 expandFetchTags();
115 final boolean includedTags;
116 if (!askFor.isEmpty() && !askForIsComplete()) {
117 conn.fetch(monitor, askFor.values());
118 includedTags = conn.didFetchIncludeTags();
120 // Connection was used for object transfer. If we
121 // do another fetch we must open a new connection.
123 closeConnection();
124 } else {
125 includedTags = false;
128 if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
129 // There are more tags that we want to follow, but
130 // not all were asked for on the initial request.
132 askFor.clear();
133 for (final Ref r : additionalTags) {
134 final ObjectId id = r.getPeeledObjectId();
135 if (id == null || transport.local.hasObject(id))
136 wantTag(r);
139 if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) {
140 reopenConnection();
141 if (!askFor.isEmpty())
142 conn.doFetch(monitor, askFor.values());
145 } finally {
146 closeConnection();
149 final RevWalk walk = new RevWalk(transport.local);
150 for (TrackingRefUpdate u : localUpdates) {
151 try {
152 u.update(walk);
153 result.add(u);
154 } catch (IOException err) {
155 throw new TransportException("Failure updating tracking ref "
156 + u.getLocalName() + ": " + err.getMessage(), err);
160 if (!fetchHeadUpdates.isEmpty()) {
161 try {
162 updateFETCH_HEAD(result);
163 } catch (IOException err) {
164 throw new TransportException("Failure updating FETCH_HEAD: "
165 + err.getMessage(), err);
170 private void closeConnection() {
171 if (conn != null) {
172 conn.close();
173 conn = null;
177 private void reopenConnection() throws NotSupportedException,
178 TransportException {
179 if (conn != null)
180 return;
182 conn = transport.openFetch();
184 // Since we opened a new connection we cannot be certain
185 // that the system we connected to has the same exact set
186 // of objects available (think round-robin DNS and mirrors
187 // that aren't updated at the same time).
189 // We rebuild our askFor list using only the refs that the
190 // new connection has offered to us.
192 final HashMap<ObjectId, Ref> avail = new HashMap<ObjectId, Ref>();
193 for (final Ref r : conn.getRefs())
194 avail.put(r.getObjectId(), r);
196 final Collection<Ref> wants = new ArrayList<Ref>(askFor.values());
197 askFor.clear();
198 for (final Ref want : wants) {
199 final Ref newRef = avail.get(want.getObjectId());
200 if (newRef != null) {
201 askFor.put(newRef.getObjectId(), newRef);
202 } else {
203 removeFetchHeadRecord(want.getObjectId());
204 removeTrackingRefUpdate(want.getObjectId());
209 private void removeTrackingRefUpdate(final ObjectId want) {
210 final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
211 while (i.hasNext()) {
212 final TrackingRefUpdate u = i.next();
213 if (u.getNewObjectId().equals(want))
214 i.remove();
218 private void removeFetchHeadRecord(final ObjectId want) {
219 final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
220 while (i.hasNext()) {
221 final FetchHeadRecord fh = i.next();
222 if (fh.newValue.equals(want))
223 i.remove();
227 private void updateFETCH_HEAD(final FetchResult result) throws IOException {
228 final LockFile lock = new LockFile(new File(transport.local
229 .getDirectory(), "FETCH_HEAD"));
230 if (lock.lock()) {
231 final PrintWriter pw = new PrintWriter(new OutputStreamWriter(lock
232 .getOutputStream())) {
233 @Override
234 public void println() {
235 print('\n');
238 for (final FetchHeadRecord h : fetchHeadUpdates) {
239 h.write(pw);
240 result.add(h);
242 pw.close();
243 lock.commit();
247 private boolean askForIsComplete() throws TransportException {
248 try {
249 final ObjectWalk ow = new ObjectWalk(transport.local);
250 for (final ObjectId want : askFor.keySet())
251 ow.markStart(ow.parseAny(want));
252 for (final Ref ref : transport.local.getAllRefs().values())
253 ow.markUninteresting(ow.parseAny(ref.getObjectId()));
254 ow.checkConnectivity();
255 return true;
256 } catch (MissingObjectException e) {
257 return false;
258 } catch (IOException e) {
259 throw new TransportException("Unable to check connectivity.", e);
263 private void expandWildcard(final RefSpec spec, final Set<Ref> matched)
264 throws TransportException {
265 for (final Ref src : conn.getRefs()) {
266 if (spec.matchSource(src) && matched.add(src))
267 want(src, spec.expandFromSource(src));
271 private void expandSingle(final RefSpec spec, final Set<Ref> matched)
272 throws TransportException {
273 final Ref src = conn.getRef(spec.getSource());
274 if (src == null) {
275 throw new TransportException("Remote does not have "
276 + spec.getSource() + " available for fetch.");
278 if (matched.add(src))
279 want(src, spec);
282 private Collection<Ref> expandAutoFollowTags() throws TransportException {
283 final Collection<Ref> additionalTags = new ArrayList<Ref>();
284 final Map<String, Ref> have = transport.local.getAllRefs();
285 for (final Ref r : conn.getRefs()) {
286 if (!isTag(r))
287 continue;
288 if (r.getPeeledObjectId() == null) {
289 additionalTags.add(r);
290 continue;
293 final Ref local = have.get(r.getName());
294 if (local != null) {
295 if (!r.getObjectId().equals(local.getObjectId()))
296 wantTag(r);
297 } else if (askFor.containsKey(r.getPeeledObjectId())
298 || transport.local.hasObject(r.getPeeledObjectId()))
299 wantTag(r);
300 else
301 additionalTags.add(r);
303 return additionalTags;
306 private void expandFetchTags() throws TransportException {
307 final Map<String, Ref> have = transport.local.getAllRefs();
308 for (final Ref r : conn.getRefs()) {
309 if (!isTag(r))
310 continue;
311 final Ref local = have.get(r.getName());
312 if (local == null || !r.getObjectId().equals(local.getObjectId()))
313 wantTag(r);
317 private void wantTag(final Ref r) throws TransportException {
318 want(r, new RefSpec().setSource(r.getName())
319 .setDestination(r.getName()));
322 private void want(final Ref src, final RefSpec spec)
323 throws TransportException {
324 final ObjectId newId = src.getObjectId();
325 if (spec.getDestination() != null) {
326 try {
327 final TrackingRefUpdate tru = createUpdate(spec, newId);
328 if (newId.equals(tru.getOldObjectId()))
329 return;
330 localUpdates.add(tru);
331 } catch (IOException err) {
332 // Bad symbolic ref? That is the most likely cause.
334 throw new TransportException("Cannot resolve"
335 + " local tracking ref " + spec.getDestination()
336 + " for updating.", err);
340 askFor.put(newId, src);
342 final FetchHeadRecord fhr = new FetchHeadRecord();
343 fhr.newValue = newId;
344 fhr.notForMerge = spec.getDestination() != null;
345 fhr.sourceName = src.getName();
346 fhr.sourceURI = transport.getURI();
347 fetchHeadUpdates.add(fhr);
350 private TrackingRefUpdate createUpdate(final RefSpec spec,
351 final ObjectId newId) throws IOException {
352 return new TrackingRefUpdate(transport.local, spec, newId, "fetch");
355 private static boolean isTag(final Ref r) {
356 return isTag(r.getName());
359 private static boolean isTag(final String name) {
360 return name.startsWith(Constants.TAGS_PREFIX + "/");