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
.Constants
;
55 import org
.spearce
.jgit
.lib
.NullProgressMonitor
;
56 import org
.spearce
.jgit
.lib
.ProgressMonitor
;
57 import org
.spearce
.jgit
.lib
.Ref
;
58 import org
.spearce
.jgit
.lib
.Repository
;
59 import org
.spearce
.jgit
.lib
.TransferConfig
;
62 * Connects two Git repositories together and copies objects between them.
64 * A transport can be used for either fetching (copying objects into the
65 * caller's repository from the remote repository) or pushing (copying objects
66 * into the remote repository from the caller's repository). Each transport
67 * implementation is responsible for the details associated with establishing
68 * the network connection(s) necessary for the copy, as well as actually
69 * shuffling data back and forth.
71 * Transport instances and the connections they create are not thread-safe.
72 * Callers must ensure a transport is accessed by only one thread at a time.
74 public abstract class Transport
{
76 * Open a new transport instance to connect two repositories.
79 * existing local repository.
81 * location of the remote repository - may be URI or remote
83 * @return the new transport instance. Never null. In case of multiple URIs
84 * in remote configuration, only the first is chosen.
85 * @throws URISyntaxException
86 * the location is not a remote defined in the configuration
87 * file and is not a well-formed URL.
88 * @throws NotSupportedException
89 * the protocol specified is not supported.
91 public static Transport
open(final Repository local
, final String remote
)
92 throws NotSupportedException
, URISyntaxException
{
93 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
94 final List
<URIish
> uris
= cfg
.getURIs();
96 return open(local
, new URIish(remote
));
97 return open(local
, cfg
);
101 * Open new transport instances to connect two repositories.
104 * existing local repository.
106 * location of the remote repository - may be URI or remote
107 * configuration name.
108 * @return the list of new transport instances for every URI in remote
110 * @throws URISyntaxException
111 * the location is not a remote defined in the configuration
112 * file and is not a well-formed URL.
113 * @throws NotSupportedException
114 * the protocol specified is not supported.
116 public static List
<Transport
> openAll(final Repository local
,
117 final String remote
) throws NotSupportedException
,
119 final RemoteConfig cfg
= new RemoteConfig(local
.getConfig(), remote
);
120 final List
<URIish
> uris
= cfg
.getURIs();
121 if (uris
.size() == 0) {
122 final ArrayList
<Transport
> transports
= new ArrayList
<Transport
>(1);
123 transports
.add(open(local
, new URIish(remote
)));
126 return openAll(local
, cfg
);
130 * Open a new transport instance to connect two repositories.
133 * existing local repository.
135 * configuration describing how to connect to the remote
137 * @return the new transport instance. Never null. In case of multiple URIs
138 * in remote configuration, only the first is chosen.
139 * @throws NotSupportedException
140 * the protocol specified is not supported.
141 * @throws IllegalArgumentException
142 * if provided remote configuration doesn't have any URI
145 public static Transport
open(final Repository local
, final RemoteConfig cfg
)
146 throws NotSupportedException
{
147 if (cfg
.getURIs().isEmpty())
148 throw new IllegalArgumentException(
150 + cfg
.getName() + "\" has no URIs associated");
151 final Transport tn
= open(local
, cfg
.getURIs().get(0));
157 * Open new transport instances to connect two repositories.
160 * existing local repository.
162 * configuration describing how to connect to the remote
164 * @return the list of new transport instances for every URI in remote
166 * @throws NotSupportedException
167 * the protocol specified is not supported.
169 public static List
<Transport
> openAll(final Repository local
,
170 final RemoteConfig cfg
) throws NotSupportedException
{
171 final List
<URIish
> uris
= cfg
.getURIs();
172 final List
<Transport
> transports
= new ArrayList
<Transport
>(uris
.size());
173 for (final URIish uri
: uris
) {
174 final Transport tn
= open(local
, uri
);
182 * Open a new transport instance to connect two repositories.
185 * existing local repository.
187 * location of the remote repository.
188 * @return the new transport instance. Never null.
189 * @throws NotSupportedException
190 * the protocol specified is not supported.
192 public static Transport
open(final Repository local
, final URIish remote
)
193 throws NotSupportedException
{
194 if (TransportGitSsh
.canHandle(remote
))
195 return new TransportGitSsh(local
, remote
);
197 else if (TransportHttp
.canHandle(remote
))
198 return new TransportHttp(local
, remote
);
200 else if (TransportSftp
.canHandle(remote
))
201 return new TransportSftp(local
, remote
);
203 else if (TransportGitAnon
.canHandle(remote
))
204 return new TransportGitAnon(local
, remote
);
206 else if (TransportAmazonS3
.canHandle(remote
))
207 return new TransportAmazonS3(local
, remote
);
209 else if (TransportBundleFile
.canHandle(remote
))
210 return new TransportBundleFile(local
, remote
);
212 else if (TransportLocal
.canHandle(remote
))
213 return new TransportLocal(local
, remote
);
215 throw new NotSupportedException("URI not supported: " + remote
);
219 * Convert push remote refs update specification from {@link RefSpec} form
220 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
221 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
222 * always set as null. Tracking branch is configured if RefSpec destination
223 * matches source of any fetch ref spec for this transport remote
229 * collection of RefSpec to convert.
231 * fetch specifications used for finding localtracking refs. May
232 * be null or empty collection.
233 * @return collection of set up {@link RemoteRefUpdate}.
234 * @throws IOException
235 * when problem occurred during conversion or specification set
236 * up: most probably, missing objects or refs.
238 public static Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
239 final Repository db
, final Collection
<RefSpec
> specs
,
240 Collection
<RefSpec
> fetchSpecs
) throws IOException
{
241 if (fetchSpecs
== null)
242 fetchSpecs
= Collections
.emptyList();
243 final List
<RemoteRefUpdate
> result
= new LinkedList
<RemoteRefUpdate
>();
244 final Collection
<RefSpec
> procRefs
= expandPushWildcardsFor(db
, specs
);
246 for (final RefSpec spec
: procRefs
) {
247 String srcRef
= spec
.getSource();
248 final Ref src
= db
.getRef(srcRef
);
250 srcRef
= src
.getName();
251 String remoteName
= spec
.getDestination();
252 // null destination (no-colon in ref-spec) is a special case
253 if (remoteName
== null) {
256 if (!remoteName
.startsWith(Constants
.R_REFS
)) {
257 // null source is another special case (delete)
258 if (srcRef
!= null) {
259 // assume the same type of ref at the destination
260 String srcPrefix
= srcRef
.substring(0, srcRef
.indexOf('/', Constants
.R_REFS
.length()));
261 remoteName
= srcPrefix
+ "/" + remoteName
;
265 final boolean forceUpdate
= spec
.isForceUpdate();
266 final String localName
= findTrackingRefName(remoteName
, fetchSpecs
);
268 final RemoteRefUpdate rru
= new RemoteRefUpdate(db
, srcRef
,
269 remoteName
, forceUpdate
, localName
, null);
275 private static Collection
<RefSpec
> expandPushWildcardsFor(
276 final Repository db
, final Collection
<RefSpec
> specs
) {
277 final Map
<String
, Ref
> localRefs
= db
.getAllRefs();
278 final Collection
<RefSpec
> procRefs
= new HashSet
<RefSpec
>();
280 for (final RefSpec spec
: specs
) {
281 if (spec
.isWildcard()) {
282 for (final Ref localRef
: localRefs
.values()) {
283 if (spec
.matchSource(localRef
))
284 procRefs
.add(spec
.expandFromSource(localRef
));
293 private static String
findTrackingRefName(final String remoteName
,
294 final Collection
<RefSpec
> fetchSpecs
) {
295 // try to find matching tracking refs
296 for (final RefSpec fetchSpec
: fetchSpecs
) {
297 if (fetchSpec
.matchSource(remoteName
)) {
298 if (fetchSpec
.isWildcard())
299 return fetchSpec
.expandFromSource(remoteName
)
302 return fetchSpec
.getDestination();
309 * Default setting for {@link #fetchThin} option.
311 public static final boolean DEFAULT_FETCH_THIN
= true;
314 * Default setting for {@link #pushThin} option.
316 public static final boolean DEFAULT_PUSH_THIN
= false;
319 * Specification for fetch or push operations, to fetch or push all tags.
322 public static final RefSpec REFSPEC_TAGS
= new RefSpec(
323 "refs/tags/*:refs/tags/*");
326 * Specification for push operation, to push all refs under refs/heads. Acts
329 public static final RefSpec REFSPEC_PUSH_ALL
= new RefSpec(
330 "refs/heads/*:refs/heads/*");
332 /** The repository this transport fetches into, or pushes out of. */
333 protected final Repository local
;
335 /** The URI used to create this transport. */
336 protected final URIish uri
;
338 /** Name of the upload pack program, if it must be executed. */
339 private String optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
341 /** Specifications to apply during fetch. */
342 private List
<RefSpec
> fetch
= Collections
.emptyList();
345 * How {@link #fetch(ProgressMonitor, Collection)} should handle tags.
347 * We default to {@link TagOpt#NO_TAGS} so as to avoid fetching annotated
348 * tags during one-shot fetches used for later merges. This prevents
349 * dragging down tags from repositories that we do not have established
350 * tracking branches for. If we do not track the source repository, we most
351 * likely do not care about any tags it publishes.
353 private TagOpt tagopt
= TagOpt
.NO_TAGS
;
355 /** Should fetch request thin-pack if remote repository can produce it. */
356 private boolean fetchThin
= DEFAULT_FETCH_THIN
;
358 /** Name of the receive pack program, if it must be executed. */
359 private String optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
361 /** Specifications to apply during push. */
362 private List
<RefSpec
> push
= Collections
.emptyList();
364 /** Should push produce thin-pack when sending objects to remote repository. */
365 private boolean pushThin
= DEFAULT_PUSH_THIN
;
367 /** Should push just check for operation result, not really push. */
368 private boolean dryRun
;
370 /** Should an incoming (fetch) transfer validate objects? */
371 private boolean checkFetchedObjects
;
373 /** Should refs no longer on the source be pruned from the destination? */
374 private boolean removeDeletedRefs
;
377 * Create a new transport instance.
380 * the repository this instance will fetch into, or push out of.
381 * This must be the repository passed to
382 * {@link #open(Repository, URIish)}.
384 * the URI used to access the remote repository. This must be the
385 * URI passed to {@link #open(Repository, URIish)}.
387 protected Transport(final Repository local
, final URIish uri
) {
388 final TransferConfig tc
= local
.getConfig().getTransfer();
391 this.checkFetchedObjects
= tc
.isFsckObjects();
395 * Get the URI this transport connects to.
397 * Each transport instance connects to at most one URI at any point in time.
399 * @return the URI describing the location of the remote repository.
401 public URIish
getURI() {
406 * Get the name of the remote executable providing upload-pack service.
408 * @return typically "git-upload-pack".
410 public String
getOptionUploadPack() {
411 return optionUploadPack
;
415 * Set the name of the remote executable providing upload-pack services.
418 * name of the executable.
420 public void setOptionUploadPack(final String where
) {
421 if (where
!= null && where
.length() > 0)
422 optionUploadPack
= where
;
424 optionUploadPack
= RemoteConfig
.DEFAULT_UPLOAD_PACK
;
428 * Get the description of how annotated tags should be treated during fetch.
430 * @return option indicating the behavior of annotated tags in fetch.
432 public TagOpt
getTagOpt() {
437 * Set the description of how annotated tags should be treated on fetch.
440 * method to use when handling annotated tags.
442 public void setTagOpt(final TagOpt option
) {
443 tagopt
= option
!= null ? option
: TagOpt
.AUTO_FOLLOW
;
447 * Default setting is: {@link #DEFAULT_FETCH_THIN}
449 * @return true if fetch should request thin-pack when possible; false
453 public boolean isFetchThin() {
458 * Set the thin-pack preference for fetch operation. Default setting is:
459 * {@link #DEFAULT_FETCH_THIN}
462 * true when fetch should request thin-pack when possible; false
466 public void setFetchThin(final boolean fetchThin
) {
467 this.fetchThin
= fetchThin
;
471 * @return true if fetch will verify received objects are formatted
472 * correctly. Validating objects requires more CPU time on the
473 * client side of the connection.
475 public boolean isCheckFetchedObjects() {
476 return checkFetchedObjects
;
481 * true to enable checking received objects; false to assume all
482 * received objects are valid.
484 public void setCheckFetchedObjects(final boolean check
) {
485 checkFetchedObjects
= check
;
489 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
491 * @return remote executable providing receive-pack service for pack
495 public String
getOptionReceivePack() {
496 return optionReceivePack
;
500 * Set remote executable providing receive-pack service for pack transports.
501 * Default setting is: {@value RemoteConfig#DEFAULT_RECEIVE_PACK}
503 * @param optionReceivePack
504 * remote executable, if null or empty default one is set;
506 public void setOptionReceivePack(String optionReceivePack
) {
507 if (optionReceivePack
!= null && optionReceivePack
.length() > 0)
508 this.optionReceivePack
= optionReceivePack
;
510 this.optionReceivePack
= RemoteConfig
.DEFAULT_RECEIVE_PACK
;
514 * Default setting is: {@value #DEFAULT_PUSH_THIN}
516 * @return true if push should produce thin-pack in pack transports
519 public boolean isPushThin() {
524 * Set thin-pack preference for push operation. Default setting is:
525 * {@value #DEFAULT_PUSH_THIN}
528 * true when push should produce thin-pack in pack transports;
529 * false when it shouldn't
532 public void setPushThin(final boolean pushThin
) {
533 this.pushThin
= pushThin
;
537 * @return true if destination refs should be removed if they no longer
538 * exist at the source repository.
540 public boolean isRemoveDeletedRefs() {
541 return removeDeletedRefs
;
545 * Set whether or not to remove refs which no longer exist in the source.
547 * If true, refs at the destination repository (local for fetch, remote for
548 * push) are deleted if they no longer exist on the source side (remote for
549 * fetch, local for push).
551 * False by default, as this may cause data to become unreachable, and
552 * eventually be deleted on the next GC.
554 * @param remove true to remove refs that no longer exist.
556 public void setRemoveDeletedRefs(final boolean remove
) {
557 removeDeletedRefs
= remove
;
561 * Apply provided remote configuration on this transport.
564 * configuration to apply on this transport.
566 public void applyConfig(final RemoteConfig cfg
) {
567 setOptionUploadPack(cfg
.getUploadPack());
568 fetch
= cfg
.getFetchRefSpecs();
569 setTagOpt(cfg
.getTagOpt());
570 optionReceivePack
= cfg
.getReceivePack();
571 push
= cfg
.getPushRefSpecs();
575 * @return true if push operation should just check for possible result and
576 * not really update remote refs, false otherwise - when push should
579 public boolean isDryRun() {
584 * Set dry run option for push operation.
587 * true if push operation should just check for possible result
588 * and not really update remote refs, false otherwise - when push
589 * should act normally.
591 public void setDryRun(final boolean dryRun
) {
592 this.dryRun
= dryRun
;
596 * Fetch objects and refs from the remote repository to the local one.
598 * This is a utility function providing standard fetch behavior. Local
599 * tracking refs associated with the remote repository are automatically
600 * updated if this transport was created from a {@link RemoteConfig} with
601 * fetch RefSpecs defined.
604 * progress monitor to inform the user about our processing
605 * activity. Must not be null. Use {@link NullProgressMonitor} if
606 * progress updates are not interesting or necessary.
608 * specification of refs to fetch locally. May be null or the
609 * empty collection to use the specifications from the
610 * RemoteConfig. Source for each RefSpec can't be null.
611 * @return information describing the tracking refs updated.
612 * @throws NotSupportedException
613 * this transport implementation does not support fetching
615 * @throws TransportException
616 * the remote connection could not be established or object
617 * copying (if necessary) failed or update specification was
620 public FetchResult
fetch(final ProgressMonitor monitor
,
621 Collection
<RefSpec
> toFetch
) throws NotSupportedException
,
623 if (toFetch
== null || toFetch
.isEmpty()) {
624 // If the caller did not ask for anything use the defaults.
627 throw new TransportException("Nothing to fetch.");
629 } else if (!fetch
.isEmpty()) {
630 // If the caller asked for something specific without giving
631 // us the local tracking branch see if we can update any of
632 // the local tracking branches without incurring additional
633 // object transfer overheads.
635 final Collection
<RefSpec
> tmp
= new ArrayList
<RefSpec
>(toFetch
);
636 for (final RefSpec requested
: toFetch
) {
637 final String reqSrc
= requested
.getSource();
638 for (final RefSpec configured
: fetch
) {
639 final String cfgSrc
= configured
.getSource();
640 final String cfgDst
= configured
.getDestination();
641 if (cfgSrc
.equals(reqSrc
) && cfgDst
!= null) {
650 final FetchResult result
= new FetchResult();
651 new FetchProcess(this, toFetch
).execute(monitor
, result
);
656 * Push objects and refs from the local repository to the remote one.
658 * This is a utility function providing standard push behavior. It updates
659 * remote refs and send there necessary objects according to remote ref
660 * update specification. After successful remote ref update, associated
661 * locally stored tracking branch is updated if set up accordingly. Detailed
662 * operation result is provided after execution.
664 * For setting up remote ref update specification from ref spec, see helper
665 * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs
666 * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using
667 * directly {@link RemoteRefUpdate} for more possibilities.
669 * When {@link #isDryRun()} is true, result of this operation is just
670 * estimation of real operation result, no real action is performed.
672 * @see RemoteRefUpdate
675 * progress monitor to inform the user about our processing
676 * activity. Must not be null. Use {@link NullProgressMonitor} if
677 * progress updates are not interesting or necessary.
679 * specification of refs to push. May be null or the empty
680 * collection to use the specifications from the RemoteConfig
681 * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No
682 * more than 1 RemoteRefUpdate with the same remoteName is
683 * allowed. These objects are modified during this call.
684 * @return information about results of remote refs updates, tracking refs
685 * updates and refs advertised by remote repository.
686 * @throws NotSupportedException
687 * this transport implementation does not support pushing
689 * @throws TransportException
690 * the remote connection could not be established or object
691 * copying (if necessary) failed at I/O or protocol level or
692 * update specification was incorrect.
694 public PushResult
push(final ProgressMonitor monitor
,
695 Collection
<RemoteRefUpdate
> toPush
) throws NotSupportedException
,
697 if (toPush
== null || toPush
.isEmpty()) {
698 // If the caller did not ask for anything use the defaults.
700 toPush
= findRemoteRefUpdatesFor(push
);
701 } catch (final IOException e
) {
702 throw new TransportException(
703 "Problem with resolving push ref specs locally: "
704 + e
.getMessage(), e
);
706 if (toPush
.isEmpty())
707 throw new TransportException("Nothing to push.");
709 final PushProcess pushProcess
= new PushProcess(this, toPush
);
710 return pushProcess
.execute(monitor
);
714 * Convert push remote refs update specification from {@link RefSpec} form
715 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
716 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
717 * always set as null. Tracking branch is configured if RefSpec destination
718 * matches source of any fetch ref spec for this transport remote
721 * Conversion is performed for context of this transport (database, fetch
725 * collection of RefSpec to convert.
726 * @return collection of set up {@link RemoteRefUpdate}.
727 * @throws IOException
728 * when problem occurred during conversion or specification set
729 * up: most probably, missing objects or refs.
731 public Collection
<RemoteRefUpdate
> findRemoteRefUpdatesFor(
732 final Collection
<RefSpec
> specs
) throws IOException
{
733 return findRemoteRefUpdatesFor(local
, specs
, fetch
);
737 * Begins a new connection for fetching from the remote repository.
739 * @return a fresh connection to fetch from the remote repository.
740 * @throws NotSupportedException
741 * the implementation does not support fetching.
742 * @throws TransportException
743 * the remote connection could not be established.
745 public abstract FetchConnection
openFetch() throws NotSupportedException
,
749 * Begins a new connection for pushing into the remote repository.
751 * @return a fresh connection to push into the remote repository.
752 * @throws NotSupportedException
753 * the implementation does not support pushing.
754 * @throws TransportException
755 * the remote connection could not be established
757 public abstract PushConnection
openPush() throws NotSupportedException
,
761 * Close any resources used by this transport.
763 * If the remote repository is contacted by a network socket this method
764 * must close that network socket, disconnecting the two peers. If the
765 * remote repository is actually local (same system) this method must close
766 * any open file handles used to read the "remote" repository.
768 public abstract void close();