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 package org.eclipse.jgit.internal.storage.file;
48
49 import static org.eclipse.jgit.lib.Constants.CHARSET;
50 import static org.eclipse.jgit.lib.Constants.HEAD;
51 import static org.eclipse.jgit.lib.Constants.LOGS;
52 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
53 import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
54 import static org.eclipse.jgit.lib.Constants.R_HEADS;
55 import static org.eclipse.jgit.lib.Constants.R_REFS;
56 import static org.eclipse.jgit.lib.Constants.R_TAGS;
57 import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
58 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
59 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
60
61 import java.io.BufferedReader;
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.FileNotFoundException;
65 import java.io.IOException;
66 import java.io.InputStreamReader;
67 import java.io.InterruptedIOException;
68 import java.nio.file.Files;
69 import java.security.DigestInputStream;
70 import java.security.MessageDigest;
71 import java.text.MessageFormat;
72 import java.util.Arrays;
73 import java.util.Collection;
74 import java.util.Collections;
75 import java.util.LinkedList;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.concurrent.atomic.AtomicInteger;
79 import java.util.concurrent.atomic.AtomicReference;
80 import java.util.concurrent.locks.ReentrantLock;
81
82 import org.eclipse.jgit.annotations.NonNull;
83 import org.eclipse.jgit.annotations.Nullable;
84 import org.eclipse.jgit.errors.InvalidObjectIdException;
85 import org.eclipse.jgit.errors.LockFailedException;
86 import org.eclipse.jgit.errors.MissingObjectException;
87 import org.eclipse.jgit.errors.ObjectWritingException;
88 import org.eclipse.jgit.events.RefsChangedEvent;
89 import org.eclipse.jgit.internal.JGitText;
90 import org.eclipse.jgit.lib.ConfigConstants;
91 import org.eclipse.jgit.lib.Constants;
92 import org.eclipse.jgit.lib.ObjectId;
93 import org.eclipse.jgit.lib.ObjectIdRef;
94 import org.eclipse.jgit.lib.Ref;
95 import org.eclipse.jgit.lib.RefComparator;
96 import org.eclipse.jgit.lib.RefDatabase;
97 import org.eclipse.jgit.lib.RefUpdate;
98 import org.eclipse.jgit.lib.RefWriter;
99 import org.eclipse.jgit.lib.Repository;
100 import org.eclipse.jgit.lib.SymbolicRef;
101 import org.eclipse.jgit.revwalk.RevObject;
102 import org.eclipse.jgit.revwalk.RevTag;
103 import org.eclipse.jgit.revwalk.RevWalk;
104 import org.eclipse.jgit.util.FS;
105 import org.eclipse.jgit.util.FileUtils;
106 import org.eclipse.jgit.util.IO;
107 import org.eclipse.jgit.util.RawParseUtils;
108 import org.eclipse.jgit.util.RefList;
109 import org.eclipse.jgit.util.RefMap;
110 import org.slf4j.Logger;
111 import org.slf4j.LoggerFactory;
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 public class RefDirectory extends RefDatabase {
129 private final static Logger LOG = LoggerFactory
130 .getLogger(RefDirectory.class);
131
132
133 public static final String SYMREF = "ref: ";
134
135
136 public static final String PACKED_REFS_HEADER = "# pack-refs with:";
137
138
139 public static final String PACKED_REFS_PEELED = " peeled";
140
141
142 private static final String[] additionalRefsNames = new String[] {
143 Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
144 Constants.CHERRY_PICK_HEAD };
145
146 @SuppressWarnings("boxing")
147 private static final List<Integer> RETRY_SLEEP_MS =
148 Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
149
150 private final FileRepository parent;
151
152 private final File gitDir;
153
154 final File refsDir;
155
156 final File packedRefsFile;
157
158 final File logsDir;
159
160 final File logsRefsDir;
161
162
163
164
165
166
167
168
169
170 private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();
171
172
173 final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
190
191
192
193
194
195
196
197 private final AtomicInteger modCnt = new AtomicInteger();
198
199
200
201
202
203
204
205 private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
206
207 private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
208
209 RefDirectory(final FileRepository db) {
210 final FS fs = db.getFS();
211 parent = db;
212 gitDir = db.getDirectory();
213 refsDir = fs.resolve(gitDir, R_REFS);
214 logsDir = fs.resolve(gitDir, LOGS);
215 logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
216 packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
217
218 looseRefs.set(RefList.<LooseRef> emptyList());
219 packedRefs.set(NO_PACKED_REFS);
220 }
221
222 Repository getRepository() {
223 return parent;
224 }
225
226 ReflogWriter newLogWriter(boolean force) {
227 return new ReflogWriter(this, force);
228 }
229
230
231
232
233
234
235
236
237
238 public File logFor(String name) {
239 if (name.startsWith(R_REFS)) {
240 name = name.substring(R_REFS.length());
241 return new File(logsRefsDir, name);
242 }
243 return new File(logsDir, name);
244 }
245
246 @Override
247 public void create() throws IOException {
248 FileUtils.mkdir(refsDir);
249 FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
250 FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
251 newLogWriter(false).create();
252 }
253
254 @Override
255 public void close() {
256 clearReferences();
257 }
258
259 private void clearReferences() {
260 looseRefs.set(RefList.<LooseRef> emptyList());
261 packedRefs.set(NO_PACKED_REFS);
262 }
263
264 @Override
265 public void refresh() {
266 super.refresh();
267 clearReferences();
268 }
269
270 @Override
271 public boolean isNameConflicting(String name) throws IOException {
272 RefList<Ref> packed = getPackedRefs();
273 RefList<LooseRef> loose = getLooseRefs();
274
275
276 int lastSlash = name.lastIndexOf('/');
277 while (0 < lastSlash) {
278 String needle = name.substring(0, lastSlash);
279 if (loose.contains(needle) || packed.contains(needle))
280 return true;
281 lastSlash = name.lastIndexOf('/', lastSlash - 1);
282 }
283
284
285 String prefix = name + '/';
286 int idx;
287
288 idx = -(packed.find(prefix) + 1);
289 if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
290 return true;
291
292 idx = -(loose.find(prefix) + 1);
293 if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
294 return true;
295
296 return false;
297 }
298
299 private RefList<LooseRef> getLooseRefs() {
300 final RefList<LooseRef> oldLoose = looseRefs.get();
301
302 LooseScanner scan = new LooseScanner(oldLoose);
303 scan.scan(ALL);
304
305 RefList<LooseRef> loose;
306 if (scan.newLoose != null) {
307 loose = scan.newLoose.toRefList();
308 if (looseRefs.compareAndSet(oldLoose, loose))
309 modCnt.incrementAndGet();
310 } else
311 loose = oldLoose;
312 return loose;
313 }
314
315 @Override
316 public Ref exactRef(String name) throws IOException {
317 RefList<Ref> packed = getPackedRefs();
318 Ref ref;
319 try {
320 ref = readRef(name, packed);
321 if (ref != null) {
322 ref = resolve(ref, 0, null, null, packed);
323 }
324 } catch (IOException e) {
325 if (name.contains("/")
326 || !(e.getCause() instanceof InvalidObjectIdException)) {
327 throw e;
328 }
329
330
331
332
333 ref = null;
334 }
335 fireRefsChanged();
336 return ref;
337 }
338
339 @Override
340 public Ref getRef(final String needle) throws IOException {
341 final RefList<Ref> packed = getPackedRefs();
342 Ref ref = null;
343 for (String prefix : SEARCH_PATH) {
344 try {
345 ref = readRef(prefix + needle, packed);
346 if (ref != null) {
347 ref = resolve(ref, 0, null, null, packed);
348 }
349 if (ref != null) {
350 break;
351 }
352 } catch (IOException e) {
353 if (!(!needle.contains("/") && "".equals(prefix) && e
354 .getCause() instanceof InvalidObjectIdException)) {
355 throw e;
356 }
357 }
358 }
359 fireRefsChanged();
360 return ref;
361 }
362
363 @Override
364 public Map<String, Ref> getRefs(String prefix) throws IOException {
365 final RefList<LooseRef> oldLoose = looseRefs.get();
366 LooseScanner scan = new LooseScanner(oldLoose);
367 scan.scan(prefix);
368 final RefList<Ref> packed = getPackedRefs();
369
370 RefList<LooseRef> loose;
371 if (scan.newLoose != null) {
372 scan.newLoose.sort();
373 loose = scan.newLoose.toRefList();
374 if (looseRefs.compareAndSet(oldLoose, loose))
375 modCnt.incrementAndGet();
376 } else
377 loose = oldLoose;
378 fireRefsChanged();
379
380 RefList.Builder<Ref> symbolic = scan.symbolic;
381 for (int idx = 0; idx < symbolic.size();) {
382 final Ref symbolicRef = symbolic.get(idx);
383 final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
384 if (resolvedRef != null && resolvedRef.getObjectId() != null) {
385 symbolic.set(idx, resolvedRef);
386 idx++;
387 } else {
388
389
390
391 symbolic.remove(idx);
392 final int toRemove = loose.find(symbolicRef.getName());
393 if (0 <= toRemove)
394 loose = loose.remove(toRemove);
395 }
396 }
397 symbolic.sort();
398
399 return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
400 }
401
402 @Override
403 public List<Ref> getAdditionalRefs() throws IOException {
404 List<Ref> ret = new LinkedList<>();
405 for (String name : additionalRefsNames) {
406 Ref r = getRef(name);
407 if (r != null)
408 ret.add(r);
409 }
410 return ret;
411 }
412
413 @SuppressWarnings("unchecked")
414 private RefList<Ref> upcast(RefList<? extends Ref> loose) {
415 return (RefList<Ref>) loose;
416 }
417
418 private class LooseScanner {
419 private final RefList<LooseRef> curLoose;
420
421 private int curIdx;
422
423 final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);
424
425 RefList.Builder<LooseRef> newLoose;
426
427 LooseScanner(final RefList<LooseRef> curLoose) {
428 this.curLoose = curLoose;
429 }
430
431 void scan(String prefix) {
432 if (ALL.equals(prefix)) {
433 scanOne(HEAD);
434 scanTree(R_REFS, refsDir);
435
436
437 if (newLoose == null && curIdx < curLoose.size())
438 newLoose = curLoose.copy(curIdx);
439
440 } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) {
441 curIdx = -(curLoose.find(prefix) + 1);
442 File dir = new File(refsDir, prefix.substring(R_REFS.length()));
443 scanTree(prefix, dir);
444
445
446
447 while (curIdx < curLoose.size()) {
448 if (!curLoose.get(curIdx).getName().startsWith(prefix))
449 break;
450 if (newLoose == null)
451 newLoose = curLoose.copy(curIdx);
452 curIdx++;
453 }
454
455
456
457 if (newLoose != null) {
458 while (curIdx < curLoose.size())
459 newLoose.add(curLoose.get(curIdx++));
460 }
461 }
462 }
463
464 private boolean scanTree(String prefix, File dir) {
465 final String[] entries = dir.list(LockFile.FILTER);
466 if (entries == null)
467 return false;
468 if (0 < entries.length) {
469 for (int i = 0; i < entries.length; ++i) {
470 String e = entries[i];
471 File f = new File(dir, e);
472 if (f.isDirectory())
473 entries[i] += '/';
474 }
475 Arrays.sort(entries);
476 for (String name : entries) {
477 if (name.charAt(name.length() - 1) == '/')
478 scanTree(prefix + name, new File(dir, name));
479 else
480 scanOne(prefix + name);
481 }
482 }
483 return true;
484 }
485
486 private void scanOne(String name) {
487 LooseRef cur;
488
489 if (curIdx < curLoose.size()) {
490 do {
491 cur = curLoose.get(curIdx);
492 int cmp = RefComparator.compareTo(cur, name);
493 if (cmp < 0) {
494
495
496 if (newLoose == null)
497 newLoose = curLoose.copy(curIdx);
498 curIdx++;
499 cur = null;
500 continue;
501 }
502
503 if (cmp > 0)
504 cur = null;
505 break;
506 } while (curIdx < curLoose.size());
507 } else
508 cur = null;
509
510 LooseRef n;
511 try {
512 n = scanRef(cur, name);
513 } catch (IOException notValid) {
514 n = null;
515 }
516
517 if (n != null) {
518 if (cur != n && newLoose == null)
519 newLoose = curLoose.copy(curIdx);
520 if (newLoose != null)
521 newLoose.add(n);
522 if (n.isSymbolic())
523 symbolic.add(n);
524 } else if (cur != null) {
525
526
527 if (newLoose == null)
528 newLoose = curLoose.copy(curIdx);
529 }
530
531 if (cur != null)
532 curIdx++;
533 }
534 }
535
536 @Override
537 public Ref peel(final Ref ref) throws IOException {
538 final Ref leaf = ref.getLeaf();
539 if (leaf.isPeeled() || leaf.getObjectId() == null)
540 return ref;
541
542 ObjectIdRef newLeaf = doPeel(leaf);
543
544
545
546 if (leaf.getStorage().isLoose()) {
547 RefList<LooseRef> curList = looseRefs.get();
548 int idx = curList.find(leaf.getName());
549 if (0 <= idx && curList.get(idx) == leaf) {
550 LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
551 RefList<LooseRef> newList = curList.set(idx, asPeeled);
552 looseRefs.compareAndSet(curList, newList);
553 }
554 }
555
556 return recreate(ref, newLeaf);
557 }
558
559 private ObjectIdRef doPeel(final Ref leaf) throws MissingObjectException,
560 IOException {
561 try (RevWalk rw = new RevWalk(getRepository())) {
562 RevObject obj = rw.parseAny(leaf.getObjectId());
563 if (obj instanceof RevTag) {
564 return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
565 .getName(), leaf.getObjectId(), rw.peel(obj).copy());
566 } else {
567 return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
568 .getName(), leaf.getObjectId());
569 }
570 }
571 }
572
573 private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
574 if (old.isSymbolic()) {
575 Ref dst = recreate(old.getTarget(), leaf);
576 return new SymbolicRef(old.getName(), dst);
577 }
578 return leaf;
579 }
580
581 void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
582 String target) {
583 putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
584 fireRefsChanged();
585 }
586
587 @Override
588 public RefDirectoryUpdate newUpdate(String name, boolean detach)
589 throws IOException {
590 boolean detachingSymbolicRef = false;
591 final RefList<Ref> packed = getPackedRefs();
592 Ref ref = readRef(name, packed);
593 if (ref != null)
594 ref = resolve(ref, 0, null, null, packed);
595 if (ref == null)
596 ref = new ObjectIdRef.Unpeeled(NEW, name, null);
597 else {
598 detachingSymbolicRef = detach && ref.isSymbolic();
599 }
600 RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
601 if (detachingSymbolicRef)
602 refDirUpdate.setDetachingSymbolicRef();
603 return refDirUpdate;
604 }
605
606 @Override
607 public RefDirectoryRename newRename(String fromName, String toName)
608 throws IOException {
609 RefDirectoryUpdate from = newUpdate(fromName, false);
610 RefDirectoryUpdate to = newUpdate(toName, false);
611 return new RefDirectoryRename(from, to);
612 }
613
614 @Override
615 public PackedBatchRefUpdate newBatchUpdate() {
616 return new PackedBatchRefUpdate(this);
617 }
618
619 @Override
620 public boolean performsAtomicTransactions() {
621 return true;
622 }
623
624 void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
625 final ObjectId target = update.getNewObjectId().copy();
626 final Ref leaf = update.getRef().getLeaf();
627 putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
628 }
629
630 private void putLooseRef(LooseRef ref) {
631 RefList<LooseRef> cList, nList;
632 do {
633 cList = looseRefs.get();
634 nList = cList.put(ref);
635 } while (!looseRefs.compareAndSet(cList, nList));
636 modCnt.incrementAndGet();
637 fireRefsChanged();
638 }
639
640 void delete(RefDirectoryUpdate update) throws IOException {
641 Ref dst = update.getRef();
642 if (!update.isDetachingSymbolicRef()) {
643 dst = dst.getLeaf();
644 }
645 String name = dst.getName();
646
647
648
649
650 final PackedRefList packed = getPackedRefs();
651 if (packed.contains(name)) {
652 inProcessPackedRefsLock.lock();
653 try {
654 LockFile lck = lockPackedRefsOrThrow();
655 try {
656 PackedRefList cur = readPackedRefs();
657 int idx = cur.find(name);
658 if (0 <= idx) {
659 commitPackedRefs(lck, cur.remove(idx), packed, true);
660 }
661 } finally {
662 lck.unlock();
663 }
664 } finally {
665 inProcessPackedRefsLock.unlock();
666 }
667 }
668
669 RefList<LooseRef> curLoose, newLoose;
670 do {
671 curLoose = looseRefs.get();
672 int idx = curLoose.find(name);
673 if (idx < 0)
674 break;
675 newLoose = curLoose.remove(idx);
676 } while (!looseRefs.compareAndSet(curLoose, newLoose));
677
678 int levels = levelsIn(name) - 2;
679 delete(logFor(name), levels);
680 if (dst.getStorage().isLoose()) {
681 update.unlock();
682 delete(fileFor(name), levels);
683 }
684
685 modCnt.incrementAndGet();
686 fireRefsChanged();
687 }
688
689
690
691
692
693
694
695
696
697
698
699
700 public void pack(List<String> refs) throws IOException {
701 pack(refs, Collections.emptyMap());
702 }
703
704 PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
705 return pack(heldLocks.keySet(), heldLocks);
706 }
707
708 private PackedRefList pack(Collection<String> refs,
709 Map<String, LockFile> heldLocks) throws IOException {
710 for (LockFile ol : heldLocks.values()) {
711 ol.requireLock();
712 }
713 if (refs.size() == 0) {
714 return null;
715 }
716 FS fs = parent.getFS();
717
718
719 inProcessPackedRefsLock.lock();
720 try {
721 LockFile lck = lockPackedRefsOrThrow();
722 try {
723 final PackedRefList packed = getPackedRefs();
724 RefList<Ref> cur = readPackedRefs();
725
726
727 boolean dirty = false;
728 for (String refName : refs) {
729 Ref oldRef = readRef(refName, cur);
730 if (oldRef == null) {
731 continue;
732 }
733 if (oldRef.isSymbolic()) {
734 continue;
735 }
736
737 Ref newRef = peeledPackedRef(oldRef);
738 if (newRef == oldRef) {
739
740
741
742 continue;
743 }
744
745 dirty = true;
746 int idx = cur.find(refName);
747 if (idx >= 0) {
748 cur = cur.set(idx, newRef);
749 } else {
750 cur = cur.add(idx, newRef);
751 }
752 }
753 if (!dirty) {
754
755 return packed;
756 }
757
758
759 PackedRefList result = commitPackedRefs(lck, cur, packed,
760 false);
761
762
763 for (String refName : refs) {
764
765 File refFile = fileFor(refName);
766 if (!fs.exists(refFile)) {
767 continue;
768 }
769
770 LockFile rLck = heldLocks.get(refName);
771 boolean shouldUnlock;
772 if (rLck == null) {
773 rLck = new LockFile(refFile);
774 if (!rLck.lock()) {
775 continue;
776 }
777 shouldUnlock = true;
778 } else {
779 shouldUnlock = false;
780 }
781
782 try {
783 LooseRef currentLooseRef = scanRef(null, refName);
784 if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
785 continue;
786 }
787 Ref packedRef = cur.get(refName);
788 ObjectId clr_oid = currentLooseRef.getObjectId();
789 if (clr_oid != null
790 && clr_oid.equals(packedRef.getObjectId())) {
791 RefList<LooseRef> curLoose, newLoose;
792 do {
793 curLoose = looseRefs.get();
794 int idx = curLoose.find(refName);
795 if (idx < 0) {
796 break;
797 }
798 newLoose = curLoose.remove(idx);
799 } while (!looseRefs.compareAndSet(curLoose, newLoose));
800 int levels = levelsIn(refName) - 2;
801 delete(refFile, levels, rLck);
802 }
803 } finally {
804 if (shouldUnlock) {
805 rLck.unlock();
806 }
807 }
808 }
809
810
811 return result;
812 } finally {
813 lck.unlock();
814 }
815 } finally {
816 inProcessPackedRefsLock.unlock();
817 }
818 }
819
820 @Nullable
821 LockFile lockPackedRefs() throws IOException {
822 LockFile lck = new LockFile(packedRefsFile);
823 for (int ms : getRetrySleepMs()) {
824 sleep(ms);
825 if (lck.lock()) {
826 return lck;
827 }
828 }
829 return null;
830 }
831
832 private LockFile lockPackedRefsOrThrow() throws IOException {
833 LockFile lck = lockPackedRefs();
834 if (lck == null) {
835 throw new LockFailedException(packedRefsFile);
836 }
837 return lck;
838 }
839
840
841
842
843
844
845
846
847
848
849
850 private Ref peeledPackedRef(Ref f)
851 throws MissingObjectException, IOException {
852 if (f.getStorage().isPacked() && f.isPeeled()) {
853 return f;
854 }
855 if (!f.isPeeled()) {
856 f = peel(f);
857 }
858 ObjectId peeledObjectId = f.getPeeledObjectId();
859 if (peeledObjectId != null) {
860 return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
861 f.getObjectId(), peeledObjectId);
862 } else {
863 return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
864 f.getObjectId());
865 }
866 }
867
868 void log(boolean force, RefUpdate update, String msg, boolean deref)
869 throws IOException {
870 newLogWriter(force).log(update, msg, deref);
871 }
872
873 private Ref resolve(final Ref ref, int depth, String prefix,
874 RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
875 if (ref.isSymbolic()) {
876 Ref dst = ref.getTarget();
877
878 if (MAX_SYMBOLIC_REF_DEPTH <= depth)
879 return null;
880
881
882
883 if (loose != null && dst.getName().startsWith(prefix)) {
884 int idx;
885 if (0 <= (idx = loose.find(dst.getName())))
886 dst = loose.get(idx);
887 else if (0 <= (idx = packed.find(dst.getName())))
888 dst = packed.get(idx);
889 else
890 return ref;
891 } else {
892 dst = readRef(dst.getName(), packed);
893 if (dst == null)
894 return ref;
895 }
896
897 dst = resolve(dst, depth + 1, prefix, loose, packed);
898 if (dst == null)
899 return null;
900 return new SymbolicRef(ref.getName(), dst);
901 }
902 return ref;
903 }
904
905 PackedRefList getPackedRefs() throws IOException {
906 boolean trustFolderStat = getRepository().getConfig().getBoolean(
907 ConfigConstants.CONFIG_CORE_SECTION,
908 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
909
910 final PackedRefList curList = packedRefs.get();
911 if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
912 return curList;
913 }
914
915 final PackedRefList newList = readPackedRefs();
916 if (packedRefs.compareAndSet(curList, newList)
917 && !curList.id.equals(newList.id)) {
918 modCnt.incrementAndGet();
919 }
920 return newList;
921 }
922
923 private PackedRefList readPackedRefs() throws IOException {
924 int maxStaleRetries = 5;
925 int retries = 0;
926 while (true) {
927 final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
928 final BufferedReader br;
929 final MessageDigest digest = Constants.newMessageDigest();
930 try {
931 br = new BufferedReader(new InputStreamReader(
932 new DigestInputStream(new FileInputStream(packedRefsFile),
933 digest), CHARSET));
934 } catch (FileNotFoundException noPackedRefs) {
935 if (packedRefsFile.exists()) {
936 throw noPackedRefs;
937 }
938
939 return NO_PACKED_REFS;
940 }
941 try {
942 return new PackedRefList(parsePackedRefs(br), snapshot,
943 ObjectId.fromRaw(digest.digest()));
944 } catch (IOException e) {
945 if (FileUtils.isStaleFileHandleInCausalChain(e)
946 && retries < maxStaleRetries) {
947 if (LOG.isDebugEnabled()) {
948 LOG.debug(MessageFormat.format(
949 JGitText.get().packedRefsHandleIsStale,
950 Integer.valueOf(retries)), e);
951 }
952 retries++;
953 continue;
954 }
955 throw e;
956 } finally {
957 br.close();
958 }
959 }
960 }
961
962 private RefList<Ref> parsePackedRefs(final BufferedReader br)
963 throws IOException {
964 RefList.Builder<Ref> all = new RefList.Builder<>();
965 Ref last = null;
966 boolean peeled = false;
967 boolean needSort = false;
968
969 String p;
970 while ((p = br.readLine()) != null) {
971 if (p.charAt(0) == '#') {
972 if (p.startsWith(PACKED_REFS_HEADER)) {
973 p = p.substring(PACKED_REFS_HEADER.length());
974 peeled = p.contains(PACKED_REFS_PEELED);
975 }
976 continue;
977 }
978
979 if (p.charAt(0) == '^') {
980 if (last == null)
981 throw new IOException(JGitText.get().peeledLineBeforeRef);
982
983 ObjectId id = ObjectId.fromString(p.substring(1));
984 last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
985 .getObjectId(), id);
986 all.set(all.size() - 1, last);
987 continue;
988 }
989
990 int sp = p.indexOf(' ');
991 if (sp < 0) {
992 throw new IOException(MessageFormat.format(
993 JGitText.get().packedRefsCorruptionDetected,
994 packedRefsFile.getAbsolutePath()));
995 }
996 ObjectId id = ObjectId.fromString(p.substring(0, sp));
997 String name = copy(p, sp + 1, p.length());
998 ObjectIdRef cur;
999 if (peeled)
1000 cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
1001 else
1002 cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
1003 if (last != null && RefComparator.compareTo(last, cur) > 0)
1004 needSort = true;
1005 all.add(cur);
1006 last = cur;
1007 }
1008
1009 if (needSort)
1010 all.sort();
1011 return all.toRefList();
1012 }
1013
1014 private static String copy(final String src, final int off, final int end) {
1015
1016
1017 return new StringBuilder(end - off).append(src, off, end).toString();
1018 }
1019
1020 PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
1021 final PackedRefList oldPackedList, boolean changed)
1022 throws IOException {
1023
1024
1025 AtomicReference<PackedRefList> result = new AtomicReference<>();
1026 new RefWriter(refs) {
1027 @Override
1028 protected void writeFile(String name, byte[] content)
1029 throws IOException {
1030 lck.setFSync(true);
1031 lck.setNeedSnapshot(true);
1032 try {
1033 lck.write(content);
1034 } catch (IOException ioe) {
1035 throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
1036 }
1037 try {
1038 lck.waitForStatChange();
1039 } catch (InterruptedException e) {
1040 lck.unlock();
1041 throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name));
1042 }
1043 if (!lck.commit())
1044 throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
1045
1046 byte[] digest = Constants.newMessageDigest().digest(content);
1047 PackedRefList newPackedList = new PackedRefList(
1048 refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059 PackedRefList afterUpdate = packedRefs.updateAndGet(
1060 p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
1061 if (!afterUpdate.id.equals(newPackedList.id)) {
1062 throw new ObjectWritingException(
1063 MessageFormat.format(JGitText.get().unableToWrite, name));
1064 }
1065 if (changed) {
1066 modCnt.incrementAndGet();
1067 }
1068 result.set(newPackedList);
1069 }
1070 }.writePackedRefs();
1071 return result.get();
1072 }
1073
1074 private Ref readRef(String name, RefList<Ref> packed) throws IOException {
1075 final RefList<LooseRef> curList = looseRefs.get();
1076 final int idx = curList.find(name);
1077 if (0 <= idx) {
1078 final LooseRef o = curList.get(idx);
1079 final LooseRef n = scanRef(o, name);
1080 if (n == null) {
1081 if (looseRefs.compareAndSet(curList, curList.remove(idx)))
1082 modCnt.incrementAndGet();
1083 return packed.get(name);
1084 }
1085
1086 if (o == n)
1087 return n;
1088 if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
1089 modCnt.incrementAndGet();
1090 return n;
1091 }
1092
1093 final LooseRef n = scanRef(null, name);
1094 if (n == null)
1095 return packed.get(name);
1096
1097
1098
1099 for (int i = 0; i < additionalRefsNames.length; i++)
1100 if (name.equals(additionalRefsNames[i]))
1101 return n;
1102
1103 if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
1104 modCnt.incrementAndGet();
1105 return n;
1106 }
1107
1108 LooseRef scanRef(LooseRef ref, String name) throws IOException {
1109 final File path = fileFor(name);
1110 FileSnapshot currentSnapshot = null;
1111
1112 if (ref != null) {
1113 currentSnapshot = ref.getSnapShot();
1114 if (!currentSnapshot.isModified(path))
1115 return ref;
1116 name = ref.getName();
1117 }
1118
1119 final int limit = 4096;
1120 final byte[] buf;
1121 FileSnapshot otherSnapshot = FileSnapshot.save(path);
1122 try {
1123 buf = IO.readSome(path, limit);
1124 } catch (FileNotFoundException noFile) {
1125 if (path.exists() && path.isFile()) {
1126 throw noFile;
1127 }
1128 return null;
1129 }
1130
1131 int n = buf.length;
1132 if (n == 0)
1133 return null;
1134
1135 if (isSymRef(buf, n)) {
1136 if (n == limit)
1137 return null;
1138
1139
1140 while (0 < n && Character.isWhitespace(buf[n - 1]))
1141 n--;
1142 if (n < 6) {
1143 String content = RawParseUtils.decode(buf, 0, n);
1144 throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
1145 }
1146 final String target = RawParseUtils.decode(buf, 5, n);
1147 if (ref != null && ref.isSymbolic()
1148 && ref.getTarget().getName().equals(target)) {
1149 assert(currentSnapshot != null);
1150 currentSnapshot.setClean(otherSnapshot);
1151 return ref;
1152 }
1153 return newSymbolicRef(otherSnapshot, name, target);
1154 }
1155
1156 if (n < OBJECT_ID_STRING_LENGTH)
1157 return null;
1158
1159 final ObjectId id;
1160 try {
1161 id = ObjectId.fromString(buf, 0);
1162 if (ref != null && !ref.isSymbolic()
1163 && id.equals(ref.getTarget().getObjectId())) {
1164 assert(currentSnapshot != null);
1165 currentSnapshot.setClean(otherSnapshot);
1166 return ref;
1167 }
1168
1169 } catch (IllegalArgumentException notRef) {
1170 while (0 < n && Character.isWhitespace(buf[n - 1]))
1171 n--;
1172 String content = RawParseUtils.decode(buf, 0, n);
1173
1174 IOException ioException = new IOException(MessageFormat.format(JGitText.get().notARef,
1175 name, content));
1176 ioException.initCause(notRef);
1177 throw ioException;
1178 }
1179 return new LooseUnpeeled(otherSnapshot, name, id);
1180 }
1181
1182 private static boolean isSymRef(final byte[] buf, int n) {
1183 if (n < 6)
1184 return false;
1185 return buf[0] == 'r'
1186 && buf[1] == 'e'
1187 && buf[2] == 'f'
1188 && buf[3] == ':'
1189 && buf[4] == ' ';
1190 }
1191
1192
1193
1194
1195
1196
1197
1198 boolean isInClone() throws IOException {
1199 return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
1200 }
1201
1202 private boolean hasDanglingHead() throws IOException {
1203 Ref head = exactRef(Constants.HEAD);
1204 if (head != null) {
1205 ObjectId id = head.getObjectId();
1206 return id == null || id.equals(ObjectId.zeroId());
1207 }
1208 return false;
1209 }
1210
1211 private boolean hasLooseRef() throws IOException {
1212 return Files.walk(refsDir.toPath()).anyMatch(Files::isRegularFile);
1213 }
1214
1215
1216 void fireRefsChanged() {
1217 final int last = lastNotifiedModCnt.get();
1218 final int curr = modCnt.get();
1219 if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1220 parent.fireEvent(new RefsChangedEvent());
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230 RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1231 File tmp = File.createTempFile("renamed_", "_ref", refsDir);
1232 String name = Constants.R_REFS + tmp.getName();
1233 Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1234 return new RefDirectoryUpdate(this, ref);
1235 }
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245 File fileFor(String name) {
1246 if (name.startsWith(R_REFS)) {
1247 name = name.substring(R_REFS.length());
1248 return new File(refsDir, name);
1249 }
1250 return new File(gitDir, name);
1251 }
1252
1253 static int levelsIn(final String name) {
1254 int count = 0;
1255 for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1256 count++;
1257 return count;
1258 }
1259
1260 static void delete(final File file, final int depth) throws IOException {
1261 delete(file, depth, null);
1262 }
1263
1264 private static void delete(final File file, final int depth, LockFile rLck)
1265 throws IOException {
1266 if (!file.delete() && file.isFile()) {
1267 throw new IOException(MessageFormat.format(
1268 JGitText.get().fileCannotBeDeleted, file));
1269 }
1270
1271 if (rLck != null) {
1272 rLck.unlock();
1273 }
1274 File dir = file.getParentFile();
1275 for (int i = 0; i < depth; ++i) {
1276 if (!dir.delete()) {
1277 break;
1278 }
1279 dir = dir.getParentFile();
1280 }
1281 }
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306 Iterable<Integer> getRetrySleepMs() {
1307 return retrySleepMs;
1308 }
1309
1310 void setRetrySleepMs(List<Integer> retrySleepMs) {
1311 if (retrySleepMs == null || retrySleepMs.isEmpty()
1312 || retrySleepMs.get(0).intValue() != 0) {
1313 throw new IllegalArgumentException();
1314 }
1315 this.retrySleepMs = retrySleepMs;
1316 }
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327 static void sleep(long ms) throws InterruptedIOException {
1328 if (ms <= 0) {
1329 return;
1330 }
1331 try {
1332 Thread.sleep(ms);
1333 } catch (InterruptedException e) {
1334 InterruptedIOException ie = new InterruptedIOException();
1335 ie.initCause(e);
1336 throw ie;
1337 }
1338 }
1339
1340 static class PackedRefList extends RefList<Ref> {
1341
1342 private final FileSnapshot snapshot;
1343
1344 private final ObjectId id;
1345
1346 private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1347 super(src);
1348 snapshot = s;
1349 id = i;
1350 }
1351 }
1352
1353 private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1354 RefList.emptyList(), FileSnapshot.MISSING_FILE,
1355 ObjectId.zeroId());
1356
1357 private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1358 String name, String target) {
1359 Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1360 return new LooseSymbolicRef(snapshot, name, dst);
1361 }
1362
1363 private static interface LooseRef extends Ref {
1364 FileSnapshot getSnapShot();
1365
1366 LooseRef peel(ObjectIdRef newLeaf);
1367 }
1368
1369 private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
1370 implements LooseRef {
1371 private final FileSnapshot snapShot;
1372
1373 LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1374 @NonNull ObjectId id, @NonNull ObjectId p) {
1375 super(LOOSE, refName, id, p);
1376 this.snapShot = snapshot;
1377 }
1378
1379 @Override
1380 public FileSnapshot getSnapShot() {
1381 return snapShot;
1382 }
1383
1384 @Override
1385 public LooseRef peel(ObjectIdRef newLeaf) {
1386 return this;
1387 }
1388 }
1389
1390 private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
1391 implements LooseRef {
1392 private final FileSnapshot snapShot;
1393
1394 LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1395 @NonNull ObjectId id) {
1396 super(LOOSE, refName, id);
1397 this.snapShot = snapshot;
1398 }
1399
1400 @Override
1401 public FileSnapshot getSnapShot() {
1402 return snapShot;
1403 }
1404
1405 @Override
1406 public LooseRef peel(ObjectIdRef newLeaf) {
1407 return this;
1408 }
1409 }
1410
1411 private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
1412 implements LooseRef {
1413 private FileSnapshot snapShot;
1414
1415 LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1416 @NonNull ObjectId id) {
1417 super(LOOSE, refName, id);
1418 this.snapShot = snapShot;
1419 }
1420
1421 @Override
1422 public FileSnapshot getSnapShot() {
1423 return snapShot;
1424 }
1425
1426 @NonNull
1427 @Override
1428 public ObjectId getObjectId() {
1429 ObjectId id = super.getObjectId();
1430 assert id != null;
1431 return id;
1432 }
1433
1434 @Override
1435 public LooseRef peel(ObjectIdRef newLeaf) {
1436 ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1437 ObjectId objectId = getObjectId();
1438 if (peeledObjectId != null) {
1439 return new LoosePeeledTag(snapShot, getName(),
1440 objectId, peeledObjectId);
1441 } else {
1442 return new LooseNonTag(snapShot, getName(),
1443 objectId);
1444 }
1445 }
1446 }
1447
1448 private final static class LooseSymbolicRef extends SymbolicRef implements
1449 LooseRef {
1450 private final FileSnapshot snapShot;
1451
1452 LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1453 @NonNull Ref target) {
1454 super(refName, target);
1455 this.snapShot = snapshot;
1456 }
1457
1458 @Override
1459 public FileSnapshot getSnapShot() {
1460 return snapShot;
1461 }
1462
1463 @Override
1464 public LooseRef peel(ObjectIdRef newLeaf) {
1465
1466 throw new UnsupportedOperationException();
1467 }
1468 }
1469 }