FileRepository.java

  1. /*
  2.  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3.  * Copyright (C) 2008-2010, Google Inc.
  4.  * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
  5.  * Copyright (C) 2006-2008, 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.util.stream.Collectors.toList;

  15. import java.io.File;
  16. import java.io.FileInputStream;
  17. import java.io.FileNotFoundException;
  18. import java.io.FileOutputStream;
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.text.MessageFormat;
  22. import java.text.ParseException;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Locale;
  28. import java.util.Objects;
  29. import java.util.Set;

  30. import org.eclipse.jgit.annotations.Nullable;
  31. import org.eclipse.jgit.api.errors.JGitInternalException;
  32. import org.eclipse.jgit.attributes.AttributesNode;
  33. import org.eclipse.jgit.attributes.AttributesNodeProvider;
  34. import org.eclipse.jgit.errors.ConfigInvalidException;
  35. import org.eclipse.jgit.events.IndexChangedEvent;
  36. import org.eclipse.jgit.internal.JGitText;
  37. import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
  38. import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
  39. import org.eclipse.jgit.lib.BaseRepositoryBuilder;
  40. import org.eclipse.jgit.lib.BatchRefUpdate;
  41. import org.eclipse.jgit.lib.ConfigConstants;
  42. import org.eclipse.jgit.lib.Constants;
  43. import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
  44. import org.eclipse.jgit.lib.CoreConfig.SymLinks;
  45. import org.eclipse.jgit.lib.NullProgressMonitor;
  46. import org.eclipse.jgit.lib.ObjectId;
  47. import org.eclipse.jgit.lib.ProgressMonitor;
  48. import org.eclipse.jgit.lib.Ref;
  49. import org.eclipse.jgit.lib.RefDatabase;
  50. import org.eclipse.jgit.lib.RefUpdate;
  51. import org.eclipse.jgit.lib.ReflogEntry;
  52. import org.eclipse.jgit.lib.ReflogReader;
  53. import org.eclipse.jgit.lib.Repository;
  54. import org.eclipse.jgit.lib.StoredConfig;
  55. import org.eclipse.jgit.revwalk.RevWalk;
  56. import org.eclipse.jgit.storage.file.FileBasedConfig;
  57. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  58. import org.eclipse.jgit.storage.pack.PackConfig;
  59. import org.eclipse.jgit.transport.ReceiveCommand;
  60. import org.eclipse.jgit.util.FileUtils;
  61. import org.eclipse.jgit.util.IO;
  62. import org.eclipse.jgit.util.RawParseUtils;
  63. import org.eclipse.jgit.util.StringUtils;
  64. import org.eclipse.jgit.util.SystemReader;
  65. import org.slf4j.Logger;
  66. import org.slf4j.LoggerFactory;

  67. /**
  68.  * Represents a Git repository. A repository holds all objects and refs used for
  69.  * managing source code (could by any type of file, but source code is what
  70.  * SCM's are typically used for).
  71.  *
  72.  * In Git terms all data is stored in GIT_DIR, typically a directory called
  73.  * .git. A work tree is maintained unless the repository is a bare repository.
  74.  * Typically the .git directory is located at the root of the work dir.
  75.  *
  76.  * <ul>
  77.  * <li>GIT_DIR
  78.  *  <ul>
  79.  *      <li>objects/ - objects</li>
  80.  *      <li>refs/ - tags and heads</li>
  81.  *      <li>config - configuration</li>
  82.  *      <li>info/ - more configurations</li>
  83.  *  </ul>
  84.  * </li>
  85.  * </ul>
  86.  * <p>
  87.  * This class is thread-safe.
  88.  * <p>
  89.  * This implementation only handles a subtly undocumented subset of git features.
  90.  */
  91. public class FileRepository extends Repository {
  92.     private static final Logger LOG = LoggerFactory
  93.             .getLogger(FileRepository.class);
  94.     private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$

  95.     private final FileBasedConfig repoConfig;
  96.     private RefDatabase refs;
  97.     private final ObjectDirectory objectDatabase;

  98.     private final Object snapshotLock = new Object();

  99.     // protected by snapshotLock
  100.     private FileSnapshot snapshot;

  101.     /**
  102.      * Construct a representation of a Git repository.
  103.      * <p>
  104.      * The work tree, object directory, alternate object directories and index
  105.      * file locations are deduced from the given git directory and the default
  106.      * rules by running
  107.      * {@link org.eclipse.jgit.storage.file.FileRepositoryBuilder}. This
  108.      * constructor is the same as saying:
  109.      *
  110.      * <pre>
  111.      * new FileRepositoryBuilder().setGitDir(gitDir).build()
  112.      * </pre>
  113.      *
  114.      * @param gitDir
  115.      *            GIT_DIR (the location of the repository metadata).
  116.      * @throws java.io.IOException
  117.      *             the repository appears to already exist but cannot be
  118.      *             accessed.
  119.      * @see FileRepositoryBuilder
  120.      */
  121.     public FileRepository(File gitDir) throws IOException {
  122.         this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
  123.     }

  124.     /**
  125.      * A convenience API for {@link #FileRepository(File)}.
  126.      *
  127.      * @param gitDir
  128.      *            GIT_DIR (the location of the repository metadata).
  129.      * @throws java.io.IOException
  130.      *             the repository appears to already exist but cannot be
  131.      *             accessed.
  132.      * @see FileRepositoryBuilder
  133.      */
  134.     public FileRepository(String gitDir) throws IOException {
  135.         this(new File(gitDir));
  136.     }

  137.     /**
  138.      * Create a repository using the local file system.
  139.      *
  140.      * @param options
  141.      *            description of the repository's important paths.
  142.      * @throws java.io.IOException
  143.      *             the user configuration file or repository configuration file
  144.      *             cannot be accessed.
  145.      */
  146.     public FileRepository(BaseRepositoryBuilder options) throws IOException {
  147.         super(options);
  148.         StoredConfig userConfig = null;
  149.         try {
  150.             userConfig = SystemReader.getInstance().getUserConfig();
  151.         } catch (ConfigInvalidException e) {
  152.             LOG.error(e.getMessage(), e);
  153.             throw new IOException(e.getMessage(), e);
  154.         }
  155.         repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
  156.                 getDirectory(), Constants.CONFIG),
  157.                 getFS());
  158.         loadRepoConfig();

  159.         repoConfig.addChangeListener(this::fireEvent);

  160.         final long repositoryFormatVersion = getConfig().getLong(
  161.                 ConfigConstants.CONFIG_CORE_SECTION, null,
  162.                 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);

  163.         String reftype = repoConfig.getString(
  164.                 ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  165.                 ConfigConstants.CONFIG_KEY_REF_STORAGE);
  166.         if (repositoryFormatVersion >= 1 && reftype != null) {
  167.             if (StringUtils.equalsIgnoreCase(reftype,
  168.                     ConfigConstants.CONFIG_REF_STORAGE_REFTABLE)) {
  169.                 refs = new FileReftableDatabase(this);
  170.             } else {
  171.                 throw new IOException(JGitText.get().unknownRepositoryFormat);
  172.             }
  173.         } else {
  174.             refs = new RefDirectory(this);
  175.         }

  176.         objectDatabase = new ObjectDirectory(repoConfig, //
  177.                 options.getObjectDirectory(), //
  178.                 options.getAlternateObjectDirectories(), //
  179.                 getFS(), //
  180.                 new File(getDirectory(), Constants.SHALLOW));

  181.         if (objectDatabase.exists()) {
  182.             if (repositoryFormatVersion > 1)
  183.                 throw new IOException(MessageFormat.format(
  184.                         JGitText.get().unknownRepositoryFormat2,
  185.                         Long.valueOf(repositoryFormatVersion)));
  186.         }

  187.         if (!isBare()) {
  188.             snapshot = FileSnapshot.save(getIndexFile());
  189.         }
  190.     }

  191.     private void loadRepoConfig() throws IOException {
  192.         try {
  193.             repoConfig.load();
  194.         } catch (ConfigInvalidException e) {
  195.             throw new IOException(JGitText.get().unknownRepositoryFormat, e);
  196.         }
  197.     }

  198.     /**
  199.      * {@inheritDoc}
  200.      * <p>
  201.      * Create a new Git repository initializing the necessary files and
  202.      * directories.
  203.      */
  204.     @Override
  205.     public void create(boolean bare) throws IOException {
  206.         final FileBasedConfig cfg = getConfig();
  207.         if (cfg.getFile().exists()) {
  208.             throw new IllegalStateException(MessageFormat.format(
  209.                     JGitText.get().repositoryAlreadyExists, getDirectory()));
  210.         }
  211.         FileUtils.mkdirs(getDirectory(), true);
  212.         HideDotFiles hideDotFiles = getConfig().getEnum(
  213.                 ConfigConstants.CONFIG_CORE_SECTION, null,
  214.                 ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
  215.                 HideDotFiles.DOTGITONLY);
  216.         if (hideDotFiles != HideDotFiles.FALSE && !isBare()
  217.                 && getDirectory().getName().startsWith(".")) //$NON-NLS-1$
  218.             getFS().setHidden(getDirectory(), true);
  219.         refs.create();
  220.         objectDatabase.create();

  221.         FileUtils.mkdir(new File(getDirectory(), "branches")); //$NON-NLS-1$
  222.         FileUtils.mkdir(new File(getDirectory(), "hooks")); //$NON-NLS-1$

  223.         RefUpdate head = updateRef(Constants.HEAD);
  224.         head.disableRefLog();
  225.         head.link(Constants.R_HEADS + getInitialBranch());

  226.         final boolean fileMode;
  227.         if (getFS().supportsExecute()) {
  228.             File tmp = File.createTempFile("try", "execute", getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$

  229.             getFS().setExecute(tmp, true);
  230.             final boolean on = getFS().canExecute(tmp);

  231.             getFS().setExecute(tmp, false);
  232.             final boolean off = getFS().canExecute(tmp);
  233.             FileUtils.delete(tmp);

  234.             fileMode = on && !off;
  235.         } else {
  236.             fileMode = false;
  237.         }

  238.         SymLinks symLinks = SymLinks.FALSE;
  239.         if (getFS().supportsSymlinks()) {
  240.             File tmp = new File(getDirectory(), "tmplink"); //$NON-NLS-1$
  241.             try {
  242.                 getFS().createSymLink(tmp, "target"); //$NON-NLS-1$
  243.                 symLinks = null;
  244.                 FileUtils.delete(tmp);
  245.             } catch (IOException e) {
  246.                 // Normally a java.nio.file.FileSystemException
  247.             }
  248.         }
  249.         if (symLinks != null)
  250.             cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
  251.                     ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
  252.                             .toLowerCase(Locale.ROOT));
  253.         cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
  254.                 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
  255.         cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  256.                 ConfigConstants.CONFIG_KEY_FILEMODE, fileMode);
  257.         if (bare)
  258.             cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  259.                     ConfigConstants.CONFIG_KEY_BARE, true);
  260.         cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  261.                 ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
  262.         if (SystemReader.getInstance().isMacOS())
  263.             // Java has no other way
  264.             cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  265.                     ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
  266.         if (!bare) {
  267.             File workTree = getWorkTree();
  268.             if (!getDirectory().getParentFile().equals(workTree)) {
  269.                 cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
  270.                         ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
  271.                                 .getAbsolutePath());
  272.                 LockFile dotGitLockFile = new LockFile(new File(workTree,
  273.                         Constants.DOT_GIT));
  274.                 try {
  275.                     if (dotGitLockFile.lock()) {
  276.                         dotGitLockFile.write(Constants.encode(Constants.GITDIR
  277.                                 + getDirectory().getAbsolutePath()));
  278.                         dotGitLockFile.commit();
  279.                     }
  280.                 } finally {
  281.                     dotGitLockFile.unlock();
  282.                 }
  283.             }
  284.         }
  285.         cfg.save();
  286.     }

  287.     /**
  288.      * Get the directory containing the objects owned by this repository
  289.      *
  290.      * @return the directory containing the objects owned by this repository.
  291.      */
  292.     public File getObjectsDirectory() {
  293.         return objectDatabase.getDirectory();
  294.     }

  295.     /** {@inheritDoc} */
  296.     @Override
  297.     public ObjectDirectory getObjectDatabase() {
  298.         return objectDatabase;
  299.     }

  300.     /** {@inheritDoc} */
  301.     @Override
  302.     public RefDatabase getRefDatabase() {
  303.         return refs;
  304.     }

  305.     /** {@inheritDoc} */
  306.     @Override
  307.     public String getIdentifier() {
  308.         File directory = getDirectory();
  309.         if (directory != null) {
  310.             return directory.getPath();
  311.         }
  312.         throw new IllegalStateException();
  313.     }

  314.     /** {@inheritDoc} */
  315.     @Override
  316.     public FileBasedConfig getConfig() {
  317.         try {
  318.             SystemReader.getInstance().getUserConfig();
  319.             if (repoConfig.isOutdated()) {
  320.                 loadRepoConfig();
  321.             }
  322.         } catch (IOException | ConfigInvalidException e) {
  323.             throw new RuntimeException(e);
  324.         }
  325.         return repoConfig;
  326.     }

  327.     /** {@inheritDoc} */
  328.     @Override
  329.     @Nullable
  330.     public String getGitwebDescription() throws IOException {
  331.         String d;
  332.         try {
  333.             d = RawParseUtils.decode(IO.readFully(descriptionFile()));
  334.         } catch (FileNotFoundException err) {
  335.             return null;
  336.         }
  337.         if (d != null) {
  338.             d = d.trim();
  339.             if (d.isEmpty() || UNNAMED.equals(d)) {
  340.                 return null;
  341.             }
  342.         }
  343.         return d;
  344.     }

  345.     /** {@inheritDoc} */
  346.     @Override
  347.     public void setGitwebDescription(@Nullable String description)
  348.             throws IOException {
  349.         String old = getGitwebDescription();
  350.         if (Objects.equals(old, description)) {
  351.             return;
  352.         }

  353.         File path = descriptionFile();
  354.         LockFile lock = new LockFile(path);
  355.         if (!lock.lock()) {
  356.             throw new IOException(MessageFormat.format(JGitText.get().lockError,
  357.                     path.getAbsolutePath()));
  358.         }
  359.         try {
  360.             String d = description;
  361.             if (d != null) {
  362.                 d = d.trim();
  363.                 if (!d.isEmpty()) {
  364.                     d += '\n';
  365.                 }
  366.             } else {
  367.                 d = ""; //$NON-NLS-1$
  368.             }
  369.             lock.write(Constants.encode(d));
  370.             lock.commit();
  371.         } finally {
  372.             lock.unlock();
  373.         }
  374.     }

  375.     private File descriptionFile() {
  376.         return new File(getDirectory(), "description"); //$NON-NLS-1$
  377.     }

  378.     /**
  379.      * {@inheritDoc}
  380.      * <p>
  381.      * Objects known to exist but not expressed by {@code #getAllRefs()}.
  382.      * <p>
  383.      * When a repository borrows objects from another repository, it can
  384.      * advertise that it safely has that other repository's references, without
  385.      * exposing any other details about the other repository. This may help a
  386.      * client trying to push changes avoid pushing more than it needs to.
  387.      *
  388.      * @throws IOException
  389.      */
  390.     @Override
  391.     public Set<ObjectId> getAdditionalHaves() throws IOException {
  392.         return getAdditionalHaves(null);
  393.     }

  394.     /**
  395.      * Objects known to exist but not expressed by {@code #getAllRefs()}.
  396.      * <p>
  397.      * When a repository borrows objects from another repository, it can
  398.      * advertise that it safely has that other repository's references, without
  399.      * exposing any other details about the other repository. This may help a
  400.      * client trying to push changes avoid pushing more than it needs to.
  401.      *
  402.      * @param skips
  403.      *            Set of AlternateHandle Ids already seen
  404.      *
  405.      * @return unmodifiable collection of other known objects.
  406.      * @throws IOException
  407.      *             if getting refs hits an IO error
  408.      */
  409.     private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips)
  410.             throws IOException {
  411.         HashSet<ObjectId> r = new HashSet<>();
  412.         skips = objectDatabase.addMe(skips);
  413.         for (AlternateHandle d : objectDatabase.myAlternates()) {
  414.             if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
  415.                 FileRepository repo;

  416.                 repo = ((AlternateRepository) d).repository;
  417.                 for (Ref ref : repo.getRefDatabase().getRefs()) {
  418.                     if (ref.getObjectId() != null)
  419.                         r.add(ref.getObjectId());
  420.                     if (ref.getPeeledObjectId() != null)
  421.                         r.add(ref.getPeeledObjectId());
  422.                 }
  423.                 r.addAll(repo.getAdditionalHaves(skips));
  424.             }
  425.         }
  426.         return r;
  427.     }

  428.     /**
  429.      * Add a single existing pack to the list of available pack files.
  430.      *
  431.      * @param pack
  432.      *            path of the pack file to open.
  433.      * @throws java.io.IOException
  434.      *             index file could not be opened, read, or is not recognized as
  435.      *             a Git pack file index.
  436.      */
  437.     public void openPack(File pack) throws IOException {
  438.         objectDatabase.openPack(pack);
  439.     }

  440.     /** {@inheritDoc} */
  441.     @Override
  442.     public void scanForRepoChanges() throws IOException {
  443.         getRefDatabase().getRefs(); // This will look for changes to refs
  444.         detectIndexChanges();
  445.     }

  446.     /** Detect index changes. */
  447.     private void detectIndexChanges() {
  448.         if (isBare()) {
  449.             return;
  450.         }

  451.         File indexFile = getIndexFile();
  452.         synchronized (snapshotLock) {
  453.             if (snapshot == null) {
  454.                 snapshot = FileSnapshot.save(indexFile);
  455.                 return;
  456.             }
  457.             if (!snapshot.isModified(indexFile)) {
  458.                 return;
  459.             }
  460.         }
  461.         notifyIndexChanged(false);
  462.     }

  463.     /** {@inheritDoc} */
  464.     @Override
  465.     public void notifyIndexChanged(boolean internal) {
  466.         synchronized (snapshotLock) {
  467.             snapshot = FileSnapshot.save(getIndexFile());
  468.         }
  469.         fireEvent(new IndexChangedEvent(internal));
  470.     }

  471.     /** {@inheritDoc} */
  472.     @Override
  473.     public ReflogReader getReflogReader(String refName) throws IOException {
  474.         if (refs instanceof FileReftableDatabase) {
  475.             // Cannot use findRef: reftable stores log data for deleted or renamed
  476.             // branches.
  477.             return ((FileReftableDatabase)refs).getReflogReader(refName);
  478.         }

  479.         // TODO: use exactRef here, which offers more predictable and therefore preferable
  480.         // behavior.
  481.         Ref ref = findRef(refName);
  482.         if (ref == null) {
  483.             return null;
  484.         }
  485.         return new ReflogReaderImpl(this, ref.getName());
  486.     }

  487.     /** {@inheritDoc} */
  488.     @Override
  489.     public AttributesNodeProvider createAttributesNodeProvider() {
  490.         return new AttributesNodeProviderImpl(this);
  491.     }

  492.     /**
  493.      * Implementation a {@link AttributesNodeProvider} for a
  494.      * {@link FileRepository}.
  495.      *
  496.      * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
  497.      *
  498.      */
  499.     static class AttributesNodeProviderImpl implements
  500.             AttributesNodeProvider {

  501.         private AttributesNode infoAttributesNode;

  502.         private AttributesNode globalAttributesNode;

  503.         /**
  504.          * Constructor.
  505.          *
  506.          * @param repo
  507.          *            {@link Repository} that will provide the attribute nodes.
  508.          */
  509.         protected AttributesNodeProviderImpl(Repository repo) {
  510.             infoAttributesNode = new InfoAttributesNode(repo);
  511.             globalAttributesNode = new GlobalAttributesNode(repo);
  512.         }

  513.         @Override
  514.         public AttributesNode getInfoAttributesNode() throws IOException {
  515.             if (infoAttributesNode instanceof InfoAttributesNode)
  516.                 infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
  517.                         .load();
  518.             return infoAttributesNode;
  519.         }

  520.         @Override
  521.         public AttributesNode getGlobalAttributesNode() throws IOException {
  522.             if (globalAttributesNode instanceof GlobalAttributesNode)
  523.                 globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
  524.                         .load();
  525.             return globalAttributesNode;
  526.         }

  527.         static void loadRulesFromFile(AttributesNode r, File attrs)
  528.                 throws FileNotFoundException, IOException {
  529.             if (attrs.exists()) {
  530.                 try (FileInputStream in = new FileInputStream(attrs)) {
  531.                     r.parse(in);
  532.                 }
  533.             }
  534.         }

  535.     }

  536.     private boolean shouldAutoDetach() {
  537.         return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
  538.                 ConfigConstants.CONFIG_KEY_AUTODETACH, true);
  539.     }

  540.     /** {@inheritDoc} */
  541.     @SuppressWarnings("FutureReturnValueIgnored")
  542.     @Override
  543.     public void autoGC(ProgressMonitor monitor) {
  544.         GC gc = new GC(this);
  545.         gc.setPackConfig(new PackConfig(this));
  546.         gc.setProgressMonitor(monitor);
  547.         gc.setAuto(true);
  548.         gc.setBackground(shouldAutoDetach());
  549.         try {
  550.             gc.gc();
  551.         } catch (ParseException | IOException e) {
  552.             throw new JGitInternalException(JGitText.get().gcFailed, e);
  553.         }
  554.     }

  555.     /**
  556.      * Converts the RefDatabase from reftable to RefDirectory. This operation is
  557.      * not atomic.
  558.      *
  559.      * @param writeLogs
  560.      *            whether to write reflogs
  561.      * @param backup
  562.      *            whether to rename or delete the old storage files. If set to
  563.      *            {@code true}, the reftable list is left in {@code refs.old},
  564.      *            and the {@code reftable/} dir is left alone. If set to
  565.      *            {@code false}, the {@code reftable/} dir is removed, and
  566.      *            {@code refs} file is removed.
  567.      * @throws IOException
  568.      *             on IO problem
  569.      */
  570.     void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
  571.         List<Ref> all = refs.getRefs();
  572.         File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
  573.         if (packedRefs.exists()) {
  574.             throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists,
  575.                 packedRefs.getName()));
  576.         }

  577.         File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$
  578.         File refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
  579.         File headFile = new File(getDirectory(), Constants.HEAD);
  580.         FileReftableDatabase oldDb = (FileReftableDatabase) refs;

  581.         // Remove the dummy files that ensure compatibility with older git
  582.         // versions (see convertToReftable). First make room for refs/heads/
  583.         refsHeadsFile.delete();
  584.         // RefDirectory wants to create the refs/ directory from scratch, so
  585.         // remove that too.
  586.             refsFile.delete();
  587.         // remove HEAD so its previous invalid value doesn't cause issues.
  588.         headFile.delete();

  589.         // This is not atomic, but there is no way to instantiate a RefDirectory
  590.         // that is disconnected from the current repo.
  591.         RefDirectory refDir = new RefDirectory(this);
  592.         refs = refDir;
  593.         refs.create();

  594.         ReflogWriter logWriter = refDir.newLogWriter(true);
  595.         List<Ref> symrefs = new ArrayList<>();
  596.         BatchRefUpdate bru = refs.newBatchUpdate();
  597.         for (Ref r : all) {
  598.             if (r.isSymbolic()) {
  599.                 symrefs.add(r);
  600.             } else {
  601.                 bru.addCommand(new ReceiveCommand(ObjectId.zeroId(),
  602.                         r.getObjectId(), r.getName()));
  603.             }

  604.             if (writeLogs) {
  605.                 List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
  606.                         .getReverseEntries();
  607.                 Collections.reverse(logs);
  608.                 for (ReflogEntry e : logs) {
  609.                     logWriter.log(r.getName(), e);
  610.                 }
  611.             }
  612.         }

  613.         try (RevWalk rw = new RevWalk(this)) {
  614.             bru.execute(rw, NullProgressMonitor.INSTANCE);
  615.         }

  616.         oldDb.close();

  617.         List<String> failed = new ArrayList<>();
  618.         for (ReceiveCommand cmd : bru.getCommands()) {
  619.             if (cmd.getResult() != ReceiveCommand.Result.OK) {
  620.                 failed.add(cmd.getRefName() + ": " + cmd.getResult()); //$NON-NLS-1$
  621.             }
  622.         }

  623.         if (!failed.isEmpty()) {
  624.             throw new IOException(String.format("%s: %s", //$NON-NLS-1$
  625.                     JGitText.get().failedToConvert,
  626.                     StringUtils.join(failed, ", "))); //$NON-NLS-1$
  627.         }

  628.         for (Ref s : symrefs) {
  629.             RefUpdate up = refs.newUpdate(s.getName(), false);
  630.             up.setForceUpdate(true);
  631.             RefUpdate.Result res = up.link(s.getTarget().getName());
  632.             if (res != RefUpdate.Result.NEW
  633.                     && res != RefUpdate.Result.NO_CHANGE) {
  634.                 throw new IOException(
  635.                         String.format("ref %s: %s", s.getName(), res)); //$NON-NLS-1$
  636.             }
  637.         }

  638.         if (!backup) {
  639.             File reftableDir = new File(getDirectory(), Constants.REFTABLE);
  640.             FileUtils.delete(reftableDir,
  641.                     FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
  642.         }
  643.         repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  644.                 ConfigConstants.CONFIG_KEY_REF_STORAGE);
  645.         repoConfig.save();
  646.     }

  647.     /**
  648.      * Converts the RefDatabase from RefDirectory to reftable. This operation is
  649.      * not atomic.
  650.      *
  651.      * @param writeLogs
  652.      *            whether to write reflogs
  653.      * @param backup
  654.      *            whether to rename or delete the old storage files. If set to
  655.      *            {@code true}, the loose refs are left in {@code refs.old}, the
  656.      *            packed-refs in {@code packed-refs.old} and reflogs in
  657.      *            {@code refs.old/}. HEAD is left in {@code HEAD.old} and also
  658.      *            {@code .log} is appended to additional refs. If set to
  659.      *            {@code false}, the {@code refs/} and {@code logs/} directories
  660.      *            and {@code HEAD} and additional symbolic refs are removed.
  661.      * @throws IOException
  662.      *             on IO problem
  663.      */
  664.     @SuppressWarnings("nls")
  665.     void convertToReftable(boolean writeLogs, boolean backup)
  666.             throws IOException {
  667.         File reftableDir = new File(getDirectory(), Constants.REFTABLE);
  668.         File headFile = new File(getDirectory(), Constants.HEAD);
  669.         if (reftableDir.exists() && FileUtils.hasFiles(reftableDir.toPath())) {
  670.             throw new IOException(JGitText.get().reftableDirExists);
  671.         }

  672.         // Ignore return value, as it is tied to temporary newRefs file.
  673.         FileReftableDatabase.convertFrom(this, writeLogs);

  674.         File refsFile = new File(getDirectory(), "refs");

  675.         // non-atomic: remove old data.
  676.         File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
  677.         File logsDir = new File(getDirectory(), Constants.LOGS);

  678.         List<String> additional = getRefDatabase().getAdditionalRefs().stream()
  679.                 .map(Ref::getName).collect(toList());
  680.         additional.add(Constants.HEAD);
  681.         if (backup) {
  682.             FileUtils.rename(refsFile, new File(getDirectory(), "refs.old"));
  683.             if (packedRefs.exists()) {
  684.                 FileUtils.rename(packedRefs, new File(getDirectory(),
  685.                         Constants.PACKED_REFS + ".old"));
  686.             }
  687.             if (logsDir.exists()) {
  688.                 FileUtils.rename(logsDir,
  689.                         new File(getDirectory(), Constants.LOGS + ".old"));
  690.             }
  691.             for (String r : additional) {
  692.                 FileUtils.rename(new File(getDirectory(), r),
  693.                     new File(getDirectory(), r + ".old"));
  694.             }
  695.         } else {
  696.             FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING);
  697.             FileUtils.delete(headFile, FileUtils.SKIP_MISSING);
  698.             FileUtils.delete(logsDir,
  699.                     FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
  700.             FileUtils.delete(refsFile,
  701.                     FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
  702.             for (String r : additional) {
  703.                 new File(getDirectory(), r).delete();
  704.             }
  705.         }

  706.         FileUtils.mkdir(refsFile, true);

  707.         // By putting in a dummy HEAD, old versions of Git still detect a repo
  708.         // (that they can't read)
  709.         try (OutputStream os = new FileOutputStream(headFile)) {
  710.             os.write(Constants.encodeASCII("ref: refs/heads/.invalid"));
  711.         }

  712.         // Some tools might write directly into .git/refs/heads/BRANCH. By
  713.         // putting a file here, this fails spectacularly.
  714.         FileUtils.createNewFile(new File(refsFile, "heads"));

  715.         repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  716.                 ConfigConstants.CONFIG_KEY_REF_STORAGE,
  717.                 ConfigConstants.CONFIG_REF_STORAGE_REFTABLE);
  718.         repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
  719.                 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
  720.         repoConfig.save();
  721.         refs.close();
  722.         refs = new FileReftableDatabase(this);
  723.     }

  724.     /**
  725.      * Converts between ref storage formats.
  726.      *
  727.      * @param format
  728.      *            the format to convert to, either "reftable" or "refdir"
  729.      * @param writeLogs
  730.      *            whether to write reflogs
  731.      * @param backup
  732.      *            whether to make a backup of the old data
  733.      * @throws IOException
  734.      *             on I/O problems.
  735.      */
  736.     public void convertRefStorage(String format, boolean writeLogs,
  737.             boolean backup) throws IOException {
  738.         if (format.equals("reftable")) { //$NON-NLS-1$
  739.             if (refs instanceof RefDirectory) {
  740.                 convertToReftable(writeLogs, backup);
  741.             }
  742.         } else if (format.equals("refdir")) {//$NON-NLS-1$
  743.             if (refs instanceof FileReftableDatabase) {
  744.                 convertToPackedRefs(writeLogs, backup);
  745.             }
  746.         } else {
  747.             throw new IOException(MessageFormat
  748.                     .format(JGitText.get().unknownRefStorageFormat, format));
  749.         }
  750.     }
  751. }