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.

FileUtils.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. /*
  2. * Copyright (C) 2010, Google Inc.
  3. * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
  4. * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
  5. * and other copyright owners as documented in the project's IP log.
  6. *
  7. * This program and the accompanying materials are made available
  8. * under the terms of the Eclipse Distribution License v1.0 which
  9. * accompanies this distribution, is reproduced below, and is
  10. * available at http://www.eclipse.org/org/documents/edl-v10.php
  11. *
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or
  15. * without modification, are permitted provided that the following
  16. * conditions are met:
  17. *
  18. * - Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. *
  21. * - Redistributions in binary form must reproduce the above
  22. * copyright notice, this list of conditions and the following
  23. * disclaimer in the documentation and/or other materials provided
  24. * with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the
  27. * names of its contributors may be used to endorse or promote
  28. * products derived from this software without specific prior
  29. * written permission.
  30. *
  31. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  32. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  33. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  34. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  35. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  36. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  38. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  40. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  41. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  42. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  43. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. */
  45. package org.eclipse.jgit.util;
  46. import static java.nio.charset.StandardCharsets.UTF_8;
  47. import java.io.File;
  48. import java.io.IOException;
  49. import java.nio.channels.FileChannel;
  50. import java.nio.file.AtomicMoveNotSupportedException;
  51. import java.nio.file.CopyOption;
  52. import java.nio.file.Files;
  53. import java.nio.file.InvalidPathException;
  54. import java.nio.file.LinkOption;
  55. import java.nio.file.NoSuchFileException;
  56. import java.nio.file.Path;
  57. import java.nio.file.StandardCopyOption;
  58. import java.nio.file.StandardOpenOption;
  59. import java.nio.file.attribute.BasicFileAttributeView;
  60. import java.nio.file.attribute.BasicFileAttributes;
  61. import java.nio.file.attribute.FileTime;
  62. import java.nio.file.attribute.PosixFileAttributeView;
  63. import java.nio.file.attribute.PosixFileAttributes;
  64. import java.nio.file.attribute.PosixFilePermission;
  65. import java.text.MessageFormat;
  66. import java.text.Normalizer;
  67. import java.text.Normalizer.Form;
  68. import java.time.Instant;
  69. import java.util.ArrayList;
  70. import java.util.List;
  71. import java.util.Locale;
  72. import java.util.regex.Pattern;
  73. import org.eclipse.jgit.internal.JGitText;
  74. import org.eclipse.jgit.lib.Constants;
  75. import org.eclipse.jgit.util.FS.Attributes;
  76. import org.slf4j.Logger;
  77. import org.slf4j.LoggerFactory;
  78. /**
  79. * File Utilities
  80. */
  81. public class FileUtils {
  82. private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
  83. /**
  84. * Option to delete given {@code File}
  85. */
  86. public static final int NONE = 0;
  87. /**
  88. * Option to recursively delete given {@code File}
  89. */
  90. public static final int RECURSIVE = 1;
  91. /**
  92. * Option to retry deletion if not successful
  93. */
  94. public static final int RETRY = 2;
  95. /**
  96. * Option to skip deletion if file doesn't exist
  97. */
  98. public static final int SKIP_MISSING = 4;
  99. /**
  100. * Option not to throw exceptions when a deletion finally doesn't succeed.
  101. * @since 2.0
  102. */
  103. public static final int IGNORE_ERRORS = 8;
  104. /**
  105. * Option to only delete empty directories. This option can be combined with
  106. * {@link #RECURSIVE}
  107. *
  108. * @since 3.0
  109. */
  110. public static final int EMPTY_DIRECTORIES_ONLY = 16;
  111. /**
  112. * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
  113. *
  114. * @param f
  115. * {@code File} to be converted to {@code Path}
  116. * @return the path represented by the file
  117. * @throws java.io.IOException
  118. * in case the path represented by the file is not valid (
  119. * {@link java.nio.file.InvalidPathException})
  120. * @since 4.10
  121. */
  122. public static Path toPath(File f) throws IOException {
  123. try {
  124. return f.toPath();
  125. } catch (InvalidPathException ex) {
  126. throw new IOException(ex);
  127. }
  128. }
  129. /**
  130. * Delete file or empty folder
  131. *
  132. * @param f
  133. * {@code File} to be deleted
  134. * @throws java.io.IOException
  135. * if deletion of {@code f} fails. This may occur if {@code f}
  136. * didn't exist when the method was called. This can therefore
  137. * cause java.io.IOExceptions during race conditions when
  138. * multiple concurrent threads all try to delete the same file.
  139. */
  140. public static void delete(File f) throws IOException {
  141. delete(f, NONE);
  142. }
  143. /**
  144. * Delete file or folder
  145. *
  146. * @param f
  147. * {@code File} to be deleted
  148. * @param options
  149. * deletion options, {@code RECURSIVE} for recursive deletion of
  150. * a subtree, {@code RETRY} to retry when deletion failed.
  151. * Retrying may help if the underlying file system doesn't allow
  152. * deletion of files being read by another thread.
  153. * @throws java.io.IOException
  154. * if deletion of {@code f} fails. This may occur if {@code f}
  155. * didn't exist when the method was called. This can therefore
  156. * cause java.io.IOExceptions during race conditions when
  157. * multiple concurrent threads all try to delete the same file.
  158. * This exception is not thrown when IGNORE_ERRORS is set.
  159. */
  160. public static void delete(File f, int options) throws IOException {
  161. FS fs = FS.DETECTED;
  162. if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
  163. return;
  164. if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
  165. final File[] items = f.listFiles();
  166. if (items != null) {
  167. List<File> files = new ArrayList<>();
  168. List<File> dirs = new ArrayList<>();
  169. for (File c : items)
  170. if (c.isFile())
  171. files.add(c);
  172. else
  173. dirs.add(c);
  174. // Try to delete files first, otherwise options
  175. // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
  176. // directories before aborting, depending on order.
  177. for (File file : files)
  178. delete(file, options);
  179. for (File d : dirs)
  180. delete(d, options);
  181. }
  182. }
  183. boolean delete = false;
  184. if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
  185. if (f.isDirectory()) {
  186. delete = true;
  187. } else {
  188. if ((options & IGNORE_ERRORS) == 0)
  189. throw new IOException(MessageFormat.format(
  190. JGitText.get().deleteFileFailed,
  191. f.getAbsolutePath()));
  192. }
  193. } else {
  194. delete = true;
  195. }
  196. if (delete && !f.delete()) {
  197. if ((options & RETRY) != 0 && fs.exists(f)) {
  198. for (int i = 1; i < 10; i++) {
  199. try {
  200. Thread.sleep(100);
  201. } catch (InterruptedException e) {
  202. // ignore
  203. }
  204. if (f.delete())
  205. return;
  206. }
  207. }
  208. if ((options & IGNORE_ERRORS) == 0)
  209. throw new IOException(MessageFormat.format(
  210. JGitText.get().deleteFileFailed, f.getAbsolutePath()));
  211. }
  212. }
  213. /**
  214. * Rename a file or folder. If the rename fails and if we are running on a
  215. * filesystem where it makes sense to repeat a failing rename then repeat
  216. * the rename operation up to 9 times with 100ms sleep time between two
  217. * calls. Furthermore if the destination exists and is directory hierarchy
  218. * with only directories in it, the whole directory hierarchy will be
  219. * deleted. If the target represents a non-empty directory structure, empty
  220. * subdirectories within that structure may or may not be deleted even if
  221. * the method fails. Furthermore if the destination exists and is a file
  222. * then the file will be deleted and then the rename is retried.
  223. * <p>
  224. * This operation is <em>not</em> atomic.
  225. *
  226. * @see FS#retryFailedLockFileCommit()
  227. * @param src
  228. * the old {@code File}
  229. * @param dst
  230. * the new {@code File}
  231. * @throws java.io.IOException
  232. * if the rename has failed
  233. * @since 3.0
  234. */
  235. public static void rename(File src, File dst)
  236. throws IOException {
  237. rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
  238. }
  239. /**
  240. * Rename a file or folder using the passed
  241. * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
  242. * running on a filesystem where it makes sense to repeat a failing rename
  243. * then repeat the rename operation up to 9 times with 100ms sleep time
  244. * between two calls. Furthermore if the destination exists and is a
  245. * directory hierarchy with only directories in it, the whole directory
  246. * hierarchy will be deleted. If the target represents a non-empty directory
  247. * structure, empty subdirectories within that structure may or may not be
  248. * deleted even if the method fails. Furthermore if the destination exists
  249. * and is a file then the file will be replaced if
  250. * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
  251. * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
  252. * rename will be done atomically or fail with an
  253. * {@link java.nio.file.AtomicMoveNotSupportedException}
  254. *
  255. * @param src
  256. * the old file
  257. * @param dst
  258. * the new file
  259. * @param options
  260. * options to pass to
  261. * {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
  262. * @throws java.nio.file.AtomicMoveNotSupportedException
  263. * if file cannot be moved as an atomic file system operation
  264. * @throws java.io.IOException
  265. * @since 4.1
  266. */
  267. public static void rename(final File src, final File dst,
  268. CopyOption... options)
  269. throws AtomicMoveNotSupportedException, IOException {
  270. int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
  271. while (--attempts >= 0) {
  272. try {
  273. Files.move(toPath(src), toPath(dst), options);
  274. return;
  275. } catch (AtomicMoveNotSupportedException e) {
  276. throw e;
  277. } catch (IOException e) {
  278. try {
  279. if (!dst.delete()) {
  280. delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  281. }
  282. // On *nix there is no try, you do or do not
  283. Files.move(toPath(src), toPath(dst), options);
  284. return;
  285. } catch (IOException e2) {
  286. // ignore and continue retry
  287. }
  288. }
  289. try {
  290. Thread.sleep(100);
  291. } catch (InterruptedException e) {
  292. throw new IOException(
  293. MessageFormat.format(JGitText.get().renameFileFailed,
  294. src.getAbsolutePath(), dst.getAbsolutePath()));
  295. }
  296. }
  297. throw new IOException(
  298. MessageFormat.format(JGitText.get().renameFileFailed,
  299. src.getAbsolutePath(), dst.getAbsolutePath()));
  300. }
  301. /**
  302. * Creates the directory named by this abstract pathname.
  303. *
  304. * @param d
  305. * directory to be created
  306. * @throws java.io.IOException
  307. * if creation of {@code d} fails. This may occur if {@code d}
  308. * did exist when the method was called. This can therefore
  309. * cause java.io.IOExceptions during race conditions when
  310. * multiple concurrent threads all try to create the same
  311. * directory.
  312. */
  313. public static void mkdir(File d)
  314. throws IOException {
  315. mkdir(d, false);
  316. }
  317. /**
  318. * Creates the directory named by this abstract pathname.
  319. *
  320. * @param d
  321. * directory to be created
  322. * @param skipExisting
  323. * if {@code true} skip creation of the given directory if it
  324. * already exists in the file system
  325. * @throws java.io.IOException
  326. * if creation of {@code d} fails. This may occur if {@code d}
  327. * did exist when the method was called. This can therefore
  328. * cause java.io.IOExceptions during race conditions when
  329. * multiple concurrent threads all try to create the same
  330. * directory.
  331. */
  332. public static void mkdir(File d, boolean skipExisting)
  333. throws IOException {
  334. if (!d.mkdir()) {
  335. if (skipExisting && d.isDirectory())
  336. return;
  337. throw new IOException(MessageFormat.format(
  338. JGitText.get().mkDirFailed, d.getAbsolutePath()));
  339. }
  340. }
  341. /**
  342. * Creates the directory named by this abstract pathname, including any
  343. * necessary but nonexistent parent directories. Note that if this operation
  344. * fails it may have succeeded in creating some of the necessary parent
  345. * directories.
  346. *
  347. * @param d
  348. * directory to be created
  349. * @throws java.io.IOException
  350. * if creation of {@code d} fails. This may occur if {@code d}
  351. * did exist when the method was called. This can therefore
  352. * cause java.io.IOExceptions during race conditions when
  353. * multiple concurrent threads all try to create the same
  354. * directory.
  355. */
  356. public static void mkdirs(File d) throws IOException {
  357. mkdirs(d, false);
  358. }
  359. /**
  360. * Creates the directory named by this abstract pathname, including any
  361. * necessary but nonexistent parent directories. Note that if this operation
  362. * fails it may have succeeded in creating some of the necessary parent
  363. * directories.
  364. *
  365. * @param d
  366. * directory to be created
  367. * @param skipExisting
  368. * if {@code true} skip creation of the given directory if it
  369. * already exists in the file system
  370. * @throws java.io.IOException
  371. * if creation of {@code d} fails. This may occur if {@code d}
  372. * did exist when the method was called. This can therefore
  373. * cause java.io.IOExceptions during race conditions when
  374. * multiple concurrent threads all try to create the same
  375. * directory.
  376. */
  377. public static void mkdirs(File d, boolean skipExisting)
  378. throws IOException {
  379. if (!d.mkdirs()) {
  380. if (skipExisting && d.isDirectory())
  381. return;
  382. throw new IOException(MessageFormat.format(
  383. JGitText.get().mkDirsFailed, d.getAbsolutePath()));
  384. }
  385. }
  386. /**
  387. * Atomically creates a new, empty file named by this abstract pathname if
  388. * and only if a file with this name does not yet exist. The check for the
  389. * existence of the file and the creation of the file if it does not exist
  390. * are a single operation that is atomic with respect to all other
  391. * filesystem activities that might affect the file.
  392. * <p>
  393. * Note: this method should not be used for file-locking, as the resulting
  394. * protocol cannot be made to work reliably. The
  395. * {@link java.nio.channels.FileLock} facility should be used instead.
  396. *
  397. * @param f
  398. * the file to be created
  399. * @throws java.io.IOException
  400. * if the named file already exists or if an I/O error occurred
  401. */
  402. public static void createNewFile(File f) throws IOException {
  403. if (!f.createNewFile())
  404. throw new IOException(MessageFormat.format(
  405. JGitText.get().createNewFileFailed, f));
  406. }
  407. /**
  408. * Create a symbolic link
  409. *
  410. * @param path
  411. * the path of the symbolic link to create
  412. * @param target
  413. * the target of the symbolic link
  414. * @return the path to the symbolic link
  415. * @throws java.io.IOException
  416. * @since 4.2
  417. */
  418. public static Path createSymLink(File path, String target)
  419. throws IOException {
  420. Path nioPath = toPath(path);
  421. if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
  422. BasicFileAttributes attrs = Files.readAttributes(nioPath,
  423. BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  424. if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
  425. delete(path);
  426. } else {
  427. delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  428. }
  429. }
  430. if (SystemReader.getInstance().isWindows()) {
  431. target = target.replace('/', '\\');
  432. }
  433. Path nioTarget = toPath(new File(target));
  434. return Files.createSymbolicLink(nioPath, nioTarget);
  435. }
  436. /**
  437. * Read target path of the symlink.
  438. *
  439. * @param path
  440. * a {@link java.io.File} object.
  441. * @return target path of the symlink, or null if it is not a symbolic link
  442. * @throws java.io.IOException
  443. * @since 3.0
  444. */
  445. public static String readSymLink(File path) throws IOException {
  446. Path nioPath = toPath(path);
  447. Path target = Files.readSymbolicLink(nioPath);
  448. String targetString = target.toString();
  449. if (SystemReader.getInstance().isWindows()) {
  450. targetString = targetString.replace('\\', '/');
  451. } else if (SystemReader.getInstance().isMacOS()) {
  452. targetString = Normalizer.normalize(targetString, Form.NFC);
  453. }
  454. return targetString;
  455. }
  456. /**
  457. * Create a temporary directory.
  458. *
  459. * @param prefix
  460. * prefix string
  461. * @param suffix
  462. * suffix string
  463. * @param dir
  464. * The parent dir, can be null to use system default temp dir.
  465. * @return the temp dir created.
  466. * @throws java.io.IOException
  467. * @since 3.4
  468. */
  469. public static File createTempDir(String prefix, String suffix, File dir)
  470. throws IOException {
  471. final int RETRIES = 1; // When something bad happens, retry once.
  472. for (int i = 0; i < RETRIES; i++) {
  473. File tmp = File.createTempFile(prefix, suffix, dir);
  474. if (!tmp.delete())
  475. continue;
  476. if (!tmp.mkdir())
  477. continue;
  478. return tmp;
  479. }
  480. throw new IOException(JGitText.get().cannotCreateTempDir);
  481. }
  482. /**
  483. * Expresses <code>other</code> as a relative file path from
  484. * <code>base</code>. File-separator and case sensitivity are based on the
  485. * current file system.
  486. *
  487. * See also
  488. * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  489. *
  490. * @param base
  491. * Base path
  492. * @param other
  493. * Destination path
  494. * @return Relative path from <code>base</code> to <code>other</code>
  495. * @since 4.8
  496. */
  497. public static String relativizeNativePath(String base, String other) {
  498. return FS.DETECTED.relativize(base, other);
  499. }
  500. /**
  501. * Expresses <code>other</code> as a relative file path from
  502. * <code>base</code>. File-separator and case sensitivity are based on Git's
  503. * internal representation of files (which matches Unix).
  504. *
  505. * See also
  506. * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  507. *
  508. * @param base
  509. * Base path
  510. * @param other
  511. * Destination path
  512. * @return Relative path from <code>base</code> to <code>other</code>
  513. * @since 4.8
  514. */
  515. public static String relativizeGitPath(String base, String other) {
  516. return relativizePath(base, other, "/", false); //$NON-NLS-1$
  517. }
  518. /**
  519. * Expresses <code>other</code> as a relative file path from <code>base</code>
  520. * <p>
  521. * For example, if called with the two following paths :
  522. *
  523. * <pre>
  524. * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
  525. * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
  526. * </pre>
  527. *
  528. * This will return "..\\another_project\\pom.xml".
  529. *
  530. * <p>
  531. * <b>Note</b> that this will return the empty String if <code>base</code>
  532. * and <code>other</code> are equal.
  533. * </p>
  534. *
  535. * @param base
  536. * The path against which <code>other</code> should be
  537. * relativized. This will be assumed to denote the path to a
  538. * folder and not a file.
  539. * @param other
  540. * The path that will be made relative to <code>base</code>.
  541. * @param dirSeparator
  542. * A string that separates components of the path. In practice, this is "/" or "\\".
  543. * @param caseSensitive
  544. * Whether to consider differently-cased directory names as distinct
  545. * @return A relative path that, when resolved against <code>base</code>,
  546. * will yield the original <code>other</code>.
  547. * @since 4.8
  548. */
  549. public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
  550. if (base.equals(other))
  551. return ""; //$NON-NLS-1$
  552. final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
  553. final String[] otherSegments = other.split(Pattern
  554. .quote(dirSeparator));
  555. int commonPrefix = 0;
  556. while (commonPrefix < baseSegments.length
  557. && commonPrefix < otherSegments.length) {
  558. if (caseSensitive
  559. && baseSegments[commonPrefix]
  560. .equals(otherSegments[commonPrefix]))
  561. commonPrefix++;
  562. else if (!caseSensitive
  563. && baseSegments[commonPrefix]
  564. .equalsIgnoreCase(otherSegments[commonPrefix]))
  565. commonPrefix++;
  566. else
  567. break;
  568. }
  569. final StringBuilder builder = new StringBuilder();
  570. for (int i = commonPrefix; i < baseSegments.length; i++)
  571. builder.append("..").append(dirSeparator); //$NON-NLS-1$
  572. for (int i = commonPrefix; i < otherSegments.length; i++) {
  573. builder.append(otherSegments[i]);
  574. if (i < otherSegments.length - 1)
  575. builder.append(dirSeparator);
  576. }
  577. return builder.toString();
  578. }
  579. /**
  580. * Determine if an IOException is a Stale NFS File Handle
  581. *
  582. * @param ioe
  583. * an {@link java.io.IOException} object.
  584. * @return a boolean true if the IOException is a Stale NFS FIle Handle
  585. * @since 4.1
  586. */
  587. public static boolean isStaleFileHandle(IOException ioe) {
  588. String msg = ioe.getMessage();
  589. return msg != null
  590. && msg.toLowerCase(Locale.ROOT)
  591. .matches("stale .*file .*handle"); //$NON-NLS-1$
  592. }
  593. /**
  594. * Determine if a throwable or a cause in its causal chain is a Stale NFS
  595. * File Handle
  596. *
  597. * @param throwable
  598. * a {@link java.lang.Throwable} object.
  599. * @return a boolean true if the throwable or a cause in its causal chain is
  600. * a Stale NFS File Handle
  601. * @since 4.7
  602. */
  603. public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
  604. while (throwable != null) {
  605. if (throwable instanceof IOException
  606. && isStaleFileHandle((IOException) throwable)) {
  607. return true;
  608. }
  609. throwable = throwable.getCause();
  610. }
  611. return false;
  612. }
  613. /**
  614. * @param file
  615. * @return {@code true} if the passed file is a symbolic link
  616. */
  617. static boolean isSymlink(File file) {
  618. return Files.isSymbolicLink(file.toPath());
  619. }
  620. /**
  621. * @param file
  622. * @return lastModified attribute for given file, not following symbolic
  623. * links
  624. * @throws IOException
  625. * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
  626. * FileTime
  627. */
  628. @Deprecated
  629. static long lastModified(File file) throws IOException {
  630. return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
  631. .toMillis();
  632. }
  633. /**
  634. * @param path
  635. * @return lastModified attribute for given file, not following symbolic
  636. * links
  637. */
  638. static Instant lastModifiedInstant(Path path) {
  639. try {
  640. return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
  641. .toInstant();
  642. } catch (NoSuchFileException e) {
  643. LOG.debug(
  644. "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
  645. path);
  646. return Instant.EPOCH;
  647. } catch (IOException e) {
  648. LOG.error(MessageFormat
  649. .format(JGitText.get().readLastModifiedFailed, path), e);
  650. return Instant.ofEpochMilli(path.toFile().lastModified());
  651. }
  652. }
  653. /**
  654. * Return all the attributes of a file, without following symbolic links.
  655. *
  656. * @param file
  657. * @return {@link BasicFileAttributes} of the file
  658. * @throws IOException in case of any I/O errors accessing the file
  659. *
  660. * @since 4.5.6
  661. */
  662. static BasicFileAttributes fileAttributes(File file) throws IOException {
  663. return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  664. }
  665. /**
  666. * @param file
  667. * @param time
  668. * @throws IOException
  669. */
  670. @Deprecated
  671. static void setLastModified(File file, long time) throws IOException {
  672. Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
  673. }
  674. /**
  675. * @param path
  676. * @param time
  677. * @throws IOException
  678. */
  679. static void setLastModified(Path path, Instant time)
  680. throws IOException {
  681. Files.setLastModifiedTime(path, FileTime.from(time));
  682. }
  683. /**
  684. * @param file
  685. * @return {@code true} if the given file exists, not following symbolic
  686. * links
  687. */
  688. static boolean exists(File file) {
  689. return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  690. }
  691. /**
  692. * @param file
  693. * @return {@code true} if the given file is hidden
  694. * @throws IOException
  695. */
  696. static boolean isHidden(File file) throws IOException {
  697. return Files.isHidden(toPath(file));
  698. }
  699. /**
  700. * Set a file hidden (on Windows)
  701. *
  702. * @param file
  703. * a {@link java.io.File} object.
  704. * @param hidden
  705. * a boolean.
  706. * @throws java.io.IOException
  707. * @since 4.1
  708. */
  709. public static void setHidden(File file, boolean hidden) throws IOException {
  710. Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
  711. LinkOption.NOFOLLOW_LINKS);
  712. }
  713. /**
  714. * Get file length
  715. *
  716. * @param file
  717. * a {@link java.io.File}.
  718. * @return length of the given file
  719. * @throws java.io.IOException
  720. * @since 4.1
  721. */
  722. public static long getLength(File file) throws IOException {
  723. Path nioPath = toPath(file);
  724. if (Files.isSymbolicLink(nioPath))
  725. return Files.readSymbolicLink(nioPath).toString()
  726. .getBytes(UTF_8).length;
  727. return Files.size(nioPath);
  728. }
  729. /**
  730. * @param file
  731. * @return {@code true} if the given file is a directory, not following
  732. * symbolic links
  733. */
  734. static boolean isDirectory(File file) {
  735. return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  736. }
  737. /**
  738. * @param file
  739. * @return {@code true} if the given file is a file, not following symbolic
  740. * links
  741. */
  742. static boolean isFile(File file) {
  743. return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  744. }
  745. /**
  746. * Whether the given file can be executed.
  747. *
  748. * @param file
  749. * a {@link java.io.File} object.
  750. * @return {@code true} if the given file can be executed.
  751. * @since 4.1
  752. */
  753. public static boolean canExecute(File file) {
  754. if (!isFile(file)) {
  755. return false;
  756. }
  757. return Files.isExecutable(file.toPath());
  758. }
  759. /**
  760. * @param fs
  761. * @param file
  762. * @return non null attributes object
  763. */
  764. static Attributes getFileAttributesBasic(FS fs, File file) {
  765. try {
  766. Path nioPath = toPath(file);
  767. BasicFileAttributes readAttributes = nioPath
  768. .getFileSystem()
  769. .provider()
  770. .getFileAttributeView(nioPath,
  771. BasicFileAttributeView.class,
  772. LinkOption.NOFOLLOW_LINKS).readAttributes();
  773. Attributes attributes = new Attributes(fs, file,
  774. true,
  775. readAttributes.isDirectory(),
  776. fs.supportsExecute() ? file.canExecute() : false,
  777. readAttributes.isSymbolicLink(),
  778. readAttributes.isRegularFile(), //
  779. readAttributes.creationTime().toMillis(), //
  780. readAttributes.lastModifiedTime().toInstant(),
  781. readAttributes.isSymbolicLink() ? Constants
  782. .encode(readSymLink(file)).length
  783. : readAttributes.size());
  784. return attributes;
  785. } catch (IOException e) {
  786. return new Attributes(file, fs);
  787. }
  788. }
  789. /**
  790. * Get file system attributes for the given file.
  791. *
  792. * @param fs
  793. * a {@link org.eclipse.jgit.util.FS} object.
  794. * @param file
  795. * a {@link java.io.File}.
  796. * @return file system attributes for the given file.
  797. * @since 4.1
  798. */
  799. public static Attributes getFileAttributesPosix(FS fs, File file) {
  800. try {
  801. Path nioPath = toPath(file);
  802. PosixFileAttributes readAttributes = nioPath
  803. .getFileSystem()
  804. .provider()
  805. .getFileAttributeView(nioPath,
  806. PosixFileAttributeView.class,
  807. LinkOption.NOFOLLOW_LINKS).readAttributes();
  808. Attributes attributes = new Attributes(
  809. fs,
  810. file,
  811. true, //
  812. readAttributes.isDirectory(), //
  813. readAttributes.permissions().contains(
  814. PosixFilePermission.OWNER_EXECUTE),
  815. readAttributes.isSymbolicLink(),
  816. readAttributes.isRegularFile(), //
  817. readAttributes.creationTime().toMillis(), //
  818. readAttributes.lastModifiedTime().toInstant(),
  819. readAttributes.size());
  820. return attributes;
  821. } catch (IOException e) {
  822. return new Attributes(file, fs);
  823. }
  824. }
  825. /**
  826. * NFC normalize a file (on Mac), otherwise do nothing
  827. *
  828. * @param file
  829. * a {@link java.io.File}.
  830. * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
  831. * file
  832. * @since 4.1
  833. */
  834. public static File normalize(File file) {
  835. if (SystemReader.getInstance().isMacOS()) {
  836. // TODO: Would it be faster to check with isNormalized first
  837. // assuming normalized paths are much more common
  838. String normalized = Normalizer.normalize(file.getPath(),
  839. Normalizer.Form.NFC);
  840. return new File(normalized);
  841. }
  842. return file;
  843. }
  844. /**
  845. * On Mac: get NFC normalized form of given name, otherwise the given name.
  846. *
  847. * @param name
  848. * a {@link java.lang.String} object.
  849. * @return on Mac: NFC normalized form of given name
  850. * @since 4.1
  851. */
  852. public static String normalize(String name) {
  853. if (SystemReader.getInstance().isMacOS()) {
  854. if (name == null)
  855. return null;
  856. return Normalizer.normalize(name, Normalizer.Form.NFC);
  857. }
  858. return name;
  859. }
  860. /**
  861. * Best-effort variation of {@link java.io.File#getCanonicalFile()}
  862. * returning the input file if the file cannot be canonicalized instead of
  863. * throwing {@link java.io.IOException}.
  864. *
  865. * @param file
  866. * to be canonicalized; may be {@code null}
  867. * @return canonicalized file, or the unchanged input file if
  868. * canonicalization failed or if {@code file == null}
  869. * @throws java.lang.SecurityException
  870. * if {@link java.io.File#getCanonicalFile()} throws one
  871. * @since 4.2
  872. */
  873. public static File canonicalize(File file) {
  874. if (file == null) {
  875. return null;
  876. }
  877. try {
  878. return file.getCanonicalFile();
  879. } catch (IOException e) {
  880. return file;
  881. }
  882. }
  883. /**
  884. * Convert a path to String, replacing separators as necessary.
  885. *
  886. * @param file
  887. * a {@link java.io.File}.
  888. * @return file's path as a String
  889. * @since 4.10
  890. */
  891. public static String pathToString(File file) {
  892. final String path = file.getPath();
  893. if (SystemReader.getInstance().isWindows()) {
  894. return path.replace('\\', '/');
  895. }
  896. return path;
  897. }
  898. /**
  899. * Touch the given file
  900. *
  901. * @param f
  902. * the file to touch
  903. * @throws IOException
  904. * @since 5.1.8
  905. */
  906. public static void touch(Path f) throws IOException {
  907. try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
  908. StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
  909. // touch
  910. }
  911. Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
  912. }
  913. }