RefDirectory.java

  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. package org.eclipse.jgit.internal.storage.file;

  14. import static java.nio.charset.StandardCharsets.UTF_8;
  15. import static org.eclipse.jgit.lib.Constants.HEAD;
  16. import static org.eclipse.jgit.lib.Constants.LOGS;
  17. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
  18. import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
  19. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  20. import static org.eclipse.jgit.lib.Constants.R_REFS;
  21. import static org.eclipse.jgit.lib.Constants.R_TAGS;
  22. import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
  23. import static org.eclipse.jgit.lib.Ref.Storage.NEW;
  24. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;

  25. import java.io.BufferedReader;
  26. import java.io.File;
  27. import java.io.FileInputStream;
  28. import java.io.IOException;
  29. import java.io.InputStreamReader;
  30. import java.io.InterruptedIOException;
  31. import java.nio.file.DirectoryNotEmptyException;
  32. import java.nio.file.Files;
  33. import java.nio.file.Path;
  34. import java.security.DigestInputStream;
  35. import java.security.MessageDigest;
  36. import java.text.MessageFormat;
  37. import java.util.Arrays;
  38. import java.util.Collection;
  39. import java.util.Collections;
  40. import java.util.HashMap;
  41. import java.util.LinkedList;
  42. import java.util.List;
  43. import java.util.Map;
  44. import java.util.concurrent.atomic.AtomicInteger;
  45. import java.util.concurrent.atomic.AtomicReference;
  46. import java.util.concurrent.locks.ReentrantLock;
  47. import java.util.stream.Stream;

  48. import org.eclipse.jgit.annotations.NonNull;
  49. import org.eclipse.jgit.annotations.Nullable;
  50. import org.eclipse.jgit.errors.InvalidObjectIdException;
  51. import org.eclipse.jgit.errors.LockFailedException;
  52. import org.eclipse.jgit.errors.MissingObjectException;
  53. import org.eclipse.jgit.errors.ObjectWritingException;
  54. import org.eclipse.jgit.events.RefsChangedEvent;
  55. import org.eclipse.jgit.internal.JGitText;
  56. import org.eclipse.jgit.lib.ConfigConstants;
  57. import org.eclipse.jgit.lib.Constants;
  58. import org.eclipse.jgit.lib.ObjectId;
  59. import org.eclipse.jgit.lib.ObjectIdRef;
  60. import org.eclipse.jgit.lib.Ref;
  61. import org.eclipse.jgit.lib.RefComparator;
  62. import org.eclipse.jgit.lib.RefDatabase;
  63. import org.eclipse.jgit.lib.RefUpdate;
  64. import org.eclipse.jgit.lib.RefWriter;
  65. import org.eclipse.jgit.lib.Repository;
  66. import org.eclipse.jgit.lib.SymbolicRef;
  67. import org.eclipse.jgit.revwalk.RevObject;
  68. import org.eclipse.jgit.revwalk.RevTag;
  69. import org.eclipse.jgit.revwalk.RevWalk;
  70. import org.eclipse.jgit.util.FS;
  71. import org.eclipse.jgit.util.FileUtils;
  72. import org.eclipse.jgit.util.IO;
  73. import org.eclipse.jgit.util.RawParseUtils;
  74. import org.eclipse.jgit.util.RefList;
  75. import org.eclipse.jgit.util.RefMap;
  76. import org.slf4j.Logger;
  77. import org.slf4j.LoggerFactory;

  78. /**
  79.  * Traditional file system based {@link org.eclipse.jgit.lib.RefDatabase}.
  80.  * <p>
  81.  * This is the classical reference database representation for a Git repository.
  82.  * References are stored in two formats: loose, and packed.
  83.  * <p>
  84.  * Loose references are stored as individual files within the {@code refs/}
  85.  * directory. The file name matches the reference name and the file contents is
  86.  * the current {@link org.eclipse.jgit.lib.ObjectId} in string form.
  87.  * <p>
  88.  * Packed references are stored in a single text file named {@code packed-refs}.
  89.  * In the packed format, each reference is stored on its own line. This file
  90.  * reduces the number of files needed for large reference spaces, reducing the
  91.  * overall size of a Git repository on disk.
  92.  */
  93. public class RefDirectory extends RefDatabase {
  94.     private static final Logger LOG = LoggerFactory
  95.             .getLogger(RefDirectory.class);

  96.     /** Magic string denoting the start of a symbolic reference file. */
  97.     public static final String SYMREF = "ref: "; //$NON-NLS-1$

  98.     /** Magic string denoting the header of a packed-refs file. */
  99.     public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$

  100.     /** If in the header, denotes the file has peeled data. */
  101.     public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$

  102.     /** The names of the additional refs supported by this class */
  103.     private static final String[] additionalRefsNames = new String[] {
  104.             Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
  105.             Constants.CHERRY_PICK_HEAD };

  106.     @SuppressWarnings("boxing")
  107.     private static final List<Integer> RETRY_SLEEP_MS =
  108.             Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));

  109.     private final FileRepository parent;

  110.     private final File gitDir;

  111.     final File refsDir;

  112.     final File packedRefsFile;

  113.     final File logsDir;

  114.     final File logsRefsDir;

  115.     /**
  116.      * Immutable sorted list of loose references.
  117.      * <p>
  118.      * Symbolic references in this collection are stored unresolved, that is
  119.      * their target appears to be a new reference with no ObjectId. These are
  120.      * converted into resolved references during a get operation, ensuring the
  121.      * live value is always returned.
  122.      */
  123.     private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();

  124.     /** Immutable sorted list of packed references. */
  125.     final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();

  126.     /**
  127.      * Lock for coordinating operations within a single process that may contend
  128.      * on the {@code packed-refs} file.
  129.      * <p>
  130.      * All operations that write {@code packed-refs} must still acquire a
  131.      * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
  132.      * this lock, since there may be multiple {@link RefDirectory} instances or
  133.      * other processes operating on the same repo on disk.
  134.      * <p>
  135.      * This lock exists so multiple threads in the same process can wait in a fair
  136.      * queue without trying, failing, and retrying to acquire the on-disk lock. If
  137.      * {@code RepositoryCache} is used, this lock instance will be used by all
  138.      * threads.
  139.      */
  140.     final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);

  141.     /**
  142.      * Number of modifications made to this database.
  143.      * <p>
  144.      * This counter is incremented when a change is made, or detected from the
  145.      * filesystem during a read operation.
  146.      */
  147.     private final AtomicInteger modCnt = new AtomicInteger();

  148.     /**
  149.      * Last {@link #modCnt} that we sent to listeners.
  150.      * <p>
  151.      * This value is compared to {@link #modCnt}, and a notification is sent to
  152.      * the listeners only when it differs.
  153.      */
  154.     private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();

  155.     private List<Integer> retrySleepMs = RETRY_SLEEP_MS;

  156.     RefDirectory(FileRepository db) {
  157.         final FS fs = db.getFS();
  158.         parent = db;
  159.         gitDir = db.getDirectory();
  160.         refsDir = fs.resolve(gitDir, R_REFS);
  161.         logsDir = fs.resolve(gitDir, LOGS);
  162.         logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
  163.         packedRefsFile = fs.resolve(gitDir, PACKED_REFS);

  164.         looseRefs.set(RefList.<LooseRef> emptyList());
  165.         packedRefs.set(NO_PACKED_REFS);
  166.     }

  167.     Repository getRepository() {
  168.         return parent;
  169.     }

  170.     ReflogWriter newLogWriter(boolean force) {
  171.         return new ReflogWriter(this, force);
  172.     }

  173.     /**
  174.      * Locate the log file on disk for a single reference name.
  175.      *
  176.      * @param name
  177.      *            name of the ref, relative to the Git repository top level
  178.      *            directory (so typically starts with refs/).
  179.      * @return the log file location.
  180.      */
  181.     public File logFor(String name) {
  182.         if (name.startsWith(R_REFS)) {
  183.             name = name.substring(R_REFS.length());
  184.             return new File(logsRefsDir, name);
  185.         }
  186.         return new File(logsDir, name);
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public void create() throws IOException {
  191.         FileUtils.mkdir(refsDir);
  192.         FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
  193.         FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
  194.         newLogWriter(false).create();
  195.     }

  196.     /** {@inheritDoc} */
  197.     @Override
  198.     public void close() {
  199.         clearReferences();
  200.     }

  201.     private void clearReferences() {
  202.         looseRefs.set(RefList.<LooseRef> emptyList());
  203.         packedRefs.set(NO_PACKED_REFS);
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     public void refresh() {
  208.         super.refresh();
  209.         clearReferences();
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     public boolean isNameConflicting(String name) throws IOException {
  214.         // Cannot be nested within an existing reference.
  215.         int lastSlash = name.lastIndexOf('/');
  216.         while (0 < lastSlash) {
  217.             String needle = name.substring(0, lastSlash);
  218.             if (exactRef(needle) != null) {
  219.                 return true;
  220.             }
  221.             lastSlash = name.lastIndexOf('/', lastSlash - 1);
  222.         }

  223.         // Cannot be the container of an existing reference.
  224.         return !getRefsByPrefix(name + '/').isEmpty();
  225.     }

  226.     @Nullable
  227.     private Ref readAndResolve(String name, RefList<Ref> packed) throws IOException {
  228.         try {
  229.             Ref ref = readRef(name, packed);
  230.             if (ref != null) {
  231.                 ref = resolve(ref, 0, null, null, packed);
  232.             }
  233.             return ref;
  234.         } catch (IOException e) {
  235.             if (name.contains("/") //$NON-NLS-1$
  236.                     || !(e.getCause() instanceof InvalidObjectIdException)) {
  237.                 throw e;
  238.             }

  239.             // While looking for a ref outside of refs/ (e.g., 'config'), we
  240.             // found a non-ref file (e.g., a config file) instead.  Treat this
  241.             // as a ref-not-found condition.
  242.             return null;
  243.         }
  244.     }

  245.     /** {@inheritDoc} */
  246.     @Override
  247.     public Ref exactRef(String name) throws IOException {
  248.         try {
  249.             return readAndResolve(name, getPackedRefs());
  250.         } finally {
  251.             fireRefsChanged();
  252.         }
  253.     }

  254.     /** {@inheritDoc} */
  255.     @Override
  256.     @NonNull
  257.     public Map<String, Ref> exactRef(String... refs) throws IOException {
  258.         try {
  259.             RefList<Ref> packed = getPackedRefs();
  260.             Map<String, Ref> result = new HashMap<>(refs.length);
  261.             for (String name : refs) {
  262.                 Ref ref = readAndResolve(name, packed);
  263.                 if (ref != null) {
  264.                     result.put(name, ref);
  265.                 }
  266.             }
  267.             return result;
  268.         } finally {
  269.             fireRefsChanged();
  270.         }
  271.     }

  272.     /** {@inheritDoc} */
  273.     @Override
  274.     @Nullable
  275.     public Ref firstExactRef(String... refs) throws IOException {
  276.         try {
  277.             RefList<Ref> packed = getPackedRefs();
  278.             for (String name : refs) {
  279.                 Ref ref = readAndResolve(name, packed);
  280.                 if (ref != null) {
  281.                     return ref;
  282.                 }
  283.             }
  284.             return null;
  285.         } finally {
  286.             fireRefsChanged();
  287.         }
  288.     }

  289.     /** {@inheritDoc} */
  290.     @Override
  291.     public Map<String, Ref> getRefs(String prefix) throws IOException {
  292.         final RefList<LooseRef> oldLoose = looseRefs.get();
  293.         LooseScanner scan = new LooseScanner(oldLoose);
  294.         scan.scan(prefix);
  295.         final RefList<Ref> packed = getPackedRefs();

  296.         RefList<LooseRef> loose;
  297.         if (scan.newLoose != null) {
  298.             scan.newLoose.sort();
  299.             loose = scan.newLoose.toRefList();
  300.             if (looseRefs.compareAndSet(oldLoose, loose))
  301.                 modCnt.incrementAndGet();
  302.         } else
  303.             loose = oldLoose;
  304.         fireRefsChanged();

  305.         RefList.Builder<Ref> symbolic = scan.symbolic;
  306.         for (int idx = 0; idx < symbolic.size();) {
  307.             final Ref symbolicRef = symbolic.get(idx);
  308.             final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
  309.             if (resolvedRef != null && resolvedRef.getObjectId() != null) {
  310.                 symbolic.set(idx, resolvedRef);
  311.                 idx++;
  312.             } else {
  313.                 // A broken symbolic reference, we have to drop it from the
  314.                 // collections the client is about to receive. Should be a
  315.                 // rare occurrence so pay a copy penalty.
  316.                 symbolic.remove(idx);
  317.                 final int toRemove = loose.find(symbolicRef.getName());
  318.                 if (0 <= toRemove)
  319.                     loose = loose.remove(toRemove);
  320.             }
  321.         }
  322.         symbolic.sort();

  323.         return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
  324.     }

  325.     /** {@inheritDoc} */
  326.     @Override
  327.     public List<Ref> getAdditionalRefs() throws IOException {
  328.         List<Ref> ret = new LinkedList<>();
  329.         for (String name : additionalRefsNames) {
  330.             Ref r = exactRef(name);
  331.             if (r != null)
  332.                 ret.add(r);
  333.         }
  334.         return ret;
  335.     }

  336.     @SuppressWarnings("unchecked")
  337.     private RefList<Ref> upcast(RefList<? extends Ref> loose) {
  338.         return (RefList<Ref>) loose;
  339.     }

  340.     private class LooseScanner {
  341.         private final RefList<LooseRef> curLoose;

  342.         private int curIdx;

  343.         final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);

  344.         RefList.Builder<LooseRef> newLoose;

  345.         LooseScanner(RefList<LooseRef> curLoose) {
  346.             this.curLoose = curLoose;
  347.         }

  348.         void scan(String prefix) {
  349.             if (ALL.equals(prefix)) {
  350.                 scanOne(HEAD);
  351.                 scanTree(R_REFS, refsDir);

  352.                 // If any entries remain, they are deleted, drop them.
  353.                 if (newLoose == null && curIdx < curLoose.size())
  354.                     newLoose = curLoose.copy(curIdx);

  355.             } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { //$NON-NLS-1$
  356.                 curIdx = -(curLoose.find(prefix) + 1);
  357.                 File dir = new File(refsDir, prefix.substring(R_REFS.length()));
  358.                 scanTree(prefix, dir);

  359.                 // Skip over entries still within the prefix; these have
  360.                 // been removed from the directory.
  361.                 while (curIdx < curLoose.size()) {
  362.                     if (!curLoose.get(curIdx).getName().startsWith(prefix))
  363.                         break;
  364.                     if (newLoose == null)
  365.                         newLoose = curLoose.copy(curIdx);
  366.                     curIdx++;
  367.                 }

  368.                 // Keep any entries outside of the prefix space, we
  369.                 // do not know anything about their status.
  370.                 if (newLoose != null) {
  371.                     while (curIdx < curLoose.size())
  372.                         newLoose.add(curLoose.get(curIdx++));
  373.                 }
  374.             }
  375.         }

  376.         private boolean scanTree(String prefix, File dir) {
  377.             final String[] entries = dir.list(LockFile.FILTER);
  378.             if (entries == null) // not a directory or an I/O error
  379.                 return false;
  380.             if (0 < entries.length) {
  381.                 for (int i = 0; i < entries.length; ++i) {
  382.                     String e = entries[i];
  383.                     File f = new File(dir, e);
  384.                     if (f.isDirectory())
  385.                         entries[i] += '/';
  386.                 }
  387.                 Arrays.sort(entries);
  388.                 for (String name : entries) {
  389.                     if (name.charAt(name.length() - 1) == '/')
  390.                         scanTree(prefix + name, new File(dir, name));
  391.                     else
  392.                         scanOne(prefix + name);
  393.                 }
  394.             }
  395.             return true;
  396.         }

  397.         private void scanOne(String name) {
  398.             LooseRef cur;

  399.             if (curIdx < curLoose.size()) {
  400.                 do {
  401.                     cur = curLoose.get(curIdx);
  402.                     int cmp = RefComparator.compareTo(cur, name);
  403.                     if (cmp < 0) {
  404.                         // Reference is not loose anymore, its been deleted.
  405.                         // Skip the name in the new result list.
  406.                         if (newLoose == null)
  407.                             newLoose = curLoose.copy(curIdx);
  408.                         curIdx++;
  409.                         cur = null;
  410.                         continue;
  411.                     }

  412.                     if (cmp > 0) // Newly discovered loose reference.
  413.                         cur = null;
  414.                     break;
  415.                 } while (curIdx < curLoose.size());
  416.             } else
  417.                 cur = null; // Newly discovered loose reference.

  418.             LooseRef n;
  419.             try {
  420.                 n = scanRef(cur, name);
  421.             } catch (IOException notValid) {
  422.                 n = null;
  423.             }

  424.             if (n != null) {
  425.                 if (cur != n && newLoose == null)
  426.                     newLoose = curLoose.copy(curIdx);
  427.                 if (newLoose != null)
  428.                     newLoose.add(n);
  429.                 if (n.isSymbolic())
  430.                     symbolic.add(n);
  431.             } else if (cur != null) {
  432.                 // Tragically, this file is no longer a loose reference.
  433.                 // Kill our cached entry of it.
  434.                 if (newLoose == null)
  435.                     newLoose = curLoose.copy(curIdx);
  436.             }

  437.             if (cur != null)
  438.                 curIdx++;
  439.         }
  440.     }

  441.     /** {@inheritDoc} */
  442.     @Override
  443.     public Ref peel(Ref ref) throws IOException {
  444.         final Ref leaf = ref.getLeaf();
  445.         if (leaf.isPeeled() || leaf.getObjectId() == null)
  446.             return ref;

  447.         ObjectIdRef newLeaf = doPeel(leaf);

  448.         // Try to remember this peeling in the cache, so we don't have to do
  449.         // it again in the future, but only if the reference is unchanged.
  450.         if (leaf.getStorage().isLoose()) {
  451.             RefList<LooseRef> curList = looseRefs.get();
  452.             int idx = curList.find(leaf.getName());
  453.             if (0 <= idx && curList.get(idx) == leaf) {
  454.                 LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
  455.                 RefList<LooseRef> newList = curList.set(idx, asPeeled);
  456.                 looseRefs.compareAndSet(curList, newList);
  457.             }
  458.         }

  459.         return recreate(ref, newLeaf);
  460.     }

  461.     private ObjectIdRef doPeel(Ref leaf) throws MissingObjectException,
  462.             IOException {
  463.         try (RevWalk rw = new RevWalk(getRepository())) {
  464.             RevObject obj = rw.parseAny(leaf.getObjectId());
  465.             if (obj instanceof RevTag) {
  466.                 return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
  467.                         .getName(), leaf.getObjectId(), rw.peel(obj).copy());
  468.             }
  469.             return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
  470.                     leaf.getName(), leaf.getObjectId());
  471.         }
  472.     }

  473.     private static Ref recreate(Ref old, ObjectIdRef leaf) {
  474.         if (old.isSymbolic()) {
  475.             Ref dst = recreate(old.getTarget(), leaf);
  476.             return new SymbolicRef(old.getName(), dst);
  477.         }
  478.         return leaf;
  479.     }

  480.     void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
  481.             String target) {
  482.         putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
  483.         fireRefsChanged();
  484.     }

  485.     /** {@inheritDoc} */
  486.     @Override
  487.     public RefDirectoryUpdate newUpdate(String name, boolean detach)
  488.             throws IOException {
  489.         boolean detachingSymbolicRef = false;
  490.         final RefList<Ref> packed = getPackedRefs();
  491.         Ref ref = readRef(name, packed);
  492.         if (ref != null)
  493.             ref = resolve(ref, 0, null, null, packed);
  494.         if (ref == null)
  495.             ref = new ObjectIdRef.Unpeeled(NEW, name, null);
  496.         else {
  497.             detachingSymbolicRef = detach && ref.isSymbolic();
  498.         }
  499.         RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
  500.         if (detachingSymbolicRef)
  501.             refDirUpdate.setDetachingSymbolicRef();
  502.         return refDirUpdate;
  503.     }

  504.     /** {@inheritDoc} */
  505.     @Override
  506.     public RefDirectoryRename newRename(String fromName, String toName)
  507.             throws IOException {
  508.         RefDirectoryUpdate from = newUpdate(fromName, false);
  509.         RefDirectoryUpdate to = newUpdate(toName, false);
  510.         return new RefDirectoryRename(from, to);
  511.     }

  512.     /** {@inheritDoc} */
  513.     @Override
  514.     public PackedBatchRefUpdate newBatchUpdate() {
  515.         return new PackedBatchRefUpdate(this);
  516.     }

  517.     /**
  518.      * Create a new batch update to attempt on this database.
  519.      *
  520.      * @param shouldLockLooseRefs
  521.      *            whether loose refs should be locked during the batch ref
  522.      *            update. Note that this should only be set to {@code false} if
  523.      *            the application using this ensures that no other ref updates
  524.      *            run concurrently to avoid lost updates caused by a race. In
  525.      *            such cases it can improve performance.
  526.      * @return a new batch update object
  527.      */
  528.     public PackedBatchRefUpdate newBatchUpdate(boolean shouldLockLooseRefs) {
  529.         return new PackedBatchRefUpdate(this, shouldLockLooseRefs);
  530.     }

  531.     /** {@inheritDoc} */
  532.     @Override
  533.     public boolean performsAtomicTransactions() {
  534.         return true;
  535.     }

  536.     void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
  537.         final ObjectId target = update.getNewObjectId().copy();
  538.         final Ref leaf = update.getRef().getLeaf();
  539.         putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
  540.     }

  541.     private void putLooseRef(LooseRef ref) {
  542.         RefList<LooseRef> cList, nList;
  543.         do {
  544.             cList = looseRefs.get();
  545.             nList = cList.put(ref);
  546.         } while (!looseRefs.compareAndSet(cList, nList));
  547.         modCnt.incrementAndGet();
  548.         fireRefsChanged();
  549.     }

  550.     void delete(RefDirectoryUpdate update) throws IOException {
  551.         Ref dst = update.getRef();
  552.         if (!update.isDetachingSymbolicRef()) {
  553.             dst = dst.getLeaf();
  554.         }
  555.         String name = dst.getName();

  556.         // Write the packed-refs file using an atomic update. We might
  557.         // wind up reading it twice, before and after the lock, to ensure
  558.         // we don't miss an edit made externally.
  559.         final PackedRefList packed = getPackedRefs();
  560.         if (packed.contains(name)) {
  561.             inProcessPackedRefsLock.lock();
  562.             try {
  563.                 LockFile lck = lockPackedRefsOrThrow();
  564.                 try {
  565.                     PackedRefList cur = readPackedRefs();
  566.                     int idx = cur.find(name);
  567.                     if (0 <= idx) {
  568.                         commitPackedRefs(lck, cur.remove(idx), packed, true);
  569.                     }
  570.                 } finally {
  571.                     lck.unlock();
  572.                 }
  573.             } finally {
  574.                 inProcessPackedRefsLock.unlock();
  575.             }
  576.         }

  577.         RefList<LooseRef> curLoose, newLoose;
  578.         do {
  579.             curLoose = looseRefs.get();
  580.             int idx = curLoose.find(name);
  581.             if (idx < 0)
  582.                 break;
  583.             newLoose = curLoose.remove(idx);
  584.         } while (!looseRefs.compareAndSet(curLoose, newLoose));

  585.         int levels = levelsIn(name) - 2;
  586.         delete(logFor(name), levels);
  587.         if (dst.getStorage().isLoose()) {
  588.             update.unlock();
  589.             delete(fileFor(name), levels);
  590.         }

  591.         modCnt.incrementAndGet();
  592.         fireRefsChanged();
  593.     }

  594.     /**
  595.      * Adds a set of refs to the set of packed-refs. Only non-symbolic refs are
  596.      * added. If a ref with the given name already existed in packed-refs it is
  597.      * updated with the new value. Each loose ref which was added to the
  598.      * packed-ref file is deleted. If a given ref can't be locked it will not be
  599.      * added to the pack file.
  600.      *
  601.      * @param refs
  602.      *            the refs to be added. Must be fully qualified.
  603.      * @throws java.io.IOException
  604.      */
  605.     public void pack(List<String> refs) throws IOException {
  606.         pack(refs, Collections.emptyMap());
  607.     }

  608.     PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
  609.         return pack(heldLocks.keySet(), heldLocks);
  610.     }

  611.     private PackedRefList pack(Collection<String> refs,
  612.             Map<String, LockFile> heldLocks) throws IOException {
  613.         for (LockFile ol : heldLocks.values()) {
  614.             ol.requireLock();
  615.         }
  616.         if (refs.isEmpty()) {
  617.             return null;
  618.         }
  619.         FS fs = parent.getFS();

  620.         // Lock the packed refs file and read the content
  621.         inProcessPackedRefsLock.lock();
  622.         try {
  623.             LockFile lck = lockPackedRefsOrThrow();
  624.             try {
  625.                 final PackedRefList packed = getPackedRefs();
  626.                 RefList<Ref> cur = readPackedRefs();

  627.                 // Iterate over all refs to be packed
  628.                 boolean dirty = false;
  629.                 for (String refName : refs) {
  630.                     Ref oldRef = readRef(refName, cur);
  631.                     if (oldRef == null) {
  632.                         continue; // A non-existent ref is already correctly packed.
  633.                     }
  634.                     if (oldRef.isSymbolic()) {
  635.                         continue; // can't pack symbolic refs
  636.                     }
  637.                     // Add/Update it to packed-refs
  638.                     Ref newRef = peeledPackedRef(oldRef);
  639.                     if (newRef == oldRef) {
  640.                         // No-op; peeledPackedRef returns the input ref only if it's already
  641.                         // packed, and readRef returns a packed ref only if there is no
  642.                         // loose ref.
  643.                         continue;
  644.                     }

  645.                     dirty = true;
  646.                     int idx = cur.find(refName);
  647.                     if (idx >= 0) {
  648.                         cur = cur.set(idx, newRef);
  649.                     } else {
  650.                         cur = cur.add(idx, newRef);
  651.                     }
  652.                 }
  653.                 if (!dirty) {
  654.                     // All requested refs were already packed accurately
  655.                     return packed;
  656.                 }

  657.                 // The new content for packed-refs is collected. Persist it.
  658.                 PackedRefList result = commitPackedRefs(lck, cur, packed,
  659.                         false);

  660.                 // Now delete the loose refs which are now packed
  661.                 for (String refName : refs) {
  662.                     // Lock the loose ref
  663.                     File refFile = fileFor(refName);
  664.                     if (!fs.exists(refFile)) {
  665.                         continue;
  666.                     }

  667.                     LockFile rLck = heldLocks.get(refName);
  668.                     boolean shouldUnlock;
  669.                     if (rLck == null) {
  670.                         rLck = new LockFile(refFile);
  671.                         if (!rLck.lock()) {
  672.                             continue;
  673.                         }
  674.                         shouldUnlock = true;
  675.                     } else {
  676.                         shouldUnlock = false;
  677.                     }

  678.                     try {
  679.                         LooseRef currentLooseRef = scanRef(null, refName);
  680.                         if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
  681.                             continue;
  682.                         }
  683.                         Ref packedRef = cur.get(refName);
  684.                         ObjectId clr_oid = currentLooseRef.getObjectId();
  685.                         if (clr_oid != null
  686.                                 && clr_oid.equals(packedRef.getObjectId())) {
  687.                             RefList<LooseRef> curLoose, newLoose;
  688.                             do {
  689.                                 curLoose = looseRefs.get();
  690.                                 int idx = curLoose.find(refName);
  691.                                 if (idx < 0) {
  692.                                     break;
  693.                                 }
  694.                                 newLoose = curLoose.remove(idx);
  695.                             } while (!looseRefs.compareAndSet(curLoose, newLoose));
  696.                             int levels = levelsIn(refName) - 2;
  697.                             delete(refFile, levels, rLck);
  698.                         }
  699.                     } finally {
  700.                         if (shouldUnlock) {
  701.                             rLck.unlock();
  702.                         }
  703.                     }
  704.                 }
  705.                 // Don't fire refsChanged. The refs have not change, only their
  706.                 // storage.
  707.                 return result;
  708.             } finally {
  709.                 lck.unlock();
  710.             }
  711.         } finally {
  712.             inProcessPackedRefsLock.unlock();
  713.         }
  714.     }

  715.     @Nullable
  716.     LockFile lockPackedRefs() throws IOException {
  717.         LockFile lck = new LockFile(packedRefsFile);
  718.         for (int ms : getRetrySleepMs()) {
  719.             sleep(ms);
  720.             if (lck.lock()) {
  721.                 return lck;
  722.             }
  723.         }
  724.         return null;
  725.     }

  726.     private LockFile lockPackedRefsOrThrow() throws IOException {
  727.         LockFile lck = lockPackedRefs();
  728.         if (lck == null) {
  729.             throw new LockFailedException(packedRefsFile);
  730.         }
  731.         return lck;
  732.     }

  733.     /**
  734.      * Make sure a ref is peeled and has the Storage PACKED. If the given ref
  735.      * has this attributes simply return it. Otherwise create a new peeled
  736.      * {@link ObjectIdRef} where Storage is set to PACKED.
  737.      *
  738.      * @param f
  739.      * @return a ref for Storage PACKED having the same name, id, peeledId as f
  740.      * @throws MissingObjectException
  741.      * @throws IOException
  742.      */
  743.     private Ref peeledPackedRef(Ref f)
  744.             throws MissingObjectException, IOException {
  745.         if (f.getStorage().isPacked() && f.isPeeled()) {
  746.             return f;
  747.         }
  748.         if (!f.isPeeled()) {
  749.             f = peel(f);
  750.         }
  751.         ObjectId peeledObjectId = f.getPeeledObjectId();
  752.         if (peeledObjectId != null) {
  753.             return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
  754.                     f.getObjectId(), peeledObjectId);
  755.         }
  756.         return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
  757.                 f.getObjectId());
  758.     }

  759.     void log(boolean force, RefUpdate update, String msg, boolean deref)
  760.             throws IOException {
  761.         newLogWriter(force).log(update, msg, deref);
  762.     }

  763.     private Ref resolve(final Ref ref, int depth, String prefix,
  764.             RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
  765.         if (ref.isSymbolic()) {
  766.             Ref dst = ref.getTarget();

  767.             if (MAX_SYMBOLIC_REF_DEPTH <= depth)
  768.                 return null; // claim it doesn't exist

  769.             // If the cached value can be assumed to be current due to a
  770.             // recent scan of the loose directory, use it.
  771.             if (loose != null && dst.getName().startsWith(prefix)) {
  772.                 int idx;
  773.                 if (0 <= (idx = loose.find(dst.getName())))
  774.                     dst = loose.get(idx);
  775.                 else if (0 <= (idx = packed.find(dst.getName())))
  776.                     dst = packed.get(idx);
  777.                 else
  778.                     return ref;
  779.             } else {
  780.                 dst = readRef(dst.getName(), packed);
  781.                 if (dst == null)
  782.                     return ref;
  783.             }

  784.             dst = resolve(dst, depth + 1, prefix, loose, packed);
  785.             if (dst == null)
  786.                 return null;
  787.             return new SymbolicRef(ref.getName(), dst);
  788.         }
  789.         return ref;
  790.     }

  791.     PackedRefList getPackedRefs() throws IOException {
  792.         boolean trustFolderStat = getRepository().getConfig().getBoolean(
  793.                 ConfigConstants.CONFIG_CORE_SECTION,
  794.                 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);

  795.         final PackedRefList curList = packedRefs.get();
  796.         if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
  797.             return curList;
  798.         }

  799.         final PackedRefList newList = readPackedRefs();
  800.         if (packedRefs.compareAndSet(curList, newList)
  801.                 && !curList.id.equals(newList.id)) {
  802.             modCnt.incrementAndGet();
  803.         }
  804.         return newList;
  805.     }

  806.     private PackedRefList readPackedRefs() throws IOException {
  807.         try {
  808.             PackedRefList result = FileUtils.readWithRetries(packedRefsFile,
  809.                     f -> {
  810.                         FileSnapshot snapshot = FileSnapshot.save(f);
  811.                         MessageDigest digest = Constants.newMessageDigest();
  812.                         try (BufferedReader br = new BufferedReader(
  813.                                 new InputStreamReader(
  814.                                         new DigestInputStream(
  815.                                                 new FileInputStream(f), digest),
  816.                                         UTF_8))) {
  817.                             return new PackedRefList(parsePackedRefs(br),
  818.                                     snapshot,
  819.                                     ObjectId.fromRaw(digest.digest()));
  820.                         }
  821.                     });
  822.             return result != null ? result : NO_PACKED_REFS;
  823.         } catch (IOException e) {
  824.             throw e;
  825.         } catch (Exception e) {
  826.             throw new IOException(MessageFormat
  827.                     .format(JGitText.get().cannotReadFile, packedRefsFile), e);
  828.         }
  829.     }

  830.     private RefList<Ref> parsePackedRefs(BufferedReader br)
  831.             throws IOException {
  832.         RefList.Builder<Ref> all = new RefList.Builder<>();
  833.         Ref last = null;
  834.         boolean peeled = false;
  835.         boolean needSort = false;

  836.         String p;
  837.         while ((p = br.readLine()) != null) {
  838.             if (p.charAt(0) == '#') {
  839.                 if (p.startsWith(PACKED_REFS_HEADER)) {
  840.                     p = p.substring(PACKED_REFS_HEADER.length());
  841.                     peeled = p.contains(PACKED_REFS_PEELED);
  842.                 }
  843.                 continue;
  844.             }

  845.             if (p.charAt(0) == '^') {
  846.                 if (last == null)
  847.                     throw new IOException(JGitText.get().peeledLineBeforeRef);

  848.                 ObjectId id = ObjectId.fromString(p.substring(1));
  849.                 last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
  850.                         .getObjectId(), id);
  851.                 all.set(all.size() - 1, last);
  852.                 continue;
  853.             }

  854.             int sp = p.indexOf(' ');
  855.             if (sp < 0) {
  856.                 throw new IOException(MessageFormat.format(
  857.                         JGitText.get().packedRefsCorruptionDetected,
  858.                         packedRefsFile.getAbsolutePath()));
  859.             }
  860.             ObjectId id = ObjectId.fromString(p.substring(0, sp));
  861.             String name = copy(p, sp + 1, p.length());
  862.             ObjectIdRef cur;
  863.             if (peeled)
  864.                 cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
  865.             else
  866.                 cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
  867.             if (last != null && RefComparator.compareTo(last, cur) > 0)
  868.                 needSort = true;
  869.             all.add(cur);
  870.             last = cur;
  871.         }

  872.         if (needSort)
  873.             all.sort();
  874.         return all.toRefList();
  875.     }

  876.     private static String copy(String src, int off, int end) {
  877.         // Don't use substring since it could leave a reference to the much
  878.         // larger existing string. Force construction of a full new object.
  879.         return new StringBuilder(end - off).append(src, off, end).toString();
  880.     }

  881.     PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
  882.             final PackedRefList oldPackedList, boolean changed)
  883.             throws IOException {
  884.         // Can't just return packedRefs.get() from this method; it might have been
  885.         // updated again after writePackedRefs() returns.
  886.         AtomicReference<PackedRefList> result = new AtomicReference<>();
  887.         new RefWriter(refs) {
  888.             @Override
  889.             protected void writeFile(String name, byte[] content)
  890.                     throws IOException {
  891.                 lck.setFSync(true);
  892.                 lck.setNeedSnapshot(true);
  893.                 try {
  894.                     lck.write(content);
  895.                 } catch (IOException ioe) {
  896.                     throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
  897.                 }
  898.                 try {
  899.                     lck.waitForStatChange();
  900.                 } catch (InterruptedException e) {
  901.                     lck.unlock();
  902.                     throw new ObjectWritingException(
  903.                             MessageFormat.format(
  904.                                     JGitText.get().interruptedWriting, name),
  905.                             e);
  906.                 }
  907.                 if (!lck.commit())
  908.                     throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));

  909.                 byte[] digest = Constants.newMessageDigest().digest(content);
  910.                 PackedRefList newPackedList = new PackedRefList(
  911.                         refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));

  912.                 // This thread holds the file lock, so no other thread or process should
  913.                 // be able to modify the packed-refs file on disk. If the list changed,
  914.                 // it means something is very wrong, so throw an exception.
  915.                 //
  916.                 // However, we can't use a naive compareAndSet to check whether the
  917.                 // update was successful, because another thread might _read_ the
  918.                 // packed refs file that was written out by this thread while holding
  919.                 // the lock, and update the packedRefs reference to point to that. So
  920.                 // compare the actual contents instead.
  921.                 PackedRefList afterUpdate = packedRefs.updateAndGet(
  922.                         p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
  923.                 if (!afterUpdate.id.equals(newPackedList.id)) {
  924.                     throw new ObjectWritingException(
  925.                             MessageFormat.format(JGitText.get().unableToWrite, name));
  926.                 }
  927.                 if (changed) {
  928.                     modCnt.incrementAndGet();
  929.                 }
  930.                 result.set(newPackedList);
  931.             }
  932.         }.writePackedRefs();
  933.         return result.get();
  934.     }

  935.     private Ref readRef(String name, RefList<Ref> packed) throws IOException {
  936.         final RefList<LooseRef> curList = looseRefs.get();
  937.         final int idx = curList.find(name);
  938.         if (0 <= idx) {
  939.             final LooseRef o = curList.get(idx);
  940.             final LooseRef n = scanRef(o, name);
  941.             if (n == null) {
  942.                 if (looseRefs.compareAndSet(curList, curList.remove(idx)))
  943.                     modCnt.incrementAndGet();
  944.                 return packed.get(name);
  945.             }

  946.             if (o == n)
  947.                 return n;
  948.             if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
  949.                 modCnt.incrementAndGet();
  950.             return n;
  951.         }

  952.         final LooseRef n = scanRef(null, name);
  953.         if (n == null)
  954.             return packed.get(name);

  955.         // check whether the found new ref is the an additional ref. These refs
  956.         // should not go into looseRefs
  957.         for (String additionalRefsName : additionalRefsNames) {
  958.             if (name.equals(additionalRefsName)) {
  959.                 return n;
  960.             }
  961.         }

  962.         if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
  963.             modCnt.incrementAndGet();
  964.         return n;
  965.     }

  966.     LooseRef scanRef(LooseRef ref, String name) throws IOException {
  967.         final File path = fileFor(name);
  968.         FileSnapshot currentSnapshot = null;

  969.         if (ref != null) {
  970.             currentSnapshot = ref.getSnapShot();
  971.             if (!currentSnapshot.isModified(path))
  972.                 return ref;
  973.             name = ref.getName();
  974.         }

  975.         final int limit = 4096;

  976.         class LooseItems {
  977.             final FileSnapshot snapshot;

  978.             final byte[] buf;

  979.             LooseItems(FileSnapshot snapshot, byte[] buf) {
  980.                 this.snapshot = snapshot;
  981.                 this.buf = buf;
  982.             }
  983.         }
  984.         LooseItems loose = null;
  985.         try {
  986.             loose = FileUtils.readWithRetries(path,
  987.                     f -> new LooseItems(FileSnapshot.save(f),
  988.                             IO.readSome(f, limit)));
  989.         } catch (IOException e) {
  990.             throw e;
  991.         } catch (Exception e) {
  992.             throw new IOException(
  993.                     MessageFormat.format(JGitText.get().cannotReadFile, path),
  994.                     e);
  995.         }
  996.         if (loose == null) {
  997.             return null;
  998.         }
  999.         int n = loose.buf.length;
  1000.         if (n == 0)
  1001.             return null; // empty file; not a reference.

  1002.         if (isSymRef(loose.buf, n)) {
  1003.             if (n == limit)
  1004.                 return null; // possibly truncated ref

  1005.             // trim trailing whitespace
  1006.             while (0 < n && Character.isWhitespace(loose.buf[n - 1]))
  1007.                 n--;
  1008.             if (n < 6) {
  1009.                 String content = RawParseUtils.decode(loose.buf, 0, n);
  1010.                 throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
  1011.             }
  1012.             final String target = RawParseUtils.decode(loose.buf, 5, n);
  1013.             if (ref != null && ref.isSymbolic()
  1014.                     && ref.getTarget().getName().equals(target)) {
  1015.                 assert(currentSnapshot != null);
  1016.                 currentSnapshot.setClean(loose.snapshot);
  1017.                 return ref;
  1018.             }
  1019.             return newSymbolicRef(loose.snapshot, name, target);
  1020.         }

  1021.         if (n < OBJECT_ID_STRING_LENGTH)
  1022.             return null; // impossibly short object identifier; not a reference.

  1023.         final ObjectId id;
  1024.         try {
  1025.             id = ObjectId.fromString(loose.buf, 0);
  1026.             if (ref != null && !ref.isSymbolic()
  1027.                     && id.equals(ref.getTarget().getObjectId())) {
  1028.                 assert(currentSnapshot != null);
  1029.                 currentSnapshot.setClean(loose.snapshot);
  1030.                 return ref;
  1031.             }

  1032.         } catch (IllegalArgumentException notRef) {
  1033.             while (0 < n && Character.isWhitespace(loose.buf[n - 1]))
  1034.                 n--;
  1035.             String content = RawParseUtils.decode(loose.buf, 0, n);

  1036.             throw new IOException(MessageFormat.format(JGitText.get().notARef,
  1037.                     name, content), notRef);
  1038.         }
  1039.         return new LooseUnpeeled(loose.snapshot, name, id);
  1040.     }

  1041.     private static boolean isSymRef(byte[] buf, int n) {
  1042.         if (n < 6)
  1043.             return false;
  1044.         return /**/buf[0] == 'r' //
  1045.                 && buf[1] == 'e' //
  1046.                 && buf[2] == 'f' //
  1047.                 && buf[3] == ':' //
  1048.                 && buf[4] == ' ';
  1049.     }

  1050.     /**
  1051.      * Detect if we are in a clone command execution
  1052.      *
  1053.      * @return {@code true} if we are currently cloning a repository
  1054.      * @throws IOException
  1055.      */
  1056.     boolean isInClone() throws IOException {
  1057.         return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
  1058.     }

  1059.     private boolean hasDanglingHead() throws IOException {
  1060.         Ref head = exactRef(Constants.HEAD);
  1061.         if (head != null) {
  1062.             ObjectId id = head.getObjectId();
  1063.             return id == null || id.equals(ObjectId.zeroId());
  1064.         }
  1065.         return false;
  1066.     }

  1067.     private boolean hasLooseRef() throws IOException {
  1068.         try (Stream<Path> stream = Files.walk(refsDir.toPath())) {
  1069.             return stream.anyMatch(Files::isRegularFile);
  1070.         }
  1071.     }

  1072.     /** If the parent should fire listeners, fires them. */
  1073.     void fireRefsChanged() {
  1074.         final int last = lastNotifiedModCnt.get();
  1075.         final int curr = modCnt.get();
  1076.         if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
  1077.             parent.fireEvent(new RefsChangedEvent());
  1078.     }

  1079.     /**
  1080.      * Create a reference update to write a temporary reference.
  1081.      *
  1082.      * @return an update for a new temporary reference.
  1083.      * @throws IOException
  1084.      *             a temporary name cannot be allocated.
  1085.      */
  1086.     RefDirectoryUpdate newTemporaryUpdate() throws IOException {
  1087.         File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$
  1088.         String name = Constants.R_REFS + tmp.getName();
  1089.         Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
  1090.         return new RefDirectoryUpdate(this, ref);
  1091.     }

  1092.     /**
  1093.      * Locate the file on disk for a single reference name.
  1094.      *
  1095.      * @param name
  1096.      *            name of the ref, relative to the Git repository top level
  1097.      *            directory (so typically starts with refs/).
  1098.      * @return the loose file location.
  1099.      */
  1100.     File fileFor(String name) {
  1101.         if (name.startsWith(R_REFS)) {
  1102.             name = name.substring(R_REFS.length());
  1103.             return new File(refsDir, name);
  1104.         }
  1105.         return new File(gitDir, name);
  1106.     }

  1107.     static int levelsIn(String name) {
  1108.         int count = 0;
  1109.         for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
  1110.             count++;
  1111.         return count;
  1112.     }

  1113.     static void delete(File file, int depth) throws IOException {
  1114.         delete(file, depth, null);
  1115.     }

  1116.     private static void delete(File file, int depth, LockFile rLck)
  1117.             throws IOException {
  1118.         if (!file.delete() && file.isFile()) {
  1119.             throw new IOException(MessageFormat.format(
  1120.                     JGitText.get().fileCannotBeDeleted, file));
  1121.         }

  1122.         if (rLck != null) {
  1123.             rLck.unlock(); // otherwise cannot delete dir below
  1124.         }
  1125.         File dir = file.getParentFile();
  1126.         for (int i = 0; i < depth; ++i) {
  1127.             try {
  1128.                 Files.deleteIfExists(dir.toPath());
  1129.             } catch (DirectoryNotEmptyException e) {
  1130.                 // Don't log; normal case when there are other refs with the
  1131.                 // same prefix
  1132.                 break;
  1133.             } catch (IOException e) {
  1134.                 LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath,
  1135.                         dir), e);
  1136.                 break;
  1137.             }
  1138.             dir = dir.getParentFile();
  1139.         }
  1140.     }

  1141.     /**
  1142.      * Get times to sleep while retrying a possibly contentious operation.
  1143.      * <p>
  1144.      * For retrying an operation that might have high contention, such as locking
  1145.      * the {@code packed-refs} file, the caller may implement a retry loop using
  1146.      * the returned values:
  1147.      *
  1148.      * <pre>
  1149.      * for (int toSleepMs : getRetrySleepMs()) {
  1150.      *   sleep(toSleepMs);
  1151.      *   if (isSuccessful(doSomething())) {
  1152.      *     return success;
  1153.      *   }
  1154.      * }
  1155.      * return failure;
  1156.      * </pre>
  1157.      *
  1158.      * The first value in the returned iterable is 0, and the caller should treat
  1159.      * a fully-consumed iterator as a timeout.
  1160.      *
  1161.      * @return iterable of times, in milliseconds, that the caller should sleep
  1162.      *         before attempting an operation.
  1163.      */
  1164.     Iterable<Integer> getRetrySleepMs() {
  1165.         return retrySleepMs;
  1166.     }

  1167.     void setRetrySleepMs(List<Integer> retrySleepMs) {
  1168.         if (retrySleepMs == null || retrySleepMs.isEmpty()
  1169.                 || retrySleepMs.get(0).intValue() != 0) {
  1170.             throw new IllegalArgumentException();
  1171.         }
  1172.         this.retrySleepMs = retrySleepMs;
  1173.     }

  1174.     /**
  1175.      * Sleep with {@link Thread#sleep(long)}, converting {@link
  1176.      * InterruptedException} to {@link InterruptedIOException}.
  1177.      *
  1178.      * @param ms
  1179.      *            time to sleep, in milliseconds; zero or negative is a no-op.
  1180.      * @throws InterruptedIOException
  1181.      *             if sleeping was interrupted.
  1182.      */
  1183.     static void sleep(long ms) throws InterruptedIOException {
  1184.         if (ms <= 0) {
  1185.             return;
  1186.         }
  1187.         try {
  1188.             Thread.sleep(ms);
  1189.         } catch (InterruptedException e) {
  1190.             InterruptedIOException ie = new InterruptedIOException();
  1191.             ie.initCause(e);
  1192.             throw ie;
  1193.         }
  1194.     }

  1195.     static class PackedRefList extends RefList<Ref> {

  1196.         private final FileSnapshot snapshot;

  1197.         private final ObjectId id;

  1198.         private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
  1199.             super(src);
  1200.             snapshot = s;
  1201.             id = i;
  1202.         }
  1203.     }

  1204.     private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
  1205.             RefList.emptyList(), FileSnapshot.MISSING_FILE,
  1206.             ObjectId.zeroId());

  1207.     private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
  1208.             String name, String target) {
  1209.         Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
  1210.         return new LooseSymbolicRef(snapshot, name, dst);
  1211.     }

  1212.     private static interface LooseRef extends Ref {
  1213.         FileSnapshot getSnapShot();

  1214.         LooseRef peel(ObjectIdRef newLeaf);
  1215.     }

  1216.     private static final class LoosePeeledTag extends ObjectIdRef.PeeledTag
  1217.             implements LooseRef {
  1218.         private final FileSnapshot snapShot;

  1219.         LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
  1220.                 @NonNull ObjectId id, @NonNull ObjectId p) {
  1221.             super(LOOSE, refName, id, p);
  1222.             this.snapShot = snapshot;
  1223.         }

  1224.         @Override
  1225.         public FileSnapshot getSnapShot() {
  1226.             return snapShot;
  1227.         }

  1228.         @Override
  1229.         public LooseRef peel(ObjectIdRef newLeaf) {
  1230.             return this;
  1231.         }
  1232.     }

  1233.     private static final class LooseNonTag extends ObjectIdRef.PeeledNonTag
  1234.             implements LooseRef {
  1235.         private final FileSnapshot snapShot;

  1236.         LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
  1237.                 @NonNull ObjectId id) {
  1238.             super(LOOSE, refName, id);
  1239.             this.snapShot = snapshot;
  1240.         }

  1241.         @Override
  1242.         public FileSnapshot getSnapShot() {
  1243.             return snapShot;
  1244.         }

  1245.         @Override
  1246.         public LooseRef peel(ObjectIdRef newLeaf) {
  1247.             return this;
  1248.         }
  1249.     }

  1250.     private static final class LooseUnpeeled extends ObjectIdRef.Unpeeled
  1251.             implements LooseRef {
  1252.         private FileSnapshot snapShot;

  1253.         LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
  1254.                 @NonNull ObjectId id) {
  1255.             super(LOOSE, refName, id);
  1256.             this.snapShot = snapShot;
  1257.         }

  1258.         @Override
  1259.         public FileSnapshot getSnapShot() {
  1260.             return snapShot;
  1261.         }

  1262.         @NonNull
  1263.         @Override
  1264.         public ObjectId getObjectId() {
  1265.             ObjectId id = super.getObjectId();
  1266.             assert id != null; // checked in constructor
  1267.             return id;
  1268.         }

  1269.         @Override
  1270.         public LooseRef peel(ObjectIdRef newLeaf) {
  1271.             ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
  1272.             ObjectId objectId = getObjectId();
  1273.             if (peeledObjectId != null) {
  1274.                 return new LoosePeeledTag(snapShot, getName(),
  1275.                         objectId, peeledObjectId);
  1276.             }
  1277.             return new LooseNonTag(snapShot, getName(), objectId);
  1278.         }
  1279.     }

  1280.     private static final class LooseSymbolicRef extends SymbolicRef implements
  1281.             LooseRef {
  1282.         private final FileSnapshot snapShot;

  1283.         LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
  1284.                 @NonNull Ref target) {
  1285.             super(refName, target);
  1286.             this.snapShot = snapshot;
  1287.         }

  1288.         @Override
  1289.         public FileSnapshot getSnapShot() {
  1290.             return snapShot;
  1291.         }

  1292.         @Override
  1293.         public LooseRef peel(ObjectIdRef newLeaf) {
  1294.             // We should never try to peel the symbolic references.
  1295.             throw new UnsupportedOperationException();
  1296.         }
  1297.     }
  1298. }