Honor receive.fsckobjects during any fetch connection
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / Transport.java
blob28700b7210276f71ade6e1f93c9e84b59b49411c
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;
58 import org.spearce.jgit.lib.TransferConfig;
60 /**
61 * Connects two Git repositories together and copies objects between them.
62 * <p>
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.
69 * <p>
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 {
74 /**
75 * Open a new transport instance to connect two repositories.
77 * @param local
78 * existing local repository.
79 * @param remote
80 * location of the remote repository - may be URI or remote
81 * configuration name.
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();
94 if (uris.size() == 0)
95 return open(local, new URIish(remote));
96 return open(local, cfg);
99 /**
100 * Open new transport instances to connect two repositories.
102 * @param local
103 * existing local repository.
104 * @param remote
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
108 * configuration.
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,
117 URISyntaxException {
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)));
123 return transports;
125 return openAll(local, cfg);
129 * Open a new transport instance to connect two repositories.
131 * @param local
132 * existing local repository.
133 * @param cfg
134 * configuration describing how to connect to the remote
135 * repository.
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
142 * associated.
144 public static Transport open(final Repository local, final RemoteConfig cfg)
145 throws NotSupportedException {
146 if (cfg.getURIs().isEmpty())
147 throw new IllegalArgumentException(
148 "Remote config \""
149 + cfg.getName() + "\" has no URIs associated");
150 final Transport tn = open(local, cfg.getURIs().get(0));
151 tn.applyConfig(cfg);
152 return tn;
156 * Open new transport instances to connect two repositories.
158 * @param local
159 * existing local repository.
160 * @param cfg
161 * configuration describing how to connect to the remote
162 * repository.
163 * @return the list of new transport instances for every URI in remote
164 * configuration.
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);
174 tn.applyConfig(cfg);
175 transports.add(tn);
177 return transports;
181 * Open a new transport instance to connect two repositories.
183 * @param local
184 * existing local repository.
185 * @param remote
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
223 * configuration.
225 * @param db
226 * local database.
227 * @param specs
228 * collection of RefSpec to convert.
229 * @param fetchSpecs
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);
255 result.add(rru);
257 return result;
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));
271 } else {
272 procRefs.add(spec);
275 return procRefs;
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)
285 .getDestination();
286 else
287 return fetchSpec.getDestination();
290 return null;
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.
305 * Acts as --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
312 * as --all.
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.
331 * <p>
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.
361 * @param local
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)}.
365 * @param uri
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();
371 this.local = local;
372 this.uri = uri;
373 this.checkFetchedObjects = tc.isFsckObjects();
377 * Get the URI this transport connects to.
378 * <p>
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() {
384 return uri;
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.
399 * @param where
400 * name of the executable.
402 public void setOptionUploadPack(final String where) {
403 if (where != null && where.length() > 0)
404 optionUploadPack = where;
405 else
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() {
415 return tagopt;
419 * Set the description of how annotated tags should be treated on fetch.
421 * @param option
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
432 * otherwise
433 * @see PackTransport
435 public boolean isFetchThin() {
436 return fetchThin;
440 * Set the thin-pack preference for fetch operation. Default setting is:
441 * {@link #DEFAULT_FETCH_THIN}
443 * @param fetchThin
444 * true when fetch should request thin-pack when possible; false
445 * when it shouldn't
446 * @see PackTransport
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;
462 * @param check
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
474 * transports.
475 * @see PackTransport
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;
491 else
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
499 * @see PackTransport
501 public boolean isPushThin() {
502 return pushThin;
506 * Set thin-pack preference for push operation. Default setting is:
507 * {@value #DEFAULT_PUSH_THIN}
509 * @param pushThin
510 * true when push should produce thin-pack in pack transports;
511 * false when it shouldn't
512 * @see PackTransport
514 public void setPushThin(final boolean pushThin) {
515 this.pushThin = pushThin;
519 * Apply provided remote configuration on this transport.
521 * @param cfg
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
535 * act normally.
537 public boolean isDryRun() {
538 return dryRun;
542 * Set dry run option for push operation.
544 * @param dryRun
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.
555 * <p>
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.
561 * @param monitor
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.
565 * @param toFetch
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
572 * objects.
573 * @throws TransportException
574 * the remote connection could not be established or object
575 * copying (if necessary) failed or update specification was
576 * incorrect.
578 public FetchResult fetch(final ProgressMonitor monitor,
579 Collection<RefSpec> toFetch) throws NotSupportedException,
580 TransportException {
581 if (toFetch == null || toFetch.isEmpty()) {
582 // If the caller did not ask for anything use the defaults.
584 if (fetch.isEmpty())
585 throw new TransportException("Nothing to fetch.");
586 toFetch = 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) {
600 tmp.add(configured);
601 break;
605 toFetch = tmp;
608 final FetchResult result = new FetchResult();
609 new FetchProcess(this, toFetch, tagopt).execute(monitor, result);
610 return result;
614 * Push objects and refs from the local repository to the remote one.
615 * <p>
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.
621 * <p>
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.
626 * <p>
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
632 * @param monitor
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.
636 * @param toPush
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
646 * objects.
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,
654 TransportException {
655 if (toPush == null || toPush.isEmpty()) {
656 // If the caller did not ask for anything use the defaults.
657 try {
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
677 * configuration.
678 * <p>
679 * Conversion is performed for context of this transport (database, fetch
680 * specifications).
682 * @param specs
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,
704 TransportException;
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,
716 TransportException;
719 * Close any resources used by this transport.
720 * <p>
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();