You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FS.java 35KB

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