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