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