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.size() == 0) {
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 (int i = 0; i < additionalRefsNames.length; i++)
1132 if (name.equals(additionalRefsNames[i]))
1133 return n;
1134
1135 if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
1136 modCnt.incrementAndGet();
1137 return n;
1138 }
1139
1140 LooseRef scanRef(LooseRef ref, String name) throws IOException {
1141 final File path = fileFor(name);
1142 FileSnapshot currentSnapshot = null;
1143
1144 if (ref != null) {
1145 currentSnapshot = ref.getSnapShot();
1146 if (!currentSnapshot.isModified(path))
1147 return ref;
1148 name = ref.getName();
1149 }
1150
1151 final int limit = 4096;
1152 final byte[] buf;
1153 FileSnapshot otherSnapshot = FileSnapshot.save(path);
1154 try {
1155 buf = IO.readSome(path, limit);
1156 } catch (FileNotFoundException noFile) {
1157 if (path.exists() && path.isFile()) {
1158 throw noFile;
1159 }
1160 return null;
1161 }
1162
1163 int n = buf.length;
1164 if (n == 0)
1165 return null;
1166
1167 if (isSymRef(buf, n)) {
1168 if (n == limit)
1169 return null;
1170
1171
1172 while (0 < n && Character.isWhitespace(buf[n - 1]))
1173 n--;
1174 if (n < 6) {
1175 String content = RawParseUtils.decode(buf, 0, n);
1176 throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
1177 }
1178 final String target = RawParseUtils.decode(buf, 5, n);
1179 if (ref != null && ref.isSymbolic()
1180 && ref.getTarget().getName().equals(target)) {
1181 assert(currentSnapshot != null);
1182 currentSnapshot.setClean(otherSnapshot);
1183 return ref;
1184 }
1185 return newSymbolicRef(otherSnapshot, name, target);
1186 }
1187
1188 if (n < OBJECT_ID_STRING_LENGTH)
1189 return null;
1190
1191 final ObjectId id;
1192 try {
1193 id = ObjectId.fromString(buf, 0);
1194 if (ref != null && !ref.isSymbolic()
1195 && id.equals(ref.getTarget().getObjectId())) {
1196 assert(currentSnapshot != null);
1197 currentSnapshot.setClean(otherSnapshot);
1198 return ref;
1199 }
1200
1201 } catch (IllegalArgumentException notRef) {
1202 while (0 < n && Character.isWhitespace(buf[n - 1]))
1203 n--;
1204 String content = RawParseUtils.decode(buf, 0, n);
1205
1206 throw new IOException(MessageFormat.format(JGitText.get().notARef,
1207 name, content), notRef);
1208 }
1209 return new LooseUnpeeled(otherSnapshot, name, id);
1210 }
1211
1212 private static boolean isSymRef(byte[] buf, int n) {
1213 if (n < 6)
1214 return false;
1215 return buf[0] == 'r'
1216 && buf[1] == 'e'
1217 && buf[2] == 'f'
1218 && buf[3] == ':'
1219 && buf[4] == ' ';
1220 }
1221
1222
1223
1224
1225
1226
1227
1228 boolean isInClone() throws IOException {
1229 return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
1230 }
1231
1232 private boolean hasDanglingHead() throws IOException {
1233 Ref head = exactRef(Constants.HEAD);
1234 if (head != null) {
1235 ObjectId id = head.getObjectId();
1236 return id == null || id.equals(ObjectId.zeroId());
1237 }
1238 return false;
1239 }
1240
1241 private boolean hasLooseRef() throws IOException {
1242 try (Stream<Path> stream = Files.walk(refsDir.toPath())) {
1243 return stream.anyMatch(Files::isRegularFile);
1244 }
1245 }
1246
1247
1248 void fireRefsChanged() {
1249 final int last = lastNotifiedModCnt.get();
1250 final int curr = modCnt.get();
1251 if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1252 parent.fireEvent(new RefsChangedEvent());
1253 }
1254
1255
1256
1257
1258
1259
1260
1261
1262 RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1263 File tmp = File.createTempFile("renamed_", "_ref", refsDir);
1264 String name = Constants.R_REFS + tmp.getName();
1265 Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1266 return new RefDirectoryUpdate(this, ref);
1267 }
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277 File fileFor(String name) {
1278 if (name.startsWith(R_REFS)) {
1279 name = name.substring(R_REFS.length());
1280 return new File(refsDir, name);
1281 }
1282 return new File(gitDir, name);
1283 }
1284
1285 static int levelsIn(String name) {
1286 int count = 0;
1287 for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1288 count++;
1289 return count;
1290 }
1291
1292 static void delete(File file, int depth) throws IOException {
1293 delete(file, depth, null);
1294 }
1295
1296 private static void delete(File file, int depth, LockFile rLck)
1297 throws IOException {
1298 if (!file.delete() && file.isFile()) {
1299 throw new IOException(MessageFormat.format(
1300 JGitText.get().fileCannotBeDeleted, file));
1301 }
1302
1303 if (rLck != null) {
1304 rLck.unlock();
1305 }
1306 File dir = file.getParentFile();
1307 for (int i = 0; i < depth; ++i) {
1308 try {
1309 Files.deleteIfExists(dir.toPath());
1310 } catch (DirectoryNotEmptyException e) {
1311
1312
1313 break;
1314 } catch (IOException e) {
1315 LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath,
1316 dir), e);
1317 break;
1318 }
1319 dir = dir.getParentFile();
1320 }
1321 }
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 Iterable<Integer> getRetrySleepMs() {
1347 return retrySleepMs;
1348 }
1349
1350 void setRetrySleepMs(List<Integer> retrySleepMs) {
1351 if (retrySleepMs == null || retrySleepMs.isEmpty()
1352 || retrySleepMs.get(0).intValue() != 0) {
1353 throw new IllegalArgumentException();
1354 }
1355 this.retrySleepMs = retrySleepMs;
1356 }
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367 static void sleep(long ms) throws InterruptedIOException {
1368 if (ms <= 0) {
1369 return;
1370 }
1371 try {
1372 Thread.sleep(ms);
1373 } catch (InterruptedException e) {
1374 InterruptedIOException ie = new InterruptedIOException();
1375 ie.initCause(e);
1376 throw ie;
1377 }
1378 }
1379
1380 static class PackedRefList extends RefList<Ref> {
1381
1382 private final FileSnapshot snapshot;
1383
1384 private final ObjectId id;
1385
1386 private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1387 super(src);
1388 snapshot = s;
1389 id = i;
1390 }
1391 }
1392
1393 private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1394 RefList.emptyList(), FileSnapshot.MISSING_FILE,
1395 ObjectId.zeroId());
1396
1397 private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1398 String name, String target) {
1399 Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1400 return new LooseSymbolicRef(snapshot, name, dst);
1401 }
1402
1403 private static interface LooseRef extends Ref {
1404 FileSnapshot getSnapShot();
1405
1406 LooseRef peel(ObjectIdRef newLeaf);
1407 }
1408
1409 private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
1410 implements LooseRef {
1411 private final FileSnapshot snapShot;
1412
1413 LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1414 @NonNull ObjectId/../../../../org/eclipse/jgit/lib/ObjectId.html#ObjectId">ObjectId id, @NonNull ObjectId p) {
1415 super(LOOSE, refName, id, p);
1416 this.snapShot = snapshot;
1417 }
1418
1419 @Override
1420 public FileSnapshot getSnapShot() {
1421 return snapShot;
1422 }
1423
1424 @Override
1425 public LooseRef peel(ObjectIdRef newLeaf) {
1426 return this;
1427 }
1428 }
1429
1430 private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
1431 implements LooseRef {
1432 private final FileSnapshot snapShot;
1433
1434 LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1435 @NonNull ObjectId id) {
1436 super(LOOSE, refName, id);
1437 this.snapShot = snapshot;
1438 }
1439
1440 @Override
1441 public FileSnapshot getSnapShot() {
1442 return snapShot;
1443 }
1444
1445 @Override
1446 public LooseRef peel(ObjectIdRef newLeaf) {
1447 return this;
1448 }
1449 }
1450
1451 private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
1452 implements LooseRef {
1453 private FileSnapshot snapShot;
1454
1455 LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1456 @NonNull ObjectId id) {
1457 super(LOOSE, refName, id);
1458 this.snapShot = snapShot;
1459 }
1460
1461 @Override
1462 public FileSnapshot getSnapShot() {
1463 return snapShot;
1464 }
1465
1466 @NonNull
1467 @Override
1468 public ObjectId getObjectId() {
1469 ObjectId id = super.getObjectId();
1470 assert id != null;
1471 return id;
1472 }
1473
1474 @Override
1475 public LooseRef peel(ObjectIdRef newLeaf) {
1476 ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1477 ObjectId objectId = getObjectId();
1478 if (peeledObjectId != null) {
1479 return new LoosePeeledTag(snapShot, getName(),
1480 objectId, peeledObjectId);
1481 } else {
1482 return new LooseNonTag(snapShot, getName(),
1483 objectId);
1484 }
1485 }
1486 }
1487
1488 private final static class LooseSymbolicRef extends SymbolicRef implements
1489 LooseRef {
1490 private final FileSnapshot snapShot;
1491
1492 LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1493 @NonNull Ref target) {
1494 super(refName, target);
1495 this.snapShot = snapshot;
1496 }
1497
1498 @Override
1499 public FileSnapshot getSnapShot() {
1500 return snapShot;
1501 }
1502
1503 @Override
1504 public LooseRef peel(ObjectIdRef newLeaf) {
1505
1506 throw new UnsupportedOperationException();
1507 }
1508 }
1509 }