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