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