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