TransportLocal.java

  1. /*
  2.  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3.  * Copyright (C) 2008-2010, Google Inc.
  4.  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  5.  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  6.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  7.  * and other copyright owners as documented in the project's IP log.
  8.  *
  9.  * This program and the accompanying materials are made available
  10.  * under the terms of the Eclipse Distribution License v1.0 which
  11.  * accompanies this distribution, is reproduced below, and is
  12.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  13.  *
  14.  * All rights reserved.
  15.  *
  16.  * Redistribution and use in source and binary forms, with or
  17.  * without modification, are permitted provided that the following
  18.  * conditions are met:
  19.  *
  20.  * - Redistributions of source code must retain the above copyright
  21.  *   notice, this list of conditions and the following disclaimer.
  22.  *
  23.  * - Redistributions in binary form must reproduce the above
  24.  *   copyright notice, this list of conditions and the following
  25.  *   disclaimer in the documentation and/or other materials provided
  26.  *   with the distribution.
  27.  *
  28.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  29.  *   names of its contributors may be used to endorse or promote
  30.  *   products derived from this software without specific prior
  31.  *   written permission.
  32.  *
  33.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  34.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  35.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  36.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  37.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  38.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  39.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  40.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  41.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  42.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  43.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  44.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  45.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46.  */

  47. package org.eclipse.jgit.transport;

  48. import java.io.BufferedInputStream;
  49. import java.io.BufferedOutputStream;
  50. import java.io.File;
  51. import java.io.IOException;
  52. import java.io.InputStream;
  53. import java.io.OutputStream;
  54. import java.util.Collections;
  55. import java.util.Map;
  56. import java.util.Set;

  57. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  58. import org.eclipse.jgit.errors.NotSupportedException;
  59. import org.eclipse.jgit.errors.TransportException;
  60. import org.eclipse.jgit.internal.JGitText;
  61. import org.eclipse.jgit.lib.Repository;
  62. import org.eclipse.jgit.lib.RepositoryBuilder;
  63. import org.eclipse.jgit.lib.RepositoryCache;
  64. import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
  65. import org.eclipse.jgit.transport.resolver.UploadPackFactory;
  66. import org.eclipse.jgit.util.FS;
  67. import org.eclipse.jgit.util.io.MessageWriter;
  68. import org.eclipse.jgit.util.io.StreamCopyThread;

  69. /**
  70.  * Transport to access a local directory as though it were a remote peer.
  71.  * <p>
  72.  * This transport is suitable for use on the local system, where the caller has
  73.  * direct read or write access to the "remote" repository.
  74.  * <p>
  75.  * By default this transport works by spawning a helper thread within the same
  76.  * JVM, and processes the data transfer using a shared memory buffer between the
  77.  * calling thread and the helper thread. This is a pure-Java implementation
  78.  * which does not require forking an external process.
  79.  * <p>
  80.  * However, during {@link #openFetch()}, if the Transport has configured
  81.  * {@link Transport#getOptionUploadPack()} to be anything other than
  82.  * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
  83.  * implementation will fork and execute the external process, using an operating
  84.  * system pipe to transfer data.
  85.  * <p>
  86.  * Similarly, during {@link #openPush()}, if the Transport has configured
  87.  * {@link Transport#getOptionReceivePack()} to be anything other than
  88.  * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
  89.  * implementation will fork and execute the external process, using an operating
  90.  * system pipe to transfer data.
  91.  */
  92. class TransportLocal extends Transport implements PackTransport {
  93.     static final TransportProtocol PROTO_LOCAL = new TransportProtocol() {
  94.         @Override
  95.         public String getName() {
  96.             return JGitText.get().transportProtoLocal;
  97.         }

  98.         @Override
  99.         public Set<String> getSchemes() {
  100.             return Collections.singleton("file"); //$NON-NLS-1$
  101.         }

  102.         @Override
  103.         public boolean canHandle(URIish uri, Repository local, String remoteName) {
  104.             if (uri.getPath() == null
  105.                     || uri.getPort() > 0
  106.                     || uri.getUser() != null
  107.                     || uri.getPass() != null
  108.                     || uri.getHost() != null
  109.                     || (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
  110.                 return false;
  111.             return true;
  112.         }

  113.         @Override
  114.         public Transport open(URIish uri, Repository local, String remoteName)
  115.                 throws NoRemoteRepositoryException {
  116.             File localPath = local.isBare() ? local.getDirectory() : local.getWorkTree();
  117.             File path = local.getFS().resolve(localPath, uri.getPath());
  118.             // If the reference is to a local file, C Git behavior says
  119.             // assume this is a bundle, since repositories are directories.
  120.             if (path.isFile())
  121.                 return new TransportBundleFile(local, uri, path);

  122.             File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS());
  123.             if (gitDir == null)
  124.                 throw new NoRemoteRepositoryException(uri, JGitText.get().notFound);
  125.             return new TransportLocal(local, uri, gitDir);
  126.         }

  127.         @Override
  128.         public Transport open(URIish uri) throws NotSupportedException,
  129.                 TransportException {
  130.             File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$
  131.             // If the reference is to a local file, C Git behavior says
  132.             // assume this is a bundle, since repositories are directories.
  133.             if (path.isFile())
  134.                 return new TransportBundleFile(uri, path);

  135.             File gitDir = RepositoryCache.FileKey.resolve(path, FS.DETECTED);
  136.             if (gitDir == null)
  137.                 throw new NoRemoteRepositoryException(uri,
  138.                         JGitText.get().notFound);
  139.             return new TransportLocal(uri, gitDir);
  140.         }
  141.     };

  142.     private final File remoteGitDir;

  143.     TransportLocal(Repository local, URIish uri, File gitDir) {
  144.         super(local, uri);
  145.         remoteGitDir = gitDir;
  146.     }

  147.     TransportLocal(URIish uri, File gitDir) {
  148.         super(uri);
  149.         remoteGitDir = gitDir;
  150.     }

  151.     UploadPack createUploadPack(Repository dst) {
  152.         return new UploadPack(dst);
  153.     }

  154.     ReceivePack createReceivePack(Repository dst) {
  155.         return new ReceivePack(dst);
  156.     }

  157.     private Repository openRepo() throws TransportException {
  158.         try {
  159.             return new RepositoryBuilder()
  160.                     .setFS(local != null ? local.getFS() : FS.DETECTED)
  161.                     .setGitDir(remoteGitDir).build();
  162.         } catch (IOException err) {
  163.             throw new TransportException(uri, JGitText.get().notAGitDirectory);
  164.         }
  165.     }

  166.     /** {@inheritDoc} */
  167.     @Override
  168.     public FetchConnection openFetch() throws TransportException {
  169.         final String up = getOptionUploadPack();
  170.         if (!"git-upload-pack".equals(up) //$NON-NLS-1$
  171.                 && !"git upload-pack".equals(up)) //$NON-NLS-1$
  172.             return new ForkLocalFetchConnection();

  173.         UploadPackFactory<Void> upf = (Void req,
  174.                 Repository db) -> createUploadPack(db);
  175.         return new InternalFetchConnection<>(this, upf, null, openRepo());
  176.     }

  177.     /** {@inheritDoc} */
  178.     @Override
  179.     public PushConnection openPush() throws TransportException {
  180.         final String rp = getOptionReceivePack();
  181.         if (!"git-receive-pack".equals(rp) //$NON-NLS-1$
  182.                 && !"git receive-pack".equals(rp)) //$NON-NLS-1$
  183.             return new ForkLocalPushConnection();

  184.         ReceivePackFactory<Void> rpf = (Void req,
  185.                 Repository db) -> createReceivePack(db);
  186.         return new InternalPushConnection<>(this, rpf, null, openRepo());
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public void close() {
  191.         // Resources must be established per-connection.
  192.     }

  193.     /**
  194.      * Spawn process
  195.      *
  196.      * @param cmd
  197.      *            command
  198.      * @return a {@link java.lang.Process} object.
  199.      * @throws org.eclipse.jgit.errors.TransportException
  200.      *             if any.
  201.      */
  202.     protected Process spawn(String cmd)
  203.             throws TransportException {
  204.         try {
  205.             String[] args = { "." }; //$NON-NLS-1$
  206.             ProcessBuilder proc = local.getFS().runInShell(cmd, args);
  207.             proc.directory(remoteGitDir);

  208.             // Remove the same variables CGit does.
  209.             Map<String, String> env = proc.environment();
  210.             env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$
  211.             env.remove("GIT_CONFIG"); //$NON-NLS-1$
  212.             env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
  213.             env.remove("GIT_DIR"); //$NON-NLS-1$
  214.             env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
  215.             env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
  216.             env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
  217.             env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$

  218.             return proc.start();
  219.         } catch (IOException err) {
  220.             throw new TransportException(uri, err.getMessage(), err);
  221.         }
  222.     }

  223.     class ForkLocalFetchConnection extends BasePackFetchConnection {
  224.         private Process uploadPack;

  225.         private Thread errorReaderThread;

  226.         ForkLocalFetchConnection() throws TransportException {
  227.             super(TransportLocal.this);

  228.             final MessageWriter msg = new MessageWriter();
  229.             setMessageWriter(msg);

  230.             uploadPack = spawn(getOptionUploadPack());

  231.             final InputStream upErr = uploadPack.getErrorStream();
  232.             errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
  233.             errorReaderThread.start();

  234.             InputStream upIn = uploadPack.getInputStream();
  235.             OutputStream upOut = uploadPack.getOutputStream();

  236.             upIn = new BufferedInputStream(upIn);
  237.             upOut = new BufferedOutputStream(upOut);

  238.             init(upIn, upOut);
  239.             readAdvertisedRefs();
  240.         }

  241.         @Override
  242.         public void close() {
  243.             super.close();

  244.             if (uploadPack != null) {
  245.                 try {
  246.                     uploadPack.waitFor();
  247.                 } catch (InterruptedException ie) {
  248.                     // Stop waiting and return anyway.
  249.                 } finally {
  250.                     uploadPack = null;
  251.                 }
  252.             }

  253.             if (errorReaderThread != null) {
  254.                 try {
  255.                     errorReaderThread.join();
  256.                 } catch (InterruptedException e) {
  257.                     // Stop waiting and return anyway.
  258.                 } finally {
  259.                     errorReaderThread = null;
  260.                 }
  261.             }
  262.         }
  263.     }

  264.     class ForkLocalPushConnection extends BasePackPushConnection {
  265.         private Process receivePack;

  266.         private Thread errorReaderThread;

  267.         ForkLocalPushConnection() throws TransportException {
  268.             super(TransportLocal.this);

  269.             final MessageWriter msg = new MessageWriter();
  270.             setMessageWriter(msg);

  271.             receivePack = spawn(getOptionReceivePack());

  272.             final InputStream rpErr = receivePack.getErrorStream();
  273.             errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
  274.             errorReaderThread.start();

  275.             InputStream rpIn = receivePack.getInputStream();
  276.             OutputStream rpOut = receivePack.getOutputStream();

  277.             rpIn = new BufferedInputStream(rpIn);
  278.             rpOut = new BufferedOutputStream(rpOut);

  279.             init(rpIn, rpOut);
  280.             readAdvertisedRefs();
  281.         }

  282.         @Override
  283.         public void close() {
  284.             super.close();

  285.             if (receivePack != null) {
  286.                 try {
  287.                     receivePack.waitFor();
  288.                 } catch (InterruptedException ie) {
  289.                     // Stop waiting and return anyway.
  290.                 } finally {
  291.                     receivePack = null;
  292.                 }
  293.             }

  294.             if (errorReaderThread != null) {
  295.                 try {
  296.                     errorReaderThread.join();
  297.                 } catch (InterruptedException e) {
  298.                     // Stop waiting and return anyway.
  299.                 } finally {
  300.                     errorReaderThread = null;
  301.                 }
  302.             }
  303.         }
  304.     }
  305. }