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