View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2009-2010, Google Inc.
4    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org> and others
6    *
7    * This program and the accompanying materials are made available under the
8    * terms of the Eclipse Distribution License v. 1.0 which is available at
9    * https://www.eclipse.org/org/documents/edl-v10.php.
10   *
11   * SPDX-License-Identifier: BSD-3-Clause
12   */
13  
14  package org.eclipse.jgit.internal.storage.file;
15  
16  import static java.nio.charset.StandardCharsets.UTF_8;
17  import static org.eclipse.jgit.lib.Constants.HEAD;
18  import static org.eclipse.jgit.lib.Constants.LOGS;
19  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
20  import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
21  import static org.eclipse.jgit.lib.Constants.R_HEADS;
22  import static org.eclipse.jgit.lib.Constants.R_REFS;
23  import static org.eclipse.jgit.lib.Constants.R_TAGS;
24  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
25  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
26  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
27  
28  import java.io.BufferedReader;
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.FileNotFoundException;
32  import java.io.IOException;
33  import java.io.InputStreamReader;
34  import java.io.InterruptedIOException;
35  import java.nio.file.DirectoryNotEmptyException;
36  import java.nio.file.Files;
37  import java.nio.file.Path;
38  import java.security.DigestInputStream;
39  import java.security.MessageDigest;
40  import java.text.MessageFormat;
41  import java.util.Arrays;
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.HashMap;
45  import java.util.LinkedList;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.concurrent.atomic.AtomicInteger;
49  import java.util.concurrent.atomic.AtomicReference;
50  import java.util.concurrent.locks.ReentrantLock;
51  import java.util.stream.Stream;
52  
53  import org.eclipse.jgit.annotations.NonNull;
54  import org.eclipse.jgit.annotations.Nullable;
55  import org.eclipse.jgit.errors.InvalidObjectIdException;
56  import org.eclipse.jgit.errors.LockFailedException;
57  import org.eclipse.jgit.errors.MissingObjectException;
58  import org.eclipse.jgit.errors.ObjectWritingException;
59  import org.eclipse.jgit.events.RefsChangedEvent;
60  import org.eclipse.jgit.internal.JGitText;
61  import org.eclipse.jgit.lib.ConfigConstants;
62  import org.eclipse.jgit.lib.Constants;
63  import org.eclipse.jgit.lib.ObjectId;
64  import org.eclipse.jgit.lib.ObjectIdRef;
65  import org.eclipse.jgit.lib.Ref;
66  import org.eclipse.jgit.lib.RefComparator;
67  import org.eclipse.jgit.lib.RefDatabase;
68  import org.eclipse.jgit.lib.RefUpdate;
69  import org.eclipse.jgit.lib.RefWriter;
70  import org.eclipse.jgit.lib.Repository;
71  import org.eclipse.jgit.lib.SymbolicRef;
72  import org.eclipse.jgit.revwalk.RevObject;
73  import org.eclipse.jgit.revwalk.RevTag;
74  import org.eclipse.jgit.revwalk.RevWalk;
75  import org.eclipse.jgit.util.FS;
76  import org.eclipse.jgit.util.FileUtils;
77  import org.eclipse.jgit.util.IO;
78  import org.eclipse.jgit.util.RawParseUtils;
79  import org.eclipse.jgit.util.RefList;
80  import org.eclipse.jgit.util.RefMap;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  /**
85   * Traditional file system based {@link org.eclipse.jgit.lib.RefDatabase}.
86   * <p>
87   * This is the classical reference database representation for a Git repository.
88   * References are stored in two formats: loose, and packed.
89   * <p>
90   * Loose references are stored as individual files within the {@code refs/}
91   * directory. The file name matches the reference name and the file contents is
92   * the current {@link org.eclipse.jgit.lib.ObjectId} in string form.
93   * <p>
94   * Packed references are stored in a single text file named {@code packed-refs}.
95   * In the packed format, each reference is stored on its own line. This file
96   * reduces the number of files needed for large reference spaces, reducing the
97   * overall size of a Git repository on disk.
98   */
99  public class RefDirectory extends RefDatabase {
100 	private static final Logger LOG = LoggerFactory
101 			.getLogger(RefDirectory.class);
102 
103 	/** Magic string denoting the start of a symbolic reference file. */
104 	public static final String SYMREF = "ref: "; //$NON-NLS-1$
105 
106 	/** Magic string denoting the header of a packed-refs file. */
107 	public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$
108 
109 	/** If in the header, denotes the file has peeled data. */
110 	public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$
111 
112 	/** The names of the additional refs supported by this class */
113 	private static final String[] additionalRefsNames = new String[] {
114 			Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
115 			Constants.CHERRY_PICK_HEAD };
116 
117 	@SuppressWarnings("boxing")
118 	private static final List<Integer> RETRY_SLEEP_MS =
119 			Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
120 
121 	private final FileRepository parent;
122 
123 	private final File gitDir;
124 
125 	final File refsDir;
126 
127 	final File packedRefsFile;
128 
129 	final File logsDir;
130 
131 	final File logsRefsDir;
132 
133 	/**
134 	 * Immutable sorted list of loose references.
135 	 * <p>
136 	 * Symbolic references in this collection are stored unresolved, that is
137 	 * their target appears to be a new reference with no ObjectId. These are
138 	 * converted into resolved references during a get operation, ensuring the
139 	 * live value is always returned.
140 	 */
141 	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();
142 
143 	/** Immutable sorted list of packed references. */
144 	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
145 
146 	/**
147 	 * Lock for coordinating operations within a single process that may contend
148 	 * on the {@code packed-refs} file.
149 	 * <p>
150 	 * All operations that write {@code packed-refs} must still acquire a
151 	 * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
152 	 * this lock, since there may be multiple {@link RefDirectory} instances or
153 	 * other processes operating on the same repo on disk.
154 	 * <p>
155 	 * This lock exists so multiple threads in the same process can wait in a fair
156 	 * queue without trying, failing, and retrying to acquire the on-disk lock. If
157 	 * {@code RepositoryCache} is used, this lock instance will be used by all
158 	 * threads.
159 	 */
160 	final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
161 
162 	/**
163 	 * Number of modifications made to this database.
164 	 * <p>
165 	 * This counter is incremented when a change is made, or detected from the
166 	 * filesystem during a read operation.
167 	 */
168 	private final AtomicInteger modCnt = new AtomicInteger();
169 
170 	/**
171 	 * Last {@link #modCnt} that we sent to listeners.
172 	 * <p>
173 	 * This value is compared to {@link #modCnt}, and a notification is sent to
174 	 * the listeners only when it differs.
175 	 */
176 	private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
177 
178 	private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
179 
180 	RefDirectory(FileRepository db) {
181 		final FS fs = db.getFS();
182 		parent = db;
183 		gitDir = db.getDirectory();
184 		refsDir = fs.resolve(gitDir, R_REFS);
185 		logsDir = fs.resolve(gitDir, LOGS);
186 		logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
187 		packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
188 
189 		looseRefs.set(RefList.<LooseRef> emptyList());
190 		packedRefs.set(NO_PACKED_REFS);
191 	}
192 
193 	Repository getRepository() {
194 		return parent;
195 	}
196 
197 	ReflogWriter newLogWriter(boolean force) {
198 		return new ReflogWriter(this, force);
199 	}
200 
201 	/**
202 	 * Locate the log file on disk for a single reference name.
203 	 *
204 	 * @param name
205 	 *            name of the ref, relative to the Git repository top level
206 	 *            directory (so typically starts with refs/).
207 	 * @return the log file location.
208 	 */
209 	public File logFor(String name) {
210 		if (name.startsWith(R_REFS)) {
211 			name = name.substring(R_REFS.length());
212 			return new File(logsRefsDir, name);
213 		}
214 		return new File(logsDir, name);
215 	}
216 
217 	/** {@inheritDoc} */
218 	@Override
219 	public void create() throws IOException {
220 		FileUtils.mkdir(refsDir);
221 		FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
222 		FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
223 		newLogWriter(false).create();
224 	}
225 
226 	/** {@inheritDoc} */
227 	@Override
228 	public void close() {
229 		clearReferences();
230 	}
231 
232 	private void clearReferences() {
233 		looseRefs.set(RefList.<LooseRef> emptyList());
234 		packedRefs.set(NO_PACKED_REFS);
235 	}
236 
237 	/** {@inheritDoc} */
238 	@Override
239 	public void refresh() {
240 		super.refresh();
241 		clearReferences();
242 	}
243 
244 	/** {@inheritDoc} */
245 	@Override
246 	public boolean isNameConflicting(String name) throws IOException {
247 		// Cannot be nested within an existing reference.
248 		int lastSlash = name.lastIndexOf('/');
249 		while (0 < lastSlash) {
250 			String needle = name.substring(0, lastSlash);
251 			if (exactRef(needle) != null) {
252 				return true;
253 			}
254 			lastSlash = name.lastIndexOf('/', lastSlash - 1);
255 		}
256 
257 		// Cannot be the container of an existing reference.
258 		return !getRefsByPrefix(name + '/').isEmpty();
259 	}
260 
261 	@Nullable
262 	private Ref readAndResolve(String name, RefList<Ref> packed) throws IOException {
263 		try {
264 			Ref ref = readRef(name, packed);
265 			if (ref != null) {
266 				ref = resolve(ref, 0, null, null, packed);
267 			}
268 			return ref;
269 		} catch (IOException e) {
270 			if (name.contains("/") //$NON-NLS-1$
271 					|| !(e.getCause() instanceof InvalidObjectIdException)) {
272 				throw e;
273 			}
274 
275 			// While looking for a ref outside of refs/ (e.g., 'config'), we
276 			// found a non-ref file (e.g., a config file) instead.  Treat this
277 			// as a ref-not-found condition.
278 			return null;
279 		}
280 	}
281 
282 	/** {@inheritDoc} */
283 	@Override
284 	public Ref exactRef(String name) throws IOException {
285 		try {
286 			return readAndResolve(name, getPackedRefs());
287 		} finally {
288 			fireRefsChanged();
289 		}
290 	}
291 
292 	/** {@inheritDoc} */
293 	@Override
294 	@NonNull
295 	public Map<String, Ref> exactRef(String... refs) throws IOException {
296 		try {
297 			RefList<Ref> packed = getPackedRefs();
298 			Map<String, Ref> result = new HashMap<>(refs.length);
299 			for (String name : refs) {
300 				Ref ref = readAndResolve(name, packed);
301 				if (ref != null) {
302 					result.put(name, ref);
303 				}
304 			}
305 			return result;
306 		} finally {
307 			fireRefsChanged();
308 		}
309 	}
310 
311 	/** {@inheritDoc} */
312 	@Override
313 	@Nullable
314 	public Ref firstExactRef(String... refs) throws IOException {
315 		try {
316 			RefList<Ref> packed = getPackedRefs();
317 			for (String name : refs) {
318 				Ref ref = readAndResolve(name, packed);
319 				if (ref != null) {
320 					return ref;
321 				}
322 			}
323 			return null;
324 		} finally {
325 			fireRefsChanged();
326 		}
327 	}
328 
329 	/** {@inheritDoc} */
330 	@Override
331 	public Map<String, Ref> getRefs(String prefix) throws IOException {
332 		final RefList<LooseRef> oldLoose = looseRefs.get();
333 		LooseScanner scan = new LooseScanner(oldLoose);
334 		scan.scan(prefix);
335 		final RefList<Ref> packed = getPackedRefs();
336 
337 		RefList<LooseRef> loose;
338 		if (scan.newLoose != null) {
339 			scan.newLoose.sort();
340 			loose = scan.newLoose.toRefList();
341 			if (looseRefs.compareAndSet(oldLoose, loose))
342 				modCnt.incrementAndGet();
343 		} else
344 			loose = oldLoose;
345 		fireRefsChanged();
346 
347 		RefList.Builder<Ref> symbolic = scan.symbolic;
348 		for (int idx = 0; idx < symbolic.size();) {
349 			final Ref symbolicRef = symbolic.get(idx);
350 			final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
351 			if (resolvedRef != null && resolvedRef.getObjectId() != null) {
352 				symbolic.set(idx, resolvedRef);
353 				idx++;
354 			} else {
355 				// A broken symbolic reference, we have to drop it from the
356 				// collections the client is about to receive. Should be a
357 				// rare occurrence so pay a copy penalty.
358 				symbolic.remove(idx);
359 				final int toRemove = loose.find(symbolicRef.getName());
360 				if (0 <= toRemove)
361 					loose = loose.remove(toRemove);
362 			}
363 		}
364 		symbolic.sort();
365 
366 		return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
367 	}
368 
369 	/** {@inheritDoc} */
370 	@Override
371 	public List<Ref> getAdditionalRefs() throws IOException {
372 		List<Ref> ret = new LinkedList<>();
373 		for (String name : additionalRefsNames) {
374 			Ref r = exactRef(name);
375 			if (r != null)
376 				ret.add(r);
377 		}
378 		return ret;
379 	}
380 
381 	@SuppressWarnings("unchecked")
382 	private RefList<Ref> upcast(RefList<? extends Ref> loose) {
383 		return (RefList<Ref>) loose;
384 	}
385 
386 	private class LooseScanner {
387 		private final RefList<LooseRef> curLoose;
388 
389 		private int curIdx;
390 
391 		final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);
392 
393 		RefList.Builder<LooseRef> newLoose;
394 
395 		LooseScanner(RefList<LooseRef> curLoose) {
396 			this.curLoose = curLoose;
397 		}
398 
399 		void scan(String prefix) {
400 			if (ALL.equals(prefix)) {
401 				scanOne(HEAD);
402 				scanTree(R_REFS, refsDir);
403 
404 				// If any entries remain, they are deleted, drop them.
405 				if (newLoose == null && curIdx < curLoose.size())
406 					newLoose = curLoose.copy(curIdx);
407 
408 			} else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { //$NON-NLS-1$
409 				curIdx = -(curLoose.find(prefix) + 1);
410 				File dir = new File(refsDir, prefix.substring(R_REFS.length()));
411 				scanTree(prefix, dir);
412 
413 				// Skip over entries still within the prefix; these have
414 				// been removed from the directory.
415 				while (curIdx < curLoose.size()) {
416 					if (!curLoose.get(curIdx).getName().startsWith(prefix))
417 						break;
418 					if (newLoose == null)
419 						newLoose = curLoose.copy(curIdx);
420 					curIdx++;
421 				}
422 
423 				// Keep any entries outside of the prefix space, we
424 				// do not know anything about their status.
425 				if (newLoose != null) {
426 					while (curIdx < curLoose.size())
427 						newLoose.add(curLoose.get(curIdx++));
428 				}
429 			}
430 		}
431 
432 		private boolean scanTree(String prefix, File dir) {
433 			final String[] entries = dir.list(LockFile.FILTER);
434 			if (entries == null) // not a directory or an I/O error
435 				return false;
436 			if (0 < entries.length) {
437 				for (int i = 0; i < entries.length; ++i) {
438 					String e = entries[i];
439 					File f = new File(dir, e);
440 					if (f.isDirectory())
441 						entries[i] += '/';
442 				}
443 				Arrays.sort(entries);
444 				for (String name : entries) {
445 					if (name.charAt(name.length() - 1) == '/')
446 						scanTree(prefix + name, new File(dir, name));
447 					else
448 						scanOne(prefix + name);
449 				}
450 			}
451 			return true;
452 		}
453 
454 		private void scanOne(String name) {
455 			LooseRef cur;
456 
457 			if (curIdx < curLoose.size()) {
458 				do {
459 					cur = curLoose.get(curIdx);
460 					int cmp = RefComparator.compareTo(cur, name);
461 					if (cmp < 0) {
462 						// Reference is not loose anymore, its been deleted.
463 						// Skip the name in the new result list.
464 						if (newLoose == null)
465 							newLoose = curLoose.copy(curIdx);
466 						curIdx++;
467 						cur = null;
468 						continue;
469 					}
470 
471 					if (cmp > 0) // Newly discovered loose reference.
472 						cur = null;
473 					break;
474 				} while (curIdx < curLoose.size());
475 			} else
476 				cur = null; // Newly discovered loose reference.
477 
478 			LooseRef n;
479 			try {
480 				n = scanRef(cur, name);
481 			} catch (IOException notValid) {
482 				n = null;
483 			}
484 
485 			if (n != null) {
486 				if (cur != n && newLoose == null)
487 					newLoose = curLoose.copy(curIdx);
488 				if (newLoose != null)
489 					newLoose.add(n);
490 				if (n.isSymbolic())
491 					symbolic.add(n);
492 			} else if (cur != null) {
493 				// Tragically, this file is no longer a loose reference.
494 				// Kill our cached entry of it.
495 				if (newLoose == null)
496 					newLoose = curLoose.copy(curIdx);
497 			}
498 
499 			if (cur != null)
500 				curIdx++;
501 		}
502 	}
503 
504 	/** {@inheritDoc} */
505 	@Override
506 	public Ref peel(Ref ref) throws IOException {
507 		final Ref leaf = ref.getLeaf();
508 		if (leaf.isPeeled() || leaf.getObjectId() == null)
509 			return ref;
510 
511 		ObjectIdRef newLeaf = doPeel(leaf);
512 
513 		// Try to remember this peeling in the cache, so we don't have to do
514 		// it again in the future, but only if the reference is unchanged.
515 		if (leaf.getStorage().isLoose()) {
516 			RefList<LooseRef> curList = looseRefs.get();
517 			int idx = curList.find(leaf.getName());
518 			if (0 <= idx && curList.get(idx) == leaf) {
519 				LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
520 				RefList<LooseRef> newList = curList.set(idx, asPeeled);
521 				looseRefs.compareAndSet(curList, newList);
522 			}
523 		}
524 
525 		return recreate(ref, newLeaf);
526 	}
527 
528 	private ObjectIdRef doPeel(Ref leaf) throws MissingObjectException,
529 			IOException {
530 		try (RevWalk rw = new RevWalk(getRepository())) {
531 			RevObject obj = rw.parseAny(leaf.getObjectId());
532 			if (obj instanceof RevTag) {
533 				return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
534 						.getName(), leaf.getObjectId(), rw.peel(obj).copy());
535 			}
536 			return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
537 					leaf.getName(), leaf.getObjectId());
538 		}
539 	}
540 
541 	private static Ref recreate(Ref old, ObjectIdRef leaf) {
542 		if (old.isSymbolic()) {
543 			Ref dst = recreate(old.getTarget(), leaf);
544 			return new SymbolicRef(old.getName(), dst);
545 		}
546 		return leaf;
547 	}
548 
549 	void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
550 			String target) {
551 		putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
552 		fireRefsChanged();
553 	}
554 
555 	/** {@inheritDoc} */
556 	@Override
557 	public RefDirectoryUpdate newUpdate(String name, boolean detach)
558 			throws IOException {
559 		boolean detachingSymbolicRef = false;
560 		final RefList<Ref> packed = getPackedRefs();
561 		Ref ref = readRef(name, packed);
562 		if (ref != null)
563 			ref = resolve(ref, 0, null, null, packed);
564 		if (ref == null)
565 			ref = new ObjectIdRef.Unpeeled(NEW, name, null);
566 		else {
567 			detachingSymbolicRef = detach && ref.isSymbolic();
568 		}
569 		RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
570 		if (detachingSymbolicRef)
571 			refDirUpdate.setDetachingSymbolicRef();
572 		return refDirUpdate;
573 	}
574 
575 	/** {@inheritDoc} */
576 	@Override
577 	public RefDirectoryRename newRename(String fromName, String toName)
578 			throws IOException {
579 		RefDirectoryUpdate from = newUpdate(fromName, false);
580 		RefDirectoryUpdate to = newUpdate(toName, false);
581 		return new RefDirectoryRename(from, to);
582 	}
583 
584 	/** {@inheritDoc} */
585 	@Override
586 	public PackedBatchRefUpdate newBatchUpdate() {
587 		return new PackedBatchRefUpdate(this);
588 	}
589 
590 	/** {@inheritDoc} */
591 	@Override
592 	public boolean performsAtomicTransactions() {
593 		return true;
594 	}
595 
596 	void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
597 		final ObjectId target = update.getNewObjectId().copy();
598 		final Ref leaf = update.getRef().getLeaf();
599 		putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
600 	}
601 
602 	private void putLooseRef(LooseRef ref) {
603 		RefList<LooseRef> cList, nList;
604 		do {
605 			cList = looseRefs.get();
606 			nList = cList.put(ref);
607 		} while (!looseRefs.compareAndSet(cList, nList));
608 		modCnt.incrementAndGet();
609 		fireRefsChanged();
610 	}
611 
612 	void delete(RefDirectoryUpdate update) throws IOException {
613 		Ref dst = update.getRef();
614 		if (!update.isDetachingSymbolicRef()) {
615 			dst = dst.getLeaf();
616 		}
617 		String name = dst.getName();
618 
619 		// Write the packed-refs file using an atomic update. We might
620 		// wind up reading it twice, before and after the lock, to ensure
621 		// we don't miss an edit made externally.
622 		final PackedRefList packed = getPackedRefs();
623 		if (packed.contains(name)) {
624 			inProcessPackedRefsLock.lock();
625 			try {
626 				LockFile lck = lockPackedRefsOrThrow();
627 				try {
628 					PackedRefList cur = readPackedRefs();
629 					int idx = cur.find(name);
630 					if (0 <= idx) {
631 						commitPackedRefs(lck, cur.remove(idx), packed, true);
632 					}
633 				} finally {
634 					lck.unlock();
635 				}
636 			} finally {
637 				inProcessPackedRefsLock.unlock();
638 			}
639 		}
640 
641 		RefList<LooseRef> curLoose, newLoose;
642 		do {
643 			curLoose = looseRefs.get();
644 			int idx = curLoose.find(name);
645 			if (idx < 0)
646 				break;
647 			newLoose = curLoose.remove(idx);
648 		} while (!looseRefs.compareAndSet(curLoose, newLoose));
649 
650 		int levels = levelsIn(name) - 2;
651 		delete(logFor(name), levels);
652 		if (dst.getStorage().isLoose()) {
653 			update.unlock();
654 			delete(fileFor(name), levels);
655 		}
656 
657 		modCnt.incrementAndGet();
658 		fireRefsChanged();
659 	}
660 
661 	/**
662 	 * Adds a set of refs to the set of packed-refs. Only non-symbolic refs are
663 	 * added. If a ref with the given name already existed in packed-refs it is
664 	 * updated with the new value. Each loose ref which was added to the
665 	 * packed-ref file is deleted. If a given ref can't be locked it will not be
666 	 * added to the pack file.
667 	 *
668 	 * @param refs
669 	 *            the refs to be added. Must be fully qualified.
670 	 * @throws java.io.IOException
671 	 */
672 	public void pack(List<String> refs) throws IOException {
673 		pack(refs, Collections.emptyMap());
674 	}
675 
676 	PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
677 		return pack(heldLocks.keySet(), heldLocks);
678 	}
679 
680 	private PackedRefList pack(Collection<String> refs,
681 			Map<String, LockFile> heldLocks) throws IOException {
682 		for (LockFile ol : heldLocks.values()) {
683 			ol.requireLock();
684 		}
685 		if (refs.isEmpty()) {
686 			return null;
687 		}
688 		FS fs = parent.getFS();
689 
690 		// Lock the packed refs file and read the content
691 		inProcessPackedRefsLock.lock();
692 		try {
693 			LockFile lck = lockPackedRefsOrThrow();
694 			try {
695 				final PackedRefList packed = getPackedRefs();
696 				RefList<Ref> cur = readPackedRefs();
697 
698 				// Iterate over all refs to be packed
699 				boolean dirty = false;
700 				for (String refName : refs) {
701 					Ref oldRef = readRef(refName, cur);
702 					if (oldRef == null) {
703 						continue; // A non-existent ref is already correctly packed.
704 					}
705 					if (oldRef.isSymbolic()) {
706 						continue; // can't pack symbolic refs
707 					}
708 					// Add/Update it to packed-refs
709 					Ref newRef = peeledPackedRef(oldRef);
710 					if (newRef == oldRef) {
711 						// No-op; peeledPackedRef returns the input ref only if it's already
712 						// packed, and readRef returns a packed ref only if there is no
713 						// loose ref.
714 						continue;
715 					}
716 
717 					dirty = true;
718 					int idx = cur.find(refName);
719 					if (idx >= 0) {
720 						cur = cur.set(idx, newRef);
721 					} else {
722 						cur = cur.add(idx, newRef);
723 					}
724 				}
725 				if (!dirty) {
726 					// All requested refs were already packed accurately
727 					return packed;
728 				}
729 
730 				// The new content for packed-refs is collected. Persist it.
731 				PackedRefList result = commitPackedRefs(lck, cur, packed,
732 						false);
733 
734 				// Now delete the loose refs which are now packed
735 				for (String refName : refs) {
736 					// Lock the loose ref
737 					File refFile = fileFor(refName);
738 					if (!fs.exists(refFile)) {
739 						continue;
740 					}
741 
742 					LockFile rLck = heldLocks.get(refName);
743 					boolean shouldUnlock;
744 					if (rLck == null) {
745 						rLck = new LockFile(refFile);
746 						if (!rLck.lock()) {
747 							continue;
748 						}
749 						shouldUnlock = true;
750 					} else {
751 						shouldUnlock = false;
752 					}
753 
754 					try {
755 						LooseRef currentLooseRef = scanRef(null, refName);
756 						if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
757 							continue;
758 						}
759 						Ref packedRef = cur.get(refName);
760 						ObjectId clr_oid = currentLooseRef.getObjectId();
761 						if (clr_oid != null
762 								&& clr_oid.equals(packedRef.getObjectId())) {
763 							RefList<LooseRef> curLoose, newLoose;
764 							do {
765 								curLoose = looseRefs.get();
766 								int idx = curLoose.find(refName);
767 								if (idx < 0) {
768 									break;
769 								}
770 								newLoose = curLoose.remove(idx);
771 							} while (!looseRefs.compareAndSet(curLoose, newLoose));
772 							int levels = levelsIn(refName) - 2;
773 							delete(refFile, levels, rLck);
774 						}
775 					} finally {
776 						if (shouldUnlock) {
777 							rLck.unlock();
778 						}
779 					}
780 				}
781 				// Don't fire refsChanged. The refs have not change, only their
782 				// storage.
783 				return result;
784 			} finally {
785 				lck.unlock();
786 			}
787 		} finally {
788 			inProcessPackedRefsLock.unlock();
789 		}
790 	}
791 
792 	@Nullable
793 	LockFile lockPackedRefs() throws IOException {
794 		LockFile lck = new LockFile(packedRefsFile);
795 		for (int ms : getRetrySleepMs()) {
796 			sleep(ms);
797 			if (lck.lock()) {
798 				return lck;
799 			}
800 		}
801 		return null;
802 	}
803 
804 	private LockFile lockPackedRefsOrThrow() throws IOException {
805 		LockFile lck = lockPackedRefs();
806 		if (lck == null) {
807 			throw new LockFailedException(packedRefsFile);
808 		}
809 		return lck;
810 	}
811 
812 	/**
813 	 * Make sure a ref is peeled and has the Storage PACKED. If the given ref
814 	 * has this attributes simply return it. Otherwise create a new peeled
815 	 * {@link ObjectIdRef} where Storage is set to PACKED.
816 	 *
817 	 * @param f
818 	 * @return a ref for Storage PACKED having the same name, id, peeledId as f
819 	 * @throws MissingObjectException
820 	 * @throws IOException
821 	 */
822 	private Ref peeledPackedRef(Ref f)
823 			throws MissingObjectException, IOException {
824 		if (f.getStorage().isPacked() && f.isPeeled()) {
825 			return f;
826 		}
827 		if (!f.isPeeled()) {
828 			f = peel(f);
829 		}
830 		ObjectId peeledObjectId = f.getPeeledObjectId();
831 		if (peeledObjectId != null) {
832 			return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
833 					f.getObjectId(), peeledObjectId);
834 		}
835 		return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
836 				f.getObjectId());
837 	}
838 
839 	void log(boolean force, RefUpdate update, String msg, boolean deref)
840 			throws IOException {
841 		newLogWriter(force).log(update, msg, deref);
842 	}
843 
844 	private Ref resolve(final Ref ref, int depth, String prefix,
845 			RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
846 		if (ref.isSymbolic()) {
847 			Ref dst = ref.getTarget();
848 
849 			if (MAX_SYMBOLIC_REF_DEPTH <= depth)
850 				return null; // claim it doesn't exist
851 
852 			// If the cached value can be assumed to be current due to a
853 			// recent scan of the loose directory, use it.
854 			if (loose != null && dst.getName().startsWith(prefix)) {
855 				int idx;
856 				if (0 <= (idx = loose.find(dst.getName())))
857 					dst = loose.get(idx);
858 				else if (0 <= (idx = packed.find(dst.getName())))
859 					dst = packed.get(idx);
860 				else
861 					return ref;
862 			} else {
863 				dst = readRef(dst.getName(), packed);
864 				if (dst == null)
865 					return ref;
866 			}
867 
868 			dst = resolve(dst, depth + 1, prefix, loose, packed);
869 			if (dst == null)
870 				return null;
871 			return new SymbolicRef(ref.getName(), dst);
872 		}
873 		return ref;
874 	}
875 
876 	PackedRefList getPackedRefs() throws IOException {
877 		boolean trustFolderStat = getRepository().getConfig().getBoolean(
878 				ConfigConstants.CONFIG_CORE_SECTION,
879 				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
880 
881 		final PackedRefList curList = packedRefs.get();
882 		if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
883 			return curList;
884 		}
885 
886 		final PackedRefList newList = readPackedRefs();
887 		if (packedRefs.compareAndSet(curList, newList)
888 				&& !curList.id.equals(newList.id)) {
889 			modCnt.incrementAndGet();
890 		}
891 		return newList;
892 	}
893 
894 	private PackedRefList readPackedRefs() throws IOException {
895 		int maxStaleRetries = 5;
896 		int retries = 0;
897 		while (true) {
898 			final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
899 			final MessageDigest digest = Constants.newMessageDigest();
900 			try (BufferedReader br = new BufferedReader(new InputStreamReader(
901 					new DigestInputStream(new FileInputStream(packedRefsFile),
902 							digest),
903 					UTF_8))) {
904 				try {
905 					return new PackedRefList(parsePackedRefs(br), snapshot,
906 							ObjectId.fromRaw(digest.digest()));
907 				} catch (IOException e) {
908 					if (FileUtils.isStaleFileHandleInCausalChain(e)
909 							&& retries < maxStaleRetries) {
910 						if (LOG.isDebugEnabled()) {
911 							LOG.debug(MessageFormat.format(
912 									JGitText.get().packedRefsHandleIsStale,
913 									Integer.valueOf(retries)), e);
914 						}
915 						retries++;
916 						continue;
917 					}
918 					throw e;
919 				}
920 			} catch (FileNotFoundException noPackedRefs) {
921 				if (packedRefsFile.exists()) {
922 					throw noPackedRefs;
923 				}
924 				// Ignore it and leave the new list empty.
925 				return NO_PACKED_REFS;
926 			}
927 		}
928 	}
929 
930 	private RefList<Ref> parsePackedRefs(BufferedReader br)
931 			throws IOException {
932 		RefList.Builder<Ref> all = new RefList.Builder<>();
933 		Ref last = null;
934 		boolean peeled = false;
935 		boolean needSort = false;
936 
937 		String p;
938 		while ((p = br.readLine()) != null) {
939 			if (p.charAt(0) == '#') {
940 				if (p.startsWith(PACKED_REFS_HEADER)) {
941 					p = p.substring(PACKED_REFS_HEADER.length());
942 					peeled = p.contains(PACKED_REFS_PEELED);
943 				}
944 				continue;
945 			}
946 
947 			if (p.charAt(0) == '^') {
948 				if (last == null)
949 					throw new IOException(JGitText.get().peeledLineBeforeRef);
950 
951 				ObjectId id = ObjectId.fromString(p.substring(1));
952 				last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
953 						.getObjectId(), id);
954 				all.set(all.size() - 1, last);
955 				continue;
956 			}
957 
958 			int sp = p.indexOf(' ');
959 			if (sp < 0) {
960 				throw new IOException(MessageFormat.format(
961 						JGitText.get().packedRefsCorruptionDetected,
962 						packedRefsFile.getAbsolutePath()));
963 			}
964 			ObjectId id = ObjectId.fromString(p.substring(0, sp));
965 			String name = copy(p, sp + 1, p.length());
966 			ObjectIdRef cur;
967 			if (peeled)
968 				cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
969 			else
970 				cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
971 			if (last != null && RefComparator.compareTo(last, cur) > 0)
972 				needSort = true;
973 			all.add(cur);
974 			last = cur;
975 		}
976 
977 		if (needSort)
978 			all.sort();
979 		return all.toRefList();
980 	}
981 
982 	private static String copy(String src, int off, int end) {
983 		// Don't use substring since it could leave a reference to the much
984 		// larger existing string. Force construction of a full new object.
985 		return new StringBuilder(end - off).append(src, off, end).toString();
986 	}
987 
988 	PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
989 			final PackedRefList oldPackedList, boolean changed)
990 			throws IOException {
991 		// Can't just return packedRefs.get() from this method; it might have been
992 		// updated again after writePackedRefs() returns.
993 		AtomicReference<PackedRefList> result = new AtomicReference<>();
994 		new RefWriter(refs) {
995 			@Override
996 			protected void writeFile(String name, byte[] content)
997 					throws IOException {
998 				lck.setFSync(true);
999 				lck.setNeedSnapshot(true);
1000 				try {
1001 					lck.write(content);
1002 				} catch (IOException ioe) {
1003 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
1004 				}
1005 				try {
1006 					lck.waitForStatChange();
1007 				} catch (InterruptedException e) {
1008 					lck.unlock();
1009 					throw new ObjectWritingException(
1010 							MessageFormat.format(
1011 									JGitText.get().interruptedWriting, name),
1012 							e);
1013 				}
1014 				if (!lck.commit())
1015 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
1016 
1017 				byte[] digest = Constants.newMessageDigest().digest(content);
1018 				PackedRefList newPackedList = new PackedRefList(
1019 						refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
1020 
1021 				// This thread holds the file lock, so no other thread or process should
1022 				// be able to modify the packed-refs file on disk. If the list changed,
1023 				// it means something is very wrong, so throw an exception.
1024 				//
1025 				// However, we can't use a naive compareAndSet to check whether the
1026 				// update was successful, because another thread might _read_ the
1027 				// packed refs file that was written out by this thread while holding
1028 				// the lock, and update the packedRefs reference to point to that. So
1029 				// compare the actual contents instead.
1030 				PackedRefList afterUpdate = packedRefs.updateAndGet(
1031 						p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
1032 				if (!afterUpdate.id.equals(newPackedList.id)) {
1033 					throw new ObjectWritingException(
1034 							MessageFormat.format(JGitText.get().unableToWrite, name));
1035 				}
1036 				if (changed) {
1037 					modCnt.incrementAndGet();
1038 				}
1039 				result.set(newPackedList);
1040 			}
1041 		}.writePackedRefs();
1042 		return result.get();
1043 	}
1044 
1045 	private Ref readRef(String name, RefList<Ref> packed) throws IOException {
1046 		final RefList<LooseRef> curList = looseRefs.get();
1047 		final int idx = curList.find(name);
1048 		if (0 <= idx) {
1049 			final LooseRef o = curList.get(idx);
1050 			final LooseRef n = scanRef(o, name);
1051 			if (n == null) {
1052 				if (looseRefs.compareAndSet(curList, curList.remove(idx)))
1053 					modCnt.incrementAndGet();
1054 				return packed.get(name);
1055 			}
1056 
1057 			if (o == n)
1058 				return n;
1059 			if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
1060 				modCnt.incrementAndGet();
1061 			return n;
1062 		}
1063 
1064 		final LooseRef n = scanRef(null, name);
1065 		if (n == null)
1066 			return packed.get(name);
1067 
1068 		// check whether the found new ref is the an additional ref. These refs
1069 		// should not go into looseRefs
1070 		for (String additionalRefsName : additionalRefsNames) {
1071 			if (name.equals(additionalRefsName)) {
1072 				return n;
1073 			}
1074 		}
1075 
1076 		if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
1077 			modCnt.incrementAndGet();
1078 		return n;
1079 	}
1080 
1081 	LooseRef scanRef(LooseRef ref, String name) throws IOException {
1082 		final File path = fileFor(name);
1083 		FileSnapshot currentSnapshot = null;
1084 
1085 		if (ref != null) {
1086 			currentSnapshot = ref.getSnapShot();
1087 			if (!currentSnapshot.isModified(path))
1088 				return ref;
1089 			name = ref.getName();
1090 		}
1091 
1092 		final int limit = 4096;
1093 		final byte[] buf;
1094 		FileSnapshot otherSnapshot = FileSnapshot.save(path);
1095 		try {
1096 			buf = IO.readSome(path, limit);
1097 		} catch (FileNotFoundException noFile) {
1098 			if (path.exists() && path.isFile()) {
1099 				throw noFile;
1100 			}
1101 			return null; // doesn't exist or no file; not a reference.
1102 		}
1103 
1104 		int n = buf.length;
1105 		if (n == 0)
1106 			return null; // empty file; not a reference.
1107 
1108 		if (isSymRef(buf, n)) {
1109 			if (n == limit)
1110 				return null; // possibly truncated ref
1111 
1112 			// trim trailing whitespace
1113 			while (0 < n && Character.isWhitespace(buf[n - 1]))
1114 				n--;
1115 			if (n < 6) {
1116 				String content = RawParseUtils.decode(buf, 0, n);
1117 				throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
1118 			}
1119 			final String target = RawParseUtils.decode(buf, 5, n);
1120 			if (ref != null && ref.isSymbolic()
1121 					&& ref.getTarget().getName().equals(target)) {
1122 				assert(currentSnapshot != null);
1123 				currentSnapshot.setClean(otherSnapshot);
1124 				return ref;
1125 			}
1126 			return newSymbolicRef(otherSnapshot, name, target);
1127 		}
1128 
1129 		if (n < OBJECT_ID_STRING_LENGTH)
1130 			return null; // impossibly short object identifier; not a reference.
1131 
1132 		final ObjectId id;
1133 		try {
1134 			id = ObjectId.fromString(buf, 0);
1135 			if (ref != null && !ref.isSymbolic()
1136 					&& id.equals(ref.getTarget().getObjectId())) {
1137 				assert(currentSnapshot != null);
1138 				currentSnapshot.setClean(otherSnapshot);
1139 				return ref;
1140 			}
1141 
1142 		} catch (IllegalArgumentException notRef) {
1143 			while (0 < n && Character.isWhitespace(buf[n - 1]))
1144 				n--;
1145 			String content = RawParseUtils.decode(buf, 0, n);
1146 
1147 			throw new IOException(MessageFormat.format(JGitText.get().notARef,
1148 					name, content), notRef);
1149 		}
1150 		return new LooseUnpeeled(otherSnapshot, name, id);
1151 	}
1152 
1153 	private static boolean isSymRef(byte[] buf, int n) {
1154 		if (n < 6)
1155 			return false;
1156 		return /**/buf[0] == 'r' //
1157 				&& buf[1] == 'e' //
1158 				&& buf[2] == 'f' //
1159 				&& buf[3] == ':' //
1160 				&& buf[4] == ' ';
1161 	}
1162 
1163 	/**
1164 	 * Detect if we are in a clone command execution
1165 	 *
1166 	 * @return {@code true} if we are currently cloning a repository
1167 	 * @throws IOException
1168 	 */
1169 	boolean isInClone() throws IOException {
1170 		return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
1171 	}
1172 
1173 	private boolean hasDanglingHead() throws IOException {
1174 		Ref head = exactRef(Constants.HEAD);
1175 		if (head != null) {
1176 			ObjectId id = head.getObjectId();
1177 			return id == null || id.equals(ObjectId.zeroId());
1178 		}
1179 		return false;
1180 	}
1181 
1182 	private boolean hasLooseRef() throws IOException {
1183 		try (Stream<Path> stream = Files.walk(refsDir.toPath())) {
1184 			return stream.anyMatch(Files::isRegularFile);
1185 		}
1186 	}
1187 
1188 	/** If the parent should fire listeners, fires them. */
1189 	void fireRefsChanged() {
1190 		final int last = lastNotifiedModCnt.get();
1191 		final int curr = modCnt.get();
1192 		if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1193 			parent.fireEvent(new RefsChangedEvent());
1194 	}
1195 
1196 	/**
1197 	 * Create a reference update to write a temporary reference.
1198 	 *
1199 	 * @return an update for a new temporary reference.
1200 	 * @throws IOException
1201 	 *             a temporary name cannot be allocated.
1202 	 */
1203 	RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1204 		File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$
1205 		String name = Constants.R_REFS + tmp.getName();
1206 		Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1207 		return new RefDirectoryUpdate(this, ref);
1208 	}
1209 
1210 	/**
1211 	 * Locate the file on disk for a single reference name.
1212 	 *
1213 	 * @param name
1214 	 *            name of the ref, relative to the Git repository top level
1215 	 *            directory (so typically starts with refs/).
1216 	 * @return the loose file location.
1217 	 */
1218 	File fileFor(String name) {
1219 		if (name.startsWith(R_REFS)) {
1220 			name = name.substring(R_REFS.length());
1221 			return new File(refsDir, name);
1222 		}
1223 		return new File(gitDir, name);
1224 	}
1225 
1226 	static int levelsIn(String name) {
1227 		int count = 0;
1228 		for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1229 			count++;
1230 		return count;
1231 	}
1232 
1233 	static void delete(File file, int depth) throws IOException {
1234 		delete(file, depth, null);
1235 	}
1236 
1237 	private static void delete(File file, int depth, LockFile rLck)
1238 			throws IOException {
1239 		if (!file.delete() && file.isFile()) {
1240 			throw new IOException(MessageFormat.format(
1241 					JGitText.get().fileCannotBeDeleted, file));
1242 		}
1243 
1244 		if (rLck != null) {
1245 			rLck.unlock(); // otherwise cannot delete dir below
1246 		}
1247 		File dir = file.getParentFile();
1248 		for (int i = 0; i < depth; ++i) {
1249 			try {
1250 				Files.deleteIfExists(dir.toPath());
1251 			} catch (DirectoryNotEmptyException e) {
1252 				// Don't log; normal case when there are other refs with the
1253 				// same prefix
1254 				break;
1255 			} catch (IOException e) {
1256 				LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath,
1257 						dir), e);
1258 				break;
1259 			}
1260 			dir = dir.getParentFile();
1261 		}
1262 	}
1263 
1264 	/**
1265 	 * Get times to sleep while retrying a possibly contentious operation.
1266 	 * <p>
1267 	 * For retrying an operation that might have high contention, such as locking
1268 	 * the {@code packed-refs} file, the caller may implement a retry loop using
1269 	 * the returned values:
1270 	 *
1271 	 * <pre>
1272 	 * for (int toSleepMs : getRetrySleepMs()) {
1273 	 *   sleep(toSleepMs);
1274 	 *   if (isSuccessful(doSomething())) {
1275 	 *     return success;
1276 	 *   }
1277 	 * }
1278 	 * return failure;
1279 	 * </pre>
1280 	 *
1281 	 * The first value in the returned iterable is 0, and the caller should treat
1282 	 * a fully-consumed iterator as a timeout.
1283 	 *
1284 	 * @return iterable of times, in milliseconds, that the caller should sleep
1285 	 *         before attempting an operation.
1286 	 */
1287 	Iterable<Integer> getRetrySleepMs() {
1288 		return retrySleepMs;
1289 	}
1290 
1291 	void setRetrySleepMs(List<Integer> retrySleepMs) {
1292 		if (retrySleepMs == null || retrySleepMs.isEmpty()
1293 				|| retrySleepMs.get(0).intValue() != 0) {
1294 			throw new IllegalArgumentException();
1295 		}
1296 		this.retrySleepMs = retrySleepMs;
1297 	}
1298 
1299 	/**
1300 	 * Sleep with {@link Thread#sleep(long)}, converting {@link
1301 	 * InterruptedException} to {@link InterruptedIOException}.
1302 	 *
1303 	 * @param ms
1304 	 *            time to sleep, in milliseconds; zero or negative is a no-op.
1305 	 * @throws InterruptedIOException
1306 	 *             if sleeping was interrupted.
1307 	 */
1308 	static void sleep(long ms) throws InterruptedIOException {
1309 		if (ms <= 0) {
1310 			return;
1311 		}
1312 		try {
1313 			Thread.sleep(ms);
1314 		} catch (InterruptedException e) {
1315 			InterruptedIOException ie = new InterruptedIOException();
1316 			ie.initCause(e);
1317 			throw ie;
1318 		}
1319 	}
1320 
1321 	static class PackedRefList extends RefList<Ref> {
1322 
1323 		private final FileSnapshot snapshot;
1324 
1325 		private final ObjectId id;
1326 
1327 		private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1328 			super(src);
1329 			snapshot = s;
1330 			id = i;
1331 		}
1332 	}
1333 
1334 	private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1335 			RefList.emptyList(), FileSnapshot.MISSING_FILE,
1336 			ObjectId.zeroId());
1337 
1338 	private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1339 			String name, String target) {
1340 		Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1341 		return new LooseSymbolicRef(snapshot, name, dst);
1342 	}
1343 
1344 	private static interface LooseRef extends Ref {
1345 		FileSnapshot getSnapShot();
1346 
1347 		LooseRef peel(ObjectIdRef newLeaf);
1348 	}
1349 
1350 	private static final class LoosePeeledTag extends ObjectIdRef.PeeledTag
1351 			implements LooseRef {
1352 		private final FileSnapshot snapShot;
1353 
1354 		LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1355 				@NonNull ObjectId id, @NonNull ObjectId p) {
1356 			super(LOOSE, refName, id, p);
1357 			this.snapShot = snapshot;
1358 		}
1359 
1360 		@Override
1361 		public FileSnapshot getSnapShot() {
1362 			return snapShot;
1363 		}
1364 
1365 		@Override
1366 		public LooseRef peel(ObjectIdRef newLeaf) {
1367 			return this;
1368 		}
1369 	}
1370 
1371 	private static final class LooseNonTag extends ObjectIdRef.PeeledNonTag
1372 			implements LooseRef {
1373 		private final FileSnapshot snapShot;
1374 
1375 		LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1376 				@NonNull ObjectId id) {
1377 			super(LOOSE, refName, id);
1378 			this.snapShot = snapshot;
1379 		}
1380 
1381 		@Override
1382 		public FileSnapshot getSnapShot() {
1383 			return snapShot;
1384 		}
1385 
1386 		@Override
1387 		public LooseRef peel(ObjectIdRef newLeaf) {
1388 			return this;
1389 		}
1390 	}
1391 
1392 	private static final class LooseUnpeeled extends ObjectIdRef.Unpeeled
1393 			implements LooseRef {
1394 		private FileSnapshot snapShot;
1395 
1396 		LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1397 				@NonNull ObjectId id) {
1398 			super(LOOSE, refName, id);
1399 			this.snapShot = snapShot;
1400 		}
1401 
1402 		@Override
1403 		public FileSnapshot getSnapShot() {
1404 			return snapShot;
1405 		}
1406 
1407 		@NonNull
1408 		@Override
1409 		public ObjectId getObjectId() {
1410 			ObjectId id = super.getObjectId();
1411 			assert id != null; // checked in constructor
1412 			return id;
1413 		}
1414 
1415 		@Override
1416 		public LooseRef peel(ObjectIdRef newLeaf) {
1417 			ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1418 			ObjectId objectId = getObjectId();
1419 			if (peeledObjectId != null) {
1420 				return new LoosePeeledTag(snapShot, getName(),
1421 						objectId, peeledObjectId);
1422 			}
1423 			return new LooseNonTag(snapShot, getName(), objectId);
1424 		}
1425 	}
1426 
1427 	private static final class LooseSymbolicRef extends SymbolicRef implements
1428 			LooseRef {
1429 		private final FileSnapshot snapShot;
1430 
1431 		LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1432 				@NonNull Ref target) {
1433 			super(refName, target);
1434 			this.snapShot = snapshot;
1435 		}
1436 
1437 		@Override
1438 		public FileSnapshot getSnapShot() {
1439 			return snapShot;
1440 		}
1441 
1442 		@Override
1443 		public LooseRef peel(ObjectIdRef newLeaf) {
1444 			// We should never try to peel the symbolic references.
1445 			throw new UnsupportedOperationException();
1446 		}
1447 	}
1448 }