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