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 47KB

Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Significantly speed up FileTreeIterator on Windows Getting attributes of files on Windows is an expensive operation. Windows stores file attributes in the directory, so they are basically available "for free" when a directory is listed. The implementation of Java's Files.walkFileTree() takes advantage of that (at least in the OpenJDK implementation for Windows) and provides the attributes from the directory to a FileVisitor. Using Files.walkFileTree() with a maximum depth of 1 is thus a good approach on Windows to get both the file names and the attributes in one go. In my tests, this gives a significant speed-up of FileTreeIterator over the "normal" way: using File.listFiles() and then reading the attributes of each file individually. The speed-up is hard to quantify exactly, but in my tests I've observed consistently 30-40% for staging 500 files one after another, each individually, and up to 50% for individual TreeWalks with a FileTreeIterator. On Unix, this technique is detrimental. Unix stores file attributes differently, and getting attributes of individual files is not costly. On Unix, the old way of doing a listFiles() and getting individual attributes (both native operations) is about three times faster than using walkFileTree, which is implemented in Java. Therefore, move the operation to FS/FS_Win32 and call it from FileTreeIterator, so that we can have different implementations depending on the file system. A little performance test program is included as a JUnit test (to be run manually). While this does speed up things on Windows, it doesn't solve the basic problem of bug 532300: the iterator always gets the full directory listing and the attributes of all files, and the more files there are the longer that takes. Bug: 532300 Change-Id: Ic5facb871c725256c2324b0d97b95e6efc33282a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
6 years ago
Significantly speed up FileTreeIterator on Windows Getting attributes of files on Windows is an expensive operation. Windows stores file attributes in the directory, so they are basically available "for free" when a directory is listed. The implementation of Java's Files.walkFileTree() takes advantage of that (at least in the OpenJDK implementation for Windows) and provides the attributes from the directory to a FileVisitor. Using Files.walkFileTree() with a maximum depth of 1 is thus a good approach on Windows to get both the file names and the attributes in one go. In my tests, this gives a significant speed-up of FileTreeIterator over the "normal" way: using File.listFiles() and then reading the attributes of each file individually. The speed-up is hard to quantify exactly, but in my tests I've observed consistently 30-40% for staging 500 files one after another, each individually, and up to 50% for individual TreeWalks with a FileTreeIterator. On Unix, this technique is detrimental. Unix stores file attributes differently, and getting attributes of individual files is not costly. On Unix, the old way of doing a listFiles() and getting individual attributes (both native operations) is about three times faster than using walkFileTree, which is implemented in Java. Therefore, move the operation to FS/FS_Win32 and call it from FileTreeIterator, so that we can have different implementations depending on the file system. A little performance test program is included as a JUnit test (to be run manually). While this does speed up things on Windows, it doesn't solve the basic problem of bug 532300: the iterator always gets the full directory listing and the attributes of all files, and the more files there are the longer that takes. Bug: 532300 Change-Id: Ic5facb871c725256c2324b0d97b95e6efc33282a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
6 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Fix atomic lock file creation on NFS FS_POSIX.createNewFile(File) failed to properly implement atomic file creation on NFS using the algorithm [1]: - name of the hard link must be unique to prevent that two processes using different NFS clients try to create the same link. This would render nlink useless to detect if there was a race. - the hard link must be retained for the lifetime of the file since we don't know when the state of the involved NFS clients will be synchronized. This depends on NFS configuration options. To fix these issues we need to change the signature of createNewFile which would break API. Hence deprecate the old method FS.createNewFile(File) and add a new method createNewFileAtomic(File). The new method returns a LockToken which needs to be retained by the caller (LockFile) until all involved NFS clients synchronized their state. Since we don't know when the NFS caches are synchronized we need to retain the token until the corresponding file is no longer needed. The LockToken must be closed after the LockFile using it has been committed or unlocked. On Posix, if core.supportsAtomicCreateNewFile = false this will delete the hard link which guarded the atomic creation of the file. When acquiring the lock fails ensure that the hard link is removed. [1] https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html also see file creation flag O_EXCL in http://man7.org/linux/man-pages/man2/open.2.html Change-Id: I84fcb16143a5f877e9b08c6ee0ff8fa4ea68a90d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
5 years ago
Significantly speed up FileTreeIterator on Windows Getting attributes of files on Windows is an expensive operation. Windows stores file attributes in the directory, so they are basically available "for free" when a directory is listed. The implementation of Java's Files.walkFileTree() takes advantage of that (at least in the OpenJDK implementation for Windows) and provides the attributes from the directory to a FileVisitor. Using Files.walkFileTree() with a maximum depth of 1 is thus a good approach on Windows to get both the file names and the attributes in one go. In my tests, this gives a significant speed-up of FileTreeIterator over the "normal" way: using File.listFiles() and then reading the attributes of each file individually. The speed-up is hard to quantify exactly, but in my tests I've observed consistently 30-40% for staging 500 files one after another, each individually, and up to 50% for individual TreeWalks with a FileTreeIterator. On Unix, this technique is detrimental. Unix stores file attributes differently, and getting attributes of individual files is not costly. On Unix, the old way of doing a listFiles() and getting individual attributes (both native operations) is about three times faster than using walkFileTree, which is implemented in Java. Therefore, move the operation to FS/FS_Win32 and call it from FileTreeIterator, so that we can have different implementations depending on the file system. A little performance test program is included as a JUnit test (to be run manually). While this does speed up things on Windows, it doesn't solve the basic problem of bug 532300: the iterator always gets the full directory listing and the attributes of all files, and the more files there are the longer that takes. Bug: 532300 Change-Id: Ic5facb871c725256c2324b0d97b95e6efc33282a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
6 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586
  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 static java.nio.charset.StandardCharsets.UTF_8;
  45. import java.io.BufferedReader;
  46. import java.io.ByteArrayInputStream;
  47. import java.io.Closeable;
  48. import java.io.File;
  49. import java.io.IOException;
  50. import java.io.InputStream;
  51. import java.io.InputStreamReader;
  52. import java.io.OutputStream;
  53. import java.io.PrintStream;
  54. import java.nio.charset.Charset;
  55. import java.nio.file.Files;
  56. import java.nio.file.Path;
  57. import java.security.AccessController;
  58. import java.security.PrivilegedAction;
  59. import java.text.MessageFormat;
  60. import java.util.Arrays;
  61. import java.util.HashMap;
  62. import java.util.Map;
  63. import java.util.Objects;
  64. import java.util.Optional;
  65. import java.util.concurrent.ExecutorService;
  66. import java.util.concurrent.Executors;
  67. import java.util.concurrent.TimeUnit;
  68. import java.util.concurrent.atomic.AtomicBoolean;
  69. import java.util.concurrent.atomic.AtomicReference;
  70. import org.eclipse.jgit.annotations.Nullable;
  71. import org.eclipse.jgit.api.errors.JGitInternalException;
  72. import org.eclipse.jgit.errors.CommandFailedException;
  73. import org.eclipse.jgit.internal.JGitText;
  74. import org.eclipse.jgit.lib.Constants;
  75. import org.eclipse.jgit.lib.Repository;
  76. import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
  77. import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
  78. import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
  79. import org.eclipse.jgit.util.ProcessResult.Status;
  80. import org.slf4j.Logger;
  81. import org.slf4j.LoggerFactory;
  82. /**
  83. * Abstraction to support various file system operations not in Java.
  84. */
  85. public abstract class FS {
  86. private static final Logger LOG = LoggerFactory.getLogger(FS.class);
  87. /**
  88. * An empty array of entries, suitable as a return value for
  89. * {@link #list(File, FileModeStrategy)}.
  90. *
  91. * @since 5.0
  92. */
  93. protected static final Entry[] NO_ENTRIES = {};
  94. /**
  95. * This class creates FS instances. It will be overridden by a Java7 variant
  96. * if such can be detected in {@link #detect(Boolean)}.
  97. *
  98. * @since 3.0
  99. */
  100. public static class FSFactory {
  101. /**
  102. * Constructor
  103. */
  104. protected FSFactory() {
  105. // empty
  106. }
  107. /**
  108. * Detect the file system
  109. *
  110. * @param cygwinUsed
  111. * @return FS instance
  112. */
  113. public FS detect(Boolean cygwinUsed) {
  114. if (SystemReader.getInstance().isWindows()) {
  115. if (cygwinUsed == null)
  116. cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  117. if (cygwinUsed.booleanValue())
  118. return new FS_Win32_Cygwin();
  119. else
  120. return new FS_Win32();
  121. } else {
  122. return new FS_POSIX();
  123. }
  124. }
  125. }
  126. /**
  127. * Result of an executed process. The caller is responsible to close the
  128. * contained {@link TemporaryBuffer}s
  129. *
  130. * @since 4.2
  131. */
  132. public static class ExecutionResult {
  133. private TemporaryBuffer stdout;
  134. private TemporaryBuffer stderr;
  135. private int rc;
  136. /**
  137. * @param stdout
  138. * @param stderr
  139. * @param rc
  140. */
  141. public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
  142. int rc) {
  143. this.stdout = stdout;
  144. this.stderr = stderr;
  145. this.rc = rc;
  146. }
  147. /**
  148. * @return buffered standard output stream
  149. */
  150. public TemporaryBuffer getStdout() {
  151. return stdout;
  152. }
  153. /**
  154. * @return buffered standard error stream
  155. */
  156. public TemporaryBuffer getStderr() {
  157. return stderr;
  158. }
  159. /**
  160. * @return the return code of the process
  161. */
  162. public int getRc() {
  163. return rc;
  164. }
  165. }
  166. /** The auto-detected implementation selected for this operating system and JRE. */
  167. public static final FS DETECTED = detect();
  168. private volatile static FSFactory factory;
  169. /**
  170. * Auto-detect the appropriate file system abstraction.
  171. *
  172. * @return detected file system abstraction
  173. */
  174. public static FS detect() {
  175. return detect(null);
  176. }
  177. /**
  178. * Auto-detect the appropriate file system abstraction, taking into account
  179. * the presence of a Cygwin installation on the system. Using jgit in
  180. * combination with Cygwin requires a more elaborate (and possibly slower)
  181. * resolution of file system paths.
  182. *
  183. * @param cygwinUsed
  184. * <ul>
  185. * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  186. * combination with jgit</li>
  187. * <li><code>Boolean.FALSE</code> to assume that Cygwin is
  188. * <b>not</b> used with jgit</li>
  189. * <li><code>null</code> to auto-detect whether a Cygwin
  190. * installation is present on the system and in this case assume
  191. * that Cygwin is used</li>
  192. * </ul>
  193. *
  194. * Note: this parameter is only relevant on Windows.
  195. * @return detected file system abstraction
  196. */
  197. public static FS detect(Boolean cygwinUsed) {
  198. if (factory == null) {
  199. factory = new FS.FSFactory();
  200. }
  201. return factory.detect(cygwinUsed);
  202. }
  203. private volatile Holder<File> userHome;
  204. private volatile Holder<File> gitSystemConfig;
  205. /**
  206. * Constructs a file system abstraction.
  207. */
  208. protected FS() {
  209. // Do nothing by default.
  210. }
  211. /**
  212. * Initialize this FS using another's current settings.
  213. *
  214. * @param src
  215. * the source FS to copy from.
  216. */
  217. protected FS(FS src) {
  218. userHome = src.userHome;
  219. gitSystemConfig = src.gitSystemConfig;
  220. }
  221. /**
  222. * Create a new instance of the same type of FS.
  223. *
  224. * @return a new instance of the same type of FS.
  225. */
  226. public abstract FS newInstance();
  227. /**
  228. * Does this operating system and JRE support the execute flag on files?
  229. *
  230. * @return true if this implementation can provide reasonably accurate
  231. * executable bit information; false otherwise.
  232. */
  233. public abstract boolean supportsExecute();
  234. /**
  235. * Does this file system support atomic file creation via
  236. * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
  237. * not guaranteed that when two file system clients run createNewFile() in
  238. * parallel only one will succeed. In such cases both clients may think they
  239. * created a new file.
  240. *
  241. * @return true if this implementation support atomic creation of new Files
  242. * by {@link java.io.File#createNewFile()}
  243. * @since 4.5
  244. */
  245. public boolean supportsAtomicCreateNewFile() {
  246. return true;
  247. }
  248. /**
  249. * Does this operating system and JRE supports symbolic links. The
  250. * capability to handle symbolic links is detected at runtime.
  251. *
  252. * @return true if symbolic links may be used
  253. * @since 3.0
  254. */
  255. public boolean supportsSymlinks() {
  256. return false;
  257. }
  258. /**
  259. * Is this file system case sensitive
  260. *
  261. * @return true if this implementation is case sensitive
  262. */
  263. public abstract boolean isCaseSensitive();
  264. /**
  265. * Determine if the file is executable (or not).
  266. * <p>
  267. * Not all platforms and JREs support executable flags on files. If the
  268. * feature is unsupported this method will always return false.
  269. * <p>
  270. * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  271. * this method returns false, rather than the state of the executable flags
  272. * on the target file.</em>
  273. *
  274. * @param f
  275. * abstract path to test.
  276. * @return true if the file is believed to be executable by the user.
  277. */
  278. public abstract boolean canExecute(File f);
  279. /**
  280. * Set a file to be executable by the user.
  281. * <p>
  282. * Not all platforms and JREs support executable flags on files. If the
  283. * feature is unsupported this method will always return false and no
  284. * changes will be made to the file specified.
  285. *
  286. * @param f
  287. * path to modify the executable status of.
  288. * @param canExec
  289. * true to enable execution; false to disable it.
  290. * @return true if the change succeeded; false otherwise.
  291. */
  292. public abstract boolean setExecute(File f, boolean canExec);
  293. /**
  294. * Get the last modified time of a file system object. If the OS/JRE support
  295. * symbolic links, the modification time of the link is returned, rather
  296. * than that of the link target.
  297. *
  298. * @param f
  299. * a {@link java.io.File} object.
  300. * @return last modified time of f
  301. * @throws java.io.IOException
  302. * @since 3.0
  303. */
  304. public long lastModified(File f) throws IOException {
  305. return FileUtils.lastModified(f);
  306. }
  307. /**
  308. * Set the last modified time of a file system object. If the OS/JRE support
  309. * symbolic links, the link is modified, not the target,
  310. *
  311. * @param f
  312. * a {@link java.io.File} object.
  313. * @param time
  314. * last modified time
  315. * @throws java.io.IOException
  316. * @since 3.0
  317. */
  318. public void setLastModified(File f, long time) throws IOException {
  319. FileUtils.setLastModified(f, time);
  320. }
  321. /**
  322. * Get the length of a file or link, If the OS/JRE supports symbolic links
  323. * it's the length of the link, else the length of the target.
  324. *
  325. * @param path
  326. * a {@link java.io.File} object.
  327. * @return length of a file
  328. * @throws java.io.IOException
  329. * @since 3.0
  330. */
  331. public long length(File path) throws IOException {
  332. return FileUtils.getLength(path);
  333. }
  334. /**
  335. * Delete a file. Throws an exception if delete fails.
  336. *
  337. * @param f
  338. * a {@link java.io.File} object.
  339. * @throws java.io.IOException
  340. * this may be a Java7 subclass with detailed information
  341. * @since 3.3
  342. */
  343. public void delete(File f) throws IOException {
  344. FileUtils.delete(f);
  345. }
  346. /**
  347. * Resolve this file to its actual path name that the JRE can use.
  348. * <p>
  349. * This method can be relatively expensive. Computing a translation may
  350. * require forking an external process per path name translated. Callers
  351. * should try to minimize the number of translations necessary by caching
  352. * the results.
  353. * <p>
  354. * Not all platforms and JREs require path name translation. Currently only
  355. * Cygwin on Win32 require translation for Cygwin based paths.
  356. *
  357. * @param dir
  358. * directory relative to which the path name is.
  359. * @param name
  360. * path name to translate.
  361. * @return the translated path. <code>new File(dir,name)</code> if this
  362. * platform does not require path name translation.
  363. */
  364. public File resolve(File dir, String name) {
  365. final File abspn = new File(name);
  366. if (abspn.isAbsolute())
  367. return abspn;
  368. return new File(dir, name);
  369. }
  370. /**
  371. * Determine the user's home directory (location where preferences are).
  372. * <p>
  373. * This method can be expensive on the first invocation if path name
  374. * translation is required. Subsequent invocations return a cached result.
  375. * <p>
  376. * Not all platforms and JREs require path name translation. Currently only
  377. * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  378. *
  379. * @return the user's home directory; null if the user does not have one.
  380. */
  381. public File userHome() {
  382. Holder<File> p = userHome;
  383. if (p == null) {
  384. p = new Holder<>(userHomeImpl());
  385. userHome = p;
  386. }
  387. return p.value;
  388. }
  389. /**
  390. * Set the user's home directory location.
  391. *
  392. * @param path
  393. * the location of the user's preferences; null if there is no
  394. * home directory for the current user.
  395. * @return {@code this}.
  396. */
  397. public FS setUserHome(File path) {
  398. userHome = new Holder<>(path);
  399. return this;
  400. }
  401. /**
  402. * Does this file system have problems with atomic renames?
  403. *
  404. * @return true if the caller should retry a failed rename of a lock file.
  405. */
  406. public abstract boolean retryFailedLockFileCommit();
  407. /**
  408. * Determine the user's home directory (location where preferences are).
  409. *
  410. * @return the user's home directory; null if the user does not have one.
  411. */
  412. protected File userHomeImpl() {
  413. final String home = AccessController
  414. .doPrivileged(new PrivilegedAction<String>() {
  415. @Override
  416. public String run() {
  417. return System.getProperty("user.home"); //$NON-NLS-1$
  418. }
  419. });
  420. if (home == null || home.length() == 0)
  421. return null;
  422. return new File(home).getAbsoluteFile();
  423. }
  424. /**
  425. * Searches the given path to see if it contains one of the given files.
  426. * Returns the first it finds. Returns null if not found or if path is null.
  427. *
  428. * @param path
  429. * List of paths to search separated by File.pathSeparator
  430. * @param lookFor
  431. * Files to search for in the given path
  432. * @return the first match found, or null
  433. * @since 3.0
  434. */
  435. protected static File searchPath(String path, String... lookFor) {
  436. if (path == null)
  437. return null;
  438. for (String p : path.split(File.pathSeparator)) {
  439. for (String command : lookFor) {
  440. final File e = new File(p, command);
  441. if (e.isFile())
  442. return e.getAbsoluteFile();
  443. }
  444. }
  445. return null;
  446. }
  447. /**
  448. * Execute a command and return a single line of output as a String
  449. *
  450. * @param dir
  451. * Working directory for the command
  452. * @param command
  453. * as component array
  454. * @param encoding
  455. * to be used to parse the command's output
  456. * @return the one-line output of the command or {@code null} if there is
  457. * none
  458. * @throws org.eclipse.jgit.errors.CommandFailedException
  459. * thrown when the command failed (return code was non-zero)
  460. */
  461. @Nullable
  462. protected static String readPipe(File dir, String[] command,
  463. String encoding) throws CommandFailedException {
  464. return readPipe(dir, command, encoding, null);
  465. }
  466. /**
  467. * Execute a command and return a single line of output as a String
  468. *
  469. * @param dir
  470. * Working directory for the command
  471. * @param command
  472. * as component array
  473. * @param encoding
  474. * to be used to parse the command's output
  475. * @param env
  476. * Map of environment variables to be merged with those of the
  477. * current process
  478. * @return the one-line output of the command or {@code null} if there is
  479. * none
  480. * @throws org.eclipse.jgit.errors.CommandFailedException
  481. * thrown when the command failed (return code was non-zero)
  482. * @since 4.0
  483. */
  484. @Nullable
  485. protected static String readPipe(File dir, String[] command,
  486. String encoding, Map<String, String> env)
  487. throws CommandFailedException {
  488. final boolean debug = LOG.isDebugEnabled();
  489. try {
  490. if (debug) {
  491. LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  492. + dir);
  493. }
  494. ProcessBuilder pb = new ProcessBuilder(command);
  495. pb.directory(dir);
  496. if (env != null) {
  497. pb.environment().putAll(env);
  498. }
  499. Process p;
  500. try {
  501. p = pb.start();
  502. } catch (IOException e) {
  503. // Process failed to start
  504. throw new CommandFailedException(-1, e.getMessage(), e);
  505. }
  506. p.getOutputStream().close();
  507. GobblerThread gobbler = new GobblerThread(p, command, dir);
  508. gobbler.start();
  509. String r = null;
  510. try (BufferedReader lineRead = new BufferedReader(
  511. new InputStreamReader(p.getInputStream(), encoding))) {
  512. r = lineRead.readLine();
  513. if (debug) {
  514. LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  515. LOG.debug("remaining output:\n"); //$NON-NLS-1$
  516. String l;
  517. while ((l = lineRead.readLine()) != null) {
  518. LOG.debug(l);
  519. }
  520. }
  521. }
  522. for (;;) {
  523. try {
  524. int rc = p.waitFor();
  525. gobbler.join();
  526. if (rc == 0 && !gobbler.fail.get()) {
  527. return r;
  528. } else {
  529. if (debug) {
  530. LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  531. }
  532. throw new CommandFailedException(rc,
  533. gobbler.errorMessage.get(),
  534. gobbler.exception.get());
  535. }
  536. } catch (InterruptedException ie) {
  537. // Stop bothering me, I have a zombie to reap.
  538. }
  539. }
  540. } catch (IOException e) {
  541. LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  542. }
  543. if (debug) {
  544. LOG.debug("readpipe returns null"); //$NON-NLS-1$
  545. }
  546. return null;
  547. }
  548. private static class GobblerThread extends Thread {
  549. /* The process has 5 seconds to exit after closing stderr */
  550. private static final int PROCESS_EXIT_TIMEOUT = 5;
  551. private final Process p;
  552. private final String desc;
  553. private final String dir;
  554. final AtomicBoolean fail = new AtomicBoolean();
  555. final AtomicReference<String> errorMessage = new AtomicReference<>();
  556. final AtomicReference<Throwable> exception = new AtomicReference<>();
  557. GobblerThread(Process p, String[] command, File dir) {
  558. this.p = p;
  559. this.desc = Arrays.toString(command);
  560. this.dir = Objects.toString(dir);
  561. }
  562. @Override
  563. public void run() {
  564. StringBuilder err = new StringBuilder();
  565. try (InputStream is = p.getErrorStream()) {
  566. int ch;
  567. while ((ch = is.read()) != -1) {
  568. err.append((char) ch);
  569. }
  570. } catch (IOException e) {
  571. if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  572. setError(e, e.getMessage(), p.exitValue());
  573. fail.set(true);
  574. } else {
  575. // ignore. command terminated faster and stream was just closed
  576. // or the process didn't terminate within timeout
  577. }
  578. } finally {
  579. if (waitForProcessCompletion(null) && err.length() > 0) {
  580. setError(null, err.toString(), p.exitValue());
  581. if (p.exitValue() != 0) {
  582. fail.set(true);
  583. }
  584. }
  585. }
  586. }
  587. @SuppressWarnings("boxing")
  588. private boolean waitForProcessCompletion(IOException originalError) {
  589. try {
  590. if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  591. setError(originalError, MessageFormat.format(
  592. JGitText.get().commandClosedStderrButDidntExit,
  593. desc, PROCESS_EXIT_TIMEOUT), -1);
  594. fail.set(true);
  595. return false;
  596. }
  597. } catch (InterruptedException e) {
  598. setError(originalError, MessageFormat.format(
  599. JGitText.get().threadInterruptedWhileRunning, desc), -1);
  600. fail.set(true);
  601. return false;
  602. }
  603. return true;
  604. }
  605. private void setError(IOException e, String message, int exitCode) {
  606. exception.set(e);
  607. errorMessage.set(MessageFormat.format(
  608. JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  609. desc, dir, Integer.valueOf(exitCode), message));
  610. }
  611. }
  612. /**
  613. * Discover the path to the Git executable.
  614. *
  615. * @return the path to the Git executable or {@code null} if it cannot be
  616. * determined.
  617. * @since 4.0
  618. */
  619. protected abstract File discoverGitExe();
  620. /**
  621. * Discover the path to the system-wide Git configuration file
  622. *
  623. * @return the path to the system-wide Git configuration file or
  624. * {@code null} if it cannot be determined.
  625. * @since 4.0
  626. */
  627. protected File discoverGitSystemConfig() {
  628. File gitExe = discoverGitExe();
  629. if (gitExe == null) {
  630. return null;
  631. }
  632. // Bug 480782: Check if the discovered git executable is JGit CLI
  633. String v;
  634. try {
  635. v = readPipe(gitExe.getParentFile(),
  636. new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
  637. Charset.defaultCharset().name());
  638. } catch (CommandFailedException e) {
  639. LOG.warn(e.getMessage());
  640. return null;
  641. }
  642. if (StringUtils.isEmptyOrNull(v)
  643. || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  644. return null;
  645. }
  646. // Trick Git into printing the path to the config file by using "echo"
  647. // as the editor.
  648. Map<String, String> env = new HashMap<>();
  649. env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
  650. String w;
  651. try {
  652. w = readPipe(gitExe.getParentFile(),
  653. new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  654. Charset.defaultCharset().name(), env);
  655. } catch (CommandFailedException e) {
  656. LOG.warn(e.getMessage());
  657. return null;
  658. }
  659. if (StringUtils.isEmptyOrNull(w)) {
  660. return null;
  661. }
  662. return new File(w);
  663. }
  664. /**
  665. * Get the currently used path to the system-wide Git configuration file.
  666. *
  667. * @return the currently used path to the system-wide Git configuration file
  668. * or {@code null} if none has been set.
  669. * @since 4.0
  670. */
  671. public File getGitSystemConfig() {
  672. if (gitSystemConfig == null) {
  673. gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  674. }
  675. return gitSystemConfig.value;
  676. }
  677. /**
  678. * Set the path to the system-wide Git configuration file to use.
  679. *
  680. * @param configFile
  681. * the path to the config file.
  682. * @return {@code this}
  683. * @since 4.0
  684. */
  685. public FS setGitSystemConfig(File configFile) {
  686. gitSystemConfig = new Holder<>(configFile);
  687. return this;
  688. }
  689. /**
  690. * Get the parent directory of this file's parent directory
  691. *
  692. * @param grandchild
  693. * a {@link java.io.File} object.
  694. * @return the parent directory of this file's parent directory or
  695. * {@code null} in case there's no grandparent directory
  696. * @since 4.0
  697. */
  698. protected static File resolveGrandparentFile(File grandchild) {
  699. if (grandchild != null) {
  700. File parent = grandchild.getParentFile();
  701. if (parent != null)
  702. return parent.getParentFile();
  703. }
  704. return null;
  705. }
  706. /**
  707. * Check if a file is a symbolic link and read it
  708. *
  709. * @param path
  710. * a {@link java.io.File} object.
  711. * @return target of link or null
  712. * @throws java.io.IOException
  713. * @since 3.0
  714. */
  715. public String readSymLink(File path) throws IOException {
  716. return FileUtils.readSymLink(path);
  717. }
  718. /**
  719. * Whether the path is a symbolic link (and we support these).
  720. *
  721. * @param path
  722. * a {@link java.io.File} object.
  723. * @return true if the path is a symbolic link (and we support these)
  724. * @throws java.io.IOException
  725. * @since 3.0
  726. */
  727. public boolean isSymLink(File path) throws IOException {
  728. return FileUtils.isSymlink(path);
  729. }
  730. /**
  731. * Tests if the path exists, in case of a symbolic link, true even if the
  732. * target does not exist
  733. *
  734. * @param path
  735. * a {@link java.io.File} object.
  736. * @return true if path exists
  737. * @since 3.0
  738. */
  739. public boolean exists(File path) {
  740. return FileUtils.exists(path);
  741. }
  742. /**
  743. * Check if path is a directory. If the OS/JRE supports symbolic links and
  744. * path is a symbolic link to a directory, this method returns false.
  745. *
  746. * @param path
  747. * a {@link java.io.File} object.
  748. * @return true if file is a directory,
  749. * @since 3.0
  750. */
  751. public boolean isDirectory(File path) {
  752. return FileUtils.isDirectory(path);
  753. }
  754. /**
  755. * Examine if path represents a regular file. If the OS/JRE supports
  756. * symbolic links the test returns false if path represents a symbolic link.
  757. *
  758. * @param path
  759. * a {@link java.io.File} object.
  760. * @return true if path represents a regular file
  761. * @since 3.0
  762. */
  763. public boolean isFile(File path) {
  764. return FileUtils.isFile(path);
  765. }
  766. /**
  767. * Whether path is hidden, either starts with . on unix or has the hidden
  768. * attribute in windows
  769. *
  770. * @param path
  771. * a {@link java.io.File} object.
  772. * @return true if path is hidden, either starts with . on unix or has the
  773. * hidden attribute in windows
  774. * @throws java.io.IOException
  775. * @since 3.0
  776. */
  777. public boolean isHidden(File path) throws IOException {
  778. return FileUtils.isHidden(path);
  779. }
  780. /**
  781. * Set the hidden attribute for file whose name starts with a period.
  782. *
  783. * @param path
  784. * a {@link java.io.File} object.
  785. * @param hidden
  786. * whether to set the file hidden
  787. * @throws java.io.IOException
  788. * @since 3.0
  789. */
  790. public void setHidden(File path, boolean hidden) throws IOException {
  791. FileUtils.setHidden(path, hidden);
  792. }
  793. /**
  794. * Create a symbolic link
  795. *
  796. * @param path
  797. * a {@link java.io.File} object.
  798. * @param target
  799. * target path of the symlink
  800. * @throws java.io.IOException
  801. * @since 3.0
  802. */
  803. public void createSymLink(File path, String target) throws IOException {
  804. FileUtils.createSymLink(path, target);
  805. }
  806. /**
  807. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  808. * of this class may take care to provide a safe implementation for this
  809. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  810. *
  811. * @param path
  812. * the file to be created
  813. * @return <code>true</code> if the file was created, <code>false</code> if
  814. * the file already existed
  815. * @throws java.io.IOException
  816. * @deprecated use {@link #createNewFileAtomic(File)} instead
  817. * @since 4.5
  818. */
  819. @Deprecated
  820. public boolean createNewFile(File path) throws IOException {
  821. return path.createNewFile();
  822. }
  823. /**
  824. * A token representing a file created by
  825. * {@link #createNewFileAtomic(File)}. The token must be retained until the
  826. * file has been deleted in order to guarantee that the unique file was
  827. * created atomically. As soon as the file is no longer needed the lock
  828. * token must be closed.
  829. *
  830. * @since 4.7
  831. */
  832. public static class LockToken implements Closeable {
  833. private boolean isCreated;
  834. private Optional<Path> link;
  835. LockToken(boolean isCreated, Optional<Path> link) {
  836. this.isCreated = isCreated;
  837. this.link = link;
  838. }
  839. /**
  840. * @return {@code true} if the file was created successfully
  841. */
  842. public boolean isCreated() {
  843. return isCreated;
  844. }
  845. @Override
  846. public void close() {
  847. if (!link.isPresent()) {
  848. return;
  849. }
  850. Path p = link.get();
  851. if (!Files.exists(p)) {
  852. return;
  853. }
  854. try {
  855. Files.delete(p);
  856. } catch (IOException e) {
  857. LOG.error(MessageFormat
  858. .format(JGitText.get().closeLockTokenFailed, this), e);
  859. }
  860. }
  861. @Override
  862. public String toString() {
  863. return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
  864. ", link=" //$NON-NLS-1$
  865. + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
  866. : "<null>]"); //$NON-NLS-1$
  867. }
  868. }
  869. /**
  870. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  871. * of this class may take care to provide a safe implementation for this
  872. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  873. *
  874. * @param path
  875. * the file to be created
  876. * @return LockToken this token must be closed after the created file was
  877. * deleted
  878. * @throws IOException
  879. * @since 4.7
  880. */
  881. public LockToken createNewFileAtomic(File path) throws IOException {
  882. return new LockToken(path.createNewFile(), Optional.empty());
  883. }
  884. /**
  885. * See
  886. * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  887. *
  888. * @param base
  889. * The path against which <code>other</code> should be
  890. * relativized.
  891. * @param other
  892. * The path that will be made relative to <code>base</code>.
  893. * @return A relative path that, when resolved against <code>base</code>,
  894. * will yield the original <code>other</code>.
  895. * @see FileUtils#relativizePath(String, String, String, boolean)
  896. * @since 3.7
  897. */
  898. public String relativize(String base, String other) {
  899. return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  900. }
  901. /**
  902. * Enumerates children of a directory.
  903. *
  904. * @param directory
  905. * to get the children of
  906. * @param fileModeStrategy
  907. * to use to calculate the git mode of a child
  908. * @return an array of entries for the children
  909. *
  910. * @since 5.0
  911. */
  912. public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
  913. final File[] all = directory.listFiles();
  914. if (all == null) {
  915. return NO_ENTRIES;
  916. }
  917. final Entry[] result = new Entry[all.length];
  918. for (int i = 0; i < result.length; i++) {
  919. result[i] = new FileEntry(all[i], this, fileModeStrategy);
  920. }
  921. return result;
  922. }
  923. /**
  924. * Checks whether the given hook is defined for the given repository, then
  925. * runs it with the given arguments.
  926. * <p>
  927. * The hook's standard output and error streams will be redirected to
  928. * <code>System.out</code> and <code>System.err</code> respectively. The
  929. * hook will have no stdin.
  930. * </p>
  931. *
  932. * @param repository
  933. * The repository for which a hook should be run.
  934. * @param hookName
  935. * The name of the hook to be executed.
  936. * @param args
  937. * Arguments to pass to this hook. Cannot be <code>null</code>,
  938. * but can be an empty array.
  939. * @return The ProcessResult describing this hook's execution.
  940. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  941. * if we fail to run the hook somehow. Causes may include an
  942. * interrupted process or I/O errors.
  943. * @since 4.0
  944. */
  945. public ProcessResult runHookIfPresent(Repository repository,
  946. final String hookName,
  947. String[] args) throws JGitInternalException {
  948. return runHookIfPresent(repository, hookName, args, System.out, System.err,
  949. null);
  950. }
  951. /**
  952. * Checks whether the given hook is defined for the given repository, then
  953. * runs it with the given arguments.
  954. *
  955. * @param repository
  956. * The repository for which a hook should be run.
  957. * @param hookName
  958. * The name of the hook to be executed.
  959. * @param args
  960. * Arguments to pass to this hook. Cannot be <code>null</code>,
  961. * but can be an empty array.
  962. * @param outRedirect
  963. * A print stream on which to redirect the hook's stdout. Can be
  964. * <code>null</code>, in which case the hook's standard output
  965. * will be lost.
  966. * @param errRedirect
  967. * A print stream on which to redirect the hook's stderr. Can be
  968. * <code>null</code>, in which case the hook's standard error
  969. * will be lost.
  970. * @param stdinArgs
  971. * A string to pass on to the standard input of the hook. May be
  972. * <code>null</code>.
  973. * @return The ProcessResult describing this hook's execution.
  974. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  975. * if we fail to run the hook somehow. Causes may include an
  976. * interrupted process or I/O errors.
  977. * @since 4.0
  978. */
  979. public ProcessResult runHookIfPresent(Repository repository,
  980. final String hookName,
  981. String[] args, PrintStream outRedirect, PrintStream errRedirect,
  982. String stdinArgs) throws JGitInternalException {
  983. return new ProcessResult(Status.NOT_SUPPORTED);
  984. }
  985. /**
  986. * See
  987. * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
  988. * . Should only be called by FS supporting shell scripts execution.
  989. *
  990. * @param repository
  991. * The repository for which a hook should be run.
  992. * @param hookName
  993. * The name of the hook to be executed.
  994. * @param args
  995. * Arguments to pass to this hook. Cannot be <code>null</code>,
  996. * but can be an empty array.
  997. * @param outRedirect
  998. * A print stream on which to redirect the hook's stdout. Can be
  999. * <code>null</code>, in which case the hook's standard output
  1000. * will be lost.
  1001. * @param errRedirect
  1002. * A print stream on which to redirect the hook's stderr. Can be
  1003. * <code>null</code>, in which case the hook's standard error
  1004. * will be lost.
  1005. * @param stdinArgs
  1006. * A string to pass on to the standard input of the hook. May be
  1007. * <code>null</code>.
  1008. * @return The ProcessResult describing this hook's execution.
  1009. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1010. * if we fail to run the hook somehow. Causes may include an
  1011. * interrupted process or I/O errors.
  1012. * @since 4.0
  1013. */
  1014. protected ProcessResult internalRunHookIfPresent(Repository repository,
  1015. final String hookName, String[] args, PrintStream outRedirect,
  1016. PrintStream errRedirect, String stdinArgs)
  1017. throws JGitInternalException {
  1018. final File hookFile = findHook(repository, hookName);
  1019. if (hookFile == null)
  1020. return new ProcessResult(Status.NOT_PRESENT);
  1021. final String hookPath = hookFile.getAbsolutePath();
  1022. final File runDirectory;
  1023. if (repository.isBare())
  1024. runDirectory = repository.getDirectory();
  1025. else
  1026. runDirectory = repository.getWorkTree();
  1027. final String cmd = relativize(runDirectory.getAbsolutePath(),
  1028. hookPath);
  1029. ProcessBuilder hookProcess = runInShell(cmd, args);
  1030. hookProcess.directory(runDirectory);
  1031. try {
  1032. return new ProcessResult(runProcess(hookProcess, outRedirect,
  1033. errRedirect, stdinArgs), Status.OK);
  1034. } catch (IOException e) {
  1035. throw new JGitInternalException(MessageFormat.format(
  1036. JGitText.get().exceptionCaughtDuringExecutionOfHook,
  1037. hookName), e);
  1038. } catch (InterruptedException e) {
  1039. throw new JGitInternalException(MessageFormat.format(
  1040. JGitText.get().exceptionHookExecutionInterrupted,
  1041. hookName), e);
  1042. }
  1043. }
  1044. /**
  1045. * Tries to find a hook matching the given one in the given repository.
  1046. *
  1047. * @param repository
  1048. * The repository within which to find a hook.
  1049. * @param hookName
  1050. * The name of the hook we're trying to find.
  1051. * @return The {@link java.io.File} containing this particular hook if it
  1052. * exists in the given repository, <code>null</code> otherwise.
  1053. * @since 4.0
  1054. */
  1055. public File findHook(Repository repository, String hookName) {
  1056. File gitDir = repository.getDirectory();
  1057. if (gitDir == null)
  1058. return null;
  1059. final File hookFile = new File(new File(gitDir,
  1060. Constants.HOOKS), hookName);
  1061. return hookFile.isFile() ? hookFile : null;
  1062. }
  1063. /**
  1064. * Runs the given process until termination, clearing its stdout and stderr
  1065. * streams on-the-fly.
  1066. *
  1067. * @param processBuilder
  1068. * The process builder configured for this process.
  1069. * @param outRedirect
  1070. * A OutputStream on which to redirect the processes stdout. Can
  1071. * be <code>null</code>, in which case the processes standard
  1072. * output will be lost.
  1073. * @param errRedirect
  1074. * A OutputStream on which to redirect the processes stderr. Can
  1075. * be <code>null</code>, in which case the processes standard
  1076. * error will be lost.
  1077. * @param stdinArgs
  1078. * A string to pass on to the standard input of the hook. Can be
  1079. * <code>null</code>.
  1080. * @return the exit value of this process.
  1081. * @throws java.io.IOException
  1082. * if an I/O error occurs while executing this process.
  1083. * @throws java.lang.InterruptedException
  1084. * if the current thread is interrupted while waiting for the
  1085. * process to end.
  1086. * @since 4.2
  1087. */
  1088. public int runProcess(ProcessBuilder processBuilder,
  1089. OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  1090. throws IOException, InterruptedException {
  1091. InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  1092. stdinArgs.getBytes(UTF_8));
  1093. return runProcess(processBuilder, outRedirect, errRedirect, in);
  1094. }
  1095. /**
  1096. * Runs the given process until termination, clearing its stdout and stderr
  1097. * streams on-the-fly.
  1098. *
  1099. * @param processBuilder
  1100. * The process builder configured for this process.
  1101. * @param outRedirect
  1102. * An OutputStream on which to redirect the processes stdout. Can
  1103. * be <code>null</code>, in which case the processes standard
  1104. * output will be lost.
  1105. * @param errRedirect
  1106. * An OutputStream on which to redirect the processes stderr. Can
  1107. * be <code>null</code>, in which case the processes standard
  1108. * error will be lost.
  1109. * @param inRedirect
  1110. * An InputStream from which to redirect the processes stdin. Can
  1111. * be <code>null</code>, in which case the process doesn't get
  1112. * any data over stdin. It is assumed that the whole InputStream
  1113. * will be consumed by the process. The method will close the
  1114. * inputstream after all bytes are read.
  1115. * @return the return code of this process.
  1116. * @throws java.io.IOException
  1117. * if an I/O error occurs while executing this process.
  1118. * @throws java.lang.InterruptedException
  1119. * if the current thread is interrupted while waiting for the
  1120. * process to end.
  1121. * @since 4.2
  1122. */
  1123. public int runProcess(ProcessBuilder processBuilder,
  1124. OutputStream outRedirect, OutputStream errRedirect,
  1125. InputStream inRedirect) throws IOException,
  1126. InterruptedException {
  1127. final ExecutorService executor = Executors.newFixedThreadPool(2);
  1128. Process process = null;
  1129. // We'll record the first I/O exception that occurs, but keep on trying
  1130. // to dispose of our open streams and file handles
  1131. IOException ioException = null;
  1132. try {
  1133. process = processBuilder.start();
  1134. executor.execute(
  1135. new StreamGobbler(process.getErrorStream(), errRedirect));
  1136. executor.execute(
  1137. new StreamGobbler(process.getInputStream(), outRedirect));
  1138. @SuppressWarnings("resource") // Closed in the finally block
  1139. OutputStream outputStream = process.getOutputStream();
  1140. try {
  1141. if (inRedirect != null) {
  1142. new StreamGobbler(inRedirect, outputStream).copy();
  1143. }
  1144. } finally {
  1145. try {
  1146. outputStream.close();
  1147. } catch (IOException e) {
  1148. // When the process exits before consuming the input, the OutputStream
  1149. // is replaced with the null output stream. This null output stream
  1150. // throws IOException for all write calls. When StreamGobbler fails to
  1151. // flush the buffer because of this, this close call tries to flush it
  1152. // again. This causes another IOException. Since we ignore the
  1153. // IOException in StreamGobbler, we also ignore the exception here.
  1154. }
  1155. }
  1156. return process.waitFor();
  1157. } catch (IOException e) {
  1158. ioException = e;
  1159. } finally {
  1160. shutdownAndAwaitTermination(executor);
  1161. if (process != null) {
  1162. try {
  1163. process.waitFor();
  1164. } catch (InterruptedException e) {
  1165. // Thrown by the outer try.
  1166. // Swallow this one to carry on our cleanup, and clear the
  1167. // interrupted flag (processes throw the exception without
  1168. // clearing the flag).
  1169. Thread.interrupted();
  1170. }
  1171. // A process doesn't clean its own resources even when destroyed
  1172. // Explicitly try and close all three streams, preserving the
  1173. // outer I/O exception if any.
  1174. if (inRedirect != null) {
  1175. inRedirect.close();
  1176. }
  1177. try {
  1178. process.getErrorStream().close();
  1179. } catch (IOException e) {
  1180. ioException = ioException != null ? ioException : e;
  1181. }
  1182. try {
  1183. process.getInputStream().close();
  1184. } catch (IOException e) {
  1185. ioException = ioException != null ? ioException : e;
  1186. }
  1187. try {
  1188. process.getOutputStream().close();
  1189. } catch (IOException e) {
  1190. ioException = ioException != null ? ioException : e;
  1191. }
  1192. process.destroy();
  1193. }
  1194. }
  1195. // We can only be here if the outer try threw an IOException.
  1196. throw ioException;
  1197. }
  1198. /**
  1199. * Shuts down an {@link ExecutorService} in two phases, first by calling
  1200. * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  1201. * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  1202. * necessary, to cancel any lingering tasks. Returns true if the pool has
  1203. * been properly shutdown, false otherwise.
  1204. * <p>
  1205. *
  1206. * @param pool
  1207. * the pool to shutdown
  1208. * @return <code>true</code> if the pool has been properly shutdown,
  1209. * <code>false</code> otherwise.
  1210. */
  1211. private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  1212. boolean hasShutdown = true;
  1213. pool.shutdown(); // Disable new tasks from being submitted
  1214. try {
  1215. // Wait a while for existing tasks to terminate
  1216. if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  1217. pool.shutdownNow(); // Cancel currently executing tasks
  1218. // Wait a while for tasks to respond to being canceled
  1219. if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  1220. hasShutdown = false;
  1221. }
  1222. } catch (InterruptedException ie) {
  1223. // (Re-)Cancel if current thread also interrupted
  1224. pool.shutdownNow();
  1225. // Preserve interrupt status
  1226. Thread.currentThread().interrupt();
  1227. hasShutdown = false;
  1228. }
  1229. return hasShutdown;
  1230. }
  1231. /**
  1232. * Initialize a ProcessBuilder to run a command using the system shell.
  1233. *
  1234. * @param cmd
  1235. * command to execute. This string should originate from the
  1236. * end-user, and thus is platform specific.
  1237. * @param args
  1238. * arguments to pass to command. These should be protected from
  1239. * shell evaluation.
  1240. * @return a partially completed process builder. Caller should finish
  1241. * populating directory, environment, and then start the process.
  1242. */
  1243. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  1244. /**
  1245. * Execute a command defined by a {@link java.lang.ProcessBuilder}.
  1246. *
  1247. * @param pb
  1248. * The command to be executed
  1249. * @param in
  1250. * The standard input stream passed to the process
  1251. * @return The result of the executed command
  1252. * @throws java.lang.InterruptedException
  1253. * @throws java.io.IOException
  1254. * @since 4.2
  1255. */
  1256. public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  1257. throws IOException, InterruptedException {
  1258. try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  1259. TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
  1260. 1024 * 1024)) {
  1261. int rc = runProcess(pb, stdout, stderr, in);
  1262. return new ExecutionResult(stdout, stderr, rc);
  1263. }
  1264. }
  1265. private static class Holder<V> {
  1266. final V value;
  1267. Holder(V value) {
  1268. this.value = value;
  1269. }
  1270. }
  1271. /**
  1272. * File attributes we typically care for.
  1273. *
  1274. * @since 3.3
  1275. */
  1276. public static class Attributes {
  1277. /**
  1278. * @return true if this are the attributes of a directory
  1279. */
  1280. public boolean isDirectory() {
  1281. return isDirectory;
  1282. }
  1283. /**
  1284. * @return true if this are the attributes of an executable file
  1285. */
  1286. public boolean isExecutable() {
  1287. return isExecutable;
  1288. }
  1289. /**
  1290. * @return true if this are the attributes of a symbolic link
  1291. */
  1292. public boolean isSymbolicLink() {
  1293. return isSymbolicLink;
  1294. }
  1295. /**
  1296. * @return true if this are the attributes of a regular file
  1297. */
  1298. public boolean isRegularFile() {
  1299. return isRegularFile;
  1300. }
  1301. /**
  1302. * @return the time when the file was created
  1303. */
  1304. public long getCreationTime() {
  1305. return creationTime;
  1306. }
  1307. /**
  1308. * @return the time (milliseconds since 1970-01-01) when this object was
  1309. * last modified
  1310. */
  1311. public long getLastModifiedTime() {
  1312. return lastModifiedTime;
  1313. }
  1314. private final boolean isDirectory;
  1315. private final boolean isSymbolicLink;
  1316. private final boolean isRegularFile;
  1317. private final long creationTime;
  1318. private final long lastModifiedTime;
  1319. private final boolean isExecutable;
  1320. private final File file;
  1321. private final boolean exists;
  1322. /**
  1323. * file length
  1324. */
  1325. protected long length = -1;
  1326. final FS fs;
  1327. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  1328. boolean isExecutable, boolean isSymbolicLink,
  1329. boolean isRegularFile, long creationTime,
  1330. long lastModifiedTime, long length) {
  1331. this.fs = fs;
  1332. this.file = file;
  1333. this.exists = exists;
  1334. this.isDirectory = isDirectory;
  1335. this.isExecutable = isExecutable;
  1336. this.isSymbolicLink = isSymbolicLink;
  1337. this.isRegularFile = isRegularFile;
  1338. this.creationTime = creationTime;
  1339. this.lastModifiedTime = lastModifiedTime;
  1340. this.length = length;
  1341. }
  1342. /**
  1343. * Constructor when there are issues with reading. All attributes except
  1344. * given will be set to the default values.
  1345. *
  1346. * @param fs
  1347. * @param path
  1348. */
  1349. public Attributes(File path, FS fs) {
  1350. this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
  1351. }
  1352. /**
  1353. * @return length of this file object
  1354. */
  1355. public long getLength() {
  1356. if (length == -1)
  1357. return length = file.length();
  1358. return length;
  1359. }
  1360. /**
  1361. * @return the filename
  1362. */
  1363. public String getName() {
  1364. return file.getName();
  1365. }
  1366. /**
  1367. * @return the file the attributes apply to
  1368. */
  1369. public File getFile() {
  1370. return file;
  1371. }
  1372. boolean exists() {
  1373. return exists;
  1374. }
  1375. }
  1376. /**
  1377. * Get the file attributes we care for.
  1378. *
  1379. * @param path
  1380. * a {@link java.io.File} object.
  1381. * @return the file attributes we care for.
  1382. * @since 3.3
  1383. */
  1384. public Attributes getAttributes(File path) {
  1385. boolean isDirectory = isDirectory(path);
  1386. boolean isFile = !isDirectory && path.isFile();
  1387. assert path.exists() == isDirectory || isFile;
  1388. boolean exists = isDirectory || isFile;
  1389. boolean canExecute = exists && !isDirectory && canExecute(path);
  1390. boolean isSymlink = false;
  1391. long lastModified = exists ? path.lastModified() : 0L;
  1392. long createTime = 0L;
  1393. return new Attributes(this, path, exists, isDirectory, canExecute,
  1394. isSymlink, isFile, createTime, lastModified, -1);
  1395. }
  1396. /**
  1397. * Normalize the unicode path to composed form.
  1398. *
  1399. * @param file
  1400. * a {@link java.io.File} object.
  1401. * @return NFC-format File
  1402. * @since 3.3
  1403. */
  1404. public File normalize(File file) {
  1405. return file;
  1406. }
  1407. /**
  1408. * Normalize the unicode path to composed form.
  1409. *
  1410. * @param name
  1411. * path name
  1412. * @return NFC-format string
  1413. * @since 3.3
  1414. */
  1415. public String normalize(String name) {
  1416. return name;
  1417. }
  1418. /**
  1419. * This runnable will consume an input stream's content into an output
  1420. * stream as soon as it gets available.
  1421. * <p>
  1422. * Typically used to empty processes' standard output and error, preventing
  1423. * them to choke.
  1424. * </p>
  1425. * <p>
  1426. * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  1427. * streams.
  1428. * </p>
  1429. */
  1430. private static class StreamGobbler implements Runnable {
  1431. private InputStream in;
  1432. private OutputStream out;
  1433. public StreamGobbler(InputStream stream, OutputStream output) {
  1434. this.in = stream;
  1435. this.out = output;
  1436. }
  1437. @Override
  1438. public void run() {
  1439. try {
  1440. copy();
  1441. } catch (IOException e) {
  1442. // Do nothing on read failure; leave streams open.
  1443. }
  1444. }
  1445. void copy() throws IOException {
  1446. boolean writeFailure = false;
  1447. byte buffer[] = new byte[4096];
  1448. int readBytes;
  1449. while ((readBytes = in.read(buffer)) != -1) {
  1450. // Do not try to write again after a failure, but keep
  1451. // reading as long as possible to prevent the input stream
  1452. // from choking.
  1453. if (!writeFailure && out != null) {
  1454. try {
  1455. out.write(buffer, 0, readBytes);
  1456. out.flush();
  1457. } catch (IOException e) {
  1458. writeFailure = true;
  1459. }
  1460. }
  1461. }
  1462. }
  1463. }
  1464. }