Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

FS.java 34KB

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