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.net.URISyntaxException;
61 import java.text.MessageFormat;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.concurrent.atomic.AtomicInteger;
71 import java.util.concurrent.atomic.AtomicLong;
72 import java.util.regex.Pattern;
73
74 import org.eclipse.jgit.annotations.NonNull;
75 import org.eclipse.jgit.annotations.Nullable;
76 import org.eclipse.jgit.attributes.AttributesNodeProvider;
77 import org.eclipse.jgit.dircache.DirCache;
78 import org.eclipse.jgit.errors.AmbiguousObjectException;
79 import org.eclipse.jgit.errors.CorruptObjectException;
80 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
81 import org.eclipse.jgit.errors.MissingObjectException;
82 import org.eclipse.jgit.errors.NoWorkTreeException;
83 import org.eclipse.jgit.errors.RevisionSyntaxException;
84 import org.eclipse.jgit.events.IndexChangedEvent;
85 import org.eclipse.jgit.events.IndexChangedListener;
86 import org.eclipse.jgit.events.ListenerList;
87 import org.eclipse.jgit.events.RepositoryEvent;
88 import org.eclipse.jgit.internal.JGitText;
89 import org.eclipse.jgit.revwalk.RevBlob;
90 import org.eclipse.jgit.revwalk.RevCommit;
91 import org.eclipse.jgit.revwalk.RevObject;
92 import org.eclipse.jgit.revwalk.RevTree;
93 import org.eclipse.jgit.revwalk.RevWalk;
94 import org.eclipse.jgit.transport.RefSpec;
95 import org.eclipse.jgit.transport.RemoteConfig;
96 import org.eclipse.jgit.treewalk.TreeWalk;
97 import org.eclipse.jgit.util.FS;
98 import org.eclipse.jgit.util.FileUtils;
99 import org.eclipse.jgit.util.IO;
100 import org.eclipse.jgit.util.RawParseUtils;
101 import org.eclipse.jgit.util.SystemReader;
102 import org.slf4j.Logger;
103 import org.slf4j.LoggerFactory;
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 public abstract class Repository implements AutoCloseable {
120 private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
121 private static final ListenerLististenerList">ListenerList globalListeners = new ListenerList();
122
123
124
125
126
127
128
129 private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern
130 .compile(
131 "(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?",
132 Pattern.CASE_INSENSITIVE);
133
134
135
136
137
138
139 public static ListenerList getGlobalListenerList() {
140 return globalListeners;
141 }
142
143
144 final AtomicInteger useCnt = new AtomicInteger(1);
145
146 final AtomicLong closedAt = new AtomicLong();
147
148
149 private final File gitDir;
150
151
152 private final FS fs;
153
154 private final ListenerListml#ListenerList">ListenerList myListeners = new ListenerList();
155
156
157 private final File workTree;
158
159
160 private final File indexFile;
161
162
163
164
165
166
167
168 protected Repository(BaseRepositoryBuilder options) {
169 gitDir = options.getGitDir();
170 fs = options.getFS();
171 workTree = options.getWorkTree();
172 indexFile = options.getIndexFile();
173 }
174
175
176
177
178
179
180 @NonNull
181 public ListenerList getListenerList() {
182 return myListeners;
183 }
184
185
186
187
188
189
190
191
192
193
194 public void fireEvent(RepositoryEvent<?> event) {
195 event.setRepository(this);
196 myListeners.dispatch(event);
197 globalListeners.dispatch(event);
198 }
199
200
201
202
203
204
205
206
207
208
209 public void create() throws IOException {
210 create(false);
211 }
212
213
214
215
216
217
218
219
220
221
222
223 public abstract void create(boolean bare) throws IOException;
224
225
226
227
228
229
230
231
232
233
234
235
236
237 public File getDirectory() {
238 return gitDir;
239 }
240
241
242
243
244
245
246 @NonNull
247 public abstract ObjectDatabase getObjectDatabase();
248
249
250
251
252
253
254 @NonNull
255 public ObjectInserter newObjectInserter() {
256 return getObjectDatabase().newInserter();
257 }
258
259
260
261
262
263
264 @NonNull
265 public ObjectReader newObjectReader() {
266 return getObjectDatabase().newReader();
267 }
268
269
270
271
272
273
274 @NonNull
275 public abstract RefDatabase getRefDatabase();
276
277
278
279
280
281
282 @NonNull
283 public abstract StoredConfig getConfig();
284
285
286
287
288
289
290
291
292
293
294 @NonNull
295 public abstract AttributesNodeProvider createAttributesNodeProvider();
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 public FS getFS() {
311 return fs;
312 }
313
314
315
316
317
318
319
320
321
322
323 public boolean hasObject(AnyObjectId objectId) {
324 try {
325 return getObjectDatabase().has(objectId);
326 } catch (IOException e) {
327
328 return false;
329 }
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347 @NonNull
348 public ObjectLoader open(AnyObjectId objectId)
349 throws MissingObjectException, IOException {
350 return getObjectDatabase().open(objectId);
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
376 @NonNull
377 public ObjectLoader open(AnyObjectId objectId, int typeHint)
378 throws MissingObjectException, IncorrectObjectTypeException,
379 IOException {
380 return getObjectDatabase().open(objectId, typeHint);
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395 @NonNull
396 public RefUpdate updateRef(String ref) throws IOException {
397 return updateRef(ref, false);
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414 @NonNull
415 public RefUpdate updateRef(String ref, boolean detach) throws IOException {
416 return getRefDatabase().newUpdate(ref, detach);
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430 @NonNull
431 public RefRename renameRef(String fromRef, String toRef) throws IOException {
432 return getRefDatabase().newRename(fromRef, toRef);
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
485 @Nullable
486 public ObjectId resolve(String revstr)
487 throws AmbiguousObjectException, IncorrectObjectTypeException,
488 RevisionSyntaxException, IOException {
489 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(this)) {
490 Object resolved = resolve(rw, revstr);
491 if (resolved instanceof String) {
492 final Ref ref = findRef((String) resolved);
493 return ref != null ? ref.getLeaf().getObjectId() : null;
494 } else {
495 return (ObjectId) resolved;
496 }
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511
512 @Nullable
513 public String simplify(String revstr)
514 throws AmbiguousObjectException, IOException {
515 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(this)) {
516 Object resolved = resolve(rw, revstr);
517 if (resolved != null)
518 if (resolved instanceof String)
519 return (String) resolved;
520 else
521 return ((AnyObjectId) resolved).getName();
522 return null;
523 }
524 }
525
526 @Nullable
527 private Object resolve(RevWalk rw, String revstr)
528 throws IOException {
529 char[] revChars = revstr.toCharArray();
530 RevObject rev = null;
531 String name = null;
532 int done = 0;
533 for (int i = 0; i < revChars.length; ++i) {
534 switch (revChars[i]) {
535 case '^':
536 if (rev == null) {
537 if (name == null)
538 if (done == 0)
539 name = new String(revChars, done, i);
540 else {
541 done = i + 1;
542 break;
543 }
544 rev = parseSimple(rw, name);
545 name = null;
546 if (rev == null)
547 return null;
548 }
549 if (i + 1 < revChars.length) {
550 switch (revChars[i + 1]) {
551 case '0':
552 case '1':
553 case '2':
554 case '3':
555 case '4':
556 case '5':
557 case '6':
558 case '7':
559 case '8':
560 case '9':
561 int j;
562 rev = rw.parseCommit(rev);
563 for (j = i + 1; j < revChars.length; ++j) {
564 if (!Character.isDigit(revChars[j]))
565 break;
566 }
567 String parentnum = new String(revChars, i + 1, j - i
568 - 1);
569 int pnum;
570 try {
571 pnum = Integer.parseInt(parentnum);
572 } catch (NumberFormatException e) {
573 throw new RevisionSyntaxException(
574 JGitText.get().invalidCommitParentNumber,
575 revstr);
576 }
577 if (pnum != 0) {
578 RevCommit commit = (RevCommit) rev;
579 if (pnum > commit.getParentCount())
580 rev = null;
581 else
582 rev = commit.getParent(pnum - 1);
583 }
584 i = j - 1;
585 done = j;
586 break;
587 case '{':
588 int k;
589 String item = null;
590 for (k = i + 2; k < revChars.length; ++k) {
591 if (revChars[k] == '}') {
592 item = new String(revChars, i + 2, k - i - 2);
593 break;
594 }
595 }
596 i = k;
597 if (item != null)
598 if (item.equals("tree")) {
599 rev = rw.parseTree(rev);
600 } else if (item.equals("commit")) {
601 rev = rw.parseCommit(rev);
602 } else if (item.equals("blob")) {
603 rev = rw.peel(rev);
604 if (!(rev instanceof RevBlob))
605 throw new IncorrectObjectTypeException(rev,
606 Constants.TYPE_BLOB);
607 } else if (item.equals("")) {
608 rev = rw.peel(rev);
609 } else
610 throw new RevisionSyntaxException(revstr);
611 else
612 throw new RevisionSyntaxException(revstr);
613 done = k;
614 break;
615 default:
616 rev = rw.peel(rev);
617 if (rev instanceof RevCommit) {
618 RevCommit commit = ((RevCommit) rev);
619 if (commit.getParentCount() == 0)
620 rev = null;
621 else
622 rev = commit.getParent(0);
623 } else
624 throw new IncorrectObjectTypeException(rev,
625 Constants.TYPE_COMMIT);
626 }
627 } else {
628 rev = rw.peel(rev);
629 if (rev instanceof RevCommit) {
630 RevCommit commit = ((RevCommit) rev);
631 if (commit.getParentCount() == 0)
632 rev = null;
633 else
634 rev = commit.getParent(0);
635 } else
636 throw new IncorrectObjectTypeException(rev,
637 Constants.TYPE_COMMIT);
638 }
639 done = i + 1;
640 break;
641 case '~':
642 if (rev == null) {
643 if (name == null)
644 if (done == 0)
645 name = new String(revChars, done, i);
646 else {
647 done = i + 1;
648 break;
649 }
650 rev = parseSimple(rw, name);
651 name = null;
652 if (rev == null)
653 return null;
654 }
655 rev = rw.peel(rev);
656 if (!(rev instanceof RevCommit))
657 throw new IncorrectObjectTypeException(rev,
658 Constants.TYPE_COMMIT);
659 int l;
660 for (l = i + 1; l < revChars.length; ++l) {
661 if (!Character.isDigit(revChars[l]))
662 break;
663 }
664 int dist;
665 if (l - i > 1) {
666 String distnum = new String(revChars, i + 1, l - i - 1);
667 try {
668 dist = Integer.parseInt(distnum);
669 } catch (NumberFormatException e) {
670 throw new RevisionSyntaxException(
671 JGitText.get().invalidAncestryLength, revstr);
672 }
673 } else
674 dist = 1;
675 while (dist > 0) {
676 RevCommit commit = (RevCommit) rev;
677 if (commit.getParentCount() == 0) {
678 rev = null;
679 break;
680 }
681 commit = commit.getParent(0);
682 rw.parseHeaders(commit);
683 rev = commit;
684 --dist;
685 }
686 i = l - 1;
687 done = l;
688 break;
689 case '@':
690 if (rev != null)
691 throw new RevisionSyntaxException(revstr);
692 if (i + 1 == revChars.length)
693 continue;
694 if (i + 1 < revChars.length && revChars[i + 1] != '{')
695 continue;
696 int m;
697 String time = null;
698 for (m = i + 2; m < revChars.length; ++m) {
699 if (revChars[m] == '}') {
700 time = new String(revChars, i + 2, m - i - 2);
701 break;
702 }
703 }
704 if (time != null) {
705 if (time.equals("upstream")) {
706 if (name == null)
707 name = new String(revChars, done, i);
708 if (name.equals(""))
709
710
711 name = Constants.HEAD;
712 if (!Repository.isValidRefName("x/" + name))
713 throw new RevisionSyntaxException(MessageFormat
714 .format(JGitText.get().invalidRefName,
715 name),
716 revstr);
717 Ref ref = findRef(name);
718 name = null;
719 if (ref == null)
720 return null;
721 if (ref.isSymbolic())
722 ref = ref.getLeaf();
723 name = ref.getName();
724
725 RemoteConfig remoteConfig;
726 try {
727 remoteConfig = new RemoteConfig(getConfig(),
728 "origin");
729 } catch (URISyntaxException e) {
730 throw new RevisionSyntaxException(revstr);
731 }
732 String remoteBranchName = getConfig()
733 .getString(
734 ConfigConstants.CONFIG_BRANCH_SECTION,
735 Repository.shortenRefName(ref.getName()),
736 ConfigConstants.CONFIG_KEY_MERGE);
737 List<RefSpec> fetchRefSpecs = remoteConfig
738 .getFetchRefSpecs();
739 for (RefSpec refSpec : fetchRefSpecs) {
740 if (refSpec.matchSource(remoteBranchName)) {
741 RefSpec expandFromSource = refSpec
742 .expandFromSource(remoteBranchName);
743 name = expandFromSource.getDestination();
744 break;
745 }
746 }
747 if (name == null)
748 throw new RevisionSyntaxException(revstr);
749 } else if (time.matches("^-\\d+$")) {
750 if (name != null)
751 throw new RevisionSyntaxException(revstr);
752 else {
753 String previousCheckout = resolveReflogCheckout(-Integer
754 .parseInt(time));
755 if (ObjectId.isId(previousCheckout))
756 rev = parseSimple(rw, previousCheckout);
757 else
758 name = previousCheckout;
759 }
760 } else {
761 if (name == null)
762 name = new String(revChars, done, i);
763 if (name.equals(""))
764 name = Constants.HEAD;
765 if (!Repository.isValidRefName("x/" + name))
766 throw new RevisionSyntaxException(MessageFormat
767 .format(JGitText.get().invalidRefName,
768 name),
769 revstr);
770 Ref ref = findRef(name);
771 name = null;
772 if (ref == null)
773 return null;
774
775
776 if (ref.isSymbolic())
777 ref = ref.getLeaf();
778 rev = resolveReflog(rw, ref, time);
779 }
780 i = m;
781 } else
782 throw new RevisionSyntaxException(revstr);
783 break;
784 case ':': {
785 RevTree tree;
786 if (rev == null) {
787 if (name == null)
788 name = new String(revChars, done, i);
789 if (name.equals(""))
790 name = Constants.HEAD;
791 rev = parseSimple(rw, name);
792 name = null;
793 }
794 if (rev == null)
795 return null;
796 tree = rw.parseTree(rev);
797 if (i == revChars.length - 1)
798 return tree.copy();
799
800 TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(),
801 new String(revChars, i + 1, revChars.length - i - 1),
802 tree);
803 return tw != null ? tw.getObjectId(0) : null;
804 }
805 default:
806 if (rev != null)
807 throw new RevisionSyntaxException(revstr);
808 }
809 }
810 if (rev != null)
811 return rev.copy();
812 if (name != null)
813 return name;
814 if (done == revstr.length())
815 return null;
816 name = revstr.substring(done);
817 if (!Repository.isValidRefName("x/" + name))
818 throw new RevisionSyntaxException(
819 MessageFormat.format(JGitText.get().invalidRefName, name),
820 revstr);
821 if (findRef(name) != null)
822 return name;
823 return resolveSimple(name);
824 }
825
826 private static boolean isHex(char c) {
827 return ('0' <= c && c <= '9')
828 || ('a' <= c && c <= 'f')
829 || ('A' <= c && c <= 'F');
830 }
831
832 private static boolean isAllHex(String str, int ptr) {
833 while (ptr < str.length()) {
834 if (!isHex(str.charAt(ptr++)))
835 return false;
836 }
837 return true;
838 }
839
840 @Nullable
841 private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
842 ObjectId id = resolveSimple(revstr);
843 return id != null ? rw.parseAny(id) : null;
844 }
845
846 @Nullable
847 private ObjectId resolveSimple(String revstr) throws IOException {
848 if (ObjectId.isId(revstr))
849 return ObjectId.fromString(revstr);
850
851 if (Repository.isValidRefName("x/" + revstr)) {
852 Ref r = getRefDatabase().getRef(revstr);
853 if (r != null)
854 return r.getObjectId();
855 }
856
857 if (AbbreviatedObjectId.isId(revstr))
858 return resolveAbbreviation(revstr);
859
860 int dashg = revstr.indexOf("-g");
861 if ((dashg + 5) < revstr.length() && 0 <= dashg
862 && isHex(revstr.charAt(dashg + 2))
863 && isHex(revstr.charAt(dashg + 3))
864 && isAllHex(revstr, dashg + 4)) {
865
866 String s = revstr.substring(dashg + 2);
867 if (AbbreviatedObjectId.isId(s))
868 return resolveAbbreviation(s);
869 }
870
871 return null;
872 }
873
874 @Nullable
875 private String resolveReflogCheckout(int checkoutNo)
876 throws IOException {
877 ReflogReader reader = getReflogReader(Constants.HEAD);
878 if (reader == null) {
879 return null;
880 }
881 List<ReflogEntry> reflogEntries = reader.getReverseEntries();
882 for (ReflogEntry entry : reflogEntries) {
883 CheckoutEntry checkout = entry.parseCheckout();
884 if (checkout != null)
885 if (checkoutNo-- == 1)
886 return checkout.getFromBranch();
887 }
888 return null;
889 }
890
891 private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
892 throws IOException {
893 int number;
894 try {
895 number = Integer.parseInt(time);
896 } catch (NumberFormatException nfe) {
897 throw new RevisionSyntaxException(MessageFormat.format(
898 JGitText.get().invalidReflogRevision, time));
899 }
900 assert number >= 0;
901 ReflogReader reader = getReflogReader(ref.getName());
902 if (reader == null) {
903 throw new RevisionSyntaxException(
904 MessageFormat.format(JGitText.get().reflogEntryNotFound,
905 Integer.valueOf(number), ref.getName()));
906 }
907 ReflogEntry entry = reader.getReverseEntry(number);
908 if (entry == null)
909 throw new RevisionSyntaxException(MessageFormat.format(
910 JGitText.get().reflogEntryNotFound,
911 Integer.valueOf(number), ref.getName()));
912
913 return rw.parseCommit(entry.getNewId());
914 }
915
916 @Nullable
917 private ObjectId resolveAbbreviation(String revstr) throws IOException,
918 AmbiguousObjectException {
919 AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
920 try (ObjectReader reader = newObjectReader()) {
921 Collection<ObjectId> matches = reader.resolve(id);
922 if (matches.size() == 0)
923 return null;
924 else if (matches.size() == 1)
925 return matches.iterator().next();
926 else
927 throw new AmbiguousObjectException(id, matches);
928 }
929 }
930
931
932
933
934 public void incrementOpen() {
935 useCnt.incrementAndGet();
936 }
937
938
939
940
941
942
943 @Override
944 public void close() {
945 int newCount = useCnt.decrementAndGet();
946 if (newCount == 0) {
947 if (RepositoryCache.isCached(this)) {
948 closedAt.set(System.currentTimeMillis());
949 } else {
950 doClose();
951 }
952 } else if (newCount == -1) {
953
954
955 String message = MessageFormat.format(JGitText.get().corruptUseCnt,
956 toString());
957 if (LOG.isDebugEnabled()) {
958 LOG.debug(message, new IllegalStateException());
959 } else {
960 LOG.warn(message);
961 }
962 if (RepositoryCache.isCached(this)) {
963 closedAt.set(System.currentTimeMillis());
964 }
965 }
966 }
967
968
969
970
971
972
973 protected void doClose() {
974 getObjectDatabase().close();
975 getRefDatabase().close();
976 }
977
978
979 @Override
980 @NonNull
981 public String toString() {
982 String desc;
983 File directory = getDirectory();
984 if (directory != null)
985 desc = directory.getPath();
986 else
987 desc = getClass().getSimpleName() + "-"
988 + System.identityHashCode(this);
989 return "Repository[" + desc + "]";
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010 @Nullable
1011 public String getFullBranch() throws IOException {
1012 Ref head = exactRef(Constants.HEAD);
1013 if (head == null) {
1014 return null;
1015 }
1016 if (head.isSymbolic()) {
1017 return head.getTarget().getName();
1018 }
1019 ObjectId objectId = head.getObjectId();
1020 if (objectId != null) {
1021 return objectId.name();
1022 }
1023 return null;
1024 }
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038 @Nullable
1039 public String getBranch() throws IOException {
1040 String name = getFullBranch();
1041 if (name != null)
1042 return shortenRefName(name);
1043 return null;
1044 }
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056 @NonNull
1057 public Set<ObjectId> getAdditionalHaves() {
1058 return Collections.emptySet();
1059 }
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072 @Nullable
1073 public final Ref exactRef(String name) throws IOException {
1074 return getRefDatabase().exactRef(name);
1075 }
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088 @Nullable
1089 public final Ref findRef(String name) throws IOException {
1090 return getRefDatabase().getRef(name);
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100 @Deprecated
1101 @NonNull
1102 public Map<String, Ref> getAllRefs() {
1103 try {
1104 return getRefDatabase().getRefs(RefDatabase.ALL);
1105 } catch (IOException e) {
1106 return new HashMap<>();
1107 }
1108 }
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118 @Deprecated
1119 @NonNull
1120 public Map<String, Ref> getTags() {
1121 try {
1122 return getRefDatabase().getRefs(Constants.R_TAGS);
1123 } catch (IOException e) {
1124 return new HashMap<>();
1125 }
1126 }
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143 @Deprecated
1144 @NonNull
1145 public Ref" href="../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref peel(Ref ref) {
1146 try {
1147 return getRefDatabase().peel(ref);
1148 } catch (IOException e) {
1149
1150
1151
1152 return ref;
1153 }
1154 }
1155
1156
1157
1158
1159
1160
1161 @NonNull
1162 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
1163 Map<String, Ref> allRefs = getAllRefs();
1164 Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
1165 for (Ref ref : allRefs.values()) {
1166 ref = peel(ref);
1167 AnyObjectId target = ref.getPeeledObjectId();
1168 if (target == null)
1169 target = ref.getObjectId();
1170
1171 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
1172 if (oset != null) {
1173
1174 if (oset.size() == 1) {
1175
1176 oset = new HashSet<>(oset);
1177 }
1178 ret.put(target, oset);
1179 oset.add(ref);
1180 }
1181 }
1182 return ret;
1183 }
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194 @NonNull
1195 public File getIndexFile() throws NoWorkTreeException {
1196 if (isBare())
1197 throw new NoWorkTreeException();
1198 return indexFile;
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218 public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
1219 IOException, MissingObjectException {
1220 if (id instanceof RevCommit../../org/eclipse/jgit/revwalk/RevCommit.html#RevCommit">RevCommit && ((RevCommit) id).getRawBuffer() != null) {
1221 return (RevCommit) id;
1222 }
1223 try (RevWalkvWalk.html#RevWalk">RevWalk walk = new RevWalk(this)) {
1224 return walk.parseCommit(id);
1225 }
1226 }
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246 @NonNull
1247 public DirCache readDirCache() throws NoWorkTreeException,
1248 CorruptObjectException, IOException {
1249 return DirCache.read(this);
1250 }
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271 @NonNull
1272 public DirCache lockDirCache() throws NoWorkTreeException,
1273 CorruptObjectException, IOException {
1274
1275
1276 IndexChangedListener l = new IndexChangedListener() {
1277 @Override
1278 public void onIndexChanged(IndexChangedEvent event) {
1279 notifyIndexChanged(true);
1280 }
1281 };
1282 return DirCache.lock(this, l);
1283 }
1284
1285
1286
1287
1288
1289
1290 @NonNull
1291 public RepositoryState getRepositoryState() {
1292 if (isBare() || getDirectory() == null)
1293 return RepositoryState.BARE;
1294
1295
1296 if (new File(getWorkTree(), ".dotest").exists())
1297 return RepositoryState.REBASING;
1298 if (new File(getDirectory(), ".dotest-merge").exists())
1299 return RepositoryState.REBASING_INTERACTIVE;
1300
1301
1302 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1303 return RepositoryState.REBASING_REBASING;
1304 if (new File(getDirectory(),"rebase-apply/applying").exists())
1305 return RepositoryState.APPLY;
1306 if (new File(getDirectory(),"rebase-apply").exists())
1307 return RepositoryState.REBASING;
1308
1309 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1310 return RepositoryState.REBASING_INTERACTIVE;
1311 if (new File(getDirectory(),"rebase-merge").exists())
1312 return RepositoryState.REBASING_MERGE;
1313
1314
1315 if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
1316
1317 try {
1318 if (!readDirCache().hasUnmergedPaths()) {
1319
1320 return RepositoryState.MERGING_RESOLVED;
1321 }
1322 } catch (IOException e) {
1323
1324
1325
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
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
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 }