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