1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.eclipse.jgit.lib;
17
18 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
19 import static java.nio.charset.StandardCharsets.UTF_8;
20
21 import java.io.BufferedOutputStream;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.UncheckedIOException;
28 import java.net.URISyntaxException;
29 import java.text.MessageFormat;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.concurrent.atomic.AtomicLong;
40 import java.util.regex.Pattern;
41
42 import org.eclipse.jgit.annotations.NonNull;
43 import org.eclipse.jgit.annotations.Nullable;
44 import org.eclipse.jgit.attributes.AttributesNodeProvider;
45 import org.eclipse.jgit.dircache.DirCache;
46 import org.eclipse.jgit.errors.AmbiguousObjectException;
47 import org.eclipse.jgit.errors.CorruptObjectException;
48 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
49 import org.eclipse.jgit.errors.MissingObjectException;
50 import org.eclipse.jgit.errors.NoWorkTreeException;
51 import org.eclipse.jgit.errors.RevisionSyntaxException;
52 import org.eclipse.jgit.events.IndexChangedEvent;
53 import org.eclipse.jgit.events.IndexChangedListener;
54 import org.eclipse.jgit.events.ListenerList;
55 import org.eclipse.jgit.events.RepositoryEvent;
56 import org.eclipse.jgit.internal.JGitText;
57 import org.eclipse.jgit.revwalk.RevBlob;
58 import org.eclipse.jgit.revwalk.RevCommit;
59 import org.eclipse.jgit.revwalk.RevObject;
60 import org.eclipse.jgit.revwalk.RevTree;
61 import org.eclipse.jgit.revwalk.RevWalk;
62 import org.eclipse.jgit.transport.RefSpec;
63 import org.eclipse.jgit.transport.RemoteConfig;
64 import org.eclipse.jgit.treewalk.TreeWalk;
65 import org.eclipse.jgit.util.FS;
66 import org.eclipse.jgit.util.FileUtils;
67 import org.eclipse.jgit.util.IO;
68 import org.eclipse.jgit.util.RawParseUtils;
69 import org.eclipse.jgit.util.SystemReader;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public abstract class Repository implements AutoCloseable {
88 private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
89 private static final ListenerList globalListeners = new ListenerList();
90
91
92
93
94
95
96
97 private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern
98 .compile(
99 "(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?",
100 Pattern.CASE_INSENSITIVE);
101
102
103
104
105
106
107 public static ListenerList getGlobalListenerList() {
108 return globalListeners;
109 }
110
111
112 final AtomicInteger useCnt = new AtomicInteger(1);
113
114 final AtomicLong closedAt = new AtomicLong();
115
116
117 private final File gitDir;
118
119
120 private final FS fs;
121
122 private final ListenerList myListeners = new ListenerList();
123
124
125 private final File workTree;
126
127
128 private final File indexFile;
129
130 private final String initialBranch;
131
132
133
134
135
136
137
138 protected Repository(BaseRepositoryBuilder options) {
139 gitDir = options.getGitDir();
140 fs = options.getFS();
141 workTree = options.getWorkTree();
142 indexFile = options.getIndexFile();
143 initialBranch = options.getInitialBranch();
144 }
145
146
147
148
149
150
151 @NonNull
152 public ListenerList getListenerList() {
153 return myListeners;
154 }
155
156
157
158
159
160
161
162
163
164
165 public void fireEvent(RepositoryEvent<?> event) {
166 event.setRepository(this);
167 myListeners.dispatch(event);
168 globalListeners.dispatch(event);
169 }
170
171
172
173
174
175
176
177
178
179
180 public void create() throws IOException {
181 create(false);
182 }
183
184
185
186
187
188
189
190
191
192
193
194 public abstract void create(boolean bare) throws IOException;
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public File getDirectory() {
209 return gitDir;
210 }
211
212
213
214
215
216
217
218
219 public abstract String getIdentifier();
220
221
222
223
224
225
226 @NonNull
227 public abstract ObjectDatabase getObjectDatabase();
228
229
230
231
232
233
234 @NonNull
235 public ObjectInserter newObjectInserter() {
236 return getObjectDatabase().newInserter();
237 }
238
239
240
241
242
243
244 @NonNull
245 public ObjectReader newObjectReader() {
246 return getObjectDatabase().newReader();
247 }
248
249
250
251
252
253
254 @NonNull
255 public abstract RefDatabase getRefDatabase();
256
257
258
259
260
261
262 @NonNull
263 public abstract StoredConfig getConfig();
264
265
266
267
268
269
270
271
272
273
274 @NonNull
275 public abstract AttributesNodeProvider createAttributesNodeProvider();
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290 public FS getFS() {
291 return fs;
292 }
293
294
295
296
297
298
299
300
301
302
303
304 @Deprecated
305 public boolean hasObject(AnyObjectId objectId) {
306 try {
307 return getObjectDatabase().has(objectId);
308 } catch (IOException e) {
309 throw new UncheckedIOException(e);
310 }
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 @NonNull
329 public ObjectLoader open(AnyObjectId objectId)
330 throws MissingObjectException, IOException {
331 return getObjectDatabase().open(objectId);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357 @NonNull
358 public ObjectLoader open(AnyObjectId objectId, int typeHint)
359 throws MissingObjectException, IncorrectObjectTypeException,
360 IOException {
361 return getObjectDatabase().open(objectId, typeHint);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376 @NonNull
377 public RefUpdate updateRef(String ref) throws IOException {
378 return updateRef(ref, false);
379 }
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 @NonNull
396 public RefUpdate updateRef(String ref, boolean detach) throws IOException {
397 return getRefDatabase().newUpdate(ref, detach);
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411 @NonNull
412 public RefRename renameRef(String fromRef, String toRef) throws IOException {
413 return getRefDatabase().newRename(fromRef, toRef);
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466 @Nullable
467 public ObjectId resolve(String revstr)
468 throws AmbiguousObjectException, IncorrectObjectTypeException,
469 RevisionSyntaxException, IOException {
470 try (RevWalk rw = new RevWalk(this)) {
471 rw.setRetainBody(false);
472 Object resolved = resolve(rw, revstr);
473 if (resolved instanceof String) {
474 final Ref ref = findRef((String) resolved);
475 return ref != null ? ref.getLeaf().getObjectId() : null;
476 }
477 return (ObjectId) resolved;
478 }
479 }
480
481
482
483
484
485
486
487
488
489
490
491
492
493 @Nullable
494 public String simplify(String revstr)
495 throws AmbiguousObjectException, IOException {
496 try (RevWalk rw = new RevWalk(this)) {
497 rw.setRetainBody(true);
498 Object resolved = resolve(rw, revstr);
499 if (resolved != null) {
500 if (resolved instanceof String) {
501 return (String) resolved;
502 }
503 return ((AnyObjectId) resolved).getName();
504 }
505 return null;
506 }
507 }
508
509 @Nullable
510 private Object resolve(RevWalk rw, String revstr)
511 throws IOException {
512 char[] revChars = revstr.toCharArray();
513 RevObject rev = null;
514 String name = null;
515 int done = 0;
516 for (int i = 0; i < revChars.length; ++i) {
517 switch (revChars[i]) {
518 case '^':
519 if (rev == null) {
520 if (name == null)
521 if (done == 0)
522 name = new String(revChars, done, i);
523 else {
524 done = i + 1;
525 break;
526 }
527 rev = parseSimple(rw, name);
528 name = null;
529 if (rev == null)
530 return null;
531 }
532 if (i + 1 < revChars.length) {
533 switch (revChars[i + 1]) {
534 case '0':
535 case '1':
536 case '2':
537 case '3':
538 case '4':
539 case '5':
540 case '6':
541 case '7':
542 case '8':
543 case '9':
544 int j;
545 rev = rw.parseCommit(rev);
546 for (j = i + 1; j < revChars.length; ++j) {
547 if (!Character.isDigit(revChars[j]))
548 break;
549 }
550 String parentnum = new String(revChars, i + 1, j - i
551 - 1);
552 int pnum;
553 try {
554 pnum = Integer.parseInt(parentnum);
555 } catch (NumberFormatException e) {
556 RevisionSyntaxException rse = new RevisionSyntaxException(
557 JGitText.get().invalidCommitParentNumber,
558 revstr);
559 rse.initCause(e);
560 throw rse;
561 }
562 if (pnum != 0) {
563 RevCommit commit = (RevCommit) rev;
564 if (pnum > commit.getParentCount())
565 rev = null;
566 else
567 rev = commit.getParent(pnum - 1);
568 }
569 i = j - 1;
570 done = j;
571 break;
572 case '{':
573 int k;
574 String item = null;
575 for (k = i + 2; k < revChars.length; ++k) {
576 if (revChars[k] == '}') {
577 item = new String(revChars, i + 2, k - i - 2);
578 break;
579 }
580 }
581 i = k;
582 if (item != null)
583 if (item.equals("tree")) {
584 rev = rw.parseTree(rev);
585 } else if (item.equals("commit")) {
586 rev = rw.parseCommit(rev);
587 } else if (item.equals("blob")) {
588 rev = rw.peel(rev);
589 if (!(rev instanceof RevBlob))
590 throw new IncorrectObjectTypeException(rev,
591 Constants.TYPE_BLOB);
592 } else if (item.isEmpty()) {
593 rev = rw.peel(rev);
594 } else
595 throw new RevisionSyntaxException(revstr);
596 else
597 throw new RevisionSyntaxException(revstr);
598 done = k;
599 break;
600 default:
601 rev = rw.peel(rev);
602 if (rev instanceof RevCommit) {
603 RevCommit commit = ((RevCommit) rev);
604 if (commit.getParentCount() == 0)
605 rev = null;
606 else
607 rev = commit.getParent(0);
608 } else
609 throw new IncorrectObjectTypeException(rev,
610 Constants.TYPE_COMMIT);
611 }
612 } else {
613 rev = rw.peel(rev);
614 if (rev instanceof RevCommit) {
615 RevCommit commit = ((RevCommit) rev);
616 if (commit.getParentCount() == 0)
617 rev = null;
618 else
619 rev = commit.getParent(0);
620 } else
621 throw new IncorrectObjectTypeException(rev,
622 Constants.TYPE_COMMIT);
623 }
624 done = i + 1;
625 break;
626 case '~':
627 if (rev == null) {
628 if (name == null)
629 if (done == 0)
630 name = new String(revChars, done, i);
631 else {
632 done = i + 1;
633 break;
634 }
635 rev = parseSimple(rw, name);
636 name = null;
637 if (rev == null)
638 return null;
639 }
640 rev = rw.peel(rev);
641 if (!(rev instanceof RevCommit))
642 throw new IncorrectObjectTypeException(rev,
643 Constants.TYPE_COMMIT);
644 int l;
645 for (l = i + 1; l < revChars.length; ++l) {
646 if (!Character.isDigit(revChars[l]))
647 break;
648 }
649 int dist;
650 if (l - i > 1) {
651 String distnum = new String(revChars, i + 1, l - i - 1);
652 try {
653 dist = Integer.parseInt(distnum);
654 } catch (NumberFormatException e) {
655 RevisionSyntaxException rse = new RevisionSyntaxException(
656 JGitText.get().invalidAncestryLength, revstr);
657 rse.initCause(e);
658 throw rse;
659 }
660 } else
661 dist = 1;
662 while (dist > 0) {
663 RevCommit commit = (RevCommit) rev;
664 if (commit.getParentCount() == 0) {
665 rev = null;
666 break;
667 }
668 commit = commit.getParent(0);
669 rw.parseHeaders(commit);
670 rev = commit;
671 --dist;
672 }
673 i = l - 1;
674 done = l;
675 break;
676 case '@':
677 if (rev != null)
678 throw new RevisionSyntaxException(revstr);
679 if (i + 1 == revChars.length)
680 continue;
681 if (i + 1 < revChars.length && revChars[i + 1] != '{')
682 continue;
683 int m;
684 String time = null;
685 for (m = i + 2; m < revChars.length; ++m) {
686 if (revChars[m] == '}') {
687 time = new String(revChars, i + 2, m - i - 2);
688 break;
689 }
690 }
691 if (time != null) {
692 if (time.equals("upstream")) {
693 if (name == null)
694 name = new String(revChars, done, i);
695 if (name.isEmpty())
696
697
698 name = Constants.HEAD;
699 if (!Repository.isValidRefName("x/" + name))
700 throw new RevisionSyntaxException(MessageFormat
701 .format(JGitText.get().invalidRefName,
702 name),
703 revstr);
704 Ref ref = findRef(name);
705 name = null;
706 if (ref == null)
707 return null;
708 if (ref.isSymbolic())
709 ref = ref.getLeaf();
710 name = ref.getName();
711
712 RemoteConfig remoteConfig;
713 try {
714 remoteConfig = new RemoteConfig(getConfig(),
715 "origin");
716 } catch (URISyntaxException e) {
717 RevisionSyntaxException rse = new RevisionSyntaxException(
718 revstr);
719 rse.initCause(e);
720 throw rse;
721 }
722 String remoteBranchName = getConfig()
723 .getString(
724 ConfigConstants.CONFIG_BRANCH_SECTION,
725 Repository.shortenRefName(ref.getName()),
726 ConfigConstants.CONFIG_KEY_MERGE);
727 List<RefSpec> fetchRefSpecs = remoteConfig
728 .getFetchRefSpecs();
729 for (RefSpec refSpec : fetchRefSpecs) {
730 if (refSpec.matchSource(remoteBranchName)) {
731 RefSpec expandFromSource = refSpec
732 .expandFromSource(remoteBranchName);
733 name = expandFromSource.getDestination();
734 break;
735 }
736 }
737 if (name == null)
738 throw new RevisionSyntaxException(revstr);
739 } else if (time.matches("^-\\d+$")) {
740 if (name != null) {
741 throw new RevisionSyntaxException(revstr);
742 }
743 String previousCheckout = resolveReflogCheckout(
744 -Integer.parseInt(time));
745 if (ObjectId.isId(previousCheckout)) {
746 rev = parseSimple(rw, previousCheckout);
747 } else {
748 name = previousCheckout;
749 }
750 } else {
751 if (name == null)
752 name = new String(revChars, done, i);
753 if (name.isEmpty())
754 name = Constants.HEAD;
755 if (!Repository.isValidRefName("x/" + name))
756 throw new RevisionSyntaxException(MessageFormat
757 .format(JGitText.get().invalidRefName,
758 name),
759 revstr);
760 Ref ref = findRef(name);
761 name = null;
762 if (ref == null)
763 return null;
764
765
766 if (ref.isSymbolic())
767 ref = ref.getLeaf();
768 rev = resolveReflog(rw, ref, time);
769 }
770 i = m;
771 } else
772 throw new RevisionSyntaxException(revstr);
773 break;
774 case ':': {
775 RevTree tree;
776 if (rev == null) {
777 if (name == null)
778 name = new String(revChars, done, i);
779 if (name.isEmpty())
780 name = Constants.HEAD;
781 rev = parseSimple(rw, name);
782 name = null;
783 }
784 if (rev == null)
785 return null;
786 tree = rw.parseTree(rev);
787 if (i == revChars.length - 1)
788 return tree.copy();
789
790 TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(),
791 new String(revChars, i + 1, revChars.length - i - 1),
792 tree);
793 return tw != null ? tw.getObjectId(0) : null;
794 }
795 default:
796 if (rev != null)
797 throw new RevisionSyntaxException(revstr);
798 }
799 }
800 if (rev != null)
801 return rev.copy();
802 if (name != null)
803 return name;
804 if (done == revstr.length())
805 return null;
806 name = revstr.substring(done);
807 if (!Repository.isValidRefName("x/" + name))
808 throw new RevisionSyntaxException(
809 MessageFormat.format(JGitText.get().invalidRefName, name),
810 revstr);
811 if (findRef(name) != null)
812 return name;
813 return resolveSimple(name);
814 }
815
816 private static boolean isHex(char c) {
817 return ('0' <= c && c <= '9')
818 || ('a' <= c && c <= 'f')
819 || ('A' <= c && c <= 'F');
820 }
821
822 private static boolean isAllHex(String str, int ptr) {
823 while (ptr < str.length()) {
824 if (!isHex(str.charAt(ptr++)))
825 return false;
826 }
827 return true;
828 }
829
830 @Nullable
831 private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
832 ObjectId id = resolveSimple(revstr);
833 return id != null ? rw.parseAny(id) : null;
834 }
835
836 @Nullable
837 private ObjectId resolveSimple(String revstr) throws IOException {
838 if (ObjectId.isId(revstr))
839 return ObjectId.fromString(revstr);
840
841 if (Repository.isValidRefName("x/" + revstr)) {
842 Ref r = getRefDatabase().findRef(revstr);
843 if (r != null)
844 return r.getObjectId();
845 }
846
847 if (AbbreviatedObjectId.isId(revstr))
848 return resolveAbbreviation(revstr);
849
850 int dashg = revstr.indexOf("-g");
851 if ((dashg + 5) < revstr.length() && 0 <= dashg
852 && isHex(revstr.charAt(dashg + 2))
853 && isHex(revstr.charAt(dashg + 3))
854 && isAllHex(revstr, dashg + 4)) {
855
856 String s = revstr.substring(dashg + 2);
857 if (AbbreviatedObjectId.isId(s))
858 return resolveAbbreviation(s);
859 }
860
861 return null;
862 }
863
864 @Nullable
865 private String resolveReflogCheckout(int checkoutNo)
866 throws IOException {
867 ReflogReader reader = getReflogReader(Constants.HEAD);
868 if (reader == null) {
869 return null;
870 }
871 List<ReflogEntry> reflogEntries = reader.getReverseEntries();
872 for (ReflogEntry entry : reflogEntries) {
873 CheckoutEntry checkout = entry.parseCheckout();
874 if (checkout != null)
875 if (checkoutNo-- == 1)
876 return checkout.getFromBranch();
877 }
878 return null;
879 }
880
881 private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
882 throws IOException {
883 int number;
884 try {
885 number = Integer.parseInt(time);
886 } catch (NumberFormatException nfe) {
887 RevisionSyntaxException rse = new RevisionSyntaxException(
888 MessageFormat.format(JGitText.get().invalidReflogRevision,
889 time));
890 rse.initCause(nfe);
891 throw rse;
892 }
893 assert number >= 0;
894 ReflogReader reader = getReflogReader(ref.getName());
895 if (reader == null) {
896 throw new RevisionSyntaxException(
897 MessageFormat.format(JGitText.get().reflogEntryNotFound,
898 Integer.valueOf(number), ref.getName()));
899 }
900 ReflogEntry entry = reader.getReverseEntry(number);
901 if (entry == null)
902 throw new RevisionSyntaxException(MessageFormat.format(
903 JGitText.get().reflogEntryNotFound,
904 Integer.valueOf(number), ref.getName()));
905
906 return rw.parseCommit(entry.getNewId());
907 }
908
909 @Nullable
910 private ObjectId resolveAbbreviation(String revstr) throws IOException,
911 AmbiguousObjectException {
912 AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
913 try (ObjectReader reader = newObjectReader()) {
914 Collection<ObjectId> matches = reader.resolve(id);
915 if (matches.isEmpty())
916 return null;
917 else if (matches.size() == 1)
918 return matches.iterator().next();
919 else
920 throw new AmbiguousObjectException(id, matches);
921 }
922 }
923
924
925
926
927 public void incrementOpen() {
928 useCnt.incrementAndGet();
929 }
930
931
932
933
934
935
936 @Override
937 public void close() {
938 int newCount = useCnt.decrementAndGet();
939 if (newCount == 0) {
940 if (RepositoryCache.isCached(this)) {
941 closedAt.set(System.currentTimeMillis());
942 } else {
943 doClose();
944 }
945 } else if (newCount == -1) {
946
947
948 String message = MessageFormat.format(JGitText.get().corruptUseCnt,
949 toString());
950 if (LOG.isDebugEnabled()) {
951 LOG.debug(message, new IllegalStateException());
952 } else {
953 LOG.warn(message);
954 }
955 if (RepositoryCache.isCached(this)) {
956 closedAt.set(System.currentTimeMillis());
957 }
958 }
959 }
960
961
962
963
964
965
966 protected void doClose() {
967 getObjectDatabase().close();
968 getRefDatabase().close();
969 }
970
971
972 @Override
973 @NonNull
974 public String toString() {
975 String desc;
976 File directory = getDirectory();
977 if (directory != null)
978 desc = directory.getPath();
979 else
980 desc = getClass().getSimpleName() + "-"
981 + System.identityHashCode(this);
982 return "Repository[" + desc + "]";
983 }
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003 @Nullable
1004 public String getFullBranch() throws IOException {
1005 Ref head = exactRef(Constants.HEAD);
1006 if (head == null) {
1007 return null;
1008 }
1009 if (head.isSymbolic()) {
1010 return head.getTarget().getName();
1011 }
1012 ObjectId objectId = head.getObjectId();
1013 if (objectId != null) {
1014 return objectId.name();
1015 }
1016 return null;
1017 }
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031 @Nullable
1032 public String getBranch() throws IOException {
1033 String name = getFullBranch();
1034 if (name != null)
1035 return shortenRefName(name);
1036 return null;
1037 }
1038
1039
1040
1041
1042
1043
1044
1045 protected @NonNull String getInitialBranch() {
1046 return initialBranch;
1047 }
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060 @NonNull
1061 public Set<ObjectId> getAdditionalHaves() throws IOException {
1062 return Collections.emptySet();
1063 }
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076 @Nullable
1077 public final Ref exactRef(String name) throws IOException {
1078 return getRefDatabase().exactRef(name);
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092 @Nullable
1093 public final Ref findRef(String name) throws IOException {
1094 return getRefDatabase().findRef(name);
1095 }
1096
1097
1098
1099
1100
1101
1102
1103
1104 @Deprecated
1105 @NonNull
1106 public Map<String, Ref> getAllRefs() {
1107 try {
1108 return getRefDatabase().getRefs(RefDatabase.ALL);
1109 } catch (IOException e) {
1110 throw new UncheckedIOException(e);
1111 }
1112 }
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122 @Deprecated
1123 @NonNull
1124 public Map<String, Ref> getTags() {
1125 try {
1126 return getRefDatabase().getRefs(Constants.R_TAGS);
1127 } catch (IOException e) {
1128 throw new UncheckedIOException(e);
1129 }
1130 }
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147 @Deprecated
1148 @NonNull
1149 public Ref peel(Ref ref) {
1150 try {
1151 return getRefDatabase().peel(ref);
1152 } catch (IOException e) {
1153
1154
1155
1156 return ref;
1157 }
1158 }
1159
1160
1161
1162
1163
1164
1165
1166 @NonNull
1167 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId()
1168 throws IOException {
1169 List<Ref> allRefs = getRefDatabase().getRefs();
1170 Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
1171 for (Ref ref : allRefs) {
1172 ref = peel(ref);
1173 AnyObjectId target = ref.getPeeledObjectId();
1174 if (target == null)
1175 target = ref.getObjectId();
1176
1177 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
1178 if (oset != null) {
1179
1180 if (oset.size() == 1) {
1181
1182 oset = new HashSet<>(oset);
1183 }
1184 ret.put(target, oset);
1185 oset.add(ref);
1186 }
1187 }
1188 return ret;
1189 }
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200 @NonNull
1201 public File getIndexFile() throws NoWorkTreeException {
1202 if (isBare())
1203 throw new NoWorkTreeException();
1204 return indexFile;
1205 }
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224 public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
1225 IOException, MissingObjectException {
1226 if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) {
1227 return (RevCommit) id;
1228 }
1229 try (RevWalk walk = new RevWalk(this)) {
1230 return walk.parseCommit(id);
1231 }
1232 }
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 @NonNull
1253 public DirCache readDirCache() throws NoWorkTreeException,
1254 CorruptObjectException, IOException {
1255 return DirCache.read(this);
1256 }
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277 @NonNull
1278 public DirCache lockDirCache() throws NoWorkTreeException,
1279 CorruptObjectException, IOException {
1280
1281
1282 IndexChangedListener l = (IndexChangedEvent event) -> {
1283 notifyIndexChanged(true);
1284 };
1285 return DirCache.lock(this, l);
1286 }
1287
1288
1289
1290
1291
1292
1293 @NonNull
1294 public RepositoryState getRepositoryState() {
1295 if (isBare() || getDirectory() == null)
1296 return RepositoryState.BARE;
1297
1298
1299 if (new File(getWorkTree(), ".dotest").exists())
1300 return RepositoryState.REBASING;
1301 if (new File(getDirectory(), ".dotest-merge").exists())
1302 return RepositoryState.REBASING_INTERACTIVE;
1303
1304
1305 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1306 return RepositoryState.REBASING_REBASING;
1307 if (new File(getDirectory(),"rebase-apply/applying").exists())
1308 return RepositoryState.APPLY;
1309 if (new File(getDirectory(),"rebase-apply").exists())
1310 return RepositoryState.REBASING;
1311
1312 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1313 return RepositoryState.REBASING_INTERACTIVE;
1314 if (new File(getDirectory(),"rebase-merge").exists())
1315 return RepositoryState.REBASING_MERGE;
1316
1317
1318 if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
1319
1320 try {
1321 if (!readDirCache().hasUnmergedPaths()) {
1322
1323 return RepositoryState.MERGING_RESOLVED;
1324 }
1325 } catch (IOException e) {
1326 throw new UncheckedIOException(e);
1327 }
1328 return RepositoryState.MERGING;
1329 }
1330
1331 if (new File(getDirectory(), "BISECT_LOG").exists())
1332 return RepositoryState.BISECTING;
1333
1334 if (new File(getDirectory(), Constants.CHERRY_PICK_HEAD).exists()) {
1335 try {
1336 if (!readDirCache().hasUnmergedPaths()) {
1337
1338 return RepositoryState.CHERRY_PICKING_RESOLVED;
1339 }
1340 } catch (IOException e) {
1341 throw new UncheckedIOException(e);
1342 }
1343
1344 return RepositoryState.CHERRY_PICKING;
1345 }
1346
1347 if (new File(getDirectory(), Constants.REVERT_HEAD).exists()) {
1348 try {
1349 if (!readDirCache().hasUnmergedPaths()) {
1350
1351 return RepositoryState.REVERTING_RESOLVED;
1352 }
1353 } catch (IOException e) {
1354 throw new UncheckedIOException(e);
1355 }
1356
1357 return RepositoryState.REVERTING;
1358 }
1359
1360 return RepositoryState.SAFE;
1361 }
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373 public static boolean isValidRefName(String refName) {
1374 final int len = refName.length();
1375 if (len == 0) {
1376 return false;
1377 }
1378 if (refName.endsWith(LOCK_SUFFIX)) {
1379 return false;
1380 }
1381
1382
1383
1384 try {
1385 SystemReader.getInstance().checkPath(refName);
1386 } catch (CorruptObjectException e) {
1387 return false;
1388 }
1389
1390 int components = 1;
1391 char p = '\0';
1392 for (int i = 0; i < len; i++) {
1393 final char c = refName.charAt(i);
1394 if (c <= ' ')
1395 return false;
1396 switch (c) {
1397 case '.':
1398 switch (p) {
1399 case '\0': case '/': case '.':
1400 return false;
1401 }
1402 if (i == len -1)
1403 return false;
1404 break;
1405 case '/':
1406 if (i == 0 || i == len - 1)
1407 return false;
1408 if (p == '/')
1409 return false;
1410 components++;
1411 break;
1412 case '{':
1413 if (p == '@')
1414 return false;
1415 break;
1416 case '~': case '^': case ':':
1417 case '?': case '[': case '*':
1418 case '\\':
1419 case '\u007F':
1420 return false;
1421 }
1422 p = c;
1423 }
1424 return components > 1;
1425 }
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448 public static String normalizeBranchName(String name) {
1449 if (name == null || name.isEmpty()) {
1450 return "";
1451 }
1452 String result = name.trim();
1453 String fullName = result.startsWith(Constants.R_HEADS) ? result
1454 : Constants.R_HEADS + result;
1455 if (isValidRefName(fullName)) {
1456 return result;
1457 }
1458
1459
1460 result = result.replaceAll("(?:\\h|\\v)+", "_");
1461 StringBuilder b = new StringBuilder();
1462 char p = '/';
1463 for (int i = 0, len = result.length(); i < len; i++) {
1464 char c = result.charAt(i);
1465 if (c < ' ' || c == 127) {
1466 continue;
1467 }
1468
1469 switch (c) {
1470 case '\\':
1471 case '^':
1472 case '~':
1473 case ':':
1474 case '?':
1475 case '*':
1476 case '[':
1477 case '@':
1478 case '<':
1479 case '>':
1480 case '|':
1481 case '"':
1482 c = '-';
1483 break;
1484 default:
1485 break;
1486 }
1487
1488
1489 switch (c) {
1490 case '/':
1491 if (p == '/') {
1492 continue;
1493 }
1494 p = '/';
1495 break;
1496 case '.':
1497 case '_':
1498 case '-':
1499 if (p == '/' || p == '-') {
1500 continue;
1501 }
1502 p = '-';
1503 break;
1504 default:
1505 p = c;
1506 break;
1507 }
1508 b.append(c);
1509 }
1510
1511 result = b.toString().replaceFirst("[/_.-]+$", "")
1512 .replaceAll("\\.lock($|/)", "_lock$1");
1513 return FORBIDDEN_BRANCH_NAME_COMPONENTS.matcher(result)
1514 .replaceAll("$1+$2$3");
1515 }
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527 @NonNull
1528 public static String stripWorkDir(File workDir, File file) {
1529 final String filePath = file.getPath();
1530 final String workDirPath = workDir.getPath();
1531
1532 if (filePath.length() <= workDirPath.length()
1533 || filePath.charAt(workDirPath.length()) != File.separatorChar
1534 || !filePath.startsWith(workDirPath)) {
1535 File absWd = workDir.isAbsolute() ? workDir
1536 : workDir.getAbsoluteFile();
1537 File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
1538 if (absWd.equals(workDir) && absFile.equals(file)) {
1539 return "";
1540 }
1541 return stripWorkDir(absWd, absFile);
1542 }
1543
1544 String relName = filePath.substring(workDirPath.length() + 1);
1545 if (File.separatorChar != '/') {
1546 relName = relName.replace(File.separatorChar, '/');
1547 }
1548 return relName;
1549 }
1550
1551
1552
1553
1554
1555
1556 public boolean isBare() {
1557 return workTree == null;
1558 }
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570 @NonNull
1571 public File getWorkTree() throws NoWorkTreeException {
1572 if (isBare())
1573 throw new NoWorkTreeException();
1574 return workTree;
1575 }
1576
1577
1578
1579
1580
1581
1582
1583 public abstract void scanForRepoChanges() throws IOException;
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593 public abstract void notifyIndexChanged(boolean internal);
1594
1595
1596
1597
1598
1599
1600
1601
1602 @NonNull
1603 public static String shortenRefName(String refName) {
1604 if (refName.startsWith(Constants.R_HEADS))
1605 return refName.substring(Constants.R_HEADS.length());
1606 if (refName.startsWith(Constants.R_TAGS))
1607 return refName.substring(Constants.R_TAGS.length());
1608 if (refName.startsWith(Constants.R_REMOTES))
1609 return refName.substring(Constants.R_REMOTES.length());
1610 return refName;
1611 }
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624 @Nullable
1625 public String shortenRemoteBranchName(String refName) {
1626 for (String remote : getRemoteNames()) {
1627 String remotePrefix = Constants.R_REMOTES + remote + "/";
1628 if (refName.startsWith(remotePrefix))
1629 return refName.substring(remotePrefix.length());
1630 }
1631 return null;
1632 }
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645 @Nullable
1646 public String getRemoteName(String refName) {
1647 for (String remote : getRemoteNames()) {
1648 String remotePrefix = Constants.R_REMOTES + remote + "/";
1649 if (refName.startsWith(remotePrefix))
1650 return remote;
1651 }
1652 return null;
1653 }
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663 @Nullable
1664 public String getGitwebDescription() throws IOException {
1665 return null;
1666 }
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677 public void setGitwebDescription(@Nullable String description)
1678 throws IOException {
1679 throw new IOException(JGitText.get().unsupportedRepositoryDescription);
1680 }
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693 @Nullable
1694 public abstract ReflogReader getReflogReader(String refName)
1695 throws IOException;
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709 @Nullable
1710 public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
1711 return readCommitMsgFile(Constants.MERGE_MSG);
1712 }
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725 public void writeMergeCommitMsg(String msg) throws IOException {
1726 File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
1727 writeCommitMsg(mergeMsgFile, msg);
1728 }
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743 @Nullable
1744 public String readCommitEditMsg() throws IOException, NoWorkTreeException {
1745 return readCommitMsgFile(Constants.COMMIT_EDITMSG);
1746 }
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759 public void writeCommitEditMsg(String msg) throws IOException {
1760 File commiEditMsgFile = new File(gitDir, Constants.COMMIT_EDITMSG);
1761 writeCommitMsg(commiEditMsgFile, msg);
1762 }
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777 @Nullable
1778 public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
1779 if (isBare() || getDirectory() == null)
1780 throw new NoWorkTreeException();
1781
1782 byte[] raw = readGitDirectoryFile(Constants.MERGE_HEAD);
1783 if (raw == null)
1784 return null;
1785
1786 LinkedList<ObjectId> heads = new LinkedList<>();
1787 for (int p = 0; p < raw.length;) {
1788 heads.add(ObjectId.fromString(raw, p));
1789 p = RawParseUtils
1790 .nextLF(raw, p + Constants.OBJECT_ID_STRING_LENGTH);
1791 }
1792 return heads;
1793 }
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806 public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
1807 writeHeadsFile(heads, Constants.MERGE_HEAD);
1808 }
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821 @Nullable
1822 public ObjectId readCherryPickHead() throws IOException,
1823 NoWorkTreeException {
1824 if (isBare() || getDirectory() == null)
1825 throw new NoWorkTreeException();
1826
1827 byte[] raw = readGitDirectoryFile(Constants.CHERRY_PICK_HEAD);
1828 if (raw == null)
1829 return null;
1830
1831 return ObjectId.fromString(raw, 0);
1832 }
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845 @Nullable
1846 public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
1847 if (isBare() || getDirectory() == null)
1848 throw new NoWorkTreeException();
1849
1850 byte[] raw = readGitDirectoryFile(Constants.REVERT_HEAD);
1851 if (raw == null)
1852 return null;
1853 return ObjectId.fromString(raw, 0);
1854 }
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865 public void writeCherryPickHead(ObjectId head) throws IOException {
1866 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1867 : null;
1868 writeHeadsFile(heads, Constants.CHERRY_PICK_HEAD);
1869 }
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880 public void writeRevertHead(ObjectId head) throws IOException {
1881 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1882 : null;
1883 writeHeadsFile(heads, Constants.REVERT_HEAD);
1884 }
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894 public void writeOrigHead(ObjectId head) throws IOException {
1895 List<ObjectId> heads = head != null ? Collections.singletonList(head)
1896 : null;
1897 writeHeadsFile(heads, Constants.ORIG_HEAD);
1898 }
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911 @Nullable
1912 public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
1913 if (isBare() || getDirectory() == null)
1914 throw new NoWorkTreeException();
1915
1916 byte[] raw = readGitDirectoryFile(Constants.ORIG_HEAD);
1917 return raw != null ? ObjectId.fromString(raw, 0) : null;
1918 }
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932 @Nullable
1933 public String readSquashCommitMsg() throws IOException {
1934 return readCommitMsgFile(Constants.SQUASH_MSG);
1935 }
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948 public void writeSquashCommitMsg(String msg) throws IOException {
1949 File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG);
1950 writeCommitMsg(squashMsgFile, msg);
1951 }
1952
1953 @Nullable
1954 private String readCommitMsgFile(String msgFilename) throws IOException {
1955 if (isBare() || getDirectory() == null)
1956 throw new NoWorkTreeException();
1957
1958 File mergeMsgFile = new File(getDirectory(), msgFilename);
1959 try {
1960 return RawParseUtils.decode(IO.readFully(mergeMsgFile));
1961 } catch (FileNotFoundException e) {
1962 if (mergeMsgFile.exists()) {
1963 throw e;
1964 }
1965
1966 return null;
1967 }
1968 }
1969
1970 private void writeCommitMsg(File msgFile, String msg) throws IOException {
1971 if (msg != null) {
1972 try (FileOutputStream fos = new FileOutputStream(msgFile)) {
1973 fos.write(msg.getBytes(UTF_8));
1974 }
1975 } else {
1976 FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
1977 }
1978 }
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988 private byte[] readGitDirectoryFile(String filename) throws IOException {
1989 File file = new File(getDirectory(), filename);
1990 try {
1991 byte[] raw = IO.readFully(file);
1992 return raw.length > 0 ? raw : null;
1993 } catch (FileNotFoundException notFound) {
1994 if (file.exists()) {
1995 throw notFound;
1996 }
1997 return null;
1998 }
1999 }
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011 private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
2012 throws FileNotFoundException, IOException {
2013 File headsFile = new File(getDirectory(), filename);
2014 if (heads != null) {
2015 try (OutputStream bos = new BufferedOutputStream(
2016 new FileOutputStream(headsFile))) {
2017 for (ObjectId id : heads) {
2018 id.copyTo(bos);
2019 bos.write('\n');
2020 }
2021 }
2022 } else {
2023 FileUtils.delete(headsFile, FileUtils.SKIP_MISSING);
2024 }
2025 }
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041 @NonNull
2042 public List<RebaseTodoLine> readRebaseTodo(String path,
2043 boolean includeComments)
2044 throws IOException {
2045 return new RebaseTodoFile(this).readRebaseTodo(path, includeComments);
2046 }
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061 public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
2062 boolean append)
2063 throws IOException {
2064 new RebaseTodoFile(this).writeRebaseTodoFile(path, steps, append);
2065 }
2066
2067
2068
2069
2070
2071
2072
2073 @NonNull
2074 public Set<String> getRemoteNames() {
2075 return getConfig()
2076 .getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
2077 }
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094 public void autoGC(ProgressMonitor monitor) {
2095
2096 }
2097 }