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