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