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