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