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