View Javadoc
1   /*
2    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.util;
45  
46  import java.io.BufferedReader;
47  import java.io.ByteArrayInputStream;
48  import java.io.File;
49  import java.io.IOException;
50  import java.io.InputStream;
51  import java.io.InputStreamReader;
52  import java.io.OutputStream;
53  import java.io.PrintStream;
54  import java.nio.charset.Charset;
55  import java.security.AccessController;
56  import java.security.PrivilegedAction;
57  import java.text.MessageFormat;
58  import java.util.Arrays;
59  import java.util.HashMap;
60  import java.util.Map;
61  import java.util.Objects;
62  import java.util.concurrent.Callable;
63  import java.util.concurrent.ExecutorService;
64  import java.util.concurrent.Executors;
65  import java.util.concurrent.TimeUnit;
66  import java.util.concurrent.atomic.AtomicBoolean;
67  
68  import org.eclipse.jgit.annotations.Nullable;
69  import org.eclipse.jgit.api.errors.JGitInternalException;
70  import org.eclipse.jgit.internal.JGitText;
71  import org.eclipse.jgit.lib.Constants;
72  import org.eclipse.jgit.lib.Repository;
73  import org.eclipse.jgit.util.ProcessResult.Status;
74  import org.slf4j.Logger;
75  import org.slf4j.LoggerFactory;
76  
77  /** Abstraction to support various file system operations not in Java. */
78  public abstract class FS {
79  	/**
80  	 * This class creates FS instances. It will be overridden by a Java7 variant
81  	 * if such can be detected in {@link #detect(Boolean)}.
82  	 *
83  	 * @since 3.0
84  	 */
85  	public static class FSFactory {
86  		/**
87  		 * Constructor
88  		 */
89  		protected FSFactory() {
90  			// empty
91  		}
92  
93  		/**
94  		 * Detect the file system
95  		 *
96  		 * @param cygwinUsed
97  		 * @return FS instance
98  		 */
99  		public FS detect(Boolean cygwinUsed) {
100 			if (SystemReader.getInstance().isWindows()) {
101 				if (cygwinUsed == null)
102 					cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
103 				if (cygwinUsed.booleanValue())
104 					return new FS_Win32_Cygwin();
105 				else
106 					return new FS_Win32();
107 			} else {
108 				return new FS_POSIX();
109 			}
110 		}
111 	}
112 
113 	/**
114 	 * Result of an executed process. The caller is responsible to close the
115 	 * contained {@link TemporaryBuffer}s
116 	 *
117 	 * @since 4.2
118 	 */
119 	public static class ExecutionResult {
120 		private TemporaryBuffer stdout;
121 
122 		private TemporaryBuffer stderr;
123 
124 		private int rc;
125 
126 		/**
127 		 * @param stdout
128 		 * @param stderr
129 		 * @param rc
130 		 */
131 		public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
132 				int rc) {
133 			this.stdout = stdout;
134 			this.stderr = stderr;
135 			this.rc = rc;
136 		}
137 
138 		/**
139 		 * @return buffered standard output stream
140 		 */
141 		public TemporaryBuffer getStdout() {
142 			return stdout;
143 		}
144 
145 		/**
146 		 * @return buffered standard error stream
147 		 */
148 		public TemporaryBuffer getStderr() {
149 			return stderr;
150 		}
151 
152 		/**
153 		 * @return the return code of the process
154 		 */
155 		public int getRc() {
156 			return rc;
157 		}
158 	}
159 
160 	private final static Logger LOG = LoggerFactory.getLogger(FS.class);
161 
162 	/** The auto-detected implementation selected for this operating system and JRE. */
163 	public static final FS DETECTED = detect();
164 
165 	private static FSFactory factory;
166 
167 	/**
168 	 * Auto-detect the appropriate file system abstraction.
169 	 *
170 	 * @return detected file system abstraction
171 	 */
172 	public static FS detect() {
173 		return detect(null);
174 	}
175 
176 	/**
177 	 * Auto-detect the appropriate file system abstraction, taking into account
178 	 * the presence of a Cygwin installation on the system. Using jgit in
179 	 * combination with Cygwin requires a more elaborate (and possibly slower)
180 	 * resolution of file system paths.
181 	 *
182 	 * @param cygwinUsed
183 	 *            <ul>
184 	 *            <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
185 	 *            combination with jgit</li>
186 	 *            <li><code>Boolean.FALSE</code> to assume that Cygwin is
187 	 *            <b>not</b> used with jgit</li>
188 	 *            <li><code>null</code> to auto-detect whether a Cygwin
189 	 *            installation is present on the system and in this case assume
190 	 *            that Cygwin is used</li>
191 	 *            </ul>
192 	 *
193 	 *            Note: this parameter is only relevant on Windows.
194 	 *
195 	 * @return detected file system abstraction
196 	 */
197 	public static FS detect(Boolean cygwinUsed) {
198 		if (factory == null) {
199 			factory = new FS.FSFactory();
200 		}
201 		return factory.detect(cygwinUsed);
202 	}
203 
204 	private volatile Holder<File> userHome;
205 
206 	private volatile Holder<File> gitSystemConfig;
207 
208 	/**
209 	 * Constructs a file system abstraction.
210 	 */
211 	protected FS() {
212 		// Do nothing by default.
213 	}
214 
215 	/**
216 	 * Initialize this FS using another's current settings.
217 	 *
218 	 * @param src
219 	 *            the source FS to copy from.
220 	 */
221 	protected FS(FS src) {
222 		userHome = src.userHome;
223 		gitSystemConfig = src.gitSystemConfig;
224 	}
225 
226 	/** @return a new instance of the same type of FS. */
227 	public abstract FS newInstance();
228 
229 	/**
230 	 * Does this operating system and JRE support the execute flag on files?
231 	 *
232 	 * @return true if this implementation can provide reasonably accurate
233 	 *         executable bit information; false otherwise.
234 	 */
235 	public abstract boolean supportsExecute();
236 
237 	/**
238 	 * Does this operating system and JRE supports symbolic links. The
239 	 * capability to handle symbolic links is detected at runtime.
240 	 *
241 	 * @return true if symbolic links may be used
242 	 * @since 3.0
243 	 */
244 	public boolean supportsSymlinks() {
245 		return false;
246 	}
247 
248 	/**
249 	 * Is this file system case sensitive
250 	 *
251 	 * @return true if this implementation is case sensitive
252 	 */
253 	public abstract boolean isCaseSensitive();
254 
255 	/**
256 	 * Determine if the file is executable (or not).
257 	 * <p>
258 	 * Not all platforms and JREs support executable flags on files. If the
259 	 * feature is unsupported this method will always return false.
260 	 * <p>
261 	 * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
262 	 * this method returns false, rather than the state of the executable flags
263 	 * on the target file.</em>
264 	 *
265 	 * @param f
266 	 *            abstract path to test.
267 	 * @return true if the file is believed to be executable by the user.
268 	 */
269 	public abstract boolean canExecute(File f);
270 
271 	/**
272 	 * Set a file to be executable by the user.
273 	 * <p>
274 	 * Not all platforms and JREs support executable flags on files. If the
275 	 * feature is unsupported this method will always return false and no
276 	 * changes will be made to the file specified.
277 	 *
278 	 * @param f
279 	 *            path to modify the executable status of.
280 	 * @param canExec
281 	 *            true to enable execution; false to disable it.
282 	 * @return true if the change succeeded; false otherwise.
283 	 */
284 	public abstract boolean setExecute(File f, boolean canExec);
285 
286 	/**
287 	 * Get the last modified time of a file system object. If the OS/JRE support
288 	 * symbolic links, the modification time of the link is returned, rather
289 	 * than that of the link target.
290 	 *
291 	 * @param f
292 	 * @return last modified time of f
293 	 * @throws IOException
294 	 * @since 3.0
295 	 */
296 	public long lastModified(File f) throws IOException {
297 		return FileUtils.lastModified(f);
298 	}
299 
300 	/**
301 	 * Set the last modified time of a file system object. If the OS/JRE support
302 	 * symbolic links, the link is modified, not the target,
303 	 *
304 	 * @param f
305 	 * @param time
306 	 * @throws IOException
307 	 * @since 3.0
308 	 */
309 	public void setLastModified(File f, long time) throws IOException {
310 		FileUtils.setLastModified(f, time);
311 	}
312 
313 	/**
314 	 * Get the length of a file or link, If the OS/JRE supports symbolic links
315 	 * it's the length of the link, else the length of the target.
316 	 *
317 	 * @param path
318 	 * @return length of a file
319 	 * @throws IOException
320 	 * @since 3.0
321 	 */
322 	public long length(File path) throws IOException {
323 		return FileUtils.getLength(path);
324 	}
325 
326 	/**
327 	 * Delete a file. Throws an exception if delete fails.
328 	 *
329 	 * @param f
330 	 * @throws IOException
331 	 *             this may be a Java7 subclass with detailed information
332 	 * @since 3.3
333 	 */
334 	public void delete(File f) throws IOException {
335 		FileUtils.delete(f);
336 	}
337 
338 	/**
339 	 * Resolve this file to its actual path name that the JRE can use.
340 	 * <p>
341 	 * This method can be relatively expensive. Computing a translation may
342 	 * require forking an external process per path name translated. Callers
343 	 * should try to minimize the number of translations necessary by caching
344 	 * the results.
345 	 * <p>
346 	 * Not all platforms and JREs require path name translation. Currently only
347 	 * Cygwin on Win32 require translation for Cygwin based paths.
348 	 *
349 	 * @param dir
350 	 *            directory relative to which the path name is.
351 	 * @param name
352 	 *            path name to translate.
353 	 * @return the translated path. <code>new File(dir,name)</code> if this
354 	 *         platform does not require path name translation.
355 	 */
356 	public File resolve(final File dir, final String name) {
357 		final File abspn = new File(name);
358 		if (abspn.isAbsolute())
359 			return abspn;
360 		return new File(dir, name);
361 	}
362 
363 	/**
364 	 * Determine the user's home directory (location where preferences are).
365 	 * <p>
366 	 * This method can be expensive on the first invocation if path name
367 	 * translation is required. Subsequent invocations return a cached result.
368 	 * <p>
369 	 * Not all platforms and JREs require path name translation. Currently only
370 	 * Cygwin on Win32 requires translation of the Cygwin HOME directory.
371 	 *
372 	 * @return the user's home directory; null if the user does not have one.
373 	 */
374 	public File userHome() {
375 		Holder<File> p = userHome;
376 		if (p == null) {
377 			p = new Holder<File>(userHomeImpl());
378 			userHome = p;
379 		}
380 		return p.value;
381 	}
382 
383 	/**
384 	 * Set the user's home directory location.
385 	 *
386 	 * @param path
387 	 *            the location of the user's preferences; null if there is no
388 	 *            home directory for the current user.
389 	 * @return {@code this}.
390 	 */
391 	public FS setUserHome(File path) {
392 		userHome = new Holder<File>(path);
393 		return this;
394 	}
395 
396 	/**
397 	 * Does this file system have problems with atomic renames?
398 	 *
399 	 * @return true if the caller should retry a failed rename of a lock file.
400 	 */
401 	public abstract boolean retryFailedLockFileCommit();
402 
403 	/**
404 	 * Determine the user's home directory (location where preferences are).
405 	 *
406 	 * @return the user's home directory; null if the user does not have one.
407 	 */
408 	protected File userHomeImpl() {
409 		final String home = AccessController
410 				.doPrivileged(new PrivilegedAction<String>() {
411 					public String run() {
412 						return System.getProperty("user.home"); //$NON-NLS-1$
413 					}
414 				});
415 		if (home == null || home.length() == 0)
416 			return null;
417 		return new File(home).getAbsoluteFile();
418 	}
419 
420 	/**
421 	 * Searches the given path to see if it contains one of the given files.
422 	 * Returns the first it finds. Returns null if not found or if path is null.
423 	 *
424 	 * @param path
425 	 *            List of paths to search separated by File.pathSeparator
426 	 * @param lookFor
427 	 *            Files to search for in the given path
428 	 * @return the first match found, or null
429 	 * @since 3.0
430 	 **/
431 	protected static File searchPath(final String path, final String... lookFor) {
432 		if (path == null)
433 			return null;
434 
435 		for (final String p : path.split(File.pathSeparator)) {
436 			for (String command : lookFor) {
437 				final File e = new File(p, command);
438 				if (e.isFile())
439 					return e.getAbsoluteFile();
440 			}
441 		}
442 		return null;
443 	}
444 
445 	/**
446 	 * Execute a command and return a single line of output as a String
447 	 *
448 	 * @param dir
449 	 *            Working directory for the command
450 	 * @param command
451 	 *            as component array
452 	 * @param encoding
453 	 *            to be used to parse the command's output
454 	 * @return the one-line output of the command or {@code null} if there is
455 	 *         none
456 	 */
457 	@Nullable
458 	protected static String readPipe(File dir, String[] command, String encoding) {
459 		return readPipe(dir, command, encoding, null);
460 	}
461 
462 	/**
463 	 * Execute a command and return a single line of output as a String
464 	 *
465 	 * @param dir
466 	 *            Working directory for the command
467 	 * @param command
468 	 *            as component array
469 	 * @param encoding
470 	 *            to be used to parse the command's output
471 	 * @param env
472 	 *            Map of environment variables to be merged with those of the
473 	 *            current process
474 	 * @return the one-line output of the command or {@code null} if there is
475 	 *         none
476 	 * @since 4.0
477 	 */
478 	@Nullable
479 	protected static String readPipe(File dir, String[] command, String encoding, Map<String, String> env) {
480 		final boolean debug = LOG.isDebugEnabled();
481 		try {
482 			if (debug) {
483 				LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
484 						+ dir);
485 			}
486 			ProcessBuilder pb = new ProcessBuilder(command);
487 			pb.directory(dir);
488 			if (env != null) {
489 				pb.environment().putAll(env);
490 			}
491 			Process p = pb.start();
492 			p.getOutputStream().close();
493 			GobblerThread gobbler = new GobblerThread(p, command, dir);
494 			gobbler.start();
495 			String r = null;
496 			try (BufferedReader lineRead = new BufferedReader(
497 					new InputStreamReader(p.getInputStream(), encoding))) {
498 				r = lineRead.readLine();
499 				if (debug) {
500 					LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
501 					LOG.debug("remaining output:\n"); //$NON-NLS-1$
502 					String l;
503 					while ((l = lineRead.readLine()) != null) {
504 						LOG.debug(l);
505 					}
506 				}
507 			}
508 
509 			for (;;) {
510 				try {
511 					int rc = p.waitFor();
512 					gobbler.join();
513 					if (rc == 0 && !gobbler.fail.get()) {
514 						return r;
515 					}
516 					if (debug) {
517 						LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
518 					}
519 					break;
520 				} catch (InterruptedException ie) {
521 					// Stop bothering me, I have a zombie to reap.
522 				}
523 			}
524 		} catch (IOException e) {
525 			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
526 		}
527 		if (debug) {
528 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
529 		}
530 		return null;
531 	}
532 
533 	private static class GobblerThread extends Thread {
534 		private final Process p;
535 		private final String desc;
536 		private final String dir;
537 		final AtomicBoolean fail = new AtomicBoolean();
538 
539 		GobblerThread(Process p, String[] command, File dir) {
540 			this.p = p;
541 			this.desc = Arrays.toString(command);
542 			this.dir = Objects.toString(dir);
543 		}
544 
545 		public void run() {
546 			StringBuilder err = new StringBuilder();
547 			try (InputStream is = p.getErrorStream()) {
548 				int ch;
549 				while ((ch = is.read()) != -1) {
550 					err.append((char) ch);
551 				}
552 			} catch (IOException e) {
553 				if (p.exitValue() != 0) {
554 					logError(e);
555 					fail.set(true);
556 				} else {
557 					// ignore. git terminated faster and stream was just closed
558 				}
559 			} finally {
560 				if (err.length() > 0) {
561 					LOG.error(err.toString());
562 				}
563 			}
564 		}
565 
566 		private void logError(Throwable t) {
567 			String msg = MessageFormat.format(
568 					JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir);
569 			LOG.error(msg, t);
570 		}
571 	}
572 
573 	/**
574 	 * @return the path to the Git executable or {@code null} if it cannot be
575 	 *         determined.
576 	 * @since 4.0
577 	 */
578 	protected abstract File discoverGitExe();
579 
580 	/**
581 	 * @return the path to the system-wide Git configuration file or
582 	 *         {@code null} if it cannot be determined.
583 	 * @since 4.0
584 	 */
585 	protected File discoverGitSystemConfig() {
586 		File gitExe = discoverGitExe();
587 		if (gitExe == null) {
588 			return null;
589 		}
590 
591 		// Bug 480782: Check if the discovered git executable is JGit CLI
592 		String v = readPipe(gitExe.getParentFile(),
593 				new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
594 				Charset.defaultCharset().name());
595 		if (v != null && v.startsWith("jgit")) { //$NON-NLS-1$
596 			return null;
597 		}
598 
599 		// Trick Git into printing the path to the config file by using "echo"
600 		// as the editor.
601 		Map<String, String> env = new HashMap<>();
602 		env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
603 
604 		String w = readPipe(gitExe.getParentFile(),
605 				new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
606 				Charset.defaultCharset().name(), env);
607 		if (StringUtils.isEmptyOrNull(w)) {
608 			return null;
609 		}
610 
611 		return new File(w);
612 	}
613 
614 	/**
615 	 * @return the currently used path to the system-wide Git configuration
616 	 *         file or {@code null} if none has been set.
617 	 * @since 4.0
618 	 */
619 	public File getGitSystemConfig() {
620 		if (gitSystemConfig == null) {
621 			gitSystemConfig = new Holder<File>(discoverGitSystemConfig());
622 		}
623 		return gitSystemConfig.value;
624 	}
625 
626 	/**
627 	 * Set the path to the system-wide Git configuration file to use.
628 	 *
629 	 * @param configFile
630 	 *            the path to the config file.
631 	 * @return {@code this}
632 	 * @since 4.0
633 	 */
634 	public FS setGitSystemConfig(File configFile) {
635 		gitSystemConfig = new Holder<File>(configFile);
636 		return this;
637 	}
638 
639 	/**
640 	 * @param grandchild
641 	 * @return the parent directory of this file's parent directory or
642 	 *         {@code null} in case there's no grandparent directory
643 	 * @since 4.0
644 	 */
645 	protected static File resolveGrandparentFile(File grandchild) {
646 		if (grandchild != null) {
647 			File parent = grandchild.getParentFile();
648 			if (parent != null)
649 				return parent.getParentFile();
650 		}
651 		return null;
652 	}
653 
654 	/**
655 	 * Check if a file is a symbolic link and read it
656 	 *
657 	 * @param path
658 	 * @return target of link or null
659 	 * @throws IOException
660 	 * @since 3.0
661 	 */
662 	public String readSymLink(File path) throws IOException {
663 		return FileUtils.readSymLink(path);
664 	}
665 
666 	/**
667 	 * @param path
668 	 * @return true if the path is a symbolic link (and we support these)
669 	 * @throws IOException
670 	 * @since 3.0
671 	 */
672 	public boolean isSymLink(File path) throws IOException {
673 		return FileUtils.isSymlink(path);
674 	}
675 
676 	/**
677 	 * Tests if the path exists, in case of a symbolic link, true even if the
678 	 * target does not exist
679 	 *
680 	 * @param path
681 	 * @return true if path exists
682 	 * @since 3.0
683 	 */
684 	public boolean exists(File path) {
685 		return FileUtils.exists(path);
686 	}
687 
688 	/**
689 	 * Check if path is a directory. If the OS/JRE supports symbolic links and
690 	 * path is a symbolic link to a directory, this method returns false.
691 	 *
692 	 * @param path
693 	 * @return true if file is a directory,
694 	 * @since 3.0
695 	 */
696 	public boolean isDirectory(File path) {
697 		return FileUtils.isDirectory(path);
698 	}
699 
700 	/**
701 	 * Examine if path represents a regular file. If the OS/JRE supports
702 	 * symbolic links the test returns false if path represents a symbolic link.
703 	 *
704 	 * @param path
705 	 * @return true if path represents a regular file
706 	 * @since 3.0
707 	 */
708 	public boolean isFile(File path) {
709 		return FileUtils.isFile(path);
710 	}
711 
712 	/**
713 	 * @param path
714 	 * @return true if path is hidden, either starts with . on unix or has the
715 	 *         hidden attribute in windows
716 	 * @throws IOException
717 	 * @since 3.0
718 	 */
719 	public boolean isHidden(File path) throws IOException {
720 		return FileUtils.isHidden(path);
721 	}
722 
723 	/**
724 	 * Set the hidden attribute for file whose name starts with a period.
725 	 *
726 	 * @param path
727 	 * @param hidden
728 	 * @throws IOException
729 	 * @since 3.0
730 	 */
731 	public void setHidden(File path, boolean hidden) throws IOException {
732 		FileUtils.setHidden(path, hidden);
733 	}
734 
735 	/**
736 	 * Create a symbolic link
737 	 *
738 	 * @param path
739 	 * @param target
740 	 * @throws IOException
741 	 * @since 3.0
742 	 */
743 	public void createSymLink(File path, String target) throws IOException {
744 		FileUtils.createSymLink(path, target);
745 	}
746 
747 	/**
748 	 * See {@link FileUtils#relativize(String, String)}.
749 	 *
750 	 * @param base
751 	 *            The path against which <code>other</code> should be
752 	 *            relativized.
753 	 * @param other
754 	 *            The path that will be made relative to <code>base</code>.
755 	 * @return A relative path that, when resolved against <code>base</code>,
756 	 *         will yield the original <code>other</code>.
757 	 * @see FileUtils#relativize(String, String)
758 	 * @since 3.7
759 	 */
760 	public String relativize(String base, String other) {
761 		return FileUtils.relativize(base, other);
762 	}
763 
764 	/**
765 	 * Checks whether the given hook is defined for the given repository, then
766 	 * runs it with the given arguments.
767 	 * <p>
768 	 * The hook's standard output and error streams will be redirected to
769 	 * <code>System.out</code> and <code>System.err</code> respectively. The
770 	 * hook will have no stdin.
771 	 * </p>
772 	 *
773 	 * @param repository
774 	 *            The repository for which a hook should be run.
775 	 * @param hookName
776 	 *            The name of the hook to be executed.
777 	 * @param args
778 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
779 	 *            but can be an empty array.
780 	 * @return The ProcessResult describing this hook's execution.
781 	 * @throws JGitInternalException
782 	 *             if we fail to run the hook somehow. Causes may include an
783 	 *             interrupted process or I/O errors.
784 	 * @since 4.0
785 	 */
786 	public ProcessResult runHookIfPresent(Repository repository,
787 			final String hookName,
788 			String[] args) throws JGitInternalException {
789 		return runHookIfPresent(repository, hookName, args, System.out, System.err,
790 				null);
791 	}
792 
793 	/**
794 	 * Checks whether the given hook is defined for the given repository, then
795 	 * runs it with the given arguments.
796 	 *
797 	 * @param repository
798 	 *            The repository for which a hook should be run.
799 	 * @param hookName
800 	 *            The name of the hook to be executed.
801 	 * @param args
802 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
803 	 *            but can be an empty array.
804 	 * @param outRedirect
805 	 *            A print stream on which to redirect the hook's stdout. Can be
806 	 *            <code>null</code>, in which case the hook's standard output
807 	 *            will be lost.
808 	 * @param errRedirect
809 	 *            A print stream on which to redirect the hook's stderr. Can be
810 	 *            <code>null</code>, in which case the hook's standard error
811 	 *            will be lost.
812 	 * @param stdinArgs
813 	 *            A string to pass on to the standard input of the hook. May be
814 	 *            <code>null</code>.
815 	 * @return The ProcessResult describing this hook's execution.
816 	 * @throws JGitInternalException
817 	 *             if we fail to run the hook somehow. Causes may include an
818 	 *             interrupted process or I/O errors.
819 	 * @since 4.0
820 	 */
821 	public ProcessResult runHookIfPresent(Repository repository,
822 			final String hookName,
823 			String[] args, PrintStream outRedirect, PrintStream errRedirect,
824 			String stdinArgs) throws JGitInternalException {
825 		return new ProcessResult(Status.NOT_SUPPORTED);
826 	}
827 
828 	/**
829 	 * See
830 	 * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
831 	 * . Should only be called by FS supporting shell scripts execution.
832 	 *
833 	 * @param repository
834 	 *            The repository for which a hook should be run.
835 	 * @param hookName
836 	 *            The name of the hook to be executed.
837 	 * @param args
838 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
839 	 *            but can be an empty array.
840 	 * @param outRedirect
841 	 *            A print stream on which to redirect the hook's stdout. Can be
842 	 *            <code>null</code>, in which case the hook's standard output
843 	 *            will be lost.
844 	 * @param errRedirect
845 	 *            A print stream on which to redirect the hook's stderr. Can be
846 	 *            <code>null</code>, in which case the hook's standard error
847 	 *            will be lost.
848 	 * @param stdinArgs
849 	 *            A string to pass on to the standard input of the hook. May be
850 	 *            <code>null</code>.
851 	 * @return The ProcessResult describing this hook's execution.
852 	 * @throws JGitInternalException
853 	 *             if we fail to run the hook somehow. Causes may include an
854 	 *             interrupted process or I/O errors.
855 	 * @since 4.0
856 	 */
857 	protected ProcessResult internalRunHookIfPresent(Repository repository,
858 			final String hookName, String[] args, PrintStream outRedirect,
859 			PrintStream errRedirect, String stdinArgs)
860 			throws JGitInternalException {
861 		final File hookFile = findHook(repository, hookName);
862 		if (hookFile == null)
863 			return new ProcessResult(Status.NOT_PRESENT);
864 
865 		final String hookPath = hookFile.getAbsolutePath();
866 		final File runDirectory;
867 		if (repository.isBare())
868 			runDirectory = repository.getDirectory();
869 		else
870 			runDirectory = repository.getWorkTree();
871 		final String cmd = relativize(runDirectory.getAbsolutePath(),
872 				hookPath);
873 		ProcessBuilder hookProcess = runInShell(cmd, args);
874 		hookProcess.directory(runDirectory);
875 		try {
876 			return new ProcessResult(runProcess(hookProcess, outRedirect,
877 					errRedirect, stdinArgs), Status.OK);
878 		} catch (IOException e) {
879 			throw new JGitInternalException(MessageFormat.format(
880 					JGitText.get().exceptionCaughtDuringExecutionOfHook,
881 					hookName), e);
882 		} catch (InterruptedException e) {
883 			throw new JGitInternalException(MessageFormat.format(
884 					JGitText.get().exceptionHookExecutionInterrupted,
885 							hookName), e);
886 		}
887 	}
888 
889 
890 	/**
891 	 * Tries to find a hook matching the given one in the given repository.
892 	 *
893 	 * @param repository
894 	 *            The repository within which to find a hook.
895 	 * @param hookName
896 	 *            The name of the hook we're trying to find.
897 	 * @return The {@link File} containing this particular hook if it exists in
898 	 *         the given repository, <code>null</code> otherwise.
899 	 * @since 4.0
900 	 */
901 	public File findHook(Repository repository, final String hookName) {
902 		File gitDir = repository.getDirectory();
903 		if (gitDir == null)
904 			return null;
905 		final File hookFile = new File(new File(gitDir,
906 				Constants.HOOKS), hookName);
907 		return hookFile.isFile() ? hookFile : null;
908 	}
909 
910 	/**
911 	 * Runs the given process until termination, clearing its stdout and stderr
912 	 * streams on-the-fly.
913 	 *
914 	 * @param processBuilder
915 	 *            The process builder configured for this process.
916 	 * @param outRedirect
917 	 *            A OutputStream on which to redirect the processes stdout. Can
918 	 *            be <code>null</code>, in which case the processes standard
919 	 *            output will be lost.
920 	 * @param errRedirect
921 	 *            A OutputStream on which to redirect the processes stderr. Can
922 	 *            be <code>null</code>, in which case the processes standard
923 	 *            error will be lost.
924 	 * @param stdinArgs
925 	 *            A string to pass on to the standard input of the hook. Can be
926 	 *            <code>null</code>.
927 	 * @return the exit value of this process.
928 	 * @throws IOException
929 	 *             if an I/O error occurs while executing this process.
930 	 * @throws InterruptedException
931 	 *             if the current thread is interrupted while waiting for the
932 	 *             process to end.
933 	 * @since 4.2
934 	 */
935 	public int runProcess(ProcessBuilder processBuilder,
936 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
937 			throws IOException, InterruptedException {
938 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
939 				stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
940 		return runProcess(processBuilder, outRedirect, errRedirect, in);
941 	}
942 
943 	/**
944 	 * Runs the given process until termination, clearing its stdout and stderr
945 	 * streams on-the-fly.
946 	 *
947 	 * @param processBuilder
948 	 *            The process builder configured for this process.
949 	 * @param outRedirect
950 	 *            An OutputStream on which to redirect the processes stdout. Can
951 	 *            be <code>null</code>, in which case the processes standard
952 	 *            output will be lost.
953 	 * @param errRedirect
954 	 *            An OutputStream on which to redirect the processes stderr. Can
955 	 *            be <code>null</code>, in which case the processes standard
956 	 *            error will be lost.
957 	 * @param inRedirect
958 	 *            An InputStream from which to redirect the processes stdin. Can
959 	 *            be <code>null</code>, in which case the process doesn't get
960 	 *            any data over stdin. It is assumed that the whole InputStream
961 	 *            will be consumed by the process. The method will close the
962 	 *            inputstream after all bytes are read.
963 	 * @return the return code of this process.
964 	 * @throws IOException
965 	 *             if an I/O error occurs while executing this process.
966 	 * @throws InterruptedException
967 	 *             if the current thread is interrupted while waiting for the
968 	 *             process to end.
969 	 * @since 4.2
970 	 */
971 	public int runProcess(ProcessBuilder processBuilder,
972 			OutputStream outRedirect, OutputStream errRedirect,
973 			InputStream inRedirect) throws IOException,
974 			InterruptedException {
975 		final ExecutorService executor = Executors.newFixedThreadPool(2);
976 		Process process = null;
977 		// We'll record the first I/O exception that occurs, but keep on trying
978 		// to dispose of our open streams and file handles
979 		IOException ioException = null;
980 		try {
981 			process = processBuilder.start();
982 			final Callable<Void> errorGobbler = new StreamGobbler(
983 					process.getErrorStream(), errRedirect);
984 			final Callable<Void> outputGobbler = new StreamGobbler(
985 					process.getInputStream(), outRedirect);
986 			executor.submit(errorGobbler);
987 			executor.submit(outputGobbler);
988 			OutputStream outputStream = process.getOutputStream();
989 			if (inRedirect != null) {
990 				new StreamGobbler(inRedirect, outputStream)
991 						.call();
992 			}
993 			outputStream.close();
994 			return process.waitFor();
995 		} catch (IOException e) {
996 			ioException = e;
997 		} finally {
998 			shutdownAndAwaitTermination(executor);
999 			if (process != null) {
1000 				try {
1001 					process.waitFor();
1002 				} catch (InterruptedException e) {
1003 					// Thrown by the outer try.
1004 					// Swallow this one to carry on our cleanup, and clear the
1005 					// interrupted flag (processes throw the exception without
1006 					// clearing the flag).
1007 					Thread.interrupted();
1008 				}
1009 				// A process doesn't clean its own resources even when destroyed
1010 				// Explicitly try and close all three streams, preserving the
1011 				// outer I/O exception if any.
1012 				if (inRedirect != null) {
1013 					inRedirect.close();
1014 				}
1015 				try {
1016 					process.getErrorStream().close();
1017 				} catch (IOException e) {
1018 					ioException = ioException != null ? ioException : e;
1019 				}
1020 				try {
1021 					process.getInputStream().close();
1022 				} catch (IOException e) {
1023 					ioException = ioException != null ? ioException : e;
1024 				}
1025 				try {
1026 					process.getOutputStream().close();
1027 				} catch (IOException e) {
1028 					ioException = ioException != null ? ioException : e;
1029 				}
1030 				process.destroy();
1031 			}
1032 		}
1033 		// We can only be here if the outer try threw an IOException.
1034 		throw ioException;
1035 	}
1036 
1037 	/**
1038 	 * Shuts down an {@link ExecutorService} in two phases, first by calling
1039 	 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
1040 	 * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
1041 	 * necessary, to cancel any lingering tasks. Returns true if the pool has
1042 	 * been properly shutdown, false otherwise.
1043 	 * <p>
1044 	 *
1045 	 * @param pool
1046 	 *            the pool to shutdown
1047 	 * @return <code>true</code> if the pool has been properly shutdown,
1048 	 *         <code>false</code> otherwise.
1049 	 */
1050 	private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1051 		boolean hasShutdown = true;
1052 		pool.shutdown(); // Disable new tasks from being submitted
1053 		try {
1054 			// Wait a while for existing tasks to terminate
1055 			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1056 				pool.shutdownNow(); // Cancel currently executing tasks
1057 				// Wait a while for tasks to respond to being canceled
1058 				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1059 					hasShutdown = false;
1060 			}
1061 		} catch (InterruptedException ie) {
1062 			// (Re-)Cancel if current thread also interrupted
1063 			pool.shutdownNow();
1064 			// Preserve interrupt status
1065 			Thread.currentThread().interrupt();
1066 			hasShutdown = false;
1067 		}
1068 		return hasShutdown;
1069 	}
1070 
1071 	/**
1072 	 * Initialize a ProcessBuilder to run a command using the system shell.
1073 	 *
1074 	 * @param cmd
1075 	 *            command to execute. This string should originate from the
1076 	 *            end-user, and thus is platform specific.
1077 	 * @param args
1078 	 *            arguments to pass to command. These should be protected from
1079 	 *            shell evaluation.
1080 	 * @return a partially completed process builder. Caller should finish
1081 	 *         populating directory, environment, and then start the process.
1082 	 */
1083 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
1084 
1085 	/**
1086 	 * Execute a command defined by a {@link ProcessBuilder}.
1087 	 *
1088 	 * @param pb
1089 	 *            The command to be executed
1090 	 * @param in
1091 	 *            The standard input stream passed to the process
1092 	 * @return The result of the executed command
1093 	 * @throws InterruptedException
1094 	 * @throws IOException
1095 	 * @since 4.2
1096 	 */
1097 	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1098 			throws IOException, InterruptedException {
1099 		TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1100 		TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
1101 		try {
1102 			int rc = runProcess(pb, stdout, stderr, in);
1103 			return new ExecutionResult(stdout, stderr, rc);
1104 		} finally {
1105 			stdout.close();
1106 			stderr.close();
1107 		}
1108 	}
1109 
1110 	private static class Holder<V> {
1111 		final V value;
1112 
1113 		Holder(V value) {
1114 			this.value = value;
1115 		}
1116 	}
1117 
1118 	/**
1119 	 * File attributes we typically care for.
1120 	 *
1121 	 * @since 3.3
1122 	 */
1123 	public static class Attributes {
1124 
1125 		/**
1126 		 * @return true if this are the attributes of a directory
1127 		 */
1128 		public boolean isDirectory() {
1129 			return isDirectory;
1130 		}
1131 
1132 		/**
1133 		 * @return true if this are the attributes of an executable file
1134 		 */
1135 		public boolean isExecutable() {
1136 			return isExecutable;
1137 		}
1138 
1139 		/**
1140 		 * @return true if this are the attributes of a symbolic link
1141 		 */
1142 		public boolean isSymbolicLink() {
1143 			return isSymbolicLink;
1144 		}
1145 
1146 		/**
1147 		 * @return true if this are the attributes of a regular file
1148 		 */
1149 		public boolean isRegularFile() {
1150 			return isRegularFile;
1151 		}
1152 
1153 		/**
1154 		 * @return the time when the file was created
1155 		 */
1156 		public long getCreationTime() {
1157 			return creationTime;
1158 		}
1159 
1160 		/**
1161 		 * @return the time (milliseconds since 1970-01-01) when this object was
1162 		 *         last modified
1163 		 */
1164 		public long getLastModifiedTime() {
1165 			return lastModifiedTime;
1166 		}
1167 
1168 		private final boolean isDirectory;
1169 
1170 		private final boolean isSymbolicLink;
1171 
1172 		private final boolean isRegularFile;
1173 
1174 		private final long creationTime;
1175 
1176 		private final long lastModifiedTime;
1177 
1178 		private final boolean isExecutable;
1179 
1180 		private final File file;
1181 
1182 		private final boolean exists;
1183 
1184 		/**
1185 		 * file length
1186 		 */
1187 		protected long length = -1;
1188 
1189 		final FS fs;
1190 
1191 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1192 				boolean isExecutable, boolean isSymbolicLink,
1193 				boolean isRegularFile, long creationTime,
1194 				long lastModifiedTime, long length) {
1195 			this.fs = fs;
1196 			this.file = file;
1197 			this.exists = exists;
1198 			this.isDirectory = isDirectory;
1199 			this.isExecutable = isExecutable;
1200 			this.isSymbolicLink = isSymbolicLink;
1201 			this.isRegularFile = isRegularFile;
1202 			this.creationTime = creationTime;
1203 			this.lastModifiedTime = lastModifiedTime;
1204 			this.length = length;
1205 		}
1206 
1207 		/**
1208 		 * Constructor when there are issues with reading. All attributes except
1209 		 * given will be set to the default values.
1210 		 *
1211 		 * @param fs
1212 		 * @param path
1213 		 */
1214 		public Attributes(File path, FS fs) {
1215 			this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1216 		}
1217 
1218 		/**
1219 		 * @return length of this file object
1220 		 */
1221 		public long getLength() {
1222 			if (length == -1)
1223 				return length = file.length();
1224 			return length;
1225 		}
1226 
1227 		/**
1228 		 * @return the filename
1229 		 */
1230 		public String getName() {
1231 			return file.getName();
1232 		}
1233 
1234 		/**
1235 		 * @return the file the attributes apply to
1236 		 */
1237 		public File getFile() {
1238 			return file;
1239 		}
1240 
1241 		boolean exists() {
1242 			return exists;
1243 		}
1244 	}
1245 
1246 	/**
1247 	 * @param path
1248 	 * @return the file attributes we care for
1249 	 * @since 3.3
1250 	 */
1251 	public Attributes getAttributes(File path) {
1252 		boolean isDirectory = isDirectory(path);
1253 		boolean isFile = !isDirectory && path.isFile();
1254 		assert path.exists() == isDirectory || isFile;
1255 		boolean exists = isDirectory || isFile;
1256 		boolean canExecute = exists && !isDirectory && canExecute(path);
1257 		boolean isSymlink = false;
1258 		long lastModified = exists ? path.lastModified() : 0L;
1259 		long createTime = 0L;
1260 		return new Attributes(this, path, exists, isDirectory, canExecute,
1261 				isSymlink, isFile, createTime, lastModified, -1);
1262 	}
1263 
1264 	/**
1265 	 * Normalize the unicode path to composed form.
1266 	 *
1267 	 * @param file
1268 	 * @return NFC-format File
1269 	 * @since 3.3
1270 	 */
1271 	public File normalize(File file) {
1272 		return file;
1273 	}
1274 
1275 	/**
1276 	 * Normalize the unicode path to composed form.
1277 	 *
1278 	 * @param name
1279 	 * @return NFC-format string
1280 	 * @since 3.3
1281 	 */
1282 	public String normalize(String name) {
1283 		return name;
1284 	}
1285 
1286 	/**
1287 	 * This runnable will consume an input stream's content into an output
1288 	 * stream as soon as it gets available.
1289 	 * <p>
1290 	 * Typically used to empty processes' standard output and error, preventing
1291 	 * them to choke.
1292 	 * </p>
1293 	 * <p>
1294 	 * <b>Note</b> that a {@link StreamGobbler} will never close either of its
1295 	 * streams.
1296 	 * </p>
1297 	 */
1298 	private static class StreamGobbler implements Callable<Void> {
1299 		private InputStream in;
1300 
1301 		private OutputStream out;
1302 
1303 		public StreamGobbler(InputStream stream, OutputStream output) {
1304 			this.in = stream;
1305 			this.out = output;
1306 		}
1307 
1308 		public Void call() throws IOException {
1309 			boolean writeFailure = false;
1310 			byte buffer[] = new byte[4096];
1311 			int readBytes;
1312 			while ((readBytes = in.read(buffer)) != -1) {
1313 				// Do not try to write again after a failure, but keep
1314 				// reading as long as possible to prevent the input stream
1315 				// from choking.
1316 				if (!writeFailure && out != null) {
1317 					try {
1318 						out.write(buffer, 0, readBytes);
1319 						out.flush();
1320 					} catch (IOException e) {
1321 						writeFailure = true;
1322 					}
1323 				}
1324 			}
1325 			return null;
1326 		}
1327 	}
1328 }