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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  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. Process p = pb.start();
  411. BufferedReader lineRead = new BufferedReader(
  412. new InputStreamReader(p.getInputStream(), encoding));
  413. p.getOutputStream().close();
  414. GobblerThread gobbler = new GobblerThread(p, command, dir);
  415. gobbler.start();
  416. String r = null;
  417. try {
  418. r = lineRead.readLine();
  419. if (debug) {
  420. LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  421. LOG.debug("(ignoring remaing output:"); //$NON-NLS-1$
  422. }
  423. String l;
  424. while ((l = lineRead.readLine()) != null) {
  425. if (debug) {
  426. LOG.debug(l);
  427. }
  428. }
  429. } finally {
  430. p.getErrorStream().close();
  431. lineRead.close();
  432. }
  433. for (;;) {
  434. try {
  435. int rc = p.waitFor();
  436. gobbler.join();
  437. if (rc == 0 && r != null && r.length() > 0
  438. && !gobbler.fail.get())
  439. return r;
  440. if (debug) {
  441. LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  442. }
  443. break;
  444. } catch (InterruptedException ie) {
  445. // Stop bothering me, I have a zombie to reap.
  446. }
  447. }
  448. } catch (IOException e) {
  449. LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  450. }
  451. if (debug) {
  452. LOG.debug("readpipe returns null"); //$NON-NLS-1$
  453. }
  454. return null;
  455. }
  456. private static class GobblerThread extends Thread {
  457. private final Process p;
  458. private final String desc;
  459. private final String dir;
  460. private final boolean debug = LOG.isDebugEnabled();
  461. private final AtomicBoolean fail = new AtomicBoolean();
  462. private GobblerThread(Process p, String[] command, File dir) {
  463. this.p = p;
  464. if (debug) {
  465. this.desc = Arrays.asList(command).toString();
  466. this.dir = dir.toString();
  467. } else {
  468. this.desc = null;
  469. this.dir = null;
  470. }
  471. }
  472. public void run() {
  473. InputStream is = p.getErrorStream();
  474. try {
  475. int ch;
  476. if (debug) {
  477. while ((ch = is.read()) != -1) {
  478. System.err.print((char) ch);
  479. }
  480. } else {
  481. while (is.read() != -1) {
  482. // ignore
  483. }
  484. }
  485. } catch (IOException e) {
  486. logError(e);
  487. fail.set(true);
  488. }
  489. try {
  490. is.close();
  491. } catch (IOException e) {
  492. logError(e);
  493. fail.set(true);
  494. }
  495. }
  496. private void logError(Throwable t) {
  497. if (!debug) {
  498. return;
  499. }
  500. String msg = MessageFormat.format(
  501. JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir);
  502. LOG.debug(msg, t);
  503. }
  504. }
  505. /**
  506. * @return the path to the Git executable or {@code null} if it cannot be
  507. * determined.
  508. * @since 4.0
  509. */
  510. protected abstract File discoverGitExe();
  511. /**
  512. * @return the path to the system-wide Git configuration file or
  513. * {@code null} if it cannot be determined.
  514. * @since 4.0
  515. */
  516. protected File discoverGitSystemConfig() {
  517. File gitExe = discoverGitExe();
  518. if (gitExe == null) {
  519. return null;
  520. }
  521. // Trick Git into printing the path to the config file by using "echo"
  522. // as the editor.
  523. Map<String, String> env = new HashMap<>();
  524. env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
  525. String w = readPipe(gitExe.getParentFile(),
  526. new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  527. Charset.defaultCharset().name(), env);
  528. if (StringUtils.isEmptyOrNull(w)) {
  529. return null;
  530. }
  531. return new File(w);
  532. }
  533. /**
  534. * @return the currently used path to the system-wide Git configuration
  535. * file or {@code null} if none has been set.
  536. * @since 4.0
  537. */
  538. public File getGitSystemConfig() {
  539. if (gitSystemConfig == null) {
  540. gitSystemConfig = new Holder<File>(discoverGitSystemConfig());
  541. }
  542. return gitSystemConfig.value;
  543. }
  544. /**
  545. * Set the path to the system-wide Git configuration file to use.
  546. *
  547. * @param configFile
  548. * the path to the config file.
  549. * @return {@code this}
  550. * @since 4.0
  551. */
  552. public FS setGitSystemConfig(File configFile) {
  553. gitSystemConfig = new Holder<File>(configFile);
  554. return this;
  555. }
  556. /**
  557. * @param grandchild
  558. * @return the parent directory of this file's parent directory or
  559. * {@code null} in case there's no grandparent directory
  560. * @since 4.0
  561. */
  562. protected static File resolveGrandparentFile(File grandchild) {
  563. if (grandchild != null) {
  564. File parent = grandchild.getParentFile();
  565. if (parent != null)
  566. return parent.getParentFile();
  567. }
  568. return null;
  569. }
  570. /**
  571. * Check if a file is a symbolic link and read it
  572. *
  573. * @param path
  574. * @return target of link or null
  575. * @throws IOException
  576. * @since 3.0
  577. */
  578. public String readSymLink(File path) throws IOException {
  579. throw new SymlinksNotSupportedException(
  580. JGitText.get().errorSymlinksNotSupported);
  581. }
  582. /**
  583. * @param path
  584. * @return true if the path is a symbolic link (and we support these)
  585. * @throws IOException
  586. * @since 3.0
  587. */
  588. public boolean isSymLink(File path) throws IOException {
  589. return false;
  590. }
  591. /**
  592. * Tests if the path exists, in case of a symbolic link, true even if the
  593. * target does not exist
  594. *
  595. * @param path
  596. * @return true if path exists
  597. * @since 3.0
  598. */
  599. public boolean exists(File path) {
  600. return path.exists();
  601. }
  602. /**
  603. * Check if path is a directory. If the OS/JRE supports symbolic links and
  604. * path is a symbolic link to a directory, this method returns false.
  605. *
  606. * @param path
  607. * @return true if file is a directory,
  608. * @since 3.0
  609. */
  610. public boolean isDirectory(File path) {
  611. return path.isDirectory();
  612. }
  613. /**
  614. * Examine if path represents a regular file. If the OS/JRE supports
  615. * symbolic links the test returns false if path represents a symbolic link.
  616. *
  617. * @param path
  618. * @return true if path represents a regular file
  619. * @since 3.0
  620. */
  621. public boolean isFile(File path) {
  622. return path.isFile();
  623. }
  624. /**
  625. * @param path
  626. * @return true if path is hidden, either starts with . on unix or has the
  627. * hidden attribute in windows
  628. * @throws IOException
  629. * @since 3.0
  630. */
  631. public boolean isHidden(File path) throws IOException {
  632. return path.isHidden();
  633. }
  634. /**
  635. * Set the hidden attribute for file whose name starts with a period.
  636. *
  637. * @param path
  638. * @param hidden
  639. * @throws IOException
  640. * @since 3.0
  641. */
  642. public void setHidden(File path, boolean hidden) throws IOException {
  643. if (!path.getName().startsWith(".")) //$NON-NLS-1$
  644. throw new IllegalArgumentException(
  645. JGitText.get().hiddenFilesStartWithDot);
  646. }
  647. /**
  648. * Create a symbolic link
  649. *
  650. * @param path
  651. * @param target
  652. * @throws IOException
  653. * @since 3.0
  654. */
  655. public void createSymLink(File path, String target) throws IOException {
  656. throw new SymlinksNotSupportedException(
  657. JGitText.get().errorSymlinksNotSupported);
  658. }
  659. /**
  660. * See {@link FileUtils#relativize(String, String)}.
  661. *
  662. * @param base
  663. * The path against which <code>other</code> should be
  664. * relativized.
  665. * @param other
  666. * The path that will be made relative to <code>base</code>.
  667. * @return A relative path that, when resolved against <code>base</code>,
  668. * will yield the original <code>other</code>.
  669. * @see FileUtils#relativize(String, String)
  670. * @since 3.7
  671. */
  672. public String relativize(String base, String other) {
  673. return FileUtils.relativize(base, other);
  674. }
  675. /**
  676. * Checks whether the given hook is defined for the given repository, then
  677. * runs it with the given arguments.
  678. * <p>
  679. * The hook's standard output and error streams will be redirected to
  680. * <code>System.out</code> and <code>System.err</code> respectively. The
  681. * hook will have no stdin.
  682. * </p>
  683. *
  684. * @param repository
  685. * The repository for which a hook should be run.
  686. * @param hookName
  687. * The name of the hook to be executed.
  688. * @param args
  689. * Arguments to pass to this hook. Cannot be <code>null</code>,
  690. * but can be an empty array.
  691. * @return The ProcessResult describing this hook's execution.
  692. * @throws JGitInternalException
  693. * if we fail to run the hook somehow. Causes may include an
  694. * interrupted process or I/O errors.
  695. * @since 4.0
  696. */
  697. public ProcessResult runHookIfPresent(Repository repository,
  698. final String hookName,
  699. String[] args) throws JGitInternalException {
  700. return runHookIfPresent(repository, hookName, args, System.out, System.err,
  701. null);
  702. }
  703. /**
  704. * Checks whether the given hook is defined for the given repository, then
  705. * runs it with the given arguments.
  706. *
  707. * @param repository
  708. * The repository for which a hook should be run.
  709. * @param hookName
  710. * The name of the hook to be executed.
  711. * @param args
  712. * Arguments to pass to this hook. Cannot be <code>null</code>,
  713. * but can be an empty array.
  714. * @param outRedirect
  715. * A print stream on which to redirect the hook's stdout. Can be
  716. * <code>null</code>, in which case the hook's standard output
  717. * will be lost.
  718. * @param errRedirect
  719. * A print stream on which to redirect the hook's stderr. Can be
  720. * <code>null</code>, in which case the hook's standard error
  721. * will be lost.
  722. * @param stdinArgs
  723. * A string to pass on to the standard input of the hook. May be
  724. * <code>null</code>.
  725. * @return The ProcessResult describing this hook's execution.
  726. * @throws JGitInternalException
  727. * if we fail to run the hook somehow. Causes may include an
  728. * interrupted process or I/O errors.
  729. * @since 4.0
  730. */
  731. public ProcessResult runHookIfPresent(Repository repository,
  732. final String hookName,
  733. String[] args, PrintStream outRedirect, PrintStream errRedirect,
  734. String stdinArgs) throws JGitInternalException {
  735. return new ProcessResult(Status.NOT_SUPPORTED);
  736. }
  737. /**
  738. * See
  739. * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
  740. * . Should only be called by FS supporting shell scripts execution.
  741. *
  742. * @param repository
  743. * The repository for which a hook should be run.
  744. * @param hookName
  745. * The name of the hook to be executed.
  746. * @param args
  747. * Arguments to pass to this hook. Cannot be <code>null</code>,
  748. * but can be an empty array.
  749. * @param outRedirect
  750. * A print stream on which to redirect the hook's stdout. Can be
  751. * <code>null</code>, in which case the hook's standard output
  752. * will be lost.
  753. * @param errRedirect
  754. * A print stream on which to redirect the hook's stderr. Can be
  755. * <code>null</code>, in which case the hook's standard error
  756. * will be lost.
  757. * @param stdinArgs
  758. * A string to pass on to the standard input of the hook. May be
  759. * <code>null</code>.
  760. * @return The ProcessResult describing this hook's execution.
  761. * @throws JGitInternalException
  762. * if we fail to run the hook somehow. Causes may include an
  763. * interrupted process or I/O errors.
  764. * @since 4.0
  765. */
  766. protected ProcessResult internalRunHookIfPresent(Repository repository,
  767. final String hookName, String[] args, PrintStream outRedirect,
  768. PrintStream errRedirect, String stdinArgs)
  769. throws JGitInternalException {
  770. final File hookFile = findHook(repository, hookName);
  771. if (hookFile == null)
  772. return new ProcessResult(Status.NOT_PRESENT);
  773. final String hookPath = hookFile.getAbsolutePath();
  774. final File runDirectory;
  775. if (repository.isBare())
  776. runDirectory = repository.getDirectory();
  777. else
  778. runDirectory = repository.getWorkTree();
  779. final String cmd = relativize(runDirectory.getAbsolutePath(),
  780. hookPath);
  781. ProcessBuilder hookProcess = runInShell(cmd, args);
  782. hookProcess.directory(runDirectory);
  783. try {
  784. return new ProcessResult(runProcess(hookProcess, outRedirect,
  785. errRedirect, stdinArgs), Status.OK);
  786. } catch (IOException e) {
  787. throw new JGitInternalException(MessageFormat.format(
  788. JGitText.get().exceptionCaughtDuringExecutionOfHook,
  789. hookName), e);
  790. } catch (InterruptedException e) {
  791. throw new JGitInternalException(MessageFormat.format(
  792. JGitText.get().exceptionHookExecutionInterrupted,
  793. hookName), e);
  794. }
  795. }
  796. /**
  797. * Tries to find a hook matching the given one in the given repository.
  798. *
  799. * @param repository
  800. * The repository within which to find a hook.
  801. * @param hookName
  802. * The name of the hook we're trying to find.
  803. * @return The {@link File} containing this particular hook if it exists in
  804. * the given repository, <code>null</code> otherwise.
  805. * @since 4.0
  806. */
  807. public File findHook(Repository repository, final String hookName) {
  808. final File hookFile = new File(new File(repository.getDirectory(),
  809. Constants.HOOKS), hookName);
  810. return hookFile.isFile() ? hookFile : null;
  811. }
  812. /**
  813. * Runs the given process until termination, clearing its stdout and stderr
  814. * streams on-the-fly.
  815. *
  816. * @param hookProcessBuilder
  817. * The process builder configured for this hook.
  818. * @param outRedirect
  819. * A print stream on which to redirect the hook's stdout. Can be
  820. * <code>null</code>, in which case the hook's standard output
  821. * will be lost.
  822. * @param errRedirect
  823. * A print stream on which to redirect the hook's stderr. Can be
  824. * <code>null</code>, in which case the hook's standard error
  825. * will be lost.
  826. * @param stdinArgs
  827. * A string to pass on to the standard input of the hook. Can be
  828. * <code>null</code>.
  829. * @return the exit value of this hook.
  830. * @throws IOException
  831. * if an I/O error occurs while executing this hook.
  832. * @throws InterruptedException
  833. * if the current thread is interrupted while waiting for the
  834. * process to end.
  835. * @since 3.7
  836. */
  837. protected int runProcess(ProcessBuilder hookProcessBuilder,
  838. OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  839. throws IOException, InterruptedException {
  840. final ExecutorService executor = Executors.newFixedThreadPool(2);
  841. Process process = null;
  842. // We'll record the first I/O exception that occurs, but keep on trying
  843. // to dispose of our open streams and file handles
  844. IOException ioException = null;
  845. try {
  846. process = hookProcessBuilder.start();
  847. final Callable<Void> errorGobbler = new StreamGobbler(
  848. process.getErrorStream(), errRedirect);
  849. final Callable<Void> outputGobbler = new StreamGobbler(
  850. process.getInputStream(), outRedirect);
  851. executor.submit(errorGobbler);
  852. executor.submit(outputGobbler);
  853. if (stdinArgs != null) {
  854. final PrintWriter stdinWriter = new PrintWriter(
  855. process.getOutputStream());
  856. stdinWriter.print(stdinArgs);
  857. stdinWriter.flush();
  858. // We are done with this hook's input. Explicitly close its
  859. // stdin now to kick off any blocking read the hook might have.
  860. stdinWriter.close();
  861. }
  862. return process.waitFor();
  863. } catch (IOException e) {
  864. ioException = e;
  865. } finally {
  866. shutdownAndAwaitTermination(executor);
  867. if (process != null) {
  868. try {
  869. process.waitFor();
  870. } catch (InterruptedException e) {
  871. // Thrown by the outer try.
  872. // Swallow this one to carry on our cleanup, and clear the
  873. // interrupted flag (processes throw the exception without
  874. // clearing the flag).
  875. Thread.interrupted();
  876. }
  877. // A process doesn't clean its own resources even when destroyed
  878. // Explicitly try and close all three streams, preserving the
  879. // outer I/O exception if any.
  880. try {
  881. process.getErrorStream().close();
  882. } catch (IOException e) {
  883. ioException = ioException != null ? ioException : e;
  884. }
  885. try {
  886. process.getInputStream().close();
  887. } catch (IOException e) {
  888. ioException = ioException != null ? ioException : e;
  889. }
  890. try {
  891. process.getOutputStream().close();
  892. } catch (IOException e) {
  893. ioException = ioException != null ? ioException : e;
  894. }
  895. process.destroy();
  896. }
  897. }
  898. // We can only be here if the outer try threw an IOException.
  899. throw ioException;
  900. }
  901. /**
  902. * Shuts down an {@link ExecutorService} in two phases, first by calling
  903. * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  904. * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  905. * necessary, to cancel any lingering tasks. Returns true if the pool has
  906. * been properly shutdown, false otherwise.
  907. * <p>
  908. *
  909. * @param pool
  910. * the pool to shutdown
  911. * @return <code>true</code> if the pool has been properly shutdown,
  912. * <code>false</code> otherwise.
  913. */
  914. private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  915. boolean hasShutdown = true;
  916. pool.shutdown(); // Disable new tasks from being submitted
  917. try {
  918. // Wait a while for existing tasks to terminate
  919. if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
  920. pool.shutdownNow(); // Cancel currently executing tasks
  921. // Wait a while for tasks to respond to being canceled
  922. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  923. hasShutdown = false;
  924. }
  925. } catch (InterruptedException ie) {
  926. // (Re-)Cancel if current thread also interrupted
  927. pool.shutdownNow();
  928. // Preserve interrupt status
  929. Thread.currentThread().interrupt();
  930. hasShutdown = false;
  931. }
  932. return hasShutdown;
  933. }
  934. /**
  935. * Initialize a ProcessBuilder to run a command using the system shell.
  936. *
  937. * @param cmd
  938. * command to execute. This string should originate from the
  939. * end-user, and thus is platform specific.
  940. * @param args
  941. * arguments to pass to command. These should be protected from
  942. * shell evaluation.
  943. * @return a partially completed process builder. Caller should finish
  944. * populating directory, environment, and then start the process.
  945. */
  946. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  947. private static class Holder<V> {
  948. final V value;
  949. Holder(V value) {
  950. this.value = value;
  951. }
  952. }
  953. /**
  954. * File attributes we typically care for.
  955. *
  956. * @since 3.3
  957. */
  958. public static class Attributes {
  959. /**
  960. * @return true if this are the attributes of a directory
  961. */
  962. public boolean isDirectory() {
  963. return isDirectory;
  964. }
  965. /**
  966. * @return true if this are the attributes of an executable file
  967. */
  968. public boolean isExecutable() {
  969. return isExecutable;
  970. }
  971. /**
  972. * @return true if this are the attributes of a symbolic link
  973. */
  974. public boolean isSymbolicLink() {
  975. return isSymbolicLink;
  976. }
  977. /**
  978. * @return true if this are the attributes of a regular file
  979. */
  980. public boolean isRegularFile() {
  981. return isRegularFile;
  982. }
  983. /**
  984. * @return the time when the file was created
  985. */
  986. public long getCreationTime() {
  987. return creationTime;
  988. }
  989. /**
  990. * @return the time (milliseconds since 1970-01-01) when this object was
  991. * last modified
  992. */
  993. public long getLastModifiedTime() {
  994. return lastModifiedTime;
  995. }
  996. private boolean isDirectory;
  997. private boolean isSymbolicLink;
  998. private boolean isRegularFile;
  999. private long creationTime;
  1000. private long lastModifiedTime;
  1001. private boolean isExecutable;
  1002. private File file;
  1003. private boolean exists;
  1004. /**
  1005. * file length
  1006. */
  1007. protected long length = -1;
  1008. FS fs;
  1009. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  1010. boolean isExecutable, boolean isSymbolicLink,
  1011. boolean isRegularFile, long creationTime,
  1012. long lastModifiedTime, long length) {
  1013. this.fs = fs;
  1014. this.file = file;
  1015. this.exists = exists;
  1016. this.isDirectory = isDirectory;
  1017. this.isExecutable = isExecutable;
  1018. this.isSymbolicLink = isSymbolicLink;
  1019. this.isRegularFile = isRegularFile;
  1020. this.creationTime = creationTime;
  1021. this.lastModifiedTime = lastModifiedTime;
  1022. this.length = length;
  1023. }
  1024. /**
  1025. * Constructor when there are issues with reading
  1026. *
  1027. * @param fs
  1028. * @param path
  1029. */
  1030. public Attributes(File path, FS fs) {
  1031. this.file = path;
  1032. this.fs = fs;
  1033. }
  1034. /**
  1035. * @return length of this file object
  1036. */
  1037. public long getLength() {
  1038. if (length == -1)
  1039. return length = file.length();
  1040. return length;
  1041. }
  1042. /**
  1043. * @return the filename
  1044. */
  1045. public String getName() {
  1046. return file.getName();
  1047. }
  1048. /**
  1049. * @return the file the attributes apply to
  1050. */
  1051. public File getFile() {
  1052. return file;
  1053. }
  1054. boolean exists() {
  1055. return exists;
  1056. }
  1057. }
  1058. /**
  1059. * @param path
  1060. * @return the file attributes we care for
  1061. * @since 3.3
  1062. */
  1063. public Attributes getAttributes(File path) {
  1064. boolean isDirectory = isDirectory(path);
  1065. boolean isFile = !isDirectory && path.isFile();
  1066. assert path.exists() == isDirectory || isFile;
  1067. boolean exists = isDirectory || isFile;
  1068. boolean canExecute = exists && !isDirectory && canExecute(path);
  1069. boolean isSymlink = false;
  1070. long lastModified = exists ? path.lastModified() : 0L;
  1071. long createTime = 0L;
  1072. return new Attributes(this, path, exists, isDirectory, canExecute,
  1073. isSymlink, isFile, createTime, lastModified, -1);
  1074. }
  1075. /**
  1076. * Normalize the unicode path to composed form.
  1077. *
  1078. * @param file
  1079. * @return NFC-format File
  1080. * @since 3.3
  1081. */
  1082. public File normalize(File file) {
  1083. return file;
  1084. }
  1085. /**
  1086. * Normalize the unicode path to composed form.
  1087. *
  1088. * @param name
  1089. * @return NFC-format string
  1090. * @since 3.3
  1091. */
  1092. public String normalize(String name) {
  1093. return name;
  1094. }
  1095. /**
  1096. * This runnable will consume an input stream's content into an output
  1097. * stream as soon as it gets available.
  1098. * <p>
  1099. * Typically used to empty processes' standard output and error, preventing
  1100. * them to choke.
  1101. * </p>
  1102. * <p>
  1103. * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  1104. * streams.
  1105. * </p>
  1106. */
  1107. private static class StreamGobbler implements Callable<Void> {
  1108. private final BufferedReader reader;
  1109. private final BufferedWriter writer;
  1110. public StreamGobbler(InputStream stream, OutputStream output) {
  1111. this.reader = new BufferedReader(new InputStreamReader(stream));
  1112. if (output == null)
  1113. this.writer = null;
  1114. else
  1115. this.writer = new BufferedWriter(new OutputStreamWriter(output));
  1116. }
  1117. public Void call() throws IOException {
  1118. boolean writeFailure = false;
  1119. String line = null;
  1120. while ((line = reader.readLine()) != null) {
  1121. // Do not try to write again after a failure, but keep reading
  1122. // as long as possible to prevent the input stream from choking.
  1123. if (!writeFailure && writer != null) {
  1124. try {
  1125. writer.write(line);
  1126. writer.newLine();
  1127. writer.flush();
  1128. } catch (IOException e) {
  1129. writeFailure = true;
  1130. }
  1131. }
  1132. }
  1133. return null;
  1134. }
  1135. }
  1136. }