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