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