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
;
60 * Connects two Git repositories together and copies objects between them.
62 * A transport can be used for either fetching (copying objects into the
63 * caller's repository from the remote repository) or pushing (copying objects
64 * into the remote repository from the caller's repository). Each transport
65 * implementation is responsible for the details associated with establishing
66 * the network connection(s) necessary for the copy, as well as actually
67 * shuffling data back and forth.
69 * Transport instances and the connections they create are not thread-safe.
70 * Callers must ensure a transport is accessed by only one thread at a time.
72 public abstract class Transport
{
74 * Open a new transport instance to connect two repositories.
77 * existing local repository.
79 * location of the remote repository - may be URI or remote
81 * @return the new transport instance. Never null. In case of multiple URIs
82 * in remote configuration, only the first is chosen.
83 * @throws URISyntaxException
84 * the location is not a remote defined in the configuration
85 * file and is not a well-formed URL.
86 * @throws NotSupportedException
87 * the protocol specified is not supported.
89 public static Transport
open(final Repository local
, final String remote
)
90 throws NotSupportedException
, URISyntaxException
{
91 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
92 final List
<URIish
> uris
= cfg
.getURIs();
94 return open(local
, new URIish(remote
));
95 return open(local
, cfg
);
99 * Open new transport instances to connect two repositories.
102 * existing local repository.
104 * location of the remote repository - may be URI or remote
105 * configuration name.
106 * @return the list of new transport instances for every URI in remote
108 * @throws URISyntaxException
109 * the location is not a remote defined in the configuration
110 * file and is not a well-formed URL.
111 * @throws NotSupportedException
112 * the protocol specified is not supported.
114 public static List
<Transport
> openAll(final Repository local
,
115 final String remote
) throws NotSupportedException
,
117 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
118 final List
<URIish
> uris
= cfg
.getURIs();
119 if (uris
.size() == 0) {
120 final ArrayList
<Transport
> transports
= new ArrayList
<Transport
>(1);
121 transports
.add(open(local
, new URIish(remote
)));
124 return openAll(local
, cfg
);
128 * Open a new transport instance to connect two repositories.
131 * existing local repository.
133 * configuration describing how to connect to the remote
135 * @return the new transport instance. Never null. In case of multiple URIs
136 * in remote configuration, only the first is chosen.
137 * @throws NotSupportedException
138 * the protocol specified is not supported.
139 * @throws IllegalArgumentException
140 * if provided remote configuration doesn't have any URI
143 public static Transport
open(final Repository local
, final RemoteConfig cfg
)
144 throws NotSupportedException
{
145 if (cfg
.getURIs().isEmpty())
146 throw new IllegalArgumentException(
148 + cfg
.getName() + "\" has no URIs associated");
149 final Transport tn
= open(local
, cfg
.getURIs().get(0));
155 * Open new transport instances to connect two repositories.
158 * existing local repository.
160 * configuration describing how to connect to the remote
162 * @return the list of new transport instances for every URI in remote
164 * @throws NotSupportedException
165 * the protocol specified is not supported.
167 public static List
<Transport
> openAll(final Repository local
,
168 final RemoteConfig cfg
) throws NotSupportedException
{
169 final List
<URIish
> uris
= cfg
.getURIs();
170 final List
<Transport
> transports
= new ArrayList
<Transport
>(uris
.size());
171 for (final URIish uri
: uris
) {
172 final Transport tn
= open(local
, uri
);
180 * Open a new transport instance to connect two repositories.
183 * existing local repository.
185 * location of the remote repository.
186 * @return the new transport instance. Never null.
187 * @throws NotSupportedException
188 * the protocol specified is not supported.
190 public static Transport
open(final Repository local
, final URIish remote
)
191 throws NotSupportedException
{
192 if (TransportGitSsh
.canHandle(remote
))
193 return new TransportGitSsh(local
, remote
);
195 else if (TransportHttp
.canHandle(remote
))
196 return new TransportHttp(local
, remote
);
198 else if (TransportSftp
.canHandle(remote
))
199 return new TransportSftp(local
, remote
);
201 else if (TransportGitAnon
.canHandle(remote
))
202 return new TransportGitAnon(local
, remote
);
204 else if (TransportAmazonS3
.canHandle(remote
))
205 return new TransportAmazonS3(local
, remote
);
207 else if (TransportBundleFile
.canHandle(remote
))
208 return new TransportBundleFile(local
, remote
);
210 else if (TransportLocal
.canHandle(remote
))
211 return new TransportLocal(local
, remote
);
213 throw new NotSupportedException("URI not supported: " + remote
);
217 * Convert push remote refs update specification from {@link RefSpec} form
218 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
219 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
220 * always set as null. Tracking branch is configured if RefSpec destination
221 * matches source of any fetch ref spec for this transport remote
227 * collection of RefSpec to convert.
229 * fetch specifications used for finding localtracking refs. May
230 * be null or empty collection.
231 * @return collection of set up {@link RemoteRefUpdate}.
232 * @throws IOException
233 * when problem occurred during conversion or specification set
234 * up: most probably, missing objects or refs.
236 public static Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
237 final Repository db
, final Collection
<RefSpec
> specs
,
238 Collection
<RefSpec
> fetchSpecs
) throws IOException
{
239 if (fetchSpecs
== null)
240 fetchSpecs
= Collections
.emptyList();
241 final List
<RemoteRefUpdate
> result
= new LinkedList
<RemoteRefUpdate
>();
242 final Collection
<RefSpec
> procRefs
= expandPushWildcardsFor(db
, specs
);
244 for (final RefSpec spec
: procRefs
) {
245 final String srcRef
= spec
.getSource();
246 // null destination (no-colon in ref-spec) is a special case
247 final String remoteName
= (spec
.getDestination() == null ? spec
248 .getSource() : spec
.getDestination());
249 final boolean forceUpdate
= spec
.isForceUpdate();
250 final String localName
= findTrackingRefName(remoteName
, fetchSpecs
);
252 final RemoteRefUpdate rru
= new RemoteRefUpdate(db
, srcRef
,
253 remoteName
, forceUpdate
, localName
, null);
259 private static Collection
<RefSpec
> expandPushWildcardsFor(
260 final Repository db
, final Collection
<RefSpec
> specs
) {
261 final Map
<String
, Ref
> localRefs
= db
.getAllRefs();
262 final Collection
<RefSpec
> procRefs
= new HashSet
<RefSpec
>();
264 for (final RefSpec spec
: specs
) {
265 if (spec
.isWildcard()) {
266 for (final Ref localRef
: localRefs
.values()) {
267 if (spec
.matchSource(localRef
))
268 procRefs
.add(spec
.expandFromSource(localRef
));
277 private static String
findTrackingRefName(final String remoteName
,
278 final Collection
<RefSpec
> fetchSpecs
) {
279 // try to find matching tracking refs
280 for (final RefSpec fetchSpec
: fetchSpecs
) {
281 if (fetchSpec
.matchSource(remoteName
)) {
282 if (fetchSpec
.isWildcard())
283 return fetchSpec
.expandFromSource(remoteName
)
286 return fetchSpec
.getDestination();
293 * Default setting for {@link #fetchThin} option.
295 public static final boolean DEFAULT_FETCH_THIN
= true;
298 * Default setting for {@link #pushThin} option.
300 public static final boolean DEFAULT_PUSH_THIN
= false;
303 * Specification for fetch or push operations, to fetch or push all tags.
306 public static final RefSpec REFSPEC_TAGS
= new RefSpec(
307 "refs/tags/*:refs/tags/*");
310 * Specification for push operation, to push all refs under refs/heads. Acts
313 public static final RefSpec REFSPEC_PUSH_ALL
= new RefSpec(
314 "refs/heads/*:refs/heads/*");
316 /** The repository this transport fetches into, or pushes out of. */
317 protected final Repository local
;
319 /** The URI used to create this transport. */
320 protected final URIish uri
;
322 /** Name of the upload pack program, if it must be executed. */
323 private String optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
325 /** Specifications to apply during fetch. */
326 private List
<RefSpec
> fetch
= Collections
.emptyList();
329 * How {@link #fetch(ProgressMonitor, Collection)} should handle tags.
331 * We default to {@link TagOpt#NO_TAGS} so as to avoid fetching annotated
332 * tags during one-shot fetches used for later merges. This prevents
333 * dragging down tags from repositories that we do not have established
334 * tracking branches for. If we do not track the source repository, we most
335 * likely do not care about any tags it publishes.
337 private TagOpt tagopt
= TagOpt
.NO_TAGS
;
339 /** Should fetch request thin-pack if remote repository can produce it. */
340 private boolean fetchThin
= DEFAULT_FETCH_THIN
;
342 /** Name of the receive pack program, if it must be executed. */
343 private String optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
345 /** Specifications to apply during push. */
346 private List
<RefSpec
> push
= Collections
.emptyList();
348 /** Should push produce thin-pack when sending objects to remote repository. */
349 private boolean pushThin
= DEFAULT_PUSH_THIN
;
351 /** Should push just check for operation result, not really push. */
352 private boolean dryRun
;
355 * Create a new transport instance.
358 * the repository this instance will fetch into, or push out of.
359 * This must be the repository passed to
360 * {@link #open(Repository, URIish)}.
362 * the URI used to access the remote repository. This must be the
363 * URI passed to {@link #open(Repository, URIish)}.
365 protected Transport(final Repository local
, final URIish uri
) {
371 * Get the URI this transport connects to.
373 * Each transport instance connects to at most one URI at any point in time.
375 * @return the URI describing the location of the remote repository.
377 public URIish
getURI() {
382 * Get the name of the remote executable providing upload-pack service.
384 * @return typically "git-upload-pack".
386 public String
getOptionUploadPack() {
387 return optionUploadPack
;
391 * Set the name of the remote executable providing upload-pack services.
394 * name of the executable.
396 public void setOptionUploadPack(final String where
) {
397 if (where
!= null && where
.length() > 0)
398 optionUploadPack
= where
;
400 optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
404 * Get the description of how annotated tags should be treated during fetch.
406 * @return option indicating the behavior of annotated tags in fetch.
408 public TagOpt
getTagOpt() {
413 * Set the description of how annotated tags should be treated on fetch.
416 * method to use when handling annotated tags.
418 public void setTagOpt(final TagOpt option
) {
419 tagopt
= option
!= null ? option
: TagOpt
.AUTO_FOLLOW
;
423 * Default setting is: {@link #DEFAULT_FETCH_THIN}
425 * @return true if fetch should request thin-pack when possible; false
429 public boolean isFetchThin() {
434 * Set the thin-pack preference for fetch operation. Default setting is:
435 * {@link #DEFAULT_FETCH_THIN}
438 * true when fetch should request thin-pack when possible; false
442 public void setFetchThin(final boolean fetchThin
) {
443 this.fetchThin
= fetchThin
;
447 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
449 * @return remote executable providing receive-pack service for pack
453 public String
getOptionReceivePack() {
454 return optionReceivePack
;
458 * Set remote executable providing receive-pack service for pack transports.
459 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
461 * @param optionReceivePack
462 * remote executable, if null or empty default one is set;
464 public void setOptionReceivePack(String optionReceivePack
) {
465 if (optionReceivePack
!= null && optionReceivePack
.length() > 0)
466 this.optionReceivePack
= optionReceivePack
;
468 this.optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
472 * Default setting is: {@value #DEFAULT_PUSH_THIN}
474 * @return true if push should produce thin-pack in pack transports
477 public boolean isPushThin() {
482 * Set thin-pack preference for push operation. Default setting is:
483 * {@value #DEFAULT_PUSH_THIN}
486 * true when push should produce thin-pack in pack transports;
487 * false when it shouldn't
490 public void setPushThin(final boolean pushThin
) {
491 this.pushThin
= pushThin
;
495 * Apply provided remote configuration on this transport.
498 * configuration to apply on this transport.
500 public void applyConfig(final RemoteConfig cfg
) {
501 setOptionUploadPack(cfg
.getUploadPack());
502 fetch
= cfg
.getFetchRefSpecs();
503 setTagOpt(cfg
.getTagOpt());
504 optionReceivePack
= cfg
.getReceivePack();
505 push
= cfg
.getPushRefSpecs();
509 * @return true if push operation should just check for possible result and
510 * not really update remote refs, false otherwise - when push should
513 public boolean isDryRun() {
518 * Set dry run option for push operation.
521 * true if push operation should just check for possible result
522 * and not really update remote refs, false otherwise - when push
523 * should act normally.
525 public void setDryRun(final boolean dryRun
) {
526 this.dryRun
= dryRun
;
530 * Fetch objects and refs from the remote repository to the local one.
532 * This is a utility function providing standard fetch behavior. Local
533 * tracking refs associated with the remote repository are automatically
534 * updated if this transport was created from a {@link RemoteConfig} with
535 * fetch RefSpecs defined.
538 * progress monitor to inform the user about our processing
539 * activity. Must not be null. Use {@link NullProgressMonitor} if
540 * progress updates are not interesting or necessary.
542 * specification of refs to fetch locally. May be null or the
543 * empty collection to use the specifications from the
544 * RemoteConfig. Source for each RefSpec can't be null.
545 * @return information describing the tracking refs updated.
546 * @throws NotSupportedException
547 * this transport implementation does not support fetching
549 * @throws TransportException
550 * the remote connection could not be established or object
551 * copying (if necessary) failed or update specification was
554 public FetchResult
fetch(final ProgressMonitor monitor
,
555 Collection
<RefSpec
> toFetch
) throws NotSupportedException
,
557 if (toFetch
== null || toFetch
.isEmpty()) {
558 // If the caller did not ask for anything use the defaults.
561 throw new TransportException("Nothing to fetch.");
563 } else if (!fetch
.isEmpty()) {
564 // If the caller asked for something specific without giving
565 // us the local tracking branch see if we can update any of
566 // the local tracking branches without incurring additional
567 // object transfer overheads.
569 final Collection
<RefSpec
> tmp
= new ArrayList
<RefSpec
>(toFetch
);
570 for (final RefSpec requested
: toFetch
) {
571 final String reqSrc
= requested
.getSource();
572 for (final RefSpec configured
: fetch
) {
573 final String cfgSrc
= configured
.getSource();
574 final String cfgDst
= configured
.getDestination();
575 if (cfgSrc
.equals(reqSrc
) && cfgDst
!= null) {
584 final FetchResult result
= new FetchResult();
585 new FetchProcess(this, toFetch
, tagopt
).execute(monitor
, result
);
590 * Push objects and refs from the local repository to the remote one.
592 * This is a utility function providing standard push behavior. It updates
593 * remote refs and send there necessary objects according to remote ref
594 * update specification. After successful remote ref update, associated
595 * locally stored tracking branch is updated if set up accordingly. Detailed
596 * operation result is provided after execution.
598 * For setting up remote ref update specification from ref spec, see helper
599 * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs
600 * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using
601 * directly {@link RemoteRefUpdate} for more possibilities.
603 * When {@link #isDryRun()} is true, result of this operation is just
604 * estimation of real operation result, no real action is performed.
606 * @see RemoteRefUpdate
609 * progress monitor to inform the user about our processing
610 * activity. Must not be null. Use {@link NullProgressMonitor} if
611 * progress updates are not interesting or necessary.
613 * specification of refs to push. May be null or the empty
614 * collection to use the specifications from the RemoteConfig
615 * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No
616 * more than 1 RemoteRefUpdate with the same remoteName is
617 * allowed. These objects are modified during this call.
618 * @return information about results of remote refs updates, tracking refs
619 * updates and refs advertised by remote repository.
620 * @throws NotSupportedException
621 * this transport implementation does not support pushing
623 * @throws TransportException
624 * the remote connection could not be established or object
625 * copying (if necessary) failed at I/O or protocol level or
626 * update specification was incorrect.
628 public PushResult
push(final ProgressMonitor monitor
,
629 Collection
<RemoteRefUpdate
> toPush
) throws NotSupportedException
,
631 if (toPush
== null || toPush
.isEmpty()) {
632 // If the caller did not ask for anything use the defaults.
634 toPush
= findRemoteRefUpdatesFor(push
);
635 } catch (final IOException e
) {
636 throw new TransportException(
637 "Problem with resolving push ref specs locally: "
638 + e
.getMessage(), e
);
640 if (toPush
.isEmpty())
641 throw new TransportException("Nothing to push.");
643 final PushProcess pushProcess
= new PushProcess(this, toPush
);
644 return pushProcess
.execute(monitor
);
648 * Convert push remote refs update specification from {@link RefSpec} form
649 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
650 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
651 * always set as null. Tracking branch is configured if RefSpec destination
652 * matches source of any fetch ref spec for this transport remote
655 * Conversion is performed for context of this transport (database, fetch
659 * collection of RefSpec to convert.
660 * @return collection of set up {@link RemoteRefUpdate}.
661 * @throws IOException
662 * when problem occurred during conversion or specification set
663 * up: most probably, missing objects or refs.
665 public Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
666 final Collection
<RefSpec
> specs
) throws IOException
{
667 return findRemoteRefUpdatesFor(local
, specs
, fetch
);
671 * Begins a new connection for fetching from the remote repository.
673 * @return a fresh connection to fetch from the remote repository.
674 * @throws NotSupportedException
675 * the implementation does not support fetching.
676 * @throws TransportException
677 * the remote connection could not be established.
679 public abstract FetchConnection
openFetch() throws NotSupportedException
,
683 * Begins a new connection for pushing into the remote repository.
685 * @return a fresh connection to push into the remote repository.
686 * @throws NotSupportedException
687 * the implementation does not support pushing.
688 * @throws TransportException
689 * the remote connection could not be established
691 public abstract PushConnection
openPush() throws NotSupportedException
,
695 * Close any resources used by this transport.
697 * If the remote repository is contacted by a network socket this method
698 * must close that network socket, disconnecting the two peers. If the
699 * remote repository is actually local (same system) this method must close
700 * any open file handles used to read the "remote" repository.
702 public abstract void close();