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 @NonNull
1099 public Map<String, Ref> getAllRefs() {
1100 try {
1101 return getRefDatabase().getRefs(RefDatabase.ALL);
1102 } catch (IOException e) {
1103 return new HashMap<>();
1104 }
1105 }
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115 @Deprecated
1116 @NonNull
1117 public Map<String, Ref> getTags() {
1118 try {
1119 return getRefDatabase().getRefs(Constants.R_TAGS);
1120 } catch (IOException e) {
1121 return new HashMap<>();
1122 }
1123 }
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140 @Deprecated
1141 @NonNull
1142 public Ref peel(Ref ref) {
1143 try {
1144 return getRefDatabase().peel(ref);
1145 } catch (IOException e) {
1146
1147
1148
1149 return ref;
1150 }
1151 }
1152
1153
1154
1155
1156
1157
1158 @NonNull
1159 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
1160 Map<String, Ref> allRefs = getAllRefs();
1161 Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
1162 for (Ref ref : allRefs.values()) {
1163 ref = peel(ref);
1164 AnyObjectId target = ref.getPeeledObjectId();
1165 if (target == null)
1166 target = ref.getObjectId();
1167
1168 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
1169 if (oset != null) {
1170
1171 if (oset.size() == 1) {
1172
1173 oset = new HashSet<>(oset);
1174 }
1175 ret.put(target, oset);
1176 oset.add(ref);
1177 }
1178 }
1179 return ret;
1180 }
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191 @NonNull
1192 public File getIndexFile() throws NoWorkTreeException {
1193 if (isBare())
1194 throw new NoWorkTreeException();
1195 return indexFile;
1196 }
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215 public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
1216 IOException, MissingObjectException {
1217 if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) {
1218 return (RevCommit) id;
1219 }
1220 try (RevWalk walk = new RevWalk(this)) {
1221 return walk.parseCommit(id);
1222 }
1223 }
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243 @NonNull
1244 public DirCache readDirCache() throws NoWorkTreeException,
1245 CorruptObjectException, IOException {
1246 return DirCache.read(this);
1247 }
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268 @NonNull
1269 public DirCache lockDirCache() throws NoWorkTreeException,
1270 CorruptObjectException, IOException {
1271
1272
1273 IndexChangedListener l = new IndexChangedListener() {
1274 @Override
1275 public void onIndexChanged(IndexChangedEvent event) {
1276 notifyIndexChanged(true);
1277 }
1278 };
1279 return DirCache.lock(this, l);
1280 }
1281
1282
1283
1284
1285
1286
1287 @NonNull
1288 public RepositoryState getRepositoryState() {
1289 if (isBare() || getDirectory() == null)
1290 return RepositoryState.BARE;
1291
1292
1293 if (new File(getWorkTree(), ".dotest").exists())
1294 return RepositoryState.REBASING;
1295 if (new File(getDirectory(), ".dotest-merge").exists())
1296 return RepositoryState.REBASING_INTERACTIVE;
1297
1298
1299 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1300 return RepositoryState.REBASING_REBASING;
1301 if (new File(getDirectory(),"rebase-apply/applying").exists())
1302 return RepositoryState.APPLY;
1303 if (new File(getDirectory(),"rebase-apply").exists())
1304 return RepositoryState.REBASING;
1305
1306 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1307 return RepositoryState.REBASING_INTERACTIVE;
1308 if (new File(getDirectory(),"rebase-merge").exists())
1309 return RepositoryState.REBASING_MERGE;
1310
1311
1312 if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
1313
1314 try {
1315 if (!readDirCache().hasUnmergedPaths()) {
1316
1317 return RepositoryState.MERGING_RESOLVED;
1318 }
1319 } catch (IOException e) {
1320
1321
1322
1323 }
1324 return RepositoryState.MERGING;
1325 }
1326
1327 if (new File(getDirectory(), "BISECT_LOG").exists())
1328 return RepositoryState.BISECTING;
1329
1330 if (new File(getDirectory(), Constants.CHERRY_PICK_HEAD).exists()) {
1331 try {
1332 if (!readDirCache().hasUnmergedPaths()) {
1333
1334 return RepositoryState.CHERRY_PICKING_RESOLVED;
1335 }
1336 } catch (IOException e) {
1337
1338 }
1339
1340 return RepositoryState.CHERRY_PICKING;
1341 }
1342
1343 if (new File(getDirectory(), Constants.REVERT_HEAD).exists()) {
1344 try {
1345 if (!readDirCache().hasUnmergedPaths()) {
1346
1347 return RepositoryState.REVERTING_RESOLVED;
1348 }
1349 } catch (IOException e) {
1350
1351 }
1352
1353 return RepositoryState.REVERTING;
1354 }
1355
1356 return RepositoryState.SAFE;
1357 }
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369 public static boolean isValidRefName(String refName) {
1370 final int len = refName.length();
1371 if (len == 0) {
1372 return false;
1373 }
1374 if (refName.endsWith(LOCK_SUFFIX)) {
1375 return false;
1376 }
1377
1378
1379
1380 try {
1381 SystemReader.getInstance().checkPath(refName);
1382 } catch (CorruptObjectException e) {
1383 return false;
1384 }
1385
1386 int components = 1;
1387 char p = '\0';
1388 for (int i = 0; i < len; i++) {
1389 final char c = refName.charAt(i);
1390 if (c <= ' ')
1391 return false;
1392 switch (c) {
1393 case '.':
1394 switch (p) {
1395 case '\0': case '/': case '.':
1396 return false;
1397 }
1398 if (i == len -1)
1399 return false;
1400 break;
1401 case '/':
1402 if (i == 0 || i == len - 1)
1403 return false;
1404 if (p == '/')
1405 return false;
1406 components++;
1407 break;
1408 case '{':
1409 if (p == '@')
1410 return false;
1411 break;
1412 case '~': case '^': case ':':
1413 case '?': case '[': case '*':
1414 case '\\':
1415 case '\u007F':
1416 return false;
1417 }
1418 p = c;
1419 }
1420 return components > 1;
1421 }
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444 public static String normalizeBranchName(String name) {
1445 if (name == null || name.isEmpty()) {
1446 return "";
1447 }
1448 String result = name.trim();
1449 String fullName = result.startsWith(Constants.R_HEADS) ? result
1450 : Constants.R_HEADS + result;
1451 if (isValidRefName(fullName)) {
1452 return result;
1453 }
1454
1455
1456 result = result.replaceAll("(?:\\h|\\v)+", "_");
1457 StringBuilder b = new StringBuilder();
1458 char p = '/';
1459 for (int i = 0, len = result.length(); i < len; i++) {
1460 char c = result.charAt(i);
1461 if (c < ' ' || c == 127) {
1462 continue;
1463 }
1464
1465 switch (c) {
1466 case '\\':
1467 case '^':
1468 case '~':
1469 case ':':
1470 case '?':
1471 case '*':
1472 case '[':
1473 case '@':
1474 case '<':
1475 case '>':
1476 case '|':
1477 case '"':
1478 c = '-';
1479 break;
1480 default:
1481 break;
1482 }
1483
1484
1485 switch (c) {
1486 case '/':
1487 if (p == '/') {
1488 continue;
1489 }
1490 p = '/';
1491 break;
1492 case '.':
1493 case '_':
1494 case '-':
1495 if (p == '/' || p == '-') {
1496 continue;
1497 }
1498 p = '-';
1499 break;
1500 default:
1501 p = c;
1502 break;
1503 }
1504 b.append(c);
1505 }
1506
1507 result = b.toString().replaceFirst("[/_.-]+$", "")
1508 .replaceAll("\\.lock($|/)", "_lock$1");
1509 return FORBIDDEN_BRANCH_NAME_COMPONENTS.matcher(result)
1510 .replaceAll("$1+$2$3");
1511 }
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523 @NonNull
1524 public static String stripWorkDir(File workDir, File file) {
1525 final String filePath = file.getPath();
1526 final String workDirPath = workDir.getPath();
1527
1528 if (filePath.length() <= workDirPath.length() ||
1529 filePath.charAt(workDirPath.length()) != File.separatorChar ||
1530 !filePath.startsWith(workDirPath)) {
1531 File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
1532 File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
1533 if (absWd == workDir && absFile == file)
1534 return "";
1535 return stripWorkDir(absWd, absFile);
1536 }
1537
1538 String relName = filePath.substring(workDirPath.length() + 1);
1539 if (File.separatorChar != '/')
1540 relName = relName.replace(File.separatorChar, '/');
1541 return relName;
1542 }
1543
1544
1545
1546
1547
1548
1549 public boolean isBare() {
1550 return workTree == null;
1551 }
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563 @NonNull
1564 public File getWorkTree() throws NoWorkTreeException {
1565 if (isBare())
1566 throw new NoWorkTreeException();
1567 return workTree;
1568 }
1569
1570
1571
1572
1573
1574
1575
1576 public abstract void scanForRepoChanges() throws IOException;
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586 public abstract void notifyIndexChanged(boolean internal);
1587
1588
1589
1590
1591
1592
1593
1594
1595 @NonNull
1596 public static String shortenRefName(String refName) {
1597 if (refName.startsWith(Constants.R_HEADS))
1598 return refName.substring(Constants.R_HEADS.length());
1599 if (refName.startsWith(Constants.R_TAGS))
1600 return refName.substring(Constants.R_TAGS.length());
1601 if (refName.startsWith(Constants.R_REMOTES))
1602 return refName.substring(Constants.R_REMOTES.length());
1603 return refName;
1604 }
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617 @Nullable
1618 public String shortenRemoteBranchName(String refName) {
1619 for (String remote : getRemoteNames()) {
1620 String remotePrefix = Constants.R_REMOTES + remote + "/";
1621 if (refName.startsWith(remotePrefix))
1622 return refName.substring(remotePrefix.length());
1623 }
1624 return null;
1625 }
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638 @Nullable
1639 public String getRemoteName(String refName) {
1640 for (String remote : getRemoteNames()) {
1641 String remotePrefix = Constants.R_REMOTES + remote + "/";
1642 if (refName.startsWith(remotePrefix))
1643 return remote;
1644 }
1645 return null;
1646 }
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656 @Nullable
1657 public String getGitwebDescription() throws IOException {
1658 return null;
1659 }
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670 public void setGitwebDescription(@Nullable String description)
1671 throws IOException {
1672 throw new IOException(JGitText.get().unsupportedRepositoryDescription);
1673 }
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686 @Nullable
1687 public abstract ReflogReader getReflogReader(String refName)
1688 throws IOException;
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702 @Nullable
1703 public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
1704 return readCommitMsgFile(Constants.MERGE_MSG);
1705 }
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718 public void writeMergeCommitMsg(String msg) throws IOException {
1719 File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
1720 writeCommitMsg(mergeMsgFile, msg);
1721 }
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736 @Nullable
1737 public String readCommitEditMsg() throws IOException, NoWorkTreeException {
1738 return readCommitMsgFile(Constants.COMMIT_EDITMSG);
1739 }
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752 public void writeCommitEditMsg(String msg) throws IOException {
1753 File commiEditMsgFile = new File(gitDir, Constants.COMMIT_EDITMSG);
1754 writeCommitMsg(commiEditMsgFile, msg);
1755 }
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770 @Nullable
1771 public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
1772 if (isBare() || getDirectory() == null)
1773 throw new NoWorkTreeException();
1774
1775 byte[] raw = readGitDirectoryFile(Constants.MERGE_HEAD);
1776 if (raw == null)
1777 return null;
1778
1779 LinkedList<ObjectId> heads = new LinkedList<>();
1780 for (int p = 0; p < raw.length;) {
1781 heads.add(ObjectId.fromString(raw, p));
1782 p = RawParseUtils
1783 .nextLF(raw, p + Constants.OBJECT_ID_STRING_LENGTH);
1784 }
1785 return heads;
1786 }
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799 public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
1800 writeHeadsFile(heads, Constants.MERGE_HEAD);
1801 }
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814 @Nullable
1815 public ObjectId readCherryPickHead() throws IOException,
1816 NoWorkTreeException {
1817 if (isBare() || getDirectory() == null)
1818 throw new NoWorkTreeException();
1819
1820 byte[] raw = readGitDirectoryFile(Constants.CHERRY_PICK_HEAD);
1821 if (raw == null)
1822 return null;
1823
1824 return ObjectId.fromString(raw, 0);
1825 }
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838 @Nullable
1839 public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
1840 if (isBare() || getDirectory() == null)
1841 throw new NoWorkTreeException();
1842
1843 byte[] raw = readGitDirectoryFile(Constants.REVERT_HEAD);
1844 if (raw == null)
1845 return null;
1846 return ObjectId.fromString(raw, 0);
1847 }
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858 public void writeCherryPickHead(ObjectId head) throws IOException {
1859 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1860 : null;
1861 writeHeadsFile(heads, Constants.CHERRY_PICK_HEAD);
1862 }
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873 public void writeRevertHead(ObjectId head) throws IOException {
1874 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1875 : null;
1876 writeHeadsFile(heads, Constants.REVERT_HEAD);
1877 }
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887 public void writeOrigHead(ObjectId head) throws IOException {
1888 List<ObjectId> heads = head != null ? Collections.singletonList(head)
1889 : null;
1890 writeHeadsFile(heads, Constants.ORIG_HEAD);
1891 }
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904 @Nullable
1905 public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
1906 if (isBare() || getDirectory() == null)
1907 throw new NoWorkTreeException();
1908
1909 byte[] raw = readGitDirectoryFile(Constants.ORIG_HEAD);
1910 return raw != null ? ObjectId.fromString(raw, 0) : null;
1911 }
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925 @Nullable
1926 public String readSquashCommitMsg() throws IOException {
1927 return readCommitMsgFile(Constants.SQUASH_MSG);
1928 }
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941 public void writeSquashCommitMsg(String msg) throws IOException {
1942 File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG);
1943 writeCommitMsg(squashMsgFile, msg);
1944 }
1945
1946 @Nullable
1947 private String readCommitMsgFile(String msgFilename) throws IOException {
1948 if (isBare() || getDirectory() == null)
1949 throw new NoWorkTreeException();
1950
1951 File mergeMsgFile = new File(getDirectory(), msgFilename);
1952 try {
1953 return RawParseUtils.decode(IO.readFully(mergeMsgFile));
1954 } catch (FileNotFoundException e) {
1955 if (mergeMsgFile.exists()) {
1956 throw e;
1957 }
1958
1959 return null;
1960 }
1961 }
1962
1963 private void writeCommitMsg(File msgFile, String msg) throws IOException {
1964 if (msg != null) {
1965 try (FileOutputStream fos = new FileOutputStream(msgFile)) {
1966 fos.write(msg.getBytes(Constants.CHARACTER_ENCODING));
1967 }
1968 } else {
1969 FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
1970 }
1971 }
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981 @Nullable
1982 private byte[] readGitDirectoryFile(String filename) throws IOException {
1983 File file = new File(getDirectory(), filename);
1984 try {
1985 byte[] raw = IO.readFully(file);
1986 return raw.length > 0 ? raw : null;
1987 } catch (FileNotFoundException notFound) {
1988 if (file.exists()) {
1989 throw notFound;
1990 }
1991 return null;
1992 }
1993 }
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005 private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
2006 throws FileNotFoundException, IOException {
2007 File headsFile = new File(getDirectory(), filename);
2008 if (heads != null) {
2009 try (OutputStream bos = new BufferedOutputStream(
2010 new FileOutputStream(headsFile))) {
2011 for (ObjectId id : heads) {
2012 id.copyTo(bos);
2013 bos.write('\n');
2014 }
2015 }
2016 } else {
2017 FileUtils.delete(headsFile, FileUtils.SKIP_MISSING);
2018 }
2019 }
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035 @NonNull
2036 public List<RebaseTodoLine> readRebaseTodo(String path,
2037 boolean includeComments)
2038 throws IOException {
2039 return new RebaseTodoFile(this).readRebaseTodo(path, includeComments);
2040 }
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055 public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
2056 boolean append)
2057 throws IOException {
2058 new RebaseTodoFile(this).writeRebaseTodoFile(path, steps, append);
2059 }
2060
2061
2062
2063
2064
2065
2066
2067 @NonNull
2068 public Set<String> getRemoteNames() {
2069 return getConfig()
2070 .getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
2071 }
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088 public void autoGC(ProgressMonitor monitor) {
2089
2090 }
2091 }