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.     @Override
  389.     public Set<ObjectId> getAdditionalHaves() {
  390.         return getAdditionalHaves(null);
  391.     }

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

  411.                 repo = ((AlternateRepository) d).repository;
  412.                 for (Ref ref : repo.getAllRefs().values()) {
  413.                     if (ref.getObjectId() != null)
  414.                         r.add(ref.getObjectId());
  415.                     if (ref.getPeeledObjectId() != null)
  416.                         r.add(ref.getPeeledObjectId());
  417.                 }
  418.                 r.addAll(repo.getAdditionalHaves(skips));
  419.             }
  420.         }
  421.         return r;
  422.     }

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

  435.     /** {@inheritDoc} */
  436.     @Override
  437.     public void scanForRepoChanges() throws IOException {
  438.         getRefDatabase().getRefs(); // This will look for changes to refs
  439.         detectIndexChanges();
  440.     }

  441.     /** Detect index changes. */
  442.     private void detectIndexChanges() {
  443.         if (isBare()) {
  444.             return;
  445.         }

  446.         File indexFile = getIndexFile();
  447.         synchronized (snapshotLock) {
  448.             if (snapshot == null) {
  449.                 snapshot = FileSnapshot.save(indexFile);
  450.                 return;
  451.             }
  452.             if (!snapshot.isModified(indexFile)) {
  453.                 return;
  454.             }
  455.         }
  456.         notifyIndexChanged(false);
  457.     }

  458.     /** {@inheritDoc} */
  459.     @Override
  460.     public void notifyIndexChanged(boolean internal) {
  461.         synchronized (snapshotLock) {
  462.             snapshot = FileSnapshot.save(getIndexFile());
  463.         }
  464.         fireEvent(new IndexChangedEvent(internal));
  465.     }

  466.     /** {@inheritDoc} */
  467.     @Override
  468.     public ReflogReader getReflogReader(String refName) throws IOException {
  469.         if (refs instanceof FileReftableDatabase) {
  470.             // Cannot use findRef: reftable stores log data for deleted or renamed
  471.             // branches.
  472.             return ((FileReftableDatabase)refs).getReflogReader(refName);
  473.         }

  474.         // TODO: use exactRef here, which offers more predictable and therefore preferable
  475.         // behavior.
  476.         Ref ref = findRef(refName);
  477.         if (ref == null) {
  478.             return null;
  479.         }
  480.         return new ReflogReaderImpl(this, ref.getName());
  481.     }

  482.     /** {@inheritDoc} */
  483.     @Override
  484.     public AttributesNodeProvider createAttributesNodeProvider() {
  485.         return new AttributesNodeProviderImpl(this);
  486.     }

  487.     /**
  488.      * Implementation a {@link AttributesNodeProvider} for a
  489.      * {@link FileRepository}.
  490.      *
  491.      * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
  492.      *
  493.      */
  494.     static class AttributesNodeProviderImpl implements
  495.             AttributesNodeProvider {

  496.         private AttributesNode infoAttributesNode;

  497.         private AttributesNode globalAttributesNode;

  498.         /**
  499.          * Constructor.
  500.          *
  501.          * @param repo
  502.          *            {@link Repository} that will provide the attribute nodes.
  503.          */
  504.         protected AttributesNodeProviderImpl(Repository repo) {
  505.             infoAttributesNode = new InfoAttributesNode(repo);
  506.             globalAttributesNode = new GlobalAttributesNode(repo);
  507.         }

  508.         @Override
  509.         public AttributesNode getInfoAttributesNode() throws IOException {
  510.             if (infoAttributesNode instanceof InfoAttributesNode)
  511.                 infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
  512.                         .load();
  513.             return infoAttributesNode;
  514.         }

  515.         @Override
  516.         public AttributesNode getGlobalAttributesNode() throws IOException {
  517.             if (globalAttributesNode instanceof GlobalAttributesNode)
  518.                 globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
  519.                         .load();
  520.             return globalAttributesNode;
  521.         }

  522.         static void loadRulesFromFile(AttributesNode r, File attrs)
  523.                 throws FileNotFoundException, IOException {
  524.             if (attrs.exists()) {
  525.                 try (FileInputStream in = new FileInputStream(attrs)) {
  526.                     r.parse(in);
  527.                 }
  528.             }
  529.         }

  530.     }

  531.     private boolean shouldAutoDetach() {
  532.         return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
  533.                 ConfigConstants.CONFIG_KEY_AUTODETACH, true);
  534.     }

  535.     /** {@inheritDoc} */
  536.     @Override
  537.     public void autoGC(ProgressMonitor monitor) {
  538.         GC gc = new GC(this);
  539.         gc.setPackConfig(new PackConfig(this));
  540.         gc.setProgressMonitor(monitor);
  541.         gc.setAuto(true);
  542.         gc.setBackground(shouldAutoDetach());
  543.         try {
  544.             gc.gc();
  545.         } catch (ParseException | IOException e) {
  546.             throw new JGitInternalException(JGitText.get().gcFailed, e);
  547.         }
  548.     }

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

  571.         File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$
  572.         File refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
  573.         File headFile = new File(getDirectory(), Constants.HEAD);
  574.         FileReftableDatabase oldDb = (FileReftableDatabase) refs;

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

  583.         // This is not atomic, but there is no way to instantiate a RefDirectory
  584.         // that is disconnected from the current repo.
  585.         RefDirectory refDir = new RefDirectory(this);
  586.         refs = refDir;
  587.         refs.create();

  588.         ReflogWriter logWriter = refDir.newLogWriter(true);
  589.         List<Ref> symrefs = new ArrayList<>();
  590.         BatchRefUpdate bru = refs.newBatchUpdate();
  591.         for (Ref r : all) {
  592.             if (r.isSymbolic()) {
  593.                 symrefs.add(r);
  594.             } else {
  595.                 bru.addCommand(new ReceiveCommand(ObjectId.zeroId(),
  596.                         r.getObjectId(), r.getName()));
  597.             }

  598.             if (writeLogs) {
  599.                 List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
  600.                     .getReverseEntries();
  601.                 Collections.reverse(logs);
  602.                 for (ReflogEntry e : logs) {
  603.                     logWriter.log(r.getName(), e);
  604.                 }
  605.         }
  606.         }

  607.         try (RevWalk rw = new RevWalk(this)) {
  608.             bru.execute(rw, NullProgressMonitor.INSTANCE);
  609.         }

  610.         List<String> failed = new ArrayList<>();
  611.         for (ReceiveCommand cmd : bru.getCommands()) {
  612.             if (cmd.getResult() != ReceiveCommand.Result.OK) {
  613.                 failed.add(cmd.getRefName() + ": " + cmd.getResult()); //$NON-NLS-1$
  614.             }
  615.         }

  616.         if (!failed.isEmpty()) {
  617.             throw new IOException(String.format("%s: %s", //$NON-NLS-1$
  618.                     JGitText.get().failedToConvert,
  619.                     StringUtils.join(failed, ", "))); //$NON-NLS-1$
  620.         }

  621.         for (Ref s : symrefs) {
  622.             RefUpdate up = refs.newUpdate(s.getName(), false);
  623.             up.setForceUpdate(true);
  624.             RefUpdate.Result res = up.link(s.getTarget().getName());
  625.             if (res != RefUpdate.Result.NEW
  626.                     && res != RefUpdate.Result.NO_CHANGE) {
  627.                 throw new IOException(
  628.                         String.format("ref %s: %s", s.getName(), res)); //$NON-NLS-1$
  629.             }
  630.         }

  631.         if (!backup) {
  632.             File reftableDir = new File(getDirectory(), Constants.REFTABLE);
  633.             FileUtils.delete(reftableDir,
  634.                     FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
  635.         }
  636.         repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  637.                 ConfigConstants.CONFIG_KEY_REF_STORAGE);
  638.         repoConfig.save();
  639.     }

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

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

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

  668.         // non-atomic: remove old data.
  669.         File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
  670.         File logsDir = new File(getDirectory(), Constants.LOGS);

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

  699.         FileUtils.mkdir(refsFile, true);

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

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

  708.         repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  709.                 ConfigConstants.CONFIG_KEY_REF_STORAGE,
  710.                 ConfigConstants.CONFIG_REF_STORAGE_REFTABLE);
  711.         repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
  712.                 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
  713.         repoConfig.save();
  714.         refs.close();
  715.         refs = new FileReftableDatabase(this);
  716.     }

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