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