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