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

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