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, 2020 Shawn O. Pearce <spearce@spearce.org> and others
  7.  *
  8.  * This program and the accompanying materials are made available under the
  9.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  10.  * https://www.eclipse.org/org/documents/edl-v10.php.
  11.  *
  12.  * SPDX-License-Identifier: BSD-3-Clause
  13.  */

  14. package org.eclipse.jgit.transport;

  15. import java.io.BufferedInputStream;
  16. import java.io.BufferedOutputStream;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.Map;
  24. import java.util.Set;

  25. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  26. import org.eclipse.jgit.errors.NotSupportedException;
  27. import org.eclipse.jgit.errors.TransportException;
  28. import org.eclipse.jgit.internal.JGitText;
  29. import org.eclipse.jgit.lib.Repository;
  30. import org.eclipse.jgit.lib.RepositoryBuilder;
  31. import org.eclipse.jgit.lib.RepositoryCache;
  32. import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
  33. import org.eclipse.jgit.transport.resolver.UploadPackFactory;
  34. import org.eclipse.jgit.util.FS;
  35. import org.eclipse.jgit.util.io.MessageWriter;
  36. import org.eclipse.jgit.util.io.StreamCopyThread;

  37. /**
  38.  * Transport to access a local directory as though it were a remote peer.
  39.  * <p>
  40.  * This transport is suitable for use on the local system, where the caller has
  41.  * direct read or write access to the "remote" repository.
  42.  * <p>
  43.  * By default this transport works by spawning a helper thread within the same
  44.  * JVM, and processes the data transfer using a shared memory buffer between the
  45.  * calling thread and the helper thread. This is a pure-Java implementation
  46.  * which does not require forking an external process.
  47.  * <p>
  48.  * However, during {@link #openFetch()}, if the Transport has configured
  49.  * {@link Transport#getOptionUploadPack()} to be anything other than
  50.  * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
  51.  * implementation will fork and execute the external process, using an operating
  52.  * system pipe to transfer data.
  53.  * <p>
  54.  * Similarly, during {@link #openPush()}, if the Transport has configured
  55.  * {@link Transport#getOptionReceivePack()} to be anything other than
  56.  * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
  57.  * implementation will fork and execute the external process, using an operating
  58.  * system pipe to transfer data.
  59.  */
  60. class TransportLocal extends Transport implements PackTransport {
  61.     static final TransportProtocol PROTO_LOCAL = new TransportProtocol() {
  62.         @Override
  63.         public String getName() {
  64.             return JGitText.get().transportProtoLocal;
  65.         }

  66.         @Override
  67.         public Set<String> getSchemes() {
  68.             return Collections.singleton("file"); //$NON-NLS-1$
  69.         }

  70.         @Override
  71.         public boolean canHandle(URIish uri, Repository local, String remoteName) {
  72.             if (uri.getPath() == null
  73.                     || uri.getPort() > 0
  74.                     || uri.getUser() != null
  75.                     || uri.getPass() != null
  76.                     || uri.getHost() != null
  77.                     || (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
  78.                 return false;
  79.             return true;
  80.         }

  81.         @Override
  82.         public Transport open(URIish uri, Repository local, String remoteName)
  83.                 throws NoRemoteRepositoryException {
  84.             File localPath = local.isBare() ? local.getDirectory() : local.getWorkTree();
  85.             File path = local.getFS().resolve(localPath, uri.getPath());
  86.             // If the reference is to a local file, C Git behavior says
  87.             // assume this is a bundle, since repositories are directories.
  88.             if (path.isFile())
  89.                 return new TransportBundleFile(local, uri, path);

  90.             File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS());
  91.             if (gitDir == null)
  92.                 throw new NoRemoteRepositoryException(uri, JGitText.get().notFound);
  93.             return new TransportLocal(local, uri, gitDir);
  94.         }

  95.         @Override
  96.         public Transport open(URIish uri) throws NotSupportedException,
  97.                 TransportException {
  98.             File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$
  99.             // If the reference is to a local file, C Git behavior says
  100.             // assume this is a bundle, since repositories are directories.
  101.             if (path.isFile())
  102.                 return new TransportBundleFile(uri, path);

  103.             File gitDir = RepositoryCache.FileKey.resolve(path, FS.DETECTED);
  104.             if (gitDir == null)
  105.                 throw new NoRemoteRepositoryException(uri,
  106.                         JGitText.get().notFound);
  107.             return new TransportLocal(uri, gitDir);
  108.         }
  109.     };

  110.     private final File remoteGitDir;

  111.     TransportLocal(Repository local, URIish uri, File gitDir) {
  112.         super(local, uri);
  113.         remoteGitDir = gitDir;
  114.     }

  115.     TransportLocal(URIish uri, File gitDir) {
  116.         super(uri);
  117.         remoteGitDir = gitDir;
  118.     }

  119.     UploadPack createUploadPack(Repository dst) {
  120.         return new UploadPack(dst);
  121.     }

  122.     ReceivePack createReceivePack(Repository dst) {
  123.         return new ReceivePack(dst);
  124.     }

  125.     private Repository openRepo() throws TransportException {
  126.         try {
  127.             return new RepositoryBuilder()
  128.                     .setFS(local != null ? local.getFS() : FS.DETECTED)
  129.                     .setGitDir(remoteGitDir).build();
  130.         } catch (IOException err) {
  131.             TransportException te = new TransportException(uri,
  132.                     JGitText.get().notAGitDirectory);
  133.             te.initCause(err);
  134.             throw te;
  135.         }
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public FetchConnection openFetch() throws TransportException {
  140.         return openFetch(Collections.emptyList());
  141.     }

  142.     @Override
  143.     public FetchConnection openFetch(Collection<RefSpec> refSpecs,
  144.             String... additionalPatterns) throws TransportException {
  145.         final String up = getOptionUploadPack();
  146.         if (!"git-upload-pack".equals(up) //$NON-NLS-1$
  147.                 && !"git upload-pack".equals(up)) {//$NON-NLS-1$
  148.             return new ForkLocalFetchConnection(refSpecs, additionalPatterns);
  149.         }
  150.         UploadPackFactory<Void> upf = (Void req,
  151.                 Repository db) -> createUploadPack(db);
  152.         return new InternalFetchConnection<>(this, upf, null, openRepo());
  153.     }

  154.     /** {@inheritDoc} */
  155.     @Override
  156.     public PushConnection openPush() throws TransportException {
  157.         final String rp = getOptionReceivePack();
  158.         if (!"git-receive-pack".equals(rp) //$NON-NLS-1$
  159.                 && !"git receive-pack".equals(rp)) //$NON-NLS-1$
  160.             return new ForkLocalPushConnection();

  161.         ReceivePackFactory<Void> rpf = (Void req,
  162.                 Repository db) -> createReceivePack(db);
  163.         return new InternalPushConnection<>(this, rpf, null, openRepo());
  164.     }

  165.     /** {@inheritDoc} */
  166.     @Override
  167.     public void close() {
  168.         // Resources must be established per-connection.
  169.     }

  170.     /**
  171.      * Spawn process
  172.      *
  173.      * @param cmd
  174.      *            command
  175.      * @return a {@link java.lang.Process} object.
  176.      * @throws org.eclipse.jgit.errors.TransportException
  177.      *             if any.
  178.      */
  179.     protected Process spawn(String cmd)
  180.             throws TransportException {
  181.         return spawn(cmd, null);
  182.     }

  183.     /**
  184.      * Spawn process
  185.      *
  186.      * @param cmd
  187.      *            command
  188.      * @param protocolVersion
  189.      *            to use
  190.      * @return a {@link java.lang.Process} object.
  191.      * @throws org.eclipse.jgit.errors.TransportException
  192.      *             if any.
  193.      */
  194.     private Process spawn(String cmd,
  195.             TransferConfig.ProtocolVersion protocolVersion)
  196.             throws TransportException {
  197.         try {
  198.             String[] args = { "." }; //$NON-NLS-1$
  199.             ProcessBuilder proc = local.getFS().runInShell(cmd, args);
  200.             proc.directory(remoteGitDir);

  201.             // Remove the same variables CGit does.
  202.             Map<String, String> env = proc.environment();
  203.             env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$
  204.             env.remove("GIT_CONFIG"); //$NON-NLS-1$
  205.             env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
  206.             env.remove("GIT_DIR"); //$NON-NLS-1$
  207.             env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
  208.             env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
  209.             env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
  210.             env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$
  211.             if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
  212.                 env.put(GitProtocolConstants.PROTOCOL_ENVIRONMENT_VARIABLE,
  213.                         GitProtocolConstants.VERSION_2_REQUEST);
  214.             }
  215.             return proc.start();
  216.         } catch (IOException err) {
  217.             throw new TransportException(uri, err.getMessage(), err);
  218.         }
  219.     }

  220.     class ForkLocalFetchConnection extends BasePackFetchConnection {
  221.         private Process uploadPack;

  222.         private Thread errorReaderThread;

  223.         ForkLocalFetchConnection() throws TransportException {
  224.             this(Collections.emptyList());
  225.         }

  226.         ForkLocalFetchConnection(Collection<RefSpec> refSpecs,
  227.                 String... additionalPatterns) throws TransportException {
  228.             super(TransportLocal.this);

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

  231.             TransferConfig.ProtocolVersion gitProtocol = protocol;
  232.             if (gitProtocol == null) {
  233.                 gitProtocol = TransferConfig.ProtocolVersion.V2;
  234.             }
  235.             uploadPack = spawn(getOptionUploadPack(), gitProtocol);

  236.             final InputStream upErr = uploadPack.getErrorStream();
  237.             errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
  238.             errorReaderThread.start();

  239.             InputStream upIn = uploadPack.getInputStream();
  240.             OutputStream upOut = uploadPack.getOutputStream();

  241.             upIn = new BufferedInputStream(upIn);
  242.             upOut = new BufferedOutputStream(upOut);

  243.             init(upIn, upOut);
  244.             if (!readAdvertisedRefs()) {
  245.                 lsRefs(refSpecs, additionalPatterns);
  246.             }
  247.         }

  248.         @Override
  249.         public void close() {
  250.             super.close();

  251.             if (uploadPack != null) {
  252.                 try {
  253.                     uploadPack.waitFor();
  254.                 } catch (InterruptedException ie) {
  255.                     // Stop waiting and return anyway.
  256.                 } finally {
  257.                     uploadPack = null;
  258.                 }
  259.             }

  260.             if (errorReaderThread != null) {
  261.                 try {
  262.                     errorReaderThread.join();
  263.                 } catch (InterruptedException e) {
  264.                     // Stop waiting and return anyway.
  265.                 } finally {
  266.                     errorReaderThread = null;
  267.                 }
  268.             }
  269.         }
  270.     }

  271.     class ForkLocalPushConnection extends BasePackPushConnection {
  272.         private Process receivePack;

  273.         private Thread errorReaderThread;

  274.         ForkLocalPushConnection() throws TransportException {
  275.             super(TransportLocal.this);

  276.             final MessageWriter msg = new MessageWriter();
  277.             setMessageWriter(msg);

  278.             receivePack = spawn(getOptionReceivePack());

  279.             final InputStream rpErr = receivePack.getErrorStream();
  280.             errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
  281.             errorReaderThread.start();

  282.             InputStream rpIn = receivePack.getInputStream();
  283.             OutputStream rpOut = receivePack.getOutputStream();

  284.             rpIn = new BufferedInputStream(rpIn);
  285.             rpOut = new BufferedOutputStream(rpOut);

  286.             init(rpIn, rpOut);
  287.             readAdvertisedRefs();
  288.         }

  289.         @Override
  290.         public void close() {
  291.             super.close();

  292.             if (receivePack != null) {
  293.                 try {
  294.                     receivePack.waitFor();
  295.                 } catch (InterruptedException ie) {
  296.                     // Stop waiting and return anyway.
  297.                 } finally {
  298.                     receivePack = null;
  299.                 }
  300.             }

  301.             if (errorReaderThread != null) {
  302.                 try {
  303.                     errorReaderThread.join();
  304.                 } catch (InterruptedException e) {
  305.                     // Stop waiting and return anyway.
  306.                 } finally {
  307.                     errorReaderThread = null;
  308.                 }
  309.             }
  310.         }
  311.     }
  312. }