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.OBJECT_ID_STRING_LENGTH;
52 import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
53 import static org.eclipse.jgit.lib.Constants.R_HEADS;
54 import static org.eclipse.jgit.lib.Constants.R_REFS;
55 import static org.eclipse.jgit.lib.Constants.R_TAGS;
56 import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
57 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
58 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
59
60 import java.io.BufferedReader;
61 import java.io.File;
62 import java.io.FileInputStream;
63 import java.io.FileNotFoundException;
64 import java.io.IOException;
65 import java.io.InputStreamReader;
66 import java.security.DigestInputStream;
67 import java.security.MessageDigest;
68 import java.text.MessageFormat;
69 import java.util.Arrays;
70 import java.util.LinkedList;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.concurrent.atomic.AtomicInteger;
74 import java.util.concurrent.atomic.AtomicReference;
75
76 import org.eclipse.jgit.annotations.NonNull;
77 import org.eclipse.jgit.errors.InvalidObjectIdException;
78 import org.eclipse.jgit.errors.LockFailedException;
79 import org.eclipse.jgit.errors.MissingObjectException;
80 import org.eclipse.jgit.errors.ObjectWritingException;
81 import org.eclipse.jgit.events.RefsChangedEvent;
82 import org.eclipse.jgit.internal.JGitText;
83 import org.eclipse.jgit.lib.Constants;
84 import org.eclipse.jgit.lib.ObjectId;
85 import org.eclipse.jgit.lib.ObjectIdRef;
86 import org.eclipse.jgit.lib.Ref;
87 import org.eclipse.jgit.lib.RefComparator;
88 import org.eclipse.jgit.lib.RefDatabase;
89 import org.eclipse.jgit.lib.RefUpdate;
90 import org.eclipse.jgit.lib.RefWriter;
91 import org.eclipse.jgit.lib.Repository;
92 import org.eclipse.jgit.lib.SymbolicRef;
93 import org.eclipse.jgit.revwalk.RevObject;
94 import org.eclipse.jgit.revwalk.RevTag;
95 import org.eclipse.jgit.revwalk.RevWalk;
96 import org.eclipse.jgit.util.FS;
97 import org.eclipse.jgit.util.FileUtils;
98 import org.eclipse.jgit.util.IO;
99 import org.eclipse.jgit.util.RawParseUtils;
100 import org.eclipse.jgit.util.RefList;
101 import org.eclipse.jgit.util.RefMap;
102 import org.slf4j.Logger;
103 import org.slf4j.LoggerFactory;
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public class RefDirectory extends RefDatabase {
121 private final static Logger LOG = LoggerFactory
122 .getLogger(RefDirectory.class);
123
124
125 public static final String SYMREF = "ref: ";
126
127
128 public static final String PACKED_REFS_HEADER = "# pack-refs with:";
129
130
131 public static final String PACKED_REFS_PEELED = " peeled";
132
133
134 private static final String[] additionalRefsNames = new String[] {
135 Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
136 Constants.CHERRY_PICK_HEAD };
137
138 private final FileRepository parent;
139
140 private final File gitDir;
141
142 final File refsDir;
143
144 private final ReflogWriter logWriter;
145
146 private final File packedRefsFile;
147
148
149
150
151
152
153
154
155
156 private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
157
158
159 final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
160
161
162
163
164
165
166
167 private final AtomicInteger modCnt = new AtomicInteger();
168
169
170
171
172
173
174
175 private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
176
177 RefDirectory(final FileRepository db) {
178 final FS fs = db.getFS();
179 parent = db;
180 gitDir = db.getDirectory();
181 logWriter = new ReflogWriter(db);
182 refsDir = fs.resolve(gitDir, R_REFS);
183 packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
184
185 looseRefs.set(RefList.<LooseRef> emptyList());
186 packedRefs.set(PackedRefList.NO_PACKED_REFS);
187 }
188
189 Repository getRepository() {
190 return parent;
191 }
192
193 ReflogWriter getLogWriter() {
194 return logWriter;
195 }
196
197 public void create() throws IOException {
198 FileUtils.mkdir(refsDir);
199 FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
200 FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
201 logWriter.create();
202 }
203
204 @Override
205 public void close() {
206 clearReferences();
207 }
208
209 private void clearReferences() {
210 looseRefs.set(RefList.<LooseRef> emptyList());
211 packedRefs.set(PackedRefList.NO_PACKED_REFS);
212 }
213
214 @Override
215 public void refresh() {
216 super.refresh();
217 clearReferences();
218 }
219
220 @Override
221 public boolean isNameConflicting(String name) throws IOException {
222 RefList<Ref> packed = getPackedRefs();
223 RefList<LooseRef> loose = getLooseRefs();
224
225
226 int lastSlash = name.lastIndexOf('/');
227 while (0 < lastSlash) {
228 String needle = name.substring(0, lastSlash);
229 if (loose.contains(needle) || packed.contains(needle))
230 return true;
231 lastSlash = name.lastIndexOf('/', lastSlash - 1);
232 }
233
234
235 String prefix = name + '/';
236 int idx;
237
238 idx = -(packed.find(prefix) + 1);
239 if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
240 return true;
241
242 idx = -(loose.find(prefix) + 1);
243 if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
244 return true;
245
246 return false;
247 }
248
249 private RefList<LooseRef> getLooseRefs() {
250 final RefList<LooseRef> oldLoose = looseRefs.get();
251
252 LooseScanner scan = new LooseScanner(oldLoose);
253 scan.scan(ALL);
254
255 RefList<LooseRef> loose;
256 if (scan.newLoose != null) {
257 loose = scan.newLoose.toRefList();
258 if (looseRefs.compareAndSet(oldLoose, loose))
259 modCnt.incrementAndGet();
260 } else
261 loose = oldLoose;
262 return loose;
263 }
264
265 @Override
266 public Ref exactRef(String name) throws IOException {
267 RefList<Ref> packed = getPackedRefs();
268 Ref ref;
269 try {
270 ref = readRef(name, packed);
271 if (ref != null) {
272 ref = resolve(ref, 0, null, null, packed);
273 }
274 } catch (IOException e) {
275 if (name.contains("/")
276 || !(e.getCause() instanceof InvalidObjectIdException)) {
277 throw e;
278 }
279
280
281
282
283 ref = null;
284 }
285 fireRefsChanged();
286 return ref;
287 }
288
289 @Override
290 public Ref getRef(final String needle) throws IOException {
291 final RefList<Ref> packed = getPackedRefs();
292 Ref ref = null;
293 for (String prefix : SEARCH_PATH) {
294 try {
295 ref = readRef(prefix + needle, packed);
296 if (ref != null) {
297 ref = resolve(ref, 0, null, null, packed);
298 }
299 if (ref != null) {
300 break;
301 }
302 } catch (IOException e) {
303 if (!(!needle.contains("/") && "".equals(prefix) && e
304 .getCause() instanceof InvalidObjectIdException)) {
305 throw e;
306 }
307 }
308 }
309 fireRefsChanged();
310 return ref;
311 }
312
313 @Override
314 public Map<String, Ref> getRefs(String prefix) throws IOException {
315 final RefList<LooseRef> oldLoose = looseRefs.get();
316 LooseScanner scan = new LooseScanner(oldLoose);
317 scan.scan(prefix);
318 final RefList<Ref> packed = getPackedRefs();
319
320 RefList<LooseRef> loose;
321 if (scan.newLoose != null) {
322 scan.newLoose.sort();
323 loose = scan.newLoose.toRefList();
324 if (looseRefs.compareAndSet(oldLoose, loose))
325 modCnt.incrementAndGet();
326 } else
327 loose = oldLoose;
328 fireRefsChanged();
329
330 RefList.Builder<Ref> symbolic = scan.symbolic;
331 for (int idx = 0; idx < symbolic.size();) {
332 final Ref symbolicRef = symbolic.get(idx);
333 final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
334 if (resolvedRef != null && resolvedRef.getObjectId() != null) {
335 symbolic.set(idx, resolvedRef);
336 idx++;
337 } else {
338
339
340
341 symbolic.remove(idx);
342 final int toRemove = loose.find(symbolicRef.getName());
343 if (0 <= toRemove)
344 loose = loose.remove(toRemove);
345 }
346 }
347 symbolic.sort();
348
349 return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
350 }
351
352 @Override
353 public List<Ref> getAdditionalRefs() throws IOException {
354 List<Ref> ret = new LinkedList<Ref>();
355 for (String name : additionalRefsNames) {
356 Ref r = getRef(name);
357 if (r != null)
358 ret.add(r);
359 }
360 return ret;
361 }
362
363 @SuppressWarnings("unchecked")
364 private RefList<Ref> upcast(RefList<? extends Ref> loose) {
365 return (RefList<Ref>) loose;
366 }
367
368 private class LooseScanner {
369 private final RefList<LooseRef> curLoose;
370
371 private int curIdx;
372
373 final RefList.Builder<Ref> symbolic = new RefList.Builder<Ref>(4);
374
375 RefList.Builder<LooseRef> newLoose;
376
377 LooseScanner(final RefList<LooseRef> curLoose) {
378 this.curLoose = curLoose;
379 }
380
381 void scan(String prefix) {
382 if (ALL.equals(prefix)) {
383 scanOne(HEAD);
384 scanTree(R_REFS, refsDir);
385
386
387 if (newLoose == null && curIdx < curLoose.size())
388 newLoose = curLoose.copy(curIdx);
389
390 } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) {
391 curIdx = -(curLoose.find(prefix) + 1);
392 File dir = new File(refsDir, prefix.substring(R_REFS.length()));
393 scanTree(prefix, dir);
394
395
396
397 while (curIdx < curLoose.size()) {
398 if (!curLoose.get(curIdx).getName().startsWith(prefix))
399 break;
400 if (newLoose == null)
401 newLoose = curLoose.copy(curIdx);
402 curIdx++;
403 }
404
405
406
407 if (newLoose != null) {
408 while (curIdx < curLoose.size())
409 newLoose.add(curLoose.get(curIdx++));
410 }
411 }
412 }
413
414 private boolean scanTree(String prefix, File dir) {
415 final String[] entries = dir.list(LockFile.FILTER);
416 if (entries == null)
417 return false;
418 if (0 < entries.length) {
419 for (int i = 0; i < entries.length; ++i) {
420 String e = entries[i];
421 File f = new File(dir, e);
422 if (f.isDirectory())
423 entries[i] += '/';
424 }
425 Arrays.sort(entries);
426 for (String name : entries) {
427 if (name.charAt(name.length() - 1) == '/')
428 scanTree(prefix + name, new File(dir, name));
429 else
430 scanOne(prefix + name);
431 }
432 }
433 return true;
434 }
435
436 private void scanOne(String name) {
437 LooseRef cur;
438
439 if (curIdx < curLoose.size()) {
440 do {
441 cur = curLoose.get(curIdx);
442 int cmp = RefComparator.compareTo(cur, name);
443 if (cmp < 0) {
444
445
446 if (newLoose == null)
447 newLoose = curLoose.copy(curIdx);
448 curIdx++;
449 cur = null;
450 continue;
451 }
452
453 if (cmp > 0)
454 cur = null;
455 break;
456 } while (curIdx < curLoose.size());
457 } else
458 cur = null;
459
460 LooseRef n;
461 try {
462 n = scanRef(cur, name);
463 } catch (IOException notValid) {
464 n = null;
465 }
466
467 if (n != null) {
468 if (cur != n && newLoose == null)
469 newLoose = curLoose.copy(curIdx);
470 if (newLoose != null)
471 newLoose.add(n);
472 if (n.isSymbolic())
473 symbolic.add(n);
474 } else if (cur != null) {
475
476
477 if (newLoose == null)
478 newLoose = curLoose.copy(curIdx);
479 }
480
481 if (cur != null)
482 curIdx++;
483 }
484 }
485
486 @Override
487 public Ref peel(final Ref ref) throws IOException {
488 final Ref leaf = ref.getLeaf();
489 if (leaf.isPeeled() || leaf.getObjectId() == null)
490 return ref;
491
492 ObjectIdRef newLeaf = doPeel(leaf);
493
494
495
496 if (leaf.getStorage().isLoose()) {
497 RefList<LooseRef> curList = looseRefs.get();
498 int idx = curList.find(leaf.getName());
499 if (0 <= idx && curList.get(idx) == leaf) {
500 LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
501 RefList<LooseRef> newList = curList.set(idx, asPeeled);
502 looseRefs.compareAndSet(curList, newList);
503 }
504 }
505
506 return recreate(ref, newLeaf);
507 }
508
509 private ObjectIdRef doPeel(final Ref leaf) throws MissingObjectException,
510 IOException {
511 try (RevWalk rw = new RevWalk(getRepository())) {
512 RevObject obj = rw.parseAny(leaf.getObjectId());
513 if (obj instanceof RevTag) {
514 return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
515 .getName(), leaf.getObjectId(), rw.peel(obj).copy());
516 } else {
517 return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
518 .getName(), leaf.getObjectId());
519 }
520 }
521 }
522
523 private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
524 if (old.isSymbolic()) {
525 Ref dst = recreate(old.getTarget(), leaf);
526 return new SymbolicRef(old.getName(), dst);
527 }
528 return leaf;
529 }
530
531 void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
532 String target) {
533 putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
534 fireRefsChanged();
535 }
536
537 public RefDirectoryUpdate newUpdate(String name, boolean detach)
538 throws IOException {
539 boolean detachingSymbolicRef = false;
540 final RefList<Ref> packed = getPackedRefs();
541 Ref ref = readRef(name, packed);
542 if (ref != null)
543 ref = resolve(ref, 0, null, null, packed);
544 if (ref == null)
545 ref = new ObjectIdRef.Unpeeled(NEW, name, null);
546 else {
547 detachingSymbolicRef = detach && ref.isSymbolic();
548 if (detachingSymbolicRef)
549 ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId());
550 }
551 RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
552 if (detachingSymbolicRef)
553 refDirUpdate.setDetachingSymbolicRef();
554 return refDirUpdate;
555 }
556
557 @Override
558 public RefDirectoryRename newRename(String fromName, String toName)
559 throws IOException {
560 RefDirectoryUpdate from = newUpdate(fromName, false);
561 RefDirectoryUpdate to = newUpdate(toName, false);
562 return new RefDirectoryRename(from, to);
563 }
564
565 void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
566 final ObjectId target = update.getNewObjectId().copy();
567 final Ref leaf = update.getRef().getLeaf();
568 putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
569 }
570
571 private void putLooseRef(LooseRef ref) {
572 RefList<LooseRef> cList, nList;
573 do {
574 cList = looseRefs.get();
575 nList = cList.put(ref);
576 } while (!looseRefs.compareAndSet(cList, nList));
577 modCnt.incrementAndGet();
578 fireRefsChanged();
579 }
580
581 void delete(RefDirectoryUpdate update) throws IOException {
582 Ref dst = update.getRef().getLeaf();
583 String name = dst.getName();
584
585
586
587
588 final PackedRefList packed = getPackedRefs();
589 if (packed.contains(name)) {
590 LockFile lck = new LockFile(packedRefsFile);
591 if (!lck.lock())
592 throw new LockFailedException(packedRefsFile);
593 try {
594 PackedRefList cur = readPackedRefs();
595 int idx = cur.find(name);
596 if (0 <= idx)
597 commitPackedRefs(lck, cur.remove(idx), packed);
598 } finally {
599 lck.unlock();
600 }
601 }
602
603 RefList<LooseRef> curLoose, newLoose;
604 do {
605 curLoose = looseRefs.get();
606 int idx = curLoose.find(name);
607 if (idx < 0)
608 break;
609 newLoose = curLoose.remove(idx);
610 } while (!looseRefs.compareAndSet(curLoose, newLoose));
611
612 int levels = levelsIn(name) - 2;
613 delete(logWriter.logFor(name), levels);
614 if (dst.getStorage().isLoose()) {
615 update.unlock();
616 delete(fileFor(name), levels);
617 }
618
619 modCnt.incrementAndGet();
620 fireRefsChanged();
621 }
622
623
624
625
626
627
628
629
630
631
632
633
634 public void pack(List<String> refs) throws IOException {
635 if (refs.size() == 0)
636 return;
637 FS fs = parent.getFS();
638
639
640 LockFile lck = new LockFile(packedRefsFile);
641 if (!lck.lock())
642 throw new IOException(MessageFormat.format(
643 JGitText.get().cannotLock, packedRefsFile));
644
645 try {
646 final PackedRefList packed = getPackedRefs();
647 RefList<Ref> cur = readPackedRefs();
648
649
650 for (String refName : refs) {
651 Ref ref = readRef(refName, cur);
652 if (ref.isSymbolic())
653 continue;
654
655 int idx = cur.find(refName);
656 if (idx >= 0)
657 cur = cur.set(idx, peeledPackedRef(ref));
658 else
659 cur = cur.add(idx, peeledPackedRef(ref));
660 }
661
662
663 commitPackedRefs(lck, cur, packed);
664
665
666 for (String refName : refs) {
667
668 File refFile = fileFor(refName);
669 if (!fs.exists(refFile))
670 continue;
671 LockFile rLck = new LockFile(refFile);
672 if (!rLck.lock())
673 continue;
674 try {
675 LooseRef currentLooseRef = scanRef(null, refName);
676 if (currentLooseRef == null || currentLooseRef.isSymbolic())
677 continue;
678 Ref packedRef = cur.get(refName);
679 ObjectId clr_oid = currentLooseRef.getObjectId();
680 if (clr_oid != null
681 && clr_oid.equals(packedRef.getObjectId())) {
682 RefList<LooseRef> curLoose, newLoose;
683 do {
684 curLoose = looseRefs.get();
685 int idx = curLoose.find(refName);
686 if (idx < 0)
687 break;
688 newLoose = curLoose.remove(idx);
689 } while (!looseRefs.compareAndSet(curLoose, newLoose));
690 int levels = levelsIn(refName) - 2;
691 delete(fileFor(refName), levels);
692 }
693 } finally {
694 rLck.unlock();
695 }
696 }
697
698
699 } finally {
700 lck.unlock();
701 }
702 }
703
704
705
706
707
708
709
710
711
712
713
714 private Ref peeledPackedRef(Ref f)
715 throws MissingObjectException, IOException {
716 if (f.getStorage().isPacked() && f.isPeeled()) {
717 return f;
718 }
719 if (!f.isPeeled()) {
720 f = peel(f);
721 }
722 ObjectId peeledObjectId = f.getPeeledObjectId();
723 if (peeledObjectId != null) {
724 return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
725 f.getObjectId(), peeledObjectId);
726 } else {
727 return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
728 f.getObjectId());
729 }
730 }
731
732 void log(final RefUpdate update, final String msg, final boolean deref)
733 throws IOException {
734 logWriter.log(update, msg, deref);
735 }
736
737 private Ref resolve(final Ref ref, int depth, String prefix,
738 RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
739 if (ref.isSymbolic()) {
740 Ref dst = ref.getTarget();
741
742 if (MAX_SYMBOLIC_REF_DEPTH <= depth)
743 return null;
744
745
746
747 if (loose != null && dst.getName().startsWith(prefix)) {
748 int idx;
749 if (0 <= (idx = loose.find(dst.getName())))
750 dst = loose.get(idx);
751 else if (0 <= (idx = packed.find(dst.getName())))
752 dst = packed.get(idx);
753 else
754 return ref;
755 } else {
756 dst = readRef(dst.getName(), packed);
757 if (dst == null)
758 return ref;
759 }
760
761 dst = resolve(dst, depth + 1, prefix, loose, packed);
762 if (dst == null)
763 return null;
764 return new SymbolicRef(ref.getName(), dst);
765 }
766 return ref;
767 }
768
769 private PackedRefList getPackedRefs() throws IOException {
770 final PackedRefList curList = packedRefs.get();
771 if (!curList.snapshot.isModified(packedRefsFile))
772 return curList;
773
774 final PackedRefList newList = readPackedRefs();
775 if (packedRefs.compareAndSet(curList, newList)
776 && !curList.id.equals(newList.id))
777 modCnt.incrementAndGet();
778 return newList;
779 }
780
781 private PackedRefList readPackedRefs() throws IOException {
782 int maxStaleRetries = 5;
783 int retries = 0;
784 while (true) {
785 final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
786 final BufferedReader br;
787 final MessageDigest digest = Constants.newMessageDigest();
788 try {
789 br = new BufferedReader(new InputStreamReader(
790 new DigestInputStream(new FileInputStream(packedRefsFile),
791 digest), CHARSET));
792 } catch (FileNotFoundException noPackedRefs) {
793 if (packedRefsFile.exists()) {
794 throw noPackedRefs;
795 }
796
797 return PackedRefList.NO_PACKED_REFS;
798 }
799 try {
800 return new PackedRefList(parsePackedRefs(br), snapshot,
801 ObjectId.fromRaw(digest.digest()));
802 } catch (IOException e) {
803 if (FileUtils.isStaleFileHandle(e) && retries < maxStaleRetries) {
804 if (LOG.isDebugEnabled()) {
805 LOG.debug(MessageFormat.format(
806 JGitText.get().packedRefsHandleIsStale,
807 Integer.valueOf(retries)), e);
808 }
809 retries++;
810 continue;
811 }
812 throw e;
813 } finally {
814 br.close();
815 }
816 }
817 }
818
819 private RefList<Ref> parsePackedRefs(final BufferedReader br)
820 throws IOException {
821 RefList.Builder<Ref> all = new RefList.Builder<Ref>();
822 Ref last = null;
823 boolean peeled = false;
824 boolean needSort = false;
825
826 String p;
827 while ((p = br.readLine()) != null) {
828 if (p.charAt(0) == '#') {
829 if (p.startsWith(PACKED_REFS_HEADER)) {
830 p = p.substring(PACKED_REFS_HEADER.length());
831 peeled = p.contains(PACKED_REFS_PEELED);
832 }
833 continue;
834 }
835
836 if (p.charAt(0) == '^') {
837 if (last == null)
838 throw new IOException(JGitText.get().peeledLineBeforeRef);
839
840 ObjectId id = ObjectId.fromString(p.substring(1));
841 last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
842 .getObjectId(), id);
843 all.set(all.size() - 1, last);
844 continue;
845 }
846
847 int sp = p.indexOf(' ');
848 ObjectId id = ObjectId.fromString(p.substring(0, sp));
849 String name = copy(p, sp + 1, p.length());
850 ObjectIdRef cur;
851 if (peeled)
852 cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
853 else
854 cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
855 if (last != null && RefComparator.compareTo(last, cur) > 0)
856 needSort = true;
857 all.add(cur);
858 last = cur;
859 }
860
861 if (needSort)
862 all.sort();
863 return all.toRefList();
864 }
865
866 private static String copy(final String src, final int off, final int end) {
867
868
869 return new StringBuilder(end - off).append(src, off, end).toString();
870 }
871
872 private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
873 final PackedRefList oldPackedList) throws IOException {
874 new RefWriter(refs) {
875 @Override
876 protected void writeFile(String name, byte[] content)
877 throws IOException {
878 lck.setFSync(true);
879 lck.setNeedSnapshot(true);
880 try {
881 lck.write(content);
882 } catch (IOException ioe) {
883 throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
884 }
885 try {
886 lck.waitForStatChange();
887 } catch (InterruptedException e) {
888 lck.unlock();
889 throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name));
890 }
891 if (!lck.commit())
892 throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
893
894 byte[] digest = Constants.newMessageDigest().digest(content);
895 packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
896 lck.getCommitSnapshot(), ObjectId.fromRaw(digest)));
897 }
898 }.writePackedRefs();
899 }
900
901 private Ref readRef(String name, RefList<Ref> packed) throws IOException {
902 final RefList<LooseRef> curList = looseRefs.get();
903 final int idx = curList.find(name);
904 if (0 <= idx) {
905 final LooseRef o = curList.get(idx);
906 final LooseRef n = scanRef(o, name);
907 if (n == null) {
908 if (looseRefs.compareAndSet(curList, curList.remove(idx)))
909 modCnt.incrementAndGet();
910 return packed.get(name);
911 }
912
913 if (o == n)
914 return n;
915 if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
916 modCnt.incrementAndGet();
917 return n;
918 }
919
920 final LooseRef n = scanRef(null, name);
921 if (n == null)
922 return packed.get(name);
923
924
925
926 for (int i = 0; i < additionalRefsNames.length; i++)
927 if (name.equals(additionalRefsNames[i]))
928 return n;
929
930 if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
931 modCnt.incrementAndGet();
932 return n;
933 }
934
935 LooseRef scanRef(LooseRef ref, String name) throws IOException {
936 final File path = fileFor(name);
937 FileSnapshot currentSnapshot = null;
938
939 if (ref != null) {
940 currentSnapshot = ref.getSnapShot();
941 if (!currentSnapshot.isModified(path))
942 return ref;
943 name = ref.getName();
944 }
945
946 final int limit = 4096;
947 final byte[] buf;
948 FileSnapshot otherSnapshot = FileSnapshot.save(path);
949 try {
950 buf = IO.readSome(path, limit);
951 } catch (FileNotFoundException noFile) {
952 if (path.exists() && path.isFile()) {
953 throw noFile;
954 }
955 return null;
956 }
957
958 int n = buf.length;
959 if (n == 0)
960 return null;
961
962 if (isSymRef(buf, n)) {
963 if (n == limit)
964 return null;
965
966
967 while (0 < n && Character.isWhitespace(buf[n - 1]))
968 n--;
969 if (n < 6) {
970 String content = RawParseUtils.decode(buf, 0, n);
971 throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
972 }
973 final String target = RawParseUtils.decode(buf, 5, n);
974 if (ref != null && ref.isSymbolic()
975 && ref.getTarget().getName().equals(target)) {
976 assert(currentSnapshot != null);
977 currentSnapshot.setClean(otherSnapshot);
978 return ref;
979 }
980 return newSymbolicRef(otherSnapshot, name, target);
981 }
982
983 if (n < OBJECT_ID_STRING_LENGTH)
984 return null;
985
986 final ObjectId id;
987 try {
988 id = ObjectId.fromString(buf, 0);
989 if (ref != null && !ref.isSymbolic()
990 && id.equals(ref.getTarget().getObjectId())) {
991 assert(currentSnapshot != null);
992 currentSnapshot.setClean(otherSnapshot);
993 return ref;
994 }
995
996 } catch (IllegalArgumentException notRef) {
997 while (0 < n && Character.isWhitespace(buf[n - 1]))
998 n--;
999 String content = RawParseUtils.decode(buf, 0, n);
1000
1001 IOException ioException = new IOException(MessageFormat.format(JGitText.get().notARef,
1002 name, content));
1003 ioException.initCause(notRef);
1004 throw ioException;
1005 }
1006 return new LooseUnpeeled(otherSnapshot, name, id);
1007 }
1008
1009 private static boolean isSymRef(final byte[] buf, int n) {
1010 if (n < 6)
1011 return false;
1012 return buf[0] == 'r'
1013 && buf[1] == 'e'
1014 && buf[2] == 'f'
1015 && buf[3] == ':'
1016 && buf[4] == ' ';
1017 }
1018
1019
1020 private void fireRefsChanged() {
1021 final int last = lastNotifiedModCnt.get();
1022 final int curr = modCnt.get();
1023 if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1024 parent.fireEvent(new RefsChangedEvent());
1025 }
1026
1027
1028
1029
1030
1031
1032
1033
1034 RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1035 File tmp = File.createTempFile("renamed_", "_ref", refsDir);
1036 String name = Constants.R_REFS + tmp.getName();
1037 Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1038 return new RefDirectoryUpdate(this, ref);
1039 }
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 File fileFor(String name) {
1050 if (name.startsWith(R_REFS)) {
1051 name = name.substring(R_REFS.length());
1052 return new File(refsDir, name);
1053 }
1054 return new File(gitDir, name);
1055 }
1056
1057 static int levelsIn(final String name) {
1058 int count = 0;
1059 for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1060 count++;
1061 return count;
1062 }
1063
1064 static void delete(final File file, final int depth) throws IOException {
1065 if (!file.delete() && file.isFile())
1066 throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file));
1067
1068 File dir = file.getParentFile();
1069 for (int i = 0; i < depth; ++i) {
1070 if (!dir.delete())
1071 break;
1072 dir = dir.getParentFile();
1073 }
1074 }
1075
1076 private static class PackedRefList extends RefList<Ref> {
1077 static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1078 RefList.emptyList(), FileSnapshot.MISSING_FILE,
1079 ObjectId.zeroId());
1080
1081 final FileSnapshot snapshot;
1082
1083 final ObjectId id;
1084
1085 PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1086 super(src);
1087 snapshot = s;
1088 id = i;
1089 }
1090 }
1091
1092 private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1093 String name, String target) {
1094 Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1095 return new LooseSymbolicRef(snapshot, name, dst);
1096 }
1097
1098 private static interface LooseRef extends Ref {
1099 FileSnapshot getSnapShot();
1100
1101 LooseRef peel(ObjectIdRef newLeaf);
1102 }
1103
1104 private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
1105 implements LooseRef {
1106 private final FileSnapshot snapShot;
1107
1108 LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1109 @NonNull ObjectId id, @NonNull ObjectId p) {
1110 super(LOOSE, refName, id, p);
1111 this.snapShot = snapshot;
1112 }
1113
1114 public FileSnapshot getSnapShot() {
1115 return snapShot;
1116 }
1117
1118 public LooseRef peel(ObjectIdRef newLeaf) {
1119 return this;
1120 }
1121 }
1122
1123 private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
1124 implements LooseRef {
1125 private final FileSnapshot snapShot;
1126
1127 LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1128 @NonNull ObjectId id) {
1129 super(LOOSE, refName, id);
1130 this.snapShot = snapshot;
1131 }
1132
1133 public FileSnapshot getSnapShot() {
1134 return snapShot;
1135 }
1136
1137 public LooseRef peel(ObjectIdRef newLeaf) {
1138 return this;
1139 }
1140 }
1141
1142 private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
1143 implements LooseRef {
1144 private FileSnapshot snapShot;
1145
1146 LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1147 @NonNull ObjectId id) {
1148 super(LOOSE, refName, id);
1149 this.snapShot = snapShot;
1150 }
1151
1152 public FileSnapshot getSnapShot() {
1153 return snapShot;
1154 }
1155
1156 @NonNull
1157 @Override
1158 public ObjectId getObjectId() {
1159 ObjectId id = super.getObjectId();
1160 assert id != null;
1161 return id;
1162 }
1163
1164 public LooseRef peel(ObjectIdRef newLeaf) {
1165 ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1166 ObjectId objectId = getObjectId();
1167 if (peeledObjectId != null) {
1168 return new LoosePeeledTag(snapShot, getName(),
1169 objectId, peeledObjectId);
1170 } else {
1171 return new LooseNonTag(snapShot, getName(),
1172 objectId);
1173 }
1174 }
1175 }
1176
1177 private final static class LooseSymbolicRef extends SymbolicRef implements
1178 LooseRef {
1179 private final FileSnapshot snapShot;
1180
1181 LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1182 @NonNull Ref target) {
1183 super(refName, target);
1184 this.snapShot = snapshot;
1185 }
1186
1187 public FileSnapshot getSnapShot() {
1188 return snapShot;
1189 }
1190
1191 public LooseRef peel(ObjectIdRef newLeaf) {
1192
1193 throw new UnsupportedOperationException();
1194 }
1195 }
1196 }