2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
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
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
;
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
;
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
;
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
) {
92 void execute(final ProgressMonitor monitor
, final FetchResult result
)
93 throws NotSupportedException
, TransportException
{
96 fetchHeadUpdates
.clear();
98 conn
= transport
.openFetch();
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
);
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
)
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.
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.
133 for (final Ref r
: additionalTags
) {
134 final ObjectId id
= r
.getPeeledObjectId();
135 if (id
== null || transport
.local
.hasObject(id
))
139 if (!askFor
.isEmpty() && (!includedTags
|| !askForIsComplete())) {
141 if (!askFor
.isEmpty())
142 conn
.doFetch(monitor
, askFor
.values());
149 final RevWalk walk
= new RevWalk(transport
.local
);
150 for (TrackingRefUpdate u
: localUpdates
) {
154 } catch (IOException err
) {
155 throw new TransportException("Failure updating tracking ref "
156 + u
.getLocalName() + ": " + err
.getMessage(), err
);
160 if (!fetchHeadUpdates
.isEmpty()) {
162 updateFETCH_HEAD(result
);
163 } catch (IOException err
) {
164 throw new TransportException("Failure updating FETCH_HEAD: "
165 + err
.getMessage(), err
);
170 private void closeConnection() {
177 private void reopenConnection() throws NotSupportedException
,
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());
198 for (final Ref want
: wants
) {
199 final Ref newRef
= avail
.get(want
.getObjectId());
200 if (newRef
!= null) {
201 askFor
.put(newRef
.getObjectId(), newRef
);
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
))
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
))
227 private void updateFETCH_HEAD(final FetchResult result
) throws IOException
{
228 final LockFile lock
= new LockFile(new File(transport
.local
229 .getDirectory(), "FETCH_HEAD"));
231 final PrintWriter pw
= new PrintWriter(new OutputStreamWriter(lock
232 .getOutputStream())) {
234 public void println() {
238 for (final FetchHeadRecord h
: fetchHeadUpdates
) {
247 private boolean askForIsComplete() throws TransportException
{
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();
256 } catch (MissingObjectException e
) {
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());
275 throw new TransportException("Remote does not have "
276 + spec
.getSource() + " available for fetch.");
278 if (matched
.add(src
))
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()) {
288 if (r
.getPeeledObjectId() == null) {
289 additionalTags
.add(r
);
293 final Ref local
= have
.get(r
.getName());
295 if (!r
.getObjectId().equals(local
.getObjectId()))
297 } else if (askFor
.containsKey(r
.getPeeledObjectId())
298 || transport
.local
.hasObject(r
.getPeeledObjectId()))
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()) {
311 final Ref local
= have
.get(r
.getName());
312 if (local
== null || !r
.getObjectId().equals(local
.getObjectId()))
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) {
327 final TrackingRefUpdate tru
= createUpdate(spec
, newId
);
328 if (newId
.equals(tru
.getOldObjectId()))
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
+ "/");