2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org
.spearce
.jgit
.transport
;
42 import java
.io
.IOException
;
43 import java
.net
.URISyntaxException
;
44 import java
.util
.ArrayList
;
45 import java
.util
.Collection
;
46 import java
.util
.Collections
;
47 import java
.util
.HashSet
;
48 import java
.util
.LinkedList
;
49 import java
.util
.List
;
52 import org
.spearce
.jgit
.errors
.NotSupportedException
;
53 import org
.spearce
.jgit
.errors
.TransportException
;
54 import org
.spearce
.jgit
.lib
.NullProgressMonitor
;
55 import org
.spearce
.jgit
.lib
.ProgressMonitor
;
56 import org
.spearce
.jgit
.lib
.Ref
;
57 import org
.spearce
.jgit
.lib
.Repository
;
58 import org
.spearce
.jgit
.lib
.TransferConfig
;
61 * Connects two Git repositories together and copies objects between them.
63 * A transport can be used for either fetching (copying objects into the
64 * caller's repository from the remote repository) or pushing (copying objects
65 * into the remote repository from the caller's repository). Each transport
66 * implementation is responsible for the details associated with establishing
67 * the network connection(s) necessary for the copy, as well as actually
68 * shuffling data back and forth.
70 * Transport instances and the connections they create are not thread-safe.
71 * Callers must ensure a transport is accessed by only one thread at a time.
73 public abstract class Transport
{
75 * Open a new transport instance to connect two repositories.
78 * existing local repository.
80 * location of the remote repository - may be URI or remote
82 * @return the new transport instance. Never null. In case of multiple URIs
83 * in remote configuration, only the first is chosen.
84 * @throws URISyntaxException
85 * the location is not a remote defined in the configuration
86 * file and is not a well-formed URL.
87 * @throws NotSupportedException
88 * the protocol specified is not supported.
90 public static Transport
open(final Repository local
, final String remote
)
91 throws NotSupportedException
, URISyntaxException
{
92 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
93 final List
<URIish
> uris
= cfg
.getURIs();
95 return open(local
, new URIish(remote
));
96 return open(local
, cfg
);
100 * Open new transport instances to connect two repositories.
103 * existing local repository.
105 * location of the remote repository - may be URI or remote
106 * configuration name.
107 * @return the list of new transport instances for every URI in remote
109 * @throws URISyntaxException
110 * the location is not a remote defined in the configuration
111 * file and is not a well-formed URL.
112 * @throws NotSupportedException
113 * the protocol specified is not supported.
115 public static List
<Transport
> openAll(final Repository local
,
116 final String remote
) throws NotSupportedException
,
118 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
119 final List
<URIish
> uris
= cfg
.getURIs();
120 if (uris
.size() == 0) {
121 final ArrayList
<Transport
> transports
= new ArrayList
<Transport
>(1);
122 transports
.add(open(local
, new URIish(remote
)));
125 return openAll(local
, cfg
);
129 * Open a new transport instance to connect two repositories.
132 * existing local repository.
134 * configuration describing how to connect to the remote
136 * @return the new transport instance. Never null. In case of multiple URIs
137 * in remote configuration, only the first is chosen.
138 * @throws NotSupportedException
139 * the protocol specified is not supported.
140 * @throws IllegalArgumentException
141 * if provided remote configuration doesn't have any URI
144 public static Transport
open(final Repository local
, final RemoteConfig cfg
)
145 throws NotSupportedException
{
146 if (cfg
.getURIs().isEmpty())
147 throw new IllegalArgumentException(
149 + cfg
.getName() + "\" has no URIs associated");
150 final Transport tn
= open(local
, cfg
.getURIs().get(0));
156 * Open new transport instances to connect two repositories.
159 * existing local repository.
161 * configuration describing how to connect to the remote
163 * @return the list of new transport instances for every URI in remote
165 * @throws NotSupportedException
166 * the protocol specified is not supported.
168 public static List
<Transport
> openAll(final Repository local
,
169 final RemoteConfig cfg
) throws NotSupportedException
{
170 final List
<URIish
> uris
= cfg
.getURIs();
171 final List
<Transport
> transports
= new ArrayList
<Transport
>(uris
.size());
172 for (final URIish uri
: uris
) {
173 final Transport tn
= open(local
, uri
);
181 * Open a new transport instance to connect two repositories.
184 * existing local repository.
186 * location of the remote repository.
187 * @return the new transport instance. Never null.
188 * @throws NotSupportedException
189 * the protocol specified is not supported.
191 public static Transport
open(final Repository local
, final URIish remote
)
192 throws NotSupportedException
{
193 if (TransportGitSsh
.canHandle(remote
))
194 return new TransportGitSsh(local
, remote
);
196 else if (TransportHttp
.canHandle(remote
))
197 return new TransportHttp(local
, remote
);
199 else if (TransportSftp
.canHandle(remote
))
200 return new TransportSftp(local
, remote
);
202 else if (TransportGitAnon
.canHandle(remote
))
203 return new TransportGitAnon(local
, remote
);
205 else if (TransportAmazonS3
.canHandle(remote
))
206 return new TransportAmazonS3(local
, remote
);
208 else if (TransportBundleFile
.canHandle(remote
))
209 return new TransportBundleFile(local
, remote
);
211 else if (TransportLocal
.canHandle(remote
))
212 return new TransportLocal(local
, remote
);
214 throw new NotSupportedException("URI not supported: " + remote
);
218 * Convert push remote refs update specification from {@link RefSpec} form
219 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
220 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
221 * always set as null. Tracking branch is configured if RefSpec destination
222 * matches source of any fetch ref spec for this transport remote
228 * collection of RefSpec to convert.
230 * fetch specifications used for finding localtracking refs. May
231 * be null or empty collection.
232 * @return collection of set up {@link RemoteRefUpdate}.
233 * @throws IOException
234 * when problem occurred during conversion or specification set
235 * up: most probably, missing objects or refs.
237 public static Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
238 final Repository db
, final Collection
<RefSpec
> specs
,
239 Collection
<RefSpec
> fetchSpecs
) throws IOException
{
240 if (fetchSpecs
== null)
241 fetchSpecs
= Collections
.emptyList();
242 final List
<RemoteRefUpdate
> result
= new LinkedList
<RemoteRefUpdate
>();
243 final Collection
<RefSpec
> procRefs
= expandPushWildcardsFor(db
, specs
);
245 for (final RefSpec spec
: procRefs
) {
246 final String srcRef
= spec
.getSource();
247 // null destination (no-colon in ref-spec) is a special case
248 final String remoteName
= (spec
.getDestination() == null ? spec
249 .getSource() : spec
.getDestination());
250 final boolean forceUpdate
= spec
.isForceUpdate();
251 final String localName
= findTrackingRefName(remoteName
, fetchSpecs
);
253 final RemoteRefUpdate rru
= new RemoteRefUpdate(db
, srcRef
,
254 remoteName
, forceUpdate
, localName
, null);
260 private static Collection
<RefSpec
> expandPushWildcardsFor(
261 final Repository db
, final Collection
<RefSpec
> specs
) {
262 final Map
<String
, Ref
> localRefs
= db
.getAllRefs();
263 final Collection
<RefSpec
> procRefs
= new HashSet
<RefSpec
>();
265 for (final RefSpec spec
: specs
) {
266 if (spec
.isWildcard()) {
267 for (final Ref localRef
: localRefs
.values()) {
268 if (spec
.matchSource(localRef
))
269 procRefs
.add(spec
.expandFromSource(localRef
));
278 private static String
findTrackingRefName(final String remoteName
,
279 final Collection
<RefSpec
> fetchSpecs
) {
280 // try to find matching tracking refs
281 for (final RefSpec fetchSpec
: fetchSpecs
) {
282 if (fetchSpec
.matchSource(remoteName
)) {
283 if (fetchSpec
.isWildcard())
284 return fetchSpec
.expandFromSource(remoteName
)
287 return fetchSpec
.getDestination();
294 * Default setting for {@link #fetchThin} option.
296 public static final boolean DEFAULT_FETCH_THIN
= true;
299 * Default setting for {@link #pushThin} option.
301 public static final boolean DEFAULT_PUSH_THIN
= false;
304 * Specification for fetch or push operations, to fetch or push all tags.
307 public static final RefSpec REFSPEC_TAGS
= new RefSpec(
308 "refs/tags/*:refs/tags/*");
311 * Specification for push operation, to push all refs under refs/heads. Acts
314 public static final RefSpec REFSPEC_PUSH_ALL
= new RefSpec(
315 "refs/heads/*:refs/heads/*");
317 /** The repository this transport fetches into, or pushes out of. */
318 protected final Repository local
;
320 /** The URI used to create this transport. */
321 protected final URIish uri
;
323 /** Name of the upload pack program, if it must be executed. */
324 private String optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
326 /** Specifications to apply during fetch. */
327 private List
<RefSpec
> fetch
= Collections
.emptyList();
330 * How {@link #fetch(ProgressMonitor, Collection)} should handle tags.
332 * We default to {@link TagOpt#NO_TAGS} so as to avoid fetching annotated
333 * tags during one-shot fetches used for later merges. This prevents
334 * dragging down tags from repositories that we do not have established
335 * tracking branches for. If we do not track the source repository, we most
336 * likely do not care about any tags it publishes.
338 private TagOpt tagopt
= TagOpt
.NO_TAGS
;
340 /** Should fetch request thin-pack if remote repository can produce it. */
341 private boolean fetchThin
= DEFAULT_FETCH_THIN
;
343 /** Name of the receive pack program, if it must be executed. */
344 private String optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
346 /** Specifications to apply during push. */
347 private List
<RefSpec
> push
= Collections
.emptyList();
349 /** Should push produce thin-pack when sending objects to remote repository. */
350 private boolean pushThin
= DEFAULT_PUSH_THIN
;
352 /** Should push just check for operation result, not really push. */
353 private boolean dryRun
;
355 /** Should an incoming (fetch) transfer validate objects? */
356 private boolean checkFetchedObjects
;
359 * Create a new transport instance.
362 * the repository this instance will fetch into, or push out of.
363 * This must be the repository passed to
364 * {@link #open(Repository, URIish)}.
366 * the URI used to access the remote repository. This must be the
367 * URI passed to {@link #open(Repository, URIish)}.
369 protected Transport(final Repository local
, final URIish uri
) {
370 final TransferConfig tc
= local
.getConfig().getTransfer();
373 this.checkFetchedObjects
= tc
.isFsckObjects();
377 * Get the URI this transport connects to.
379 * Each transport instance connects to at most one URI at any point in time.
381 * @return the URI describing the location of the remote repository.
383 public URIish
getURI() {
388 * Get the name of the remote executable providing upload-pack service.
390 * @return typically "git-upload-pack".
392 public String
getOptionUploadPack() {
393 return optionUploadPack
;
397 * Set the name of the remote executable providing upload-pack services.
400 * name of the executable.
402 public void setOptionUploadPack(final String where
) {
403 if (where
!= null && where
.length() > 0)
404 optionUploadPack
= where
;
406 optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
410 * Get the description of how annotated tags should be treated during fetch.
412 * @return option indicating the behavior of annotated tags in fetch.
414 public TagOpt
getTagOpt() {
419 * Set the description of how annotated tags should be treated on fetch.
422 * method to use when handling annotated tags.
424 public void setTagOpt(final TagOpt option
) {
425 tagopt
= option
!= null ? option
: TagOpt
.AUTO_FOLLOW
;
429 * Default setting is: {@link #DEFAULT_FETCH_THIN}
431 * @return true if fetch should request thin-pack when possible; false
435 public boolean isFetchThin() {
440 * Set the thin-pack preference for fetch operation. Default setting is:
441 * {@link #DEFAULT_FETCH_THIN}
444 * true when fetch should request thin-pack when possible; false
448 public void setFetchThin(final boolean fetchThin
) {
449 this.fetchThin
= fetchThin
;
453 * @return true if fetch will verify received objects are formatted
454 * correctly. Validating objects requires more CPU time on the
455 * client side of the connection.
457 public boolean isCheckFetchedObjects() {
458 return checkFetchedObjects
;
463 * true to enable checking received objects; false to assume all
464 * received objects are valid.
466 public void setCheckFetchedObjects(final boolean check
) {
467 checkFetchedObjects
= check
;
471 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
473 * @return remote executable providing receive-pack service for pack
477 public String
getOptionReceivePack() {
478 return optionReceivePack
;
482 * Set remote executable providing receive-pack service for pack transports.
483 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
485 * @param optionReceivePack
486 * remote executable, if null or empty default one is set;
488 public void setOptionReceivePack(String optionReceivePack
) {
489 if (optionReceivePack
!= null && optionReceivePack
.length() > 0)
490 this.optionReceivePack
= optionReceivePack
;
492 this.optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
496 * Default setting is: {@value #DEFAULT_PUSH_THIN}
498 * @return true if push should produce thin-pack in pack transports
501 public boolean isPushThin() {
506 * Set thin-pack preference for push operation. Default setting is:
507 * {@value #DEFAULT_PUSH_THIN}
510 * true when push should produce thin-pack in pack transports;
511 * false when it shouldn't
514 public void setPushThin(final boolean pushThin
) {
515 this.pushThin
= pushThin
;
519 * Apply provided remote configuration on this transport.
522 * configuration to apply on this transport.
524 public void applyConfig(final RemoteConfig cfg
) {
525 setOptionUploadPack(cfg
.getUploadPack());
526 fetch
= cfg
.getFetchRefSpecs();
527 setTagOpt(cfg
.getTagOpt());
528 optionReceivePack
= cfg
.getReceivePack();
529 push
= cfg
.getPushRefSpecs();
533 * @return true if push operation should just check for possible result and
534 * not really update remote refs, false otherwise - when push should
537 public boolean isDryRun() {
542 * Set dry run option for push operation.
545 * true if push operation should just check for possible result
546 * and not really update remote refs, false otherwise - when push
547 * should act normally.
549 public void setDryRun(final boolean dryRun
) {
550 this.dryRun
= dryRun
;
554 * Fetch objects and refs from the remote repository to the local one.
556 * This is a utility function providing standard fetch behavior. Local
557 * tracking refs associated with the remote repository are automatically
558 * updated if this transport was created from a {@link RemoteConfig} with
559 * fetch RefSpecs defined.
562 * progress monitor to inform the user about our processing
563 * activity. Must not be null. Use {@link NullProgressMonitor} if
564 * progress updates are not interesting or necessary.
566 * specification of refs to fetch locally. May be null or the
567 * empty collection to use the specifications from the
568 * RemoteConfig. Source for each RefSpec can't be null.
569 * @return information describing the tracking refs updated.
570 * @throws NotSupportedException
571 * this transport implementation does not support fetching
573 * @throws TransportException
574 * the remote connection could not be established or object
575 * copying (if necessary) failed or update specification was
578 public FetchResult
fetch(final ProgressMonitor monitor
,
579 Collection
<RefSpec
> toFetch
) throws NotSupportedException
,
581 if (toFetch
== null || toFetch
.isEmpty()) {
582 // If the caller did not ask for anything use the defaults.
585 throw new TransportException("Nothing to fetch.");
587 } else if (!fetch
.isEmpty()) {
588 // If the caller asked for something specific without giving
589 // us the local tracking branch see if we can update any of
590 // the local tracking branches without incurring additional
591 // object transfer overheads.
593 final Collection
<RefSpec
> tmp
= new ArrayList
<RefSpec
>(toFetch
);
594 for (final RefSpec requested
: toFetch
) {
595 final String reqSrc
= requested
.getSource();
596 for (final RefSpec configured
: fetch
) {
597 final String cfgSrc
= configured
.getSource();
598 final String cfgDst
= configured
.getDestination();
599 if (cfgSrc
.equals(reqSrc
) && cfgDst
!= null) {
608 final FetchResult result
= new FetchResult();
609 new FetchProcess(this, toFetch
, tagopt
).execute(monitor
, result
);
614 * Push objects and refs from the local repository to the remote one.
616 * This is a utility function providing standard push behavior. It updates
617 * remote refs and send there necessary objects according to remote ref
618 * update specification. After successful remote ref update, associated
619 * locally stored tracking branch is updated if set up accordingly. Detailed
620 * operation result is provided after execution.
622 * For setting up remote ref update specification from ref spec, see helper
623 * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs
624 * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using
625 * directly {@link RemoteRefUpdate} for more possibilities.
627 * When {@link #isDryRun()} is true, result of this operation is just
628 * estimation of real operation result, no real action is performed.
630 * @see RemoteRefUpdate
633 * progress monitor to inform the user about our processing
634 * activity. Must not be null. Use {@link NullProgressMonitor} if
635 * progress updates are not interesting or necessary.
637 * specification of refs to push. May be null or the empty
638 * collection to use the specifications from the RemoteConfig
639 * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No
640 * more than 1 RemoteRefUpdate with the same remoteName is
641 * allowed. These objects are modified during this call.
642 * @return information about results of remote refs updates, tracking refs
643 * updates and refs advertised by remote repository.
644 * @throws NotSupportedException
645 * this transport implementation does not support pushing
647 * @throws TransportException
648 * the remote connection could not be established or object
649 * copying (if necessary) failed at I/O or protocol level or
650 * update specification was incorrect.
652 public PushResult
push(final ProgressMonitor monitor
,
653 Collection
<RemoteRefUpdate
> toPush
) throws NotSupportedException
,
655 if (toPush
== null || toPush
.isEmpty()) {
656 // If the caller did not ask for anything use the defaults.
658 toPush
= findRemoteRefUpdatesFor(push
);
659 } catch (final IOException e
) {
660 throw new TransportException(
661 "Problem with resolving push ref specs locally: "
662 + e
.getMessage(), e
);
664 if (toPush
.isEmpty())
665 throw new TransportException("Nothing to push.");
667 final PushProcess pushProcess
= new PushProcess(this, toPush
);
668 return pushProcess
.execute(monitor
);
672 * Convert push remote refs update specification from {@link RefSpec} form
673 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
674 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
675 * always set as null. Tracking branch is configured if RefSpec destination
676 * matches source of any fetch ref spec for this transport remote
679 * Conversion is performed for context of this transport (database, fetch
683 * collection of RefSpec to convert.
684 * @return collection of set up {@link RemoteRefUpdate}.
685 * @throws IOException
686 * when problem occurred during conversion or specification set
687 * up: most probably, missing objects or refs.
689 public Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
690 final Collection
<RefSpec
> specs
) throws IOException
{
691 return findRemoteRefUpdatesFor(local
, specs
, fetch
);
695 * Begins a new connection for fetching from the remote repository.
697 * @return a fresh connection to fetch from the remote repository.
698 * @throws NotSupportedException
699 * the implementation does not support fetching.
700 * @throws TransportException
701 * the remote connection could not be established.
703 public abstract FetchConnection
openFetch() throws NotSupportedException
,
707 * Begins a new connection for pushing into the remote repository.
709 * @return a fresh connection to push into the remote repository.
710 * @throws NotSupportedException
711 * the implementation does not support pushing.
712 * @throws TransportException
713 * the remote connection could not be established
715 public abstract PushConnection
openPush() throws NotSupportedException
,
719 * Close any resources used by this transport.
721 * If the remote repository is contacted by a network socket this method
722 * must close that network socket, disconnecting the two peers. If the
723 * remote repository is actually local (same system) this method must close
724 * any open file handles used to read the "remote" repository.
726 public abstract void close();