2009-09-30 02:47:03 +03:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
|
|
|
|
* and other copyright owners as documented in the project's IP log.
|
|
|
|
*
|
|
|
|
* This program and the accompanying materials are made available
|
|
|
|
* under the terms of the Eclipse Distribution License v1.0 which
|
|
|
|
* accompanies this distribution, is reproduced below, and is
|
|
|
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
|
|
*
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or
|
|
|
|
* without modification, are permitted provided that the following
|
|
|
|
* conditions are met:
|
|
|
|
*
|
|
|
|
* - Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* - Redistributions in binary form must reproduce the above
|
|
|
|
* copyright notice, this list of conditions and the following
|
|
|
|
* disclaimer in the documentation and/or other materials provided
|
|
|
|
* with the distribution.
|
|
|
|
*
|
|
|
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
|
|
|
* names of its contributors may be used to endorse or promote
|
|
|
|
* products derived from this software without specific prior
|
|
|
|
* written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package org.eclipse.jgit.util;
|
|
|
|
|
2010-12-28 18:15:12 +02:00
|
|
|
import java.io.BufferedReader;
|
2015-06-17 15:54:11 +03:00
|
|
|
import java.io.ByteArrayInputStream;
|
2018-08-26 20:44:29 +03:00
|
|
|
import java.io.Closeable;
|
2009-09-30 02:47:03 +03:00
|
|
|
import java.io.File;
|
2010-12-28 18:15:12 +02:00
|
|
|
import java.io.IOException;
|
2011-05-02 23:35:54 +03:00
|
|
|
import java.io.InputStream;
|
2010-12-28 18:15:12 +02:00
|
|
|
import java.io.InputStreamReader;
|
2014-10-31 15:58:07 +02:00
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.PrintStream;
|
2015-05-19 11:49:44 +03:00
|
|
|
import java.nio.charset.Charset;
|
2018-08-26 20:44:29 +03:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
2009-09-30 02:47:03 +03:00
|
|
|
import java.security.AccessController;
|
|
|
|
import java.security.PrivilegedAction;
|
2012-12-27 13:45:59 +02:00
|
|
|
import java.text.MessageFormat;
|
2011-05-02 23:35:54 +03:00
|
|
|
import java.util.Arrays;
|
2015-05-19 11:49:44 +03:00
|
|
|
import java.util.HashMap;
|
2015-05-19 11:25:48 +03:00
|
|
|
import java.util.Map;
|
2015-10-31 01:06:27 +02:00
|
|
|
import java.util.Objects;
|
2018-08-26 20:44:29 +03:00
|
|
|
import java.util.Optional;
|
2014-10-31 15:58:07 +02:00
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
2011-05-02 23:35:54 +03:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2016-09-04 13:07:37 +03:00
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
2009-09-30 02:47:03 +03:00
|
|
|
|
2015-11-09 23:42:20 +02:00
|
|
|
import org.eclipse.jgit.annotations.Nullable;
|
2014-10-31 15:58:07 +02:00
|
|
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
2016-09-04 13:07:37 +03:00
|
|
|
import org.eclipse.jgit.errors.CommandFailedException;
|
2013-02-04 02:10:26 +02:00
|
|
|
import org.eclipse.jgit.internal.JGitText;
|
2014-10-31 15:58:07 +02:00
|
|
|
import org.eclipse.jgit.lib.Constants;
|
|
|
|
import org.eclipse.jgit.lib.Repository;
|
|
|
|
import org.eclipse.jgit.util.ProcessResult.Status;
|
2015-02-09 22:54:58 +02:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2013-02-04 02:10:26 +02:00
|
|
|
|
2009-09-30 02:47:03 +03:00
|
|
|
/** Abstraction to support various file system operations not in Java. */
|
|
|
|
public abstract class FS {
|
2013-02-04 02:10:26 +02:00
|
|
|
/**
|
|
|
|
* This class creates FS instances. It will be overridden by a Java7 variant
|
|
|
|
* if such can be detected in {@link #detect(Boolean)}.
|
|
|
|
*
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public static class FSFactory {
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*/
|
|
|
|
protected FSFactory() {
|
|
|
|
// empty
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect the file system
|
|
|
|
*
|
|
|
|
* @param cygwinUsed
|
|
|
|
* @return FS instance
|
|
|
|
*/
|
|
|
|
public FS detect(Boolean cygwinUsed) {
|
|
|
|
if (SystemReader.getInstance().isWindows()) {
|
|
|
|
if (cygwinUsed == null)
|
|
|
|
cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
|
|
|
|
if (cygwinUsed.booleanValue())
|
|
|
|
return new FS_Win32_Cygwin();
|
|
|
|
else
|
|
|
|
return new FS_Win32();
|
2015-03-13 04:30:37 +02:00
|
|
|
} else {
|
|
|
|
return new FS_POSIX();
|
|
|
|
}
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-28 14:24:28 +02:00
|
|
|
/**
|
|
|
|
* Result of an executed process. The caller is responsible to close the
|
|
|
|
* contained {@link TemporaryBuffer}s
|
|
|
|
*
|
|
|
|
* @since 4.2
|
|
|
|
*/
|
|
|
|
public static class ExecutionResult {
|
|
|
|
private TemporaryBuffer stdout;
|
|
|
|
|
|
|
|
private TemporaryBuffer stderr;
|
|
|
|
|
|
|
|
private int rc;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param stdout
|
|
|
|
* @param stderr
|
|
|
|
* @param rc
|
|
|
|
*/
|
|
|
|
public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
|
|
|
|
int rc) {
|
|
|
|
this.stdout = stdout;
|
|
|
|
this.stderr = stderr;
|
|
|
|
this.rc = rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return buffered standard output stream
|
|
|
|
*/
|
|
|
|
public TemporaryBuffer getStdout() {
|
|
|
|
return stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return buffered standard error stream
|
|
|
|
*/
|
|
|
|
public TemporaryBuffer getStderr() {
|
|
|
|
return stderr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the return code of the process
|
|
|
|
*/
|
|
|
|
public int getRc() {
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final static Logger LOG = LoggerFactory.getLogger(FS.class);
|
2015-02-09 22:54:58 +02:00
|
|
|
|
2010-04-20 22:01:19 +03:00
|
|
|
/** The auto-detected implementation selected for this operating system and JRE. */
|
2011-03-14 18:06:16 +02:00
|
|
|
public static final FS DETECTED = detect();
|
|
|
|
|
2016-10-18 07:51:27 +03:00
|
|
|
private volatile static FSFactory factory;
|
2013-02-04 02:10:26 +02:00
|
|
|
|
2011-03-14 18:06:16 +02:00
|
|
|
/**
|
|
|
|
* Auto-detect the appropriate file system abstraction.
|
|
|
|
*
|
|
|
|
* @return detected file system abstraction
|
|
|
|
*/
|
|
|
|
public static FS detect() {
|
|
|
|
return detect(null);
|
|
|
|
}
|
2009-09-30 02:47:03 +03:00
|
|
|
|
2010-09-01 18:14:16 +03:00
|
|
|
/**
|
|
|
|
* Auto-detect the appropriate file system abstraction, taking into account
|
|
|
|
* the presence of a Cygwin installation on the system. Using jgit in
|
|
|
|
* combination with Cygwin requires a more elaborate (and possibly slower)
|
|
|
|
* resolution of file system paths.
|
|
|
|
*
|
|
|
|
* @param cygwinUsed
|
|
|
|
* <ul>
|
|
|
|
* <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
|
|
|
|
* combination with jgit</li>
|
|
|
|
* <li><code>Boolean.FALSE</code> to assume that Cygwin is
|
|
|
|
* <b>not</b> used with jgit</li>
|
|
|
|
* <li><code>null</code> to auto-detect whether a Cygwin
|
|
|
|
* installation is present on the system and in this case assume
|
|
|
|
* that Cygwin is used</li>
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* Note: this parameter is only relevant on Windows.
|
|
|
|
*
|
|
|
|
* @return detected file system abstraction
|
|
|
|
*/
|
|
|
|
public static FS detect(Boolean cygwinUsed) {
|
2013-02-04 02:10:26 +02:00
|
|
|
if (factory == null) {
|
2015-03-13 04:30:37 +02:00
|
|
|
factory = new FS.FSFactory();
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
return factory.detect(cygwinUsed);
|
2010-09-01 18:14:16 +03:00
|
|
|
}
|
|
|
|
|
2011-03-14 18:17:39 +02:00
|
|
|
private volatile Holder<File> userHome;
|
2010-04-20 22:01:19 +03:00
|
|
|
|
2015-05-22 18:21:27 +03:00
|
|
|
private volatile Holder<File> gitSystemConfig;
|
|
|
|
|
2010-04-20 22:01:19 +03:00
|
|
|
/**
|
|
|
|
* Constructs a file system abstraction.
|
|
|
|
*/
|
|
|
|
protected FS() {
|
2011-03-14 18:17:39 +02:00
|
|
|
// Do nothing by default.
|
2009-09-30 02:47:03 +03:00
|
|
|
}
|
|
|
|
|
2011-03-14 18:19:39 +02:00
|
|
|
/**
|
|
|
|
* Initialize this FS using another's current settings.
|
|
|
|
*
|
|
|
|
* @param src
|
|
|
|
* the source FS to copy from.
|
|
|
|
*/
|
|
|
|
protected FS(FS src) {
|
|
|
|
userHome = src.userHome;
|
2015-05-22 18:21:27 +03:00
|
|
|
gitSystemConfig = src.gitSystemConfig;
|
2011-03-14 18:19:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @return a new instance of the same type of FS. */
|
|
|
|
public abstract FS newInstance();
|
|
|
|
|
2009-09-30 02:47:03 +03:00
|
|
|
/**
|
|
|
|
* Does this operating system and JRE support the execute flag on files?
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @return true if this implementation can provide reasonably accurate
|
|
|
|
* executable bit information; false otherwise.
|
|
|
|
*/
|
|
|
|
public abstract boolean supportsExecute();
|
|
|
|
|
2017-11-14 18:08:56 +02:00
|
|
|
/**
|
|
|
|
* Does this file system support atomic file creation via
|
|
|
|
* java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
|
|
|
|
* not guaranteed that when two file system clients run createNewFile() in
|
|
|
|
* parallel only one will succeed. In such cases both clients may think they
|
|
|
|
* created a new file.
|
|
|
|
*
|
|
|
|
* @return true if this implementation support atomic creation of new
|
|
|
|
* Files by {@link File#createNewFile()}
|
|
|
|
* @since 4.5
|
|
|
|
*/
|
|
|
|
public boolean supportsAtomicCreateNewFile() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-02-04 02:10:26 +02:00
|
|
|
/**
|
|
|
|
* Does this operating system and JRE supports symbolic links. The
|
|
|
|
* capability to handle symbolic links is detected at runtime.
|
|
|
|
*
|
|
|
|
* @return true if symbolic links may be used
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean supportsSymlinks() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-05-18 17:52:50 +03:00
|
|
|
/**
|
|
|
|
* Is this file system case sensitive
|
|
|
|
*
|
|
|
|
* @return true if this implementation is case sensitive
|
|
|
|
*/
|
|
|
|
public abstract boolean isCaseSensitive();
|
|
|
|
|
2009-09-30 02:47:03 +03:00
|
|
|
/**
|
|
|
|
* Determine if the file is executable (or not).
|
|
|
|
* <p>
|
|
|
|
* Not all platforms and JREs support executable flags on files. If the
|
|
|
|
* feature is unsupported this method will always return false.
|
2013-02-04 02:10:26 +02:00
|
|
|
* <p>
|
|
|
|
* <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
|
|
|
|
* this method returns false, rather than the state of the executable flags
|
|
|
|
* on the target file.</em>
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @param f
|
|
|
|
* abstract path to test.
|
|
|
|
* @return true if the file is believed to be executable by the user.
|
|
|
|
*/
|
|
|
|
public abstract boolean canExecute(File f);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a file to be executable by the user.
|
|
|
|
* <p>
|
|
|
|
* Not all platforms and JREs support executable flags on files. If the
|
|
|
|
* feature is unsupported this method will always return false and no
|
|
|
|
* changes will be made to the file specified.
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @param f
|
|
|
|
* path to modify the executable status of.
|
|
|
|
* @param canExec
|
|
|
|
* true to enable execution; false to disable it.
|
|
|
|
* @return true if the change succeeded; false otherwise.
|
|
|
|
*/
|
|
|
|
public abstract boolean setExecute(File f, boolean canExec);
|
|
|
|
|
2013-02-04 02:10:26 +02:00
|
|
|
/**
|
|
|
|
* Get the last modified time of a file system object. If the OS/JRE support
|
|
|
|
* symbolic links, the modification time of the link is returned, rather
|
|
|
|
* than that of the link target.
|
|
|
|
*
|
|
|
|
* @param f
|
|
|
|
* @return last modified time of f
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public long lastModified(File f) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.lastModified(f);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the last modified time of a file system object. If the OS/JRE support
|
|
|
|
* symbolic links, the link is modified, not the target,
|
|
|
|
*
|
|
|
|
* @param f
|
|
|
|
* @param time
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public void setLastModified(File f, long time) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
FileUtils.setLastModified(f, time);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the length of a file or link, If the OS/JRE supports symbolic links
|
|
|
|
* it's the length of the link, else the length of the target.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @return length of a file
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public long length(File path) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.getLength(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
2012-12-27 13:45:59 +02:00
|
|
|
/**
|
|
|
|
* Delete a file. Throws an exception if delete fails.
|
|
|
|
*
|
|
|
|
* @param f
|
|
|
|
* @throws IOException
|
|
|
|
* this may be a Java7 subclass with detailed information
|
|
|
|
* @since 3.3
|
|
|
|
*/
|
|
|
|
public void delete(File f) throws IOException {
|
2015-08-16 19:15:30 +03:00
|
|
|
FileUtils.delete(f);
|
2012-12-27 13:45:59 +02:00
|
|
|
}
|
|
|
|
|
2009-09-30 02:47:03 +03:00
|
|
|
/**
|
|
|
|
* Resolve this file to its actual path name that the JRE can use.
|
|
|
|
* <p>
|
|
|
|
* This method can be relatively expensive. Computing a translation may
|
|
|
|
* require forking an external process per path name translated. Callers
|
|
|
|
* should try to minimize the number of translations necessary by caching
|
|
|
|
* the results.
|
|
|
|
* <p>
|
|
|
|
* Not all platforms and JREs require path name translation. Currently only
|
|
|
|
* Cygwin on Win32 require translation for Cygwin based paths.
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @param dir
|
|
|
|
* directory relative to which the path name is.
|
|
|
|
* @param name
|
|
|
|
* path name to translate.
|
|
|
|
* @return the translated path. <code>new File(dir,name)</code> if this
|
|
|
|
* platform does not require path name translation.
|
|
|
|
*/
|
2010-04-20 22:01:19 +03:00
|
|
|
public File resolve(final File dir, final String name) {
|
2009-09-30 02:47:03 +03:00
|
|
|
final File abspn = new File(name);
|
|
|
|
if (abspn.isAbsolute())
|
|
|
|
return abspn;
|
|
|
|
return new File(dir, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine the user's home directory (location where preferences are).
|
|
|
|
* <p>
|
|
|
|
* This method can be expensive on the first invocation if path name
|
|
|
|
* translation is required. Subsequent invocations return a cached result.
|
|
|
|
* <p>
|
|
|
|
* Not all platforms and JREs require path name translation. Currently only
|
|
|
|
* Cygwin on Win32 requires translation of the Cygwin HOME directory.
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @return the user's home directory; null if the user does not have one.
|
|
|
|
*/
|
2010-04-20 22:01:19 +03:00
|
|
|
public File userHome() {
|
2011-03-14 18:17:39 +02:00
|
|
|
Holder<File> p = userHome;
|
|
|
|
if (p == null) {
|
2017-02-20 06:17:27 +02:00
|
|
|
p = new Holder<>(userHomeImpl());
|
2011-03-14 18:17:39 +02:00
|
|
|
userHome = p;
|
|
|
|
}
|
|
|
|
return p.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the user's home directory location.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* the location of the user's preferences; null if there is no
|
|
|
|
* home directory for the current user.
|
|
|
|
* @return {@code this}.
|
|
|
|
*/
|
|
|
|
public FS setUserHome(File path) {
|
2017-02-20 06:17:27 +02:00
|
|
|
userHome = new Holder<>(path);
|
2011-03-14 18:17:39 +02:00
|
|
|
return this;
|
2009-09-30 02:47:03 +03:00
|
|
|
}
|
|
|
|
|
2010-07-21 10:35:15 +03:00
|
|
|
/**
|
|
|
|
* Does this file system have problems with atomic renames?
|
|
|
|
*
|
|
|
|
* @return true if the caller should retry a failed rename of a lock file.
|
|
|
|
*/
|
|
|
|
public abstract boolean retryFailedLockFileCommit();
|
|
|
|
|
2009-09-30 02:47:03 +03:00
|
|
|
/**
|
|
|
|
* Determine the user's home directory (location where preferences are).
|
2009-10-19 11:48:00 +03:00
|
|
|
*
|
2009-09-30 02:47:03 +03:00
|
|
|
* @return the user's home directory; null if the user does not have one.
|
|
|
|
*/
|
|
|
|
protected File userHomeImpl() {
|
|
|
|
final String home = AccessController
|
|
|
|
.doPrivileged(new PrivilegedAction<String>() {
|
2017-01-16 07:39:32 +02:00
|
|
|
@Override
|
2009-09-30 02:47:03 +03:00
|
|
|
public String run() {
|
2012-11-25 18:17:20 +02:00
|
|
|
return System.getProperty("user.home"); //$NON-NLS-1$
|
2009-09-30 02:47:03 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
if (home == null || home.length() == 0)
|
|
|
|
return null;
|
|
|
|
return new File(home).getAbsoluteFile();
|
|
|
|
}
|
2010-12-28 18:15:08 +02:00
|
|
|
|
2011-11-04 15:42:12 +02:00
|
|
|
/**
|
|
|
|
* Searches the given path to see if it contains one of the given files.
|
|
|
|
* Returns the first it finds. Returns null if not found or if path is null.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* List of paths to search separated by File.pathSeparator
|
|
|
|
* @param lookFor
|
|
|
|
* Files to search for in the given path
|
|
|
|
* @return the first match found, or null
|
2013-02-04 02:10:26 +02:00
|
|
|
* @since 3.0
|
2011-11-04 15:42:12 +02:00
|
|
|
**/
|
2013-02-04 02:10:26 +02:00
|
|
|
protected static File searchPath(final String path, final String... lookFor) {
|
2011-11-04 15:42:12 +02:00
|
|
|
if (path == null)
|
|
|
|
return null;
|
|
|
|
|
2010-12-28 18:15:08 +02:00
|
|
|
for (final String p : path.split(File.pathSeparator)) {
|
|
|
|
for (String command : lookFor) {
|
|
|
|
final File e = new File(p, command);
|
|
|
|
if (e.isFile())
|
|
|
|
return e.getAbsoluteFile();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2010-12-28 18:15:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a command and return a single line of output as a String
|
|
|
|
*
|
|
|
|
* @param dir
|
|
|
|
* Working directory for the command
|
|
|
|
* @param command
|
|
|
|
* as component array
|
|
|
|
* @param encoding
|
2015-05-19 11:21:59 +03:00
|
|
|
* to be used to parse the command's output
|
2015-11-09 23:42:20 +02:00
|
|
|
* @return the one-line output of the command or {@code null} if there is
|
|
|
|
* none
|
2016-09-04 13:07:37 +03:00
|
|
|
* @throws CommandFailedException
|
|
|
|
* thrown when the command failed (return code was non-zero)
|
2010-12-28 18:15:12 +02:00
|
|
|
*/
|
2015-11-01 14:40:02 +02:00
|
|
|
@Nullable
|
2016-09-04 13:07:37 +03:00
|
|
|
protected static String readPipe(File dir, String[] command,
|
|
|
|
String encoding) throws CommandFailedException {
|
2015-05-19 11:25:48 +03:00
|
|
|
return readPipe(dir, command, encoding, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a command and return a single line of output as a String
|
|
|
|
*
|
|
|
|
* @param dir
|
|
|
|
* Working directory for the command
|
|
|
|
* @param command
|
|
|
|
* as component array
|
|
|
|
* @param encoding
|
|
|
|
* to be used to parse the command's output
|
|
|
|
* @param env
|
|
|
|
* Map of environment variables to be merged with those of the
|
|
|
|
* current process
|
2015-11-09 23:42:20 +02:00
|
|
|
* @return the one-line output of the command or {@code null} if there is
|
|
|
|
* none
|
2016-09-04 13:07:37 +03:00
|
|
|
* @throws CommandFailedException
|
|
|
|
* thrown when the command failed (return code was non-zero)
|
2015-05-19 11:25:48 +03:00
|
|
|
* @since 4.0
|
|
|
|
*/
|
2015-11-01 14:40:02 +02:00
|
|
|
@Nullable
|
2016-09-04 13:07:37 +03:00
|
|
|
protected static String readPipe(File dir, String[] command,
|
|
|
|
String encoding, Map<String, String> env)
|
|
|
|
throws CommandFailedException {
|
2015-02-09 22:54:58 +02:00
|
|
|
final boolean debug = LOG.isDebugEnabled();
|
2010-12-28 18:15:12 +02:00
|
|
|
try {
|
2015-02-09 22:54:58 +02:00
|
|
|
if (debug) {
|
|
|
|
LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
|
2011-05-02 23:35:54 +03:00
|
|
|
+ dir);
|
2015-02-09 22:54:58 +02:00
|
|
|
}
|
2015-05-19 11:25:48 +03:00
|
|
|
ProcessBuilder pb = new ProcessBuilder(command);
|
|
|
|
pb.directory(dir);
|
|
|
|
if (env != null) {
|
|
|
|
pb.environment().putAll(env);
|
|
|
|
}
|
2015-05-27 19:40:32 +03:00
|
|
|
Process p = pb.start();
|
2011-05-02 23:35:54 +03:00
|
|
|
p.getOutputStream().close();
|
2015-05-27 19:40:32 +03:00
|
|
|
GobblerThread gobbler = new GobblerThread(p, command, dir);
|
2011-05-02 23:35:54 +03:00
|
|
|
gobbler.start();
|
2010-12-28 18:15:12 +02:00
|
|
|
String r = null;
|
2015-11-01 14:40:02 +02:00
|
|
|
try (BufferedReader lineRead = new BufferedReader(
|
|
|
|
new InputStreamReader(p.getInputStream(), encoding))) {
|
2010-12-28 18:15:12 +02:00
|
|
|
r = lineRead.readLine();
|
2011-05-02 23:35:54 +03:00
|
|
|
if (debug) {
|
2015-02-09 22:54:58 +02:00
|
|
|
LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
|
2015-11-01 14:40:02 +02:00
|
|
|
LOG.debug("remaining output:\n"); //$NON-NLS-1$
|
|
|
|
String l;
|
|
|
|
while ((l = lineRead.readLine()) != null) {
|
2015-02-09 22:54:58 +02:00
|
|
|
LOG.debug(l);
|
|
|
|
}
|
2011-05-02 23:35:54 +03:00
|
|
|
}
|
2010-12-28 18:15:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
try {
|
2011-05-02 23:35:54 +03:00
|
|
|
int rc = p.waitFor();
|
|
|
|
gobbler.join();
|
2015-11-01 14:40:02 +02:00
|
|
|
if (rc == 0 && !gobbler.fail.get()) {
|
2010-12-28 18:15:12 +02:00
|
|
|
return r;
|
2016-09-04 13:07:37 +03:00
|
|
|
} else {
|
|
|
|
if (debug) {
|
|
|
|
LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
|
|
|
|
}
|
|
|
|
throw new CommandFailedException(rc,
|
|
|
|
gobbler.errorMessage.get(),
|
|
|
|
gobbler.exception.get());
|
2015-11-01 14:40:02 +02:00
|
|
|
}
|
2010-12-28 18:15:12 +02:00
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
// Stop bothering me, I have a zombie to reap.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2015-10-31 01:06:27 +02:00
|
|
|
LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
|
2015-02-09 22:54:58 +02:00
|
|
|
}
|
|
|
|
if (debug) {
|
|
|
|
LOG.debug("readpipe returns null"); //$NON-NLS-1$
|
2010-12-28 18:15:12 +02:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2010-12-28 18:15:18 +02:00
|
|
|
|
2015-05-27 19:40:32 +03:00
|
|
|
private static class GobblerThread extends Thread {
|
|
|
|
private final Process p;
|
|
|
|
private final String desc;
|
|
|
|
private final String dir;
|
2015-10-28 21:52:43 +02:00
|
|
|
final AtomicBoolean fail = new AtomicBoolean();
|
2016-09-04 13:07:37 +03:00
|
|
|
final AtomicReference<String> errorMessage = new AtomicReference<>();
|
|
|
|
final AtomicReference<Throwable> exception = new AtomicReference<>();
|
2015-05-27 19:40:32 +03:00
|
|
|
|
2015-10-28 21:52:43 +02:00
|
|
|
GobblerThread(Process p, String[] command, File dir) {
|
2015-05-27 19:40:32 +03:00
|
|
|
this.p = p;
|
2015-10-31 01:06:27 +02:00
|
|
|
this.desc = Arrays.toString(command);
|
|
|
|
this.dir = Objects.toString(dir);
|
2015-05-27 19:40:32 +03:00
|
|
|
}
|
|
|
|
|
2016-09-04 13:07:37 +03:00
|
|
|
@Override
|
2015-05-27 19:40:32 +03:00
|
|
|
public void run() {
|
2015-11-01 14:40:02 +02:00
|
|
|
StringBuilder err = new StringBuilder();
|
|
|
|
try (InputStream is = p.getErrorStream()) {
|
2015-05-27 19:40:32 +03:00
|
|
|
int ch;
|
2015-10-31 01:06:27 +02:00
|
|
|
while ((ch = is.read()) != -1) {
|
2015-11-01 14:40:02 +02:00
|
|
|
err.append((char) ch);
|
2015-05-27 19:40:32 +03:00
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2015-11-01 14:40:02 +02:00
|
|
|
if (p.exitValue() != 0) {
|
2016-09-04 13:07:37 +03:00
|
|
|
setError(e, e.getMessage());
|
2015-11-01 14:40:02 +02:00
|
|
|
fail.set(true);
|
|
|
|
} else {
|
2016-09-04 13:07:37 +03:00
|
|
|
// ignore. command terminated faster and stream was just closed
|
2015-11-01 14:40:02 +02:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (err.length() > 0) {
|
2016-09-04 13:07:37 +03:00
|
|
|
setError(null, err.toString());
|
|
|
|
if (p.exitValue() != 0) {
|
|
|
|
fail.set(true);
|
|
|
|
}
|
2015-11-01 14:40:02 +02:00
|
|
|
}
|
2015-05-27 19:40:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-04 13:07:37 +03:00
|
|
|
private void setError(IOException e, String message) {
|
|
|
|
exception.set(e);
|
|
|
|
errorMessage.set(MessageFormat.format(
|
|
|
|
JGitText.get().exceptionCaughtDuringExcecutionOfCommand,
|
|
|
|
desc, dir, Integer.valueOf(p.exitValue()), message));
|
2015-05-27 19:40:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-19 11:48:24 +03:00
|
|
|
/**
|
2015-05-25 21:15:05 +03:00
|
|
|
* @return the path to the Git executable or {@code null} if it cannot be
|
|
|
|
* determined.
|
2015-05-19 11:48:24 +03:00
|
|
|
* @since 4.0
|
|
|
|
*/
|
|
|
|
protected abstract File discoverGitExe();
|
|
|
|
|
2015-05-19 11:49:44 +03:00
|
|
|
/**
|
2015-05-25 21:15:05 +03:00
|
|
|
* @return the path to the system-wide Git configuration file or
|
|
|
|
* {@code null} if it cannot be determined.
|
2015-05-19 11:49:44 +03:00
|
|
|
* @since 4.0
|
|
|
|
*/
|
|
|
|
protected File discoverGitSystemConfig() {
|
|
|
|
File gitExe = discoverGitExe();
|
|
|
|
if (gitExe == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-10-27 16:14:54 +02:00
|
|
|
// Bug 480782: Check if the discovered git executable is JGit CLI
|
2016-09-04 13:07:37 +03:00
|
|
|
String v;
|
|
|
|
try {
|
|
|
|
v = readPipe(gitExe.getParentFile(),
|
2015-10-27 16:14:54 +02:00
|
|
|
new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
|
|
|
|
Charset.defaultCharset().name());
|
2016-09-04 13:07:37 +03:00
|
|
|
} catch (CommandFailedException e) {
|
|
|
|
LOG.warn(e.getMessage());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (StringUtils.isEmptyOrNull(v)
|
|
|
|
|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
|
2015-10-27 16:14:54 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-05-19 11:49:44 +03:00
|
|
|
// Trick Git into printing the path to the config file by using "echo"
|
|
|
|
// as the editor.
|
|
|
|
Map<String, String> env = new HashMap<>();
|
|
|
|
env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
|
|
|
|
|
2016-09-04 13:07:37 +03:00
|
|
|
String w;
|
|
|
|
try {
|
|
|
|
w = readPipe(gitExe.getParentFile(),
|
2015-05-19 11:49:44 +03:00
|
|
|
new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
|
|
|
|
Charset.defaultCharset().name(), env);
|
2016-09-04 13:07:37 +03:00
|
|
|
} catch (CommandFailedException e) {
|
|
|
|
LOG.warn(e.getMessage());
|
|
|
|
return null;
|
|
|
|
}
|
2015-05-19 11:49:44 +03:00
|
|
|
if (StringUtils.isEmptyOrNull(w)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new File(w);
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:21:27 +03:00
|
|
|
/**
|
|
|
|
* @return the currently used path to the system-wide Git configuration
|
|
|
|
* file or {@code null} if none has been set.
|
|
|
|
* @since 4.0
|
|
|
|
*/
|
|
|
|
public File getGitSystemConfig() {
|
|
|
|
if (gitSystemConfig == null) {
|
2017-02-20 06:17:27 +02:00
|
|
|
gitSystemConfig = new Holder<>(discoverGitSystemConfig());
|
2015-05-22 18:21:27 +03:00
|
|
|
}
|
|
|
|
return gitSystemConfig.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the path to the system-wide Git configuration file to use.
|
|
|
|
*
|
|
|
|
* @param configFile
|
|
|
|
* the path to the config file.
|
|
|
|
* @return {@code this}
|
|
|
|
* @since 4.0
|
|
|
|
*/
|
|
|
|
public FS setGitSystemConfig(File configFile) {
|
2017-02-20 06:17:27 +02:00
|
|
|
gitSystemConfig = new Holder<>(configFile);
|
2015-05-22 18:21:27 +03:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2015-05-19 10:30:02 +03:00
|
|
|
/**
|
|
|
|
* @param grandchild
|
|
|
|
* @return the parent directory of this file's parent directory or
|
|
|
|
* {@code null} in case there's no grandparent directory
|
|
|
|
* @since 4.0
|
|
|
|
*/
|
|
|
|
protected static File resolveGrandparentFile(File grandchild) {
|
|
|
|
if (grandchild != null) {
|
|
|
|
File parent = grandchild.getParentFile();
|
|
|
|
if (parent != null)
|
|
|
|
return parent.getParentFile();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-02-04 02:10:26 +02:00
|
|
|
/**
|
|
|
|
* Check if a file is a symbolic link and read it
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @return target of link or null
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public String readSymLink(File path) throws IOException {
|
2015-08-16 18:41:22 +03:00
|
|
|
return FileUtils.readSymLink(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param path
|
|
|
|
* @return true if the path is a symbolic link (and we support these)
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean isSymLink(File path) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.isSymlink(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the path exists, in case of a symbolic link, true even if the
|
|
|
|
* target does not exist
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @return true if path exists
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean exists(File path) {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.exists(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if path is a directory. If the OS/JRE supports symbolic links and
|
|
|
|
* path is a symbolic link to a directory, this method returns false.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @return true if file is a directory,
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean isDirectory(File path) {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.isDirectory(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Examine if path represents a regular file. If the OS/JRE supports
|
|
|
|
* symbolic links the test returns false if path represents a symbolic link.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @return true if path represents a regular file
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean isFile(File path) {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.isFile(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param path
|
|
|
|
* @return true if path is hidden, either starts with . on unix or has the
|
|
|
|
* hidden attribute in windows
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public boolean isHidden(File path) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
return FileUtils.isHidden(path);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the hidden attribute for file whose name starts with a period.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param hidden
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public void setHidden(File path, boolean hidden) throws IOException {
|
2015-09-22 02:17:18 +03:00
|
|
|
FileUtils.setHidden(path, hidden);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a symbolic link
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param target
|
|
|
|
* @throws IOException
|
|
|
|
* @since 3.0
|
|
|
|
*/
|
|
|
|
public void createSymLink(File path, String target) throws IOException {
|
2015-08-16 18:41:22 +03:00
|
|
|
FileUtils.createSymLink(path, target);
|
2013-02-04 02:10:26 +02:00
|
|
|
}
|
|
|
|
|
2017-11-14 18:08:56 +02:00
|
|
|
/**
|
|
|
|
* Create a new file. See {@link File#createNewFile()}. Subclasses of this
|
|
|
|
* class may take care to provide a safe implementation for this even if
|
|
|
|
* {@link #supportsAtomicCreateNewFile()} is <code>false</code>
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* the file to be created
|
|
|
|
* @return <code>true</code> if the file was created, <code>false</code> if
|
|
|
|
* the file already existed
|
2018-08-26 20:44:29 +03:00
|
|
|
* @throws java.io.IOException
|
|
|
|
* @deprecated use {@link #createNewFileAtomic(File)} instead
|
2017-11-14 18:08:56 +02:00
|
|
|
* @since 4.5
|
|
|
|
*/
|
2018-08-26 20:44:29 +03:00
|
|
|
@Deprecated
|
2017-11-14 18:08:56 +02:00
|
|
|
public boolean createNewFile(File path) throws IOException {
|
|
|
|
return path.createNewFile();
|
|
|
|
}
|
|
|
|
|
2018-08-26 20:44:29 +03:00
|
|
|
/**
|
|
|
|
* A token representing a file created by
|
|
|
|
* {@link #createNewFileAtomic(File)}. The token must be retained until the
|
|
|
|
* file has been deleted in order to guarantee that the unique file was
|
|
|
|
* created atomically. As soon as the file is no longer needed the lock
|
|
|
|
* token must be closed.
|
|
|
|
*
|
|
|
|
* @since 4.7
|
|
|
|
*/
|
|
|
|
public static class LockToken implements Closeable {
|
|
|
|
private boolean isCreated;
|
|
|
|
|
|
|
|
private Optional<Path> link;
|
|
|
|
|
|
|
|
LockToken(boolean isCreated, Optional<Path> link) {
|
|
|
|
this.isCreated = isCreated;
|
|
|
|
this.link = link;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {@code true} if the file was created successfully
|
|
|
|
*/
|
|
|
|
public boolean isCreated() {
|
|
|
|
return isCreated;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void close() {
|
|
|
|
if (link.isPresent()) {
|
|
|
|
try {
|
|
|
|
Files.delete(link.get());
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOG.error(MessageFormat.format(JGitText.get().closeLockTokenFailed,
|
|
|
|
this), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
|
|
|
|
", link=" //$NON-NLS-1$
|
|
|
|
+ (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
|
|
|
|
: "<null>]"); //$NON-NLS-1$
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
|
|
|
|
* of this class may take care to provide a safe implementation for this
|
|
|
|
* even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* the file to be created
|
|
|
|
* @return LockToken this token must be closed after the created file was
|
|
|
|
* deleted
|
|
|
|
* @throws IOException
|
|
|
|
* @since 4.7
|
|
|
|
*/
|
|
|
|
public LockToken createNewFileAtomic(File path) throws IOException {
|
|
|
|
return new LockToken(path.createNewFile(), Optional.empty());
|
|
|
|
}
|
|
|
|
|
2014-10-31 15:58:07 +02:00
|
|
|
/**
|
|
|
|
* See {@link FileUtils#relativize(String, String)}.
|
|
|
|
*
|
|
|
|
* @param base
|
|
|
|
* The path against which <code>other</code> should be
|
|
|
|
* relativized.
|
|
|
|
* @param other
|
|
|
|
* The path that will be made relative to <code>base</code>.
|
|
|
|
* @return A relative path that, when resolved against <code>base</code>,
|
|
|
|
* will yield the original <code>other</code>.
|
|
|
|
* @see FileUtils#relativize(String, String)
|
|
|
|
* @since 3.7
|
|
|
|
*/
|
|
|
|
public String relativize(String base, String other) {
|
|
|
|
return FileUtils.relativize(base, other);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the given hook is defined for the given repository, then
|
|
|
|
* runs it with the given arguments.
|
|
|
|
* <p>
|
|
|
|
* The hook's standard output and error streams will be redirected to
|
|
|
|
* <code>System.out</code> and <code>System.err</code> respectively. The
|
|
|
|
* hook will have no stdin.
|
|
|
|
* </p>
|
|
|
|
*
|
|
|
|
* @param repository
|
|
|
|
* The repository for which a hook should be run.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @param hookName
|
|
|
|
* The name of the hook to be executed.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param args
|
|
|
|
* Arguments to pass to this hook. Cannot be <code>null</code>,
|
|
|
|
* but can be an empty array.
|
|
|
|
* @return The ProcessResult describing this hook's execution.
|
|
|
|
* @throws JGitInternalException
|
|
|
|
* if we fail to run the hook somehow. Causes may include an
|
|
|
|
* interrupted process or I/O errors.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @since 4.0
|
2014-10-31 15:58:07 +02:00
|
|
|
*/
|
2015-02-23 12:18:50 +02:00
|
|
|
public ProcessResult runHookIfPresent(Repository repository,
|
|
|
|
final String hookName,
|
2014-10-31 15:58:07 +02:00
|
|
|
String[] args) throws JGitInternalException {
|
2015-02-23 12:18:50 +02:00
|
|
|
return runHookIfPresent(repository, hookName, args, System.out, System.err,
|
2014-10-31 15:58:07 +02:00
|
|
|
null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the given hook is defined for the given repository, then
|
|
|
|
* runs it with the given arguments.
|
|
|
|
*
|
|
|
|
* @param repository
|
|
|
|
* The repository for which a hook should be run.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @param hookName
|
|
|
|
* The name of the hook to be executed.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param args
|
|
|
|
* Arguments to pass to this hook. Cannot be <code>null</code>,
|
|
|
|
* but can be an empty array.
|
|
|
|
* @param outRedirect
|
|
|
|
* A print stream on which to redirect the hook's stdout. Can be
|
|
|
|
* <code>null</code>, in which case the hook's standard output
|
|
|
|
* will be lost.
|
|
|
|
* @param errRedirect
|
|
|
|
* A print stream on which to redirect the hook's stderr. Can be
|
|
|
|
* <code>null</code>, in which case the hook's standard error
|
|
|
|
* will be lost.
|
|
|
|
* @param stdinArgs
|
|
|
|
* A string to pass on to the standard input of the hook. May be
|
|
|
|
* <code>null</code>.
|
|
|
|
* @return The ProcessResult describing this hook's execution.
|
|
|
|
* @throws JGitInternalException
|
|
|
|
* if we fail to run the hook somehow. Causes may include an
|
|
|
|
* interrupted process or I/O errors.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @since 4.0
|
2014-10-31 15:58:07 +02:00
|
|
|
*/
|
2015-02-23 12:18:50 +02:00
|
|
|
public ProcessResult runHookIfPresent(Repository repository,
|
|
|
|
final String hookName,
|
2014-10-31 15:58:07 +02:00
|
|
|
String[] args, PrintStream outRedirect, PrintStream errRedirect,
|
|
|
|
String stdinArgs) throws JGitInternalException {
|
|
|
|
return new ProcessResult(Status.NOT_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See
|
2015-02-23 12:18:50 +02:00
|
|
|
* {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
|
2014-10-31 15:58:07 +02:00
|
|
|
* . Should only be called by FS supporting shell scripts execution.
|
|
|
|
*
|
|
|
|
* @param repository
|
|
|
|
* The repository for which a hook should be run.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @param hookName
|
|
|
|
* The name of the hook to be executed.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param args
|
|
|
|
* Arguments to pass to this hook. Cannot be <code>null</code>,
|
|
|
|
* but can be an empty array.
|
|
|
|
* @param outRedirect
|
|
|
|
* A print stream on which to redirect the hook's stdout. Can be
|
|
|
|
* <code>null</code>, in which case the hook's standard output
|
|
|
|
* will be lost.
|
|
|
|
* @param errRedirect
|
|
|
|
* A print stream on which to redirect the hook's stderr. Can be
|
|
|
|
* <code>null</code>, in which case the hook's standard error
|
|
|
|
* will be lost.
|
|
|
|
* @param stdinArgs
|
|
|
|
* A string to pass on to the standard input of the hook. May be
|
|
|
|
* <code>null</code>.
|
|
|
|
* @return The ProcessResult describing this hook's execution.
|
|
|
|
* @throws JGitInternalException
|
|
|
|
* if we fail to run the hook somehow. Causes may include an
|
|
|
|
* interrupted process or I/O errors.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @since 4.0
|
2014-10-31 15:58:07 +02:00
|
|
|
*/
|
2015-02-23 12:18:50 +02:00
|
|
|
protected ProcessResult internalRunHookIfPresent(Repository repository,
|
|
|
|
final String hookName, String[] args, PrintStream outRedirect,
|
2014-10-31 15:58:07 +02:00
|
|
|
PrintStream errRedirect, String stdinArgs)
|
|
|
|
throws JGitInternalException {
|
2015-02-23 12:18:50 +02:00
|
|
|
final File hookFile = findHook(repository, hookName);
|
2014-10-31 15:58:07 +02:00
|
|
|
if (hookFile == null)
|
|
|
|
return new ProcessResult(Status.NOT_PRESENT);
|
|
|
|
|
|
|
|
final String hookPath = hookFile.getAbsolutePath();
|
|
|
|
final File runDirectory;
|
|
|
|
if (repository.isBare())
|
|
|
|
runDirectory = repository.getDirectory();
|
|
|
|
else
|
|
|
|
runDirectory = repository.getWorkTree();
|
|
|
|
final String cmd = relativize(runDirectory.getAbsolutePath(),
|
|
|
|
hookPath);
|
|
|
|
ProcessBuilder hookProcess = runInShell(cmd, args);
|
|
|
|
hookProcess.directory(runDirectory);
|
|
|
|
try {
|
|
|
|
return new ProcessResult(runProcess(hookProcess, outRedirect,
|
|
|
|
errRedirect, stdinArgs), Status.OK);
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new JGitInternalException(MessageFormat.format(
|
|
|
|
JGitText.get().exceptionCaughtDuringExecutionOfHook,
|
2015-02-23 12:18:50 +02:00
|
|
|
hookName), e);
|
2014-10-31 15:58:07 +02:00
|
|
|
} catch (InterruptedException e) {
|
|
|
|
throw new JGitInternalException(MessageFormat.format(
|
|
|
|
JGitText.get().exceptionHookExecutionInterrupted,
|
2015-02-23 12:18:50 +02:00
|
|
|
hookName), e);
|
2014-10-31 15:58:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to find a hook matching the given one in the given repository.
|
|
|
|
*
|
|
|
|
* @param repository
|
|
|
|
* The repository within which to find a hook.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @param hookName
|
|
|
|
* The name of the hook we're trying to find.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @return The {@link File} containing this particular hook if it exists in
|
|
|
|
* the given repository, <code>null</code> otherwise.
|
2015-02-23 12:18:50 +02:00
|
|
|
* @since 4.0
|
2014-10-31 15:58:07 +02:00
|
|
|
*/
|
2015-02-23 12:18:50 +02:00
|
|
|
public File findHook(Repository repository, final String hookName) {
|
2015-07-10 14:01:10 +03:00
|
|
|
File gitDir = repository.getDirectory();
|
|
|
|
if (gitDir == null)
|
|
|
|
return null;
|
|
|
|
final File hookFile = new File(new File(gitDir,
|
2015-02-23 12:18:50 +02:00
|
|
|
Constants.HOOKS), hookName);
|
2014-10-31 15:58:07 +02:00
|
|
|
return hookFile.isFile() ? hookFile : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the given process until termination, clearing its stdout and stderr
|
|
|
|
* streams on-the-fly.
|
|
|
|
*
|
2015-06-17 15:54:11 +03:00
|
|
|
* @param processBuilder
|
|
|
|
* The process builder configured for this process.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param outRedirect
|
2015-06-17 15:54:11 +03:00
|
|
|
* A OutputStream on which to redirect the processes stdout. Can
|
|
|
|
* be <code>null</code>, in which case the processes standard
|
|
|
|
* output will be lost.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param errRedirect
|
2015-06-17 15:54:11 +03:00
|
|
|
* A OutputStream on which to redirect the processes stderr. Can
|
|
|
|
* be <code>null</code>, in which case the processes standard
|
|
|
|
* error will be lost.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @param stdinArgs
|
|
|
|
* A string to pass on to the standard input of the hook. Can be
|
|
|
|
* <code>null</code>.
|
2015-06-17 15:54:11 +03:00
|
|
|
* @return the exit value of this process.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @throws IOException
|
2015-06-17 15:54:11 +03:00
|
|
|
* if an I/O error occurs while executing this process.
|
2014-10-31 15:58:07 +02:00
|
|
|
* @throws InterruptedException
|
|
|
|
* if the current thread is interrupted while waiting for the
|
|
|
|
* process to end.
|
2015-06-17 15:54:11 +03:00
|
|
|
* @since 4.2
|
2014-10-31 15:58:07 +02:00
|
|
|
*/
|
2015-06-17 15:54:11 +03:00
|
|
|
public int runProcess(ProcessBuilder processBuilder,
|
2014-10-31 15:58:07 +02:00
|
|
|
OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
|
|
|
|
throws IOException, InterruptedException {
|
2015-06-17 15:54:11 +03:00
|
|
|
InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
|
|
|
|
stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
|
|
|
|
return runProcess(processBuilder, outRedirect, errRedirect, in);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the given process until termination, clearing its stdout and stderr
|
|
|
|
* streams on-the-fly.
|
|
|
|
*
|
|
|
|
* @param processBuilder
|
|
|
|
* The process builder configured for this process.
|
|
|
|
* @param outRedirect
|
|
|
|
* An OutputStream on which to redirect the processes stdout. Can
|
|
|
|
* be <code>null</code>, in which case the processes standard
|
2015-11-24 12:18:59 +02:00
|
|
|
* output will be lost.
|
2015-06-17 15:54:11 +03:00
|
|
|
* @param errRedirect
|
|
|
|
* An OutputStream on which to redirect the processes stderr. Can
|
|
|
|
* be <code>null</code>, in which case the processes standard
|
|
|
|
* error will be lost.
|
|
|
|
* @param inRedirect
|
|
|
|
* An InputStream from which to redirect the processes stdin. Can
|
|
|
|
* be <code>null</code>, in which case the process doesn't get
|
2015-11-24 12:18:59 +02:00
|
|
|
* any data over stdin. It is assumed that the whole InputStream
|
|
|
|
* will be consumed by the process. The method will close the
|
|
|
|
* inputstream after all bytes are read.
|
2015-06-17 15:54:11 +03:00
|
|
|
* @return the return code of this process.
|
|
|
|
* @throws IOException
|
|
|
|
* if an I/O error occurs while executing this process.
|
|
|
|
* @throws InterruptedException
|
|
|
|
* if the current thread is interrupted while waiting for the
|
|
|
|
* process to end.
|
|
|
|
* @since 4.2
|
|
|
|
*/
|
|
|
|
public int runProcess(ProcessBuilder processBuilder,
|
|
|
|
OutputStream outRedirect, OutputStream errRedirect,
|
|
|
|
InputStream inRedirect) throws IOException,
|
|
|
|
InterruptedException {
|
2014-10-31 15:58:07 +02:00
|
|
|
final ExecutorService executor = Executors.newFixedThreadPool(2);
|
|
|
|
Process process = null;
|
|
|
|
// We'll record the first I/O exception that occurs, but keep on trying
|
|
|
|
// to dispose of our open streams and file handles
|
|
|
|
IOException ioException = null;
|
|
|
|
try {
|
2015-06-17 15:54:11 +03:00
|
|
|
process = processBuilder.start();
|
2017-01-20 18:41:29 +02:00
|
|
|
executor.execute(
|
|
|
|
new StreamGobbler(process.getErrorStream(), errRedirect));
|
|
|
|
executor.execute(
|
|
|
|
new StreamGobbler(process.getInputStream(), outRedirect));
|
2015-06-17 15:54:11 +03:00
|
|
|
OutputStream outputStream = process.getOutputStream();
|
|
|
|
if (inRedirect != null) {
|
2017-01-20 18:41:29 +02:00
|
|
|
new StreamGobbler(inRedirect, outputStream).copy();
|
2014-10-31 15:58:07 +02:00
|
|
|
}
|
2016-08-19 23:51:05 +03:00
|
|
|
try {
|
|
|
|
outputStream.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
// When the process exits before consuming the input, the OutputStream
|
|
|
|
// is replaced with the null output stream. This null output stream
|
|
|
|
// throws IOException for all write calls. When StreamGobbler fails to
|
|
|
|
// flush the buffer because of this, this close call tries to flush it
|
|
|
|
// again. This causes another IOException. Since we ignore the
|
|
|
|
// IOException in StreamGobbler, we also ignore the exception here.
|
|
|
|
}
|
2014-10-31 15:58:07 +02:00
|
|
|
return process.waitFor();
|
|
|
|
} catch (IOException e) {
|
|
|
|
ioException = e;
|
|
|
|
} finally {
|
|
|
|
shutdownAndAwaitTermination(executor);
|
|
|
|
if (process != null) {
|
|
|
|
try {
|
|
|
|
process.waitFor();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
// Thrown by the outer try.
|
|
|
|
// Swallow this one to carry on our cleanup, and clear the
|
|
|
|
// interrupted flag (processes throw the exception without
|
|
|
|
// clearing the flag).
|
|
|
|
Thread.interrupted();
|
|
|
|
}
|
|
|
|
// A process doesn't clean its own resources even when destroyed
|
|
|
|
// Explicitly try and close all three streams, preserving the
|
|
|
|
// outer I/O exception if any.
|
2015-11-24 12:18:59 +02:00
|
|
|
if (inRedirect != null) {
|
|
|
|
inRedirect.close();
|
|
|
|
}
|
2014-10-31 15:58:07 +02:00
|
|
|
try {
|
|
|
|
process.getErrorStream().close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
ioException = ioException != null ? ioException : e;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
process.getInputStream().close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
ioException = ioException != null ? ioException : e;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
process.getOutputStream().close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
ioException = ioException != null ? ioException : e;
|
|
|
|
}
|
|
|
|
process.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We can only be here if the outer try threw an IOException.
|
|
|
|
throw ioException;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shuts down an {@link ExecutorService} in two phases, first by calling
|
|
|
|
* {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
|
|
|
|
* then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
|
|
|
|
* necessary, to cancel any lingering tasks. Returns true if the pool has
|
|
|
|
* been properly shutdown, false otherwise.
|
|
|
|
* <p>
|
|
|
|
*
|
|
|
|
* @param pool
|
|
|
|
* the pool to shutdown
|
|
|
|
* @return <code>true</code> if the pool has been properly shutdown,
|
|
|
|
* <code>false</code> otherwise.
|
|
|
|
*/
|
|
|
|
private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
|
|
|
|
boolean hasShutdown = true;
|
|
|
|
pool.shutdown(); // Disable new tasks from being submitted
|
|
|
|
try {
|
|
|
|
// Wait a while for existing tasks to terminate
|
2015-10-28 14:24:28 +02:00
|
|
|
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
|
2014-10-31 15:58:07 +02:00
|
|
|
pool.shutdownNow(); // Cancel currently executing tasks
|
|
|
|
// Wait a while for tasks to respond to being canceled
|
2015-10-28 14:24:28 +02:00
|
|
|
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
|
2014-10-31 15:58:07 +02:00
|
|
|
hasShutdown = false;
|
|
|
|
}
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
// (Re-)Cancel if current thread also interrupted
|
|
|
|
pool.shutdownNow();
|
|
|
|
// Preserve interrupt status
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
hasShutdown = false;
|
|
|
|
}
|
|
|
|
return hasShutdown;
|
|
|
|
}
|
|
|
|
|
2011-02-04 15:51:27 +02:00
|
|
|
/**
|
2015-05-25 21:07:53 +03:00
|
|
|
* Initialize a ProcessBuilder to run a command using the system shell.
|
2011-02-04 15:51:27 +02:00
|
|
|
*
|
|
|
|
* @param cmd
|
|
|
|
* command to execute. This string should originate from the
|
|
|
|
* end-user, and thus is platform specific.
|
|
|
|
* @param args
|
|
|
|
* arguments to pass to command. These should be protected from
|
|
|
|
* shell evaluation.
|
|
|
|
* @return a partially completed process builder. Caller should finish
|
|
|
|
* populating directory, environment, and then start the process.
|
|
|
|
*/
|
|
|
|
public abstract ProcessBuilder runInShell(String cmd, String[] args);
|
2011-03-14 18:07:53 +02:00
|
|
|
|
2015-10-28 14:24:28 +02:00
|
|
|
/**
|
|
|
|
* Execute a command defined by a {@link ProcessBuilder}.
|
|
|
|
*
|
|
|
|
* @param pb
|
|
|
|
* The command to be executed
|
|
|
|
* @param in
|
|
|
|
* The standard input stream passed to the process
|
|
|
|
* @return The result of the executed command
|
|
|
|
* @throws InterruptedException
|
|
|
|
* @throws IOException
|
|
|
|
* @since 4.2
|
|
|
|
*/
|
|
|
|
public ExecutionResult execute(ProcessBuilder pb, InputStream in)
|
|
|
|
throws IOException, InterruptedException {
|
|
|
|
TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
|
|
|
|
TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
|
|
|
|
try {
|
|
|
|
int rc = runProcess(pb, stdout, stderr, in);
|
|
|
|
return new ExecutionResult(stdout, stderr, rc);
|
|
|
|
} finally {
|
|
|
|
stdout.close();
|
|
|
|
stderr.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-14 18:07:53 +02:00
|
|
|
private static class Holder<V> {
|
|
|
|
final V value;
|
|
|
|
|
|
|
|
Holder(V value) {
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
}
|
2014-01-23 07:07:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* File attributes we typically care for.
|
|
|
|
*
|
|
|
|
* @since 3.3
|
|
|
|
*/
|
|
|
|
public static class Attributes {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if this are the attributes of a directory
|
|
|
|
*/
|
|
|
|
public boolean isDirectory() {
|
|
|
|
return isDirectory;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if this are the attributes of an executable file
|
|
|
|
*/
|
|
|
|
public boolean isExecutable() {
|
|
|
|
return isExecutable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if this are the attributes of a symbolic link
|
|
|
|
*/
|
|
|
|
public boolean isSymbolicLink() {
|
|
|
|
return isSymbolicLink;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if this are the attributes of a regular file
|
|
|
|
*/
|
|
|
|
public boolean isRegularFile() {
|
|
|
|
return isRegularFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the time when the file was created
|
|
|
|
*/
|
|
|
|
public long getCreationTime() {
|
|
|
|
return creationTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the time (milliseconds since 1970-01-01) when this object was
|
|
|
|
* last modified
|
|
|
|
*/
|
|
|
|
public long getLastModifiedTime() {
|
|
|
|
return lastModifiedTime;
|
|
|
|
}
|
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final boolean isDirectory;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final boolean isSymbolicLink;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final boolean isRegularFile;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final long creationTime;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final long lastModifiedTime;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final boolean isExecutable;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final File file;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
private final boolean exists;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* file length
|
|
|
|
*/
|
|
|
|
protected long length = -1;
|
|
|
|
|
2015-08-10 01:18:48 +03:00
|
|
|
final FS fs;
|
2014-01-23 07:07:12 +02:00
|
|
|
|
|
|
|
Attributes(FS fs, File file, boolean exists, boolean isDirectory,
|
|
|
|
boolean isExecutable, boolean isSymbolicLink,
|
|
|
|
boolean isRegularFile, long creationTime,
|
|
|
|
long lastModifiedTime, long length) {
|
|
|
|
this.fs = fs;
|
|
|
|
this.file = file;
|
|
|
|
this.exists = exists;
|
|
|
|
this.isDirectory = isDirectory;
|
|
|
|
this.isExecutable = isExecutable;
|
|
|
|
this.isSymbolicLink = isSymbolicLink;
|
|
|
|
this.isRegularFile = isRegularFile;
|
|
|
|
this.creationTime = creationTime;
|
|
|
|
this.lastModifiedTime = lastModifiedTime;
|
|
|
|
this.length = length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-10 01:18:48 +03:00
|
|
|
* Constructor when there are issues with reading. All attributes except
|
|
|
|
* given will be set to the default values.
|
2014-01-23 07:07:12 +02:00
|
|
|
*
|
|
|
|
* @param fs
|
|
|
|
* @param path
|
|
|
|
*/
|
|
|
|
public Attributes(File path, FS fs) {
|
2015-08-10 01:18:48 +03:00
|
|
|
this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
|
2014-01-23 07:07:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return length of this file object
|
|
|
|
*/
|
|
|
|
public long getLength() {
|
|
|
|
if (length == -1)
|
|
|
|
return length = file.length();
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the filename
|
|
|
|
*/
|
|
|
|
public String getName() {
|
|
|
|
return file.getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the file the attributes apply to
|
|
|
|
*/
|
|
|
|
public File getFile() {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean exists() {
|
|
|
|
return exists;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param path
|
|
|
|
* @return the file attributes we care for
|
|
|
|
* @since 3.3
|
|
|
|
*/
|
|
|
|
public Attributes getAttributes(File path) {
|
|
|
|
boolean isDirectory = isDirectory(path);
|
|
|
|
boolean isFile = !isDirectory && path.isFile();
|
|
|
|
assert path.exists() == isDirectory || isFile;
|
|
|
|
boolean exists = isDirectory || isFile;
|
|
|
|
boolean canExecute = exists && !isDirectory && canExecute(path);
|
|
|
|
boolean isSymlink = false;
|
|
|
|
long lastModified = exists ? path.lastModified() : 0L;
|
|
|
|
long createTime = 0L;
|
|
|
|
return new Attributes(this, path, exists, isDirectory, canExecute,
|
|
|
|
isSymlink, isFile, createTime, lastModified, -1);
|
|
|
|
}
|
2013-07-11 02:11:12 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Normalize the unicode path to composed form.
|
|
|
|
*
|
|
|
|
* @param file
|
|
|
|
* @return NFC-format File
|
|
|
|
* @since 3.3
|
|
|
|
*/
|
|
|
|
public File normalize(File file) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Normalize the unicode path to composed form.
|
|
|
|
*
|
|
|
|
* @param name
|
|
|
|
* @return NFC-format string
|
|
|
|
* @since 3.3
|
|
|
|
*/
|
|
|
|
public String normalize(String name) {
|
|
|
|
return name;
|
|
|
|
}
|
2014-10-31 15:58:07 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This runnable will consume an input stream's content into an output
|
|
|
|
* stream as soon as it gets available.
|
|
|
|
* <p>
|
|
|
|
* Typically used to empty processes' standard output and error, preventing
|
|
|
|
* them to choke.
|
|
|
|
* </p>
|
|
|
|
* <p>
|
|
|
|
* <b>Note</b> that a {@link StreamGobbler} will never close either of its
|
|
|
|
* streams.
|
|
|
|
* </p>
|
|
|
|
*/
|
2017-01-20 18:41:29 +02:00
|
|
|
private static class StreamGobbler implements Runnable {
|
2015-06-17 15:54:11 +03:00
|
|
|
private InputStream in;
|
2014-10-31 15:58:07 +02:00
|
|
|
|
2015-06-17 15:54:11 +03:00
|
|
|
private OutputStream out;
|
2014-10-31 15:58:07 +02:00
|
|
|
|
|
|
|
public StreamGobbler(InputStream stream, OutputStream output) {
|
2015-06-17 15:54:11 +03:00
|
|
|
this.in = stream;
|
|
|
|
this.out = output;
|
2014-10-31 15:58:07 +02:00
|
|
|
}
|
|
|
|
|
2017-01-16 07:39:32 +02:00
|
|
|
@Override
|
2017-01-20 18:41:29 +02:00
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
copy();
|
|
|
|
} catch (IOException e) {
|
|
|
|
// Do nothing on read failure; leave streams open.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void copy() throws IOException {
|
2014-10-31 15:58:07 +02:00
|
|
|
boolean writeFailure = false;
|
2015-06-17 15:54:11 +03:00
|
|
|
byte buffer[] = new byte[4096];
|
|
|
|
int readBytes;
|
|
|
|
while ((readBytes = in.read(buffer)) != -1) {
|
|
|
|
// Do not try to write again after a failure, but keep
|
|
|
|
// reading as long as possible to prevent the input stream
|
|
|
|
// from choking.
|
|
|
|
if (!writeFailure && out != null) {
|
2014-10-31 15:58:07 +02:00
|
|
|
try {
|
2015-06-17 15:54:11 +03:00
|
|
|
out.write(buffer, 0, readBytes);
|
|
|
|
out.flush();
|
2014-10-31 15:58:07 +02:00
|
|
|
} catch (IOException e) {
|
|
|
|
writeFailure = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-09-30 02:47:03 +03:00
|
|
|
}
|