1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.eclipse.jgit.util;
47
48 import static java.nio.charset.StandardCharsets.UTF_8;
49
50 import java.io.File;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.io.OutputStream;
54 import java.nio.file.AtomicMoveNotSupportedException;
55 import java.nio.file.CopyOption;
56 import java.nio.file.Files;
57 import java.nio.file.InvalidPathException;
58 import java.nio.file.LinkOption;
59 import java.nio.file.Path;
60 import java.nio.file.StandardCopyOption;
61 import java.nio.file.attribute.BasicFileAttributeView;
62 import java.nio.file.attribute.BasicFileAttributes;
63 import java.nio.file.attribute.FileTime;
64 import java.nio.file.attribute.PosixFileAttributeView;
65 import java.nio.file.attribute.PosixFileAttributes;
66 import java.nio.file.attribute.PosixFilePermission;
67 import java.text.MessageFormat;
68 import java.text.Normalizer;
69 import java.text.Normalizer.Form;
70 import java.util.ArrayList;
71 import java.util.List;
72 import java.util.Locale;
73 import java.util.regex.Pattern;
74
75 import org.eclipse.jgit.internal.JGitText;
76 import org.eclipse.jgit.lib.Constants;
77 import org.eclipse.jgit.util.FS.Attributes;
78
79
80
81
82 public class FileUtils {
83
84
85
86
87 public static final int NONE = 0;
88
89
90
91
92 public static final int RECURSIVE = 1;
93
94
95
96
97 public static final int RETRY = 2;
98
99
100
101
102 public static final int SKIP_MISSING = 4;
103
104
105
106
107
108 public static final int IGNORE_ERRORS = 8;
109
110
111
112
113
114
115
116 public static final int EMPTY_DIRECTORIES_ONLY = 16;
117
118
119
120
121
122
123
124
125
126
127
128
129 public static Path toPath(File f) throws IOException {
130 try {
131 return f.toPath();
132 } catch (InvalidPathException ex) {
133 throw new IOException(ex);
134 }
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148 public static void delete(File f) throws IOException {
149 delete(f, NONE);
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 public static void delete(File f, int options) throws IOException {
170 FS fs = FS.DETECTED;
171 if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
172 return;
173
174 if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
175 final File[] items = f.listFiles();
176 if (items != null) {
177 List<File> files = new ArrayList<>();
178 List<File> dirs = new ArrayList<>();
179 for (File c : items)
180 if (c.isFile())
181 files.add(c);
182 else
183 dirs.add(c);
184
185
186
187 for (File file : files)
188 delete(file, options);
189 for (File d : dirs)
190 delete(d, options);
191 }
192 }
193
194 boolean delete = false;
195 if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
196 if (f.isDirectory()) {
197 delete = true;
198 } else if ((options & IGNORE_ERRORS) == 0) {
199 throw new IOException(MessageFormat.format(
200 JGitText.get().deleteFileFailed, f.getAbsolutePath()));
201 }
202 } else {
203 delete = true;
204 }
205
206 if (delete) {
207 Throwable t = null;
208 Path p = f.toPath();
209 try {
210 Files.delete(p);
211 return;
212 } catch (FileNotFoundException e) {
213 if ((options & (SKIP_MISSING | IGNORE_ERRORS)) == 0) {
214 throw new IOException(MessageFormat.format(
215 JGitText.get().deleteFileFailed,
216 f.getAbsolutePath()), e);
217 }
218 return;
219 } catch (IOException e) {
220 t = e;
221 }
222 if ((options & RETRY) != 0) {
223 for (int i = 1; i < 10; i++) {
224 try {
225 Thread.sleep(100);
226 } catch (InterruptedException ex) {
227
228 }
229 try {
230 Files.deleteIfExists(p);
231 return;
232 } catch (IOException e) {
233 t = e;
234 }
235 }
236 }
237 if ((options & IGNORE_ERRORS) == 0) {
238 throw new IOException(MessageFormat.format(
239 JGitText.get().deleteFileFailed, f.getAbsolutePath()),
240 t);
241 }
242 }
243 }
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 public static void rename(File src, File dst)
268 throws IOException {
269 rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300 public static void rename(final File src, final File dst,
301 CopyOption... options)
302 throws AtomicMoveNotSupportedException, IOException {
303 int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
304 while (--attempts >= 0) {
305 try {
306 Files.move(toPath(src), toPath(dst), options);
307 return;
308 } catch (AtomicMoveNotSupportedException e) {
309 throw e;
310 } catch (IOException e) {
311 try {
312 if (!dst.delete()) {
313 delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
314 }
315
316 Files.move(toPath(src), toPath(dst), options);
317 return;
318 } catch (IOException e2) {
319
320 }
321 }
322 try {
323 Thread.sleep(100);
324 } catch (InterruptedException e) {
325 throw new IOException(
326 MessageFormat.format(JGitText.get().renameFileFailed,
327 src.getAbsolutePath(), dst.getAbsolutePath()));
328 }
329 }
330 throw new IOException(
331 MessageFormat.format(JGitText.get().renameFileFailed,
332 src.getAbsolutePath(), dst.getAbsolutePath()));
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347 public static void mkdir(File d)
348 throws IOException {
349 mkdir(d, false);
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 public static void mkdir(File d, boolean skipExisting)
368 throws IOException {
369 if (!d.mkdir()) {
370 if (skipExisting && d.isDirectory())
371 return;
372 throw new IOException(MessageFormat.format(
373 JGitText.get().mkDirFailed, d.getAbsolutePath()));
374 }
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 public static void mkdirs(File d) throws IOException {
393 mkdirs(d, false);
394 }
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414 public static void mkdirs(File d, boolean skipExisting)
415 throws IOException {
416 if (!d.mkdirs()) {
417 if (skipExisting && d.isDirectory())
418 return;
419 throw new IOException(MessageFormat.format(
420 JGitText.get().mkDirsFailed, d.getAbsolutePath()));
421 }
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 public static void createNewFile(File f) throws IOException {
441 if (!f.createNewFile())
442 throw new IOException(MessageFormat.format(
443 JGitText.get().createNewFileFailed, f));
444 }
445
446
447
448
449
450
451
452
453
454
455
456
457 public static Path createSymLink(File path, String target)
458 throws IOException {
459 Path nioPath = toPath(path);
460 if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
461 BasicFileAttributes attrs = Files.readAttributes(nioPath,
462 BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
463 if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
464 delete(path);
465 } else {
466 delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
467 }
468 }
469 if (SystemReader.getInstance().isWindows()) {
470 target = target.replace('/', '\\');
471 }
472 Path nioTarget = toPath(new File(target));
473 return Files.createSymbolicLink(nioPath, nioTarget);
474 }
475
476
477
478
479
480
481
482
483
484
485 public static String readSymLink(File path) throws IOException {
486 Path nioPath = toPath(path);
487 Path target = Files.readSymbolicLink(nioPath);
488 String targetString = target.toString();
489 if (SystemReader.getInstance().isWindows()) {
490 targetString = targetString.replace('\\', '/');
491 } else if (SystemReader.getInstance().isMacOS()) {
492 targetString = Normalizer.normalize(targetString, Form.NFC);
493 }
494 return targetString;
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 public static File createTempDir(String prefix, String suffix, File dir)
511 throws IOException {
512 final int RETRIES = 1;
513 for (int i = 0; i < RETRIES; i++) {
514 File tmp = File.createTempFile(prefix, suffix, dir);
515 if (!tmp.delete())
516 continue;
517 if (!tmp.mkdir())
518 continue;
519 return tmp;
520 }
521 throw new IOException(JGitText.get().cannotCreateTempDir);
522 }
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539 public static String relativizeNativePath(String base, String other) {
540 return FS.DETECTED.relativize(base, other);
541 }
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558 public static String relativizeGitPath(String base, String other) {
559 return relativizePath(base, other, "/", false);
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594 public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
595 if (base.equals(other))
596 return "";
597
598 final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
599 final String[] otherSegments = other.split(Pattern
600 .quote(dirSeparator));
601
602 int commonPrefix = 0;
603 while (commonPrefix < baseSegments.length
604 && commonPrefix < otherSegments.length) {
605 if (caseSensitive
606 && baseSegments[commonPrefix]
607 .equals(otherSegments[commonPrefix]))
608 commonPrefix++;
609 else if (!caseSensitive
610 && baseSegments[commonPrefix]
611 .equalsIgnoreCase(otherSegments[commonPrefix]))
612 commonPrefix++;
613 else
614 break;
615 }
616
617 final StringBuilder builder = new StringBuilder();
618 for (int i = commonPrefix; i < baseSegments.length; i++)
619 builder.append("..").append(dirSeparator);
620 for (int i = commonPrefix; i < otherSegments.length; i++) {
621 builder.append(otherSegments[i]);
622 if (i < otherSegments.length - 1)
623 builder.append(dirSeparator);
624 }
625 return builder.toString();
626 }
627
628
629
630
631
632
633
634
635
636 public static boolean isStaleFileHandle(IOException ioe) {
637 String msg = ioe.getMessage();
638 return msg != null
639 && msg.toLowerCase(Locale.ROOT)
640 .matches("stale .*file .*handle");
641 }
642
643
644
645
646
647
648
649
650
651
652
653 public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
654 while (throwable != null) {
655 if (throwable instanceof IOException
656 && isStaleFileHandle((IOException) throwable)) {
657 return true;
658 }
659 throwable = throwable.getCause();
660 }
661 return false;
662 }
663
664
665
666
667
668 static boolean isSymlink(File file) {
669 return Files.isSymbolicLink(file.toPath());
670 }
671
672
673
674
675
676
677
678 static long lastModified(File file) throws IOException {
679 return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
680 .toMillis();
681 }
682
683
684
685
686
687
688
689
690
691
692 static BasicFileAttributes fileAttributes(File file) throws IOException {
693 return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
694 }
695
696
697
698
699
700
701 static void setLastModified(File file, long time) throws IOException {
702 Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
703 }
704
705
706
707
708
709
710 static boolean exists(File file) {
711 return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
712 }
713
714
715
716
717
718
719 static boolean isHidden(File file) throws IOException {
720 return Files.isHidden(toPath(file));
721 }
722
723
724
725
726
727
728
729
730
731
732
733 public static void setHidden(File file, boolean hidden) throws IOException {
734 Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden),
735 LinkOption.NOFOLLOW_LINKS);
736 }
737
738
739
740
741
742
743
744
745
746
747 public static long getLength(File file) throws IOException {
748 Path nioPath = toPath(file);
749 if (Files.isSymbolicLink(nioPath))
750 return Files.readSymbolicLink(nioPath).toString()
751 .getBytes(UTF_8).length;
752 return Files.size(nioPath);
753 }
754
755
756
757
758
759
760 static boolean isDirectory(File file) {
761 return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
762 }
763
764
765
766
767
768
769 static boolean isFile(File file) {
770 return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
771 }
772
773
774
775
776
777
778
779
780
781 public static boolean canExecute(File file) {
782 if (!isFile(file)) {
783 return false;
784 }
785 return Files.isExecutable(file.toPath());
786 }
787
788
789
790
791
792
793 static Attributes getFileAttributesBasic(FS fs, File file) {
794 try {
795 Path nioPath = toPath(file);
796 BasicFileAttributes readAttributes = nioPath
797 .getFileSystem()
798 .provider()
799 .getFileAttributeView(nioPath,
800 BasicFileAttributeView.class,
801 LinkOption.NOFOLLOW_LINKS).readAttributes();
802 Attributes attributes = new Attributes(fs, file,
803 true,
804 readAttributes.isDirectory(),
805 fs.supportsExecute() ? file.canExecute() : false,
806 readAttributes.isSymbolicLink(),
807 readAttributes.isRegularFile(),
808 readAttributes.creationTime().toMillis(),
809 readAttributes.lastModifiedTime().toMillis(),
810 readAttributes.isSymbolicLink() ? Constants
811 .encode(readSymLink(file)).length
812 : readAttributes.size());
813 return attributes;
814 } catch (IOException e) {
815 return new Attributes(file, fs);
816 }
817 }
818
819
820
821
822
823
824
825
826
827
828
829 public static Attributes getFileAttributesPosix(FS fs, File file) {
830 try {
831 Path nioPath = toPath(file);
832 PosixFileAttributes readAttributes = nioPath
833 .getFileSystem()
834 .provider()
835 .getFileAttributeView(nioPath,
836 PosixFileAttributeView.class,
837 LinkOption.NOFOLLOW_LINKS).readAttributes();
838 Attributes attributes = new Attributes(
839 fs,
840 file,
841 true,
842 readAttributes.isDirectory(),
843 readAttributes.permissions().contains(
844 PosixFilePermission.OWNER_EXECUTE),
845 readAttributes.isSymbolicLink(),
846 readAttributes.isRegularFile(),
847 readAttributes.creationTime().toMillis(),
848 readAttributes.lastModifiedTime().toMillis(),
849 readAttributes.size());
850 return attributes;
851 } catch (IOException e) {
852 return new Attributes(file, fs);
853 }
854 }
855
856
857
858
859
860
861
862
863
864
865 public static File normalize(File file) {
866 if (SystemReader.getInstance().isMacOS()) {
867
868
869 String normalized = Normalizer.normalize(file.getPath(),
870 Normalizer.Form.NFC);
871 return new File(normalized);
872 }
873 return file;
874 }
875
876
877
878
879
880
881
882
883
884 public static String normalize(String name) {
885 if (SystemReader.getInstance().isMacOS()) {
886 if (name == null)
887 return null;
888 return Normalizer.normalize(name, Normalizer.Form.NFC);
889 }
890 return name;
891 }
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906 public static File canonicalize(File file) {
907 if (file == null) {
908 return null;
909 }
910 try {
911 return file.getCanonicalFile();
912 } catch (IOException e) {
913 return file;
914 }
915 }
916
917
918
919
920
921
922
923
924
925 public static String pathToString(File file) {
926 final String path = file.getPath();
927 if (SystemReader.getInstance().isWindows()) {
928 return path.replace('\\', '/');
929 }
930 return path;
931 }
932
933
934
935
936
937
938
939
940
941 public static void touch(Path f) throws IOException {
942 try (OutputStream fos = Files.newOutputStream(f)) {
943
944 }
945 }
946 }