Refactor bundle transport to permit streaming from application
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / Transport.java
blob7284b2836aa90dda9920f1c8b1bf86aec40a83c6
1 /*
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>
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
10 * conditions are met:
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
23 * written permission.
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;
50 import java.util.Map;
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;
59 /**
60 * Connects two Git repositories together and copies objects between them.
61 * <p>
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.
68 * <p>
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 {
73 /**
74 * Open a new transport instance to connect two repositories.
76 * @param local
77 * existing local repository.
78 * @param remote
79 * location of the remote repository - may be URI or remote
80 * configuration name.
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();
93 if (uris.size() == 0)
94 return open(local, new URIish(remote));
95 return open(local, cfg);
98 /**
99 * Open new transport instances to connect two repositories.
101 * @param local
102 * existing local repository.
103 * @param remote
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
107 * configuration.
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,
116 URISyntaxException {
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)));
122 return transports;
124 return openAll(local, cfg);
128 * Open a new transport instance to connect two repositories.
130 * @param local
131 * existing local repository.
132 * @param cfg
133 * configuration describing how to connect to the remote
134 * repository.
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
141 * associated.
143 public static Transport open(final Repository local, final RemoteConfig cfg)
144 throws NotSupportedException {
145 if (cfg.getURIs().isEmpty())
146 throw new IllegalArgumentException(
147 "Remote config \""
148 + cfg.getName() + "\" has no URIs associated");
149 final Transport tn = open(local, cfg.getURIs().get(0));
150 tn.applyConfig(cfg);
151 return tn;
155 * Open new transport instances to connect two repositories.
157 * @param local
158 * existing local repository.
159 * @param cfg
160 * configuration describing how to connect to the remote
161 * repository.
162 * @return the list of new transport instances for every URI in remote
163 * configuration.
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);
173 tn.applyConfig(cfg);
174 transports.add(tn);
176 return transports;
180 * Open a new transport instance to connect two repositories.
182 * @param local
183 * existing local repository.
184 * @param remote
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
222 * configuration.
224 * @param db
225 * local database.
226 * @param specs
227 * collection of RefSpec to convert.
228 * @param fetchSpecs
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);
254 result.add(rru);
256 return result;
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));
270 } else {
271 procRefs.add(spec);
274 return procRefs;
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)
284 .getDestination();
285 else
286 return fetchSpec.getDestination();
289 return null;
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.
304 * Acts as --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
311 * as --all.
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.
330 * <p>
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.
357 * @param local
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)}.
361 * @param uri
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) {
366 this.local = local;
367 this.uri = uri;
371 * Get the URI this transport connects to.
372 * <p>
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() {
378 return uri;
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.
393 * @param where
394 * name of the executable.
396 public void setOptionUploadPack(final String where) {
397 if (where != null && where.length() > 0)
398 optionUploadPack = where;
399 else
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() {
409 return tagopt;
413 * Set the description of how annotated tags should be treated on fetch.
415 * @param option
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
426 * otherwise
427 * @see PackTransport
429 public boolean isFetchThin() {
430 return fetchThin;
434 * Set the thin-pack preference for fetch operation. Default setting is:
435 * {@link #DEFAULT_FETCH_THIN}
437 * @param fetchThin
438 * true when fetch should request thin-pack when possible; false
439 * when it shouldn't
440 * @see PackTransport
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
450 * transports.
451 * @see PackTransport
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;
467 else
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
475 * @see PackTransport
477 public boolean isPushThin() {
478 return pushThin;
482 * Set thin-pack preference for push operation. Default setting is:
483 * {@value #DEFAULT_PUSH_THIN}
485 * @param pushThin
486 * true when push should produce thin-pack in pack transports;
487 * false when it shouldn't
488 * @see PackTransport
490 public void setPushThin(final boolean pushThin) {
491 this.pushThin = pushThin;
495 * Apply provided remote configuration on this transport.
497 * @param cfg
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
511 * act normally.
513 public boolean isDryRun() {
514 return dryRun;
518 * Set dry run option for push operation.
520 * @param dryRun
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.
531 * <p>
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.
537 * @param monitor
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.
541 * @param toFetch
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
548 * objects.
549 * @throws TransportException
550 * the remote connection could not be established or object
551 * copying (if necessary) failed or update specification was
552 * incorrect.
554 public FetchResult fetch(final ProgressMonitor monitor,
555 Collection<RefSpec> toFetch) throws NotSupportedException,
556 TransportException {
557 if (toFetch == null || toFetch.isEmpty()) {
558 // If the caller did not ask for anything use the defaults.
560 if (fetch.isEmpty())
561 throw new TransportException("Nothing to fetch.");
562 toFetch = 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) {
576 tmp.add(configured);
577 break;
581 toFetch = tmp;
584 final FetchResult result = new FetchResult();
585 new FetchProcess(this, toFetch, tagopt).execute(monitor, result);
586 return result;
590 * Push objects and refs from the local repository to the remote one.
591 * <p>
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.
597 * <p>
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.
602 * <p>
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
608 * @param monitor
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.
612 * @param toPush
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
622 * objects.
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,
630 TransportException {
631 if (toPush == null || toPush.isEmpty()) {
632 // If the caller did not ask for anything use the defaults.
633 try {
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
653 * configuration.
654 * <p>
655 * Conversion is performed for context of this transport (database, fetch
656 * specifications).
658 * @param specs
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,
680 TransportException;
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,
692 TransportException;
695 * Close any resources used by this transport.
696 * <p>
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();