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