Java tutorial
/* * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2008-2010, Google Inc. * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.internal.storage.file; import static java.util.stream.Collectors.toList; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.events.IndexChangedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository; import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; import org.eclipse.jgit.lib.BaseRepositoryBuilder; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.HideDotFiles; import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a Git repository. A repository holds all objects and refs used for * managing source code (could by any type of file, but source code is what * SCM's are typically used for). * * In Git terms all data is stored in GIT_DIR, typically a directory called * .git. A work tree is maintained unless the repository is a bare repository. * Typically the .git directory is located at the root of the work dir. * * <ul> * <li>GIT_DIR * <ul> * <li>objects/ - objects</li> * <li>refs/ - tags and heads</li> * <li>config - configuration</li> * <li>info/ - more configurations</li> * </ul> * </li> * </ul> * <p> * This class is thread-safe. * <p> * This implementation only handles a subtly undocumented subset of git features. */ public class FileRepository extends Repository { private static final Logger LOG = LoggerFactory.getLogger(FileRepository.class); private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$ private final FileBasedConfig repoConfig; private RefDatabase refs; private final ObjectDirectory objectDatabase; private final Object snapshotLock = new Object(); // protected by snapshotLock private FileSnapshot snapshot; /** * Construct a representation of a Git repository. * <p> * The work tree, object directory, alternate object directories and index * file locations are deduced from the given git directory and the default * rules by running * {@link org.eclipse.jgit.storage.file.FileRepositoryBuilder}. This * constructor is the same as saying: * * <pre> * new FileRepositoryBuilder().setGitDir(gitDir).build() * </pre> * * @param gitDir * GIT_DIR (the location of the repository metadata). * @throws java.io.IOException * the repository appears to already exist but cannot be * accessed. * @see FileRepositoryBuilder */ public FileRepository(File gitDir) throws IOException { this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); } /** * A convenience API for {@link #FileRepository(File)}. * * @param gitDir * GIT_DIR (the location of the repository metadata). * @throws java.io.IOException * the repository appears to already exist but cannot be * accessed. * @see FileRepositoryBuilder */ public FileRepository(String gitDir) throws IOException { this(new File(gitDir)); } /** * Create a repository using the local file system. * * @param options * description of the repository's important paths. * @throws java.io.IOException * the user configuration file or repository configuration file * cannot be accessed. */ public FileRepository(BaseRepositoryBuilder options) throws IOException { super(options); StoredConfig userConfig = null; try { userConfig = SystemReader.getInstance().getUserConfig(); } catch (ConfigInvalidException e) { LOG.error(e.getMessage(), e); throw new IOException(e.getMessage(), e); } repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), Constants.CONFIG), getFS()); loadRepoConfig(); repoConfig.addChangeListener(this::fireEvent); final long repositoryFormatVersion = getConfig().getLong(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); String reftype = repoConfig.getString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null, ConfigConstants.CONFIG_KEY_REFSTORAGE); if (repositoryFormatVersion >= 1 && reftype != null) { if (StringUtils.equalsIgnoreCase(reftype, ConfigConstants.CONFIG_REFSTORAGE_REFTABLE)) { refs = new FileReftableDatabase(this, new File(getDirectory(), "refs")); //$NON-NLS-1$ } else if (StringUtils.equalsIgnoreCase(reftype, ConfigConstants.CONFIG_REFSTORAGE_REFTREE)) { refs = new RefTreeDatabase(this, new RefDirectory(this)); } else { throw new IOException(JGitText.get().unknownRepositoryFormat); } } else if (FileReftableDatabase.isReftable(getDirectory())) { refs = new FileReftableDatabase(this, new File(getDirectory(), "refs")); //$NON-NLS-1$ } else { refs = new RefDirectory(this); } objectDatabase = new ObjectDirectory(repoConfig, // options.getObjectDirectory(), // options.getAlternateObjectDirectories(), // getFS(), // new File(getDirectory(), Constants.SHALLOW)); if (objectDatabase.exists()) { if (repositoryFormatVersion > 1) throw new IOException(MessageFormat.format(JGitText.get().unknownRepositoryFormat2, Long.valueOf(repositoryFormatVersion))); } if (!isBare()) { snapshot = FileSnapshot.save(getIndexFile()); } } private void loadRepoConfig() throws IOException { try { repoConfig.load(); } catch (ConfigInvalidException e) { throw new IOException(JGitText.get().unknownRepositoryFormat, e); } } /** * {@inheritDoc} * <p> * Create a new Git repository initializing the necessary files and * directories. */ @Override public void create(boolean bare) throws IOException { final FileBasedConfig cfg = getConfig(); if (cfg.getFile().exists()) { throw new IllegalStateException( MessageFormat.format(JGitText.get().repositoryAlreadyExists, getDirectory())); } FileUtils.mkdirs(getDirectory(), true); HideDotFiles hideDotFiles = getConfig().getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_HIDEDOTFILES, HideDotFiles.DOTGITONLY); if (hideDotFiles != HideDotFiles.FALSE && !isBare() && getDirectory().getName().startsWith(".")) //$NON-NLS-1$ getFS().setHidden(getDirectory(), true); refs.create(); objectDatabase.create(); FileUtils.mkdir(new File(getDirectory(), "branches")); //$NON-NLS-1$ FileUtils.mkdir(new File(getDirectory(), "hooks")); //$NON-NLS-1$ RefUpdate head = updateRef(Constants.HEAD); head.disableRefLog(); head.link(Constants.R_HEADS + Constants.MASTER); final boolean fileMode; if (getFS().supportsExecute()) { File tmp = File.createTempFile("try", "execute", getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$ getFS().setExecute(tmp, true); final boolean on = getFS().canExecute(tmp); getFS().setExecute(tmp, false); final boolean off = getFS().canExecute(tmp); FileUtils.delete(tmp); fileMode = on && !off; } else { fileMode = false; } SymLinks symLinks = SymLinks.FALSE; if (getFS().supportsSymlinks()) { File tmp = new File(getDirectory(), "tmplink"); //$NON-NLS-1$ try { getFS().createSymLink(tmp, "target"); //$NON-NLS-1$ symLinks = null; FileUtils.delete(tmp); } catch (IOException e) { // Normally a java.nio.file.FileSystemException } } if (symLinks != null) cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name().toLowerCase(Locale.ROOT)); cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_FILEMODE, fileMode); if (bare) cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_BARE, true); cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare); if (SystemReader.getInstance().isMacOS()) // Java has no other way cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true); if (!bare) { File workTree = getWorkTree(); if (!getDirectory().getParentFile().equals(workTree)) { cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree().getAbsolutePath()); LockFile dotGitLockFile = new LockFile(new File(workTree, Constants.DOT_GIT)); try { if (dotGitLockFile.lock()) { dotGitLockFile.write(Constants.encode(Constants.GITDIR + getDirectory().getAbsolutePath())); dotGitLockFile.commit(); } } finally { dotGitLockFile.unlock(); } } } cfg.save(); } /** * Get the directory containing the objects owned by this repository * * @return the directory containing the objects owned by this repository. */ public File getObjectsDirectory() { return objectDatabase.getDirectory(); } /** {@inheritDoc} */ @Override public ObjectDirectory getObjectDatabase() { return objectDatabase; } /** {@inheritDoc} */ @Override public RefDatabase getRefDatabase() { return refs; } /** {@inheritDoc} */ @Override public String getIdentifier() { File directory = getDirectory(); if (directory != null) { return directory.getPath(); } throw new IllegalStateException(); } /** {@inheritDoc} */ @Override public FileBasedConfig getConfig() { try { SystemReader.getInstance().getUserConfig(); if (repoConfig.isOutdated()) { loadRepoConfig(); } } catch (IOException | ConfigInvalidException e) { throw new RuntimeException(e); } return repoConfig; } /** {@inheritDoc} */ @Override @Nullable public String getGitwebDescription() throws IOException { String d; try { d = RawParseUtils.decode(IO.readFully(descriptionFile())); } catch (FileNotFoundException err) { return null; } if (d != null) { d = d.trim(); if (d.isEmpty() || UNNAMED.equals(d)) { return null; } } return d; } /** {@inheritDoc} */ @Override public void setGitwebDescription(@Nullable String description) throws IOException { String old = getGitwebDescription(); if (Objects.equals(old, description)) { return; } File path = descriptionFile(); LockFile lock = new LockFile(path); if (!lock.lock()) { throw new IOException(MessageFormat.format(JGitText.get().lockError, path.getAbsolutePath())); } try { String d = description; if (d != null) { d = d.trim(); if (!d.isEmpty()) { d += '\n'; } } else { d = ""; //$NON-NLS-1$ } lock.write(Constants.encode(d)); lock.commit(); } finally { lock.unlock(); } } private File descriptionFile() { return new File(getDirectory(), "description"); //$NON-NLS-1$ } /** * {@inheritDoc} * <p> * Objects known to exist but not expressed by {@code #getAllRefs()}. * <p> * When a repository borrows objects from another repository, it can * advertise that it safely has that other repository's references, without * exposing any other details about the other repository. This may help a * client trying to push changes avoid pushing more than it needs to. */ @Override public Set<ObjectId> getAdditionalHaves() { return getAdditionalHaves(null); } /** * Objects known to exist but not expressed by {@code #getAllRefs()}. * <p> * When a repository borrows objects from another repository, it can * advertise that it safely has that other repository's references, without * exposing any other details about the other repository. This may help a * client trying to push changes avoid pushing more than it needs to. * * @param skips * Set of AlternateHandle Ids already seen * * @return unmodifiable collection of other known objects. */ private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) { HashSet<ObjectId> r = new HashSet<>(); skips = objectDatabase.addMe(skips); for (AlternateHandle d : objectDatabase.myAlternates()) { if (d instanceof AlternateRepository && !skips.contains(d.getId())) { FileRepository repo; repo = ((AlternateRepository) d).repository; for (Ref ref : repo.getAllRefs().values()) { if (ref.getObjectId() != null) r.add(ref.getObjectId()); if (ref.getPeeledObjectId() != null) r.add(ref.getPeeledObjectId()); } r.addAll(repo.getAdditionalHaves(skips)); } } return r; } /** * Add a single existing pack to the list of available pack files. * * @param pack * path of the pack file to open. * @throws java.io.IOException * index file could not be opened, read, or is not recognized as * a Git pack file index. */ public void openPack(File pack) throws IOException { objectDatabase.openPack(pack); } /** {@inheritDoc} */ @Override public void scanForRepoChanges() throws IOException { getRefDatabase().getRefs(); // This will look for changes to refs detectIndexChanges(); } /** Detect index changes. */ private void detectIndexChanges() { if (isBare()) { return; } File indexFile = getIndexFile(); synchronized (snapshotLock) { if (snapshot == null) { snapshot = FileSnapshot.save(indexFile); return; } if (!snapshot.isModified(indexFile)) { return; } } notifyIndexChanged(false); } /** {@inheritDoc} */ @Override public void notifyIndexChanged(boolean internal) { synchronized (snapshotLock) { snapshot = FileSnapshot.save(getIndexFile()); } fireEvent(new IndexChangedEvent(internal)); } /** {@inheritDoc} */ @Override public ReflogReader getReflogReader(String refName) throws IOException { if (refs instanceof FileReftableDatabase) { // Cannot use findRef: reftable stores log data for deleted or renamed // branches. return ((FileReftableDatabase) refs).getReflogReader(refName); } // TODO: use exactRef here, which offers more predictable and therefore preferable // behavior. Ref ref = findRef(refName); if (ref == null) { return null; } return new ReflogReaderImpl(this, ref.getName()); } /** {@inheritDoc} */ @Override public AttributesNodeProvider createAttributesNodeProvider() { return new AttributesNodeProviderImpl(this); } /** * Implementation a {@link AttributesNodeProvider} for a * {@link FileRepository}. * * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a> * */ static class AttributesNodeProviderImpl implements AttributesNodeProvider { private AttributesNode infoAttributesNode; private AttributesNode globalAttributesNode; /** * Constructor. * * @param repo * {@link Repository} that will provide the attribute nodes. */ protected AttributesNodeProviderImpl(Repository repo) { infoAttributesNode = new InfoAttributesNode(repo); globalAttributesNode = new GlobalAttributesNode(repo); } @Override public AttributesNode getInfoAttributesNode() throws IOException { if (infoAttributesNode instanceof InfoAttributesNode) infoAttributesNode = ((InfoAttributesNode) infoAttributesNode).load(); return infoAttributesNode; } @Override public AttributesNode getGlobalAttributesNode() throws IOException { if (globalAttributesNode instanceof GlobalAttributesNode) globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode).load(); return globalAttributesNode; } static void loadRulesFromFile(AttributesNode r, File attrs) throws FileNotFoundException, IOException { if (attrs.exists()) { try (FileInputStream in = new FileInputStream(attrs)) { r.parse(in); } } } } private boolean shouldAutoDetach() { return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION, ConfigConstants.CONFIG_KEY_AUTODETACH, true); } /** {@inheritDoc} */ @Override public void autoGC(ProgressMonitor monitor) { GC gc = new GC(this); gc.setPackConfig(new PackConfig(this)); gc.setProgressMonitor(monitor); gc.setAuto(true); gc.setBackground(shouldAutoDetach()); try { gc.gc(); } catch (ParseException | IOException e) { throw new JGitInternalException(JGitText.get().gcFailed, e); } } /** * Converts the RefDatabase from reftable to RefDirectory. This operation is * not atomic. * * @param backup * whether to rename or delete the old storage files. If set to * true, the reftable list is left in "refs.old", and the * reftable/ dir is left alone. If set to false, the reftable/ * dir is removed, and "refs" file is removed. * @throws IOException * on IO problem */ void convertToPackedRefs(boolean backup) throws IOException { List<Ref> all = refs.getRefs(); File packedRefs = new File(getDirectory(), Constants.PACKED_REFS); if (packedRefs.exists()) { throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists, packedRefs.getName())); } File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$ refs.close(); if (backup) { File refsOld = new File(getDirectory(), "refs.old"); //$NON-NLS-1$ if (refsOld.exists()) { throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists, "refs.old")); //$NON-NLS-1$ } FileUtils.rename(refsFile, refsOld); } else { refsFile.delete(); } // This is not atomic, but there is no way to instantiate a RefDirectory // that is disconnected from the current repo. refs = new RefDirectory(this); refs.create(); List<Ref> symrefs = new ArrayList<>(); BatchRefUpdate bru = refs.newBatchUpdate(); for (Ref r : all) { if (r.isSymbolic()) { symrefs.add(r); } else { bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), r.getObjectId(), r.getName())); } } try (RevWalk rw = new RevWalk(this)) { bru.execute(rw, NullProgressMonitor.INSTANCE); } List<String> failed = new ArrayList<>(); for (ReceiveCommand cmd : bru.getCommands()) { if (cmd.getResult() != ReceiveCommand.Result.OK) { failed.add(cmd.getRefName() + ": " + cmd.getResult()); //$NON-NLS-1$ } } if (!failed.isEmpty()) { throw new IOException(String.format("%s: %s", //$NON-NLS-1$ JGitText.get().failedToConvert, StringUtils.join(failed, ", "))); //$NON-NLS-1$ } for (Ref s : symrefs) { RefUpdate up = refs.newUpdate(s.getName(), false); up.setForceUpdate(true); RefUpdate.Result res = up.link(s.getTarget().getName()); if (res != RefUpdate.Result.NEW && res != RefUpdate.Result.NO_CHANGE) { throw new IOException(String.format("ref %s: %s", s.getName(), res)); //$NON-NLS-1$ } } if (!backup) { File reftableDir = new File(getDirectory(), Constants.REFTABLE); FileUtils.delete(reftableDir, FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); } repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null, ConfigConstants.CONFIG_KEY_REFSTORAGE); repoConfig.save(); } @SuppressWarnings("nls") void convertToReftable(boolean writeLogs, boolean backup) throws IOException { File newRefs = new File(getDirectory(), "refs.new"); File reftableDir = new File(getDirectory(), Constants.REFTABLE); if (reftableDir.exists() && reftableDir.listFiles().length > 0) { throw new IOException(JGitText.get().reftableDirExists); } // Ignore return value, as it is tied to temporary newRefs file. FileReftableDatabase.convertFrom(this, newRefs, writeLogs); File refsFile = new File(getDirectory(), "refs"); // non-atomic: remove old data. File packedRefs = new File(getDirectory(), Constants.PACKED_REFS); File logsDir = new File(getDirectory(), Constants.LOGS); List<String> additional = getRefDatabase().getAdditionalRefs().stream().map(Ref::getName).collect(toList()); additional.add(Constants.HEAD); if (backup) { FileUtils.rename(refsFile, new File(getDirectory(), "refs.old")); if (packedRefs.exists()) { FileUtils.rename(packedRefs, new File(getDirectory(), Constants.PACKED_REFS + ".old")); } if (logsDir.exists()) { FileUtils.rename(logsDir, new File(getDirectory(), Constants.LOGS + ".old")); } for (String r : additional) { FileUtils.rename(new File(getDirectory(), r), new File(getDirectory(), r + ".old")); } } else { packedRefs.delete(); // ignore return value. FileUtils.delete(logsDir, FileUtils.RECURSIVE); FileUtils.delete(refsFile, FileUtils.RECURSIVE); for (String r : additional) { new File(getDirectory(), r).delete(); } } // Put new data. FileUtils.rename(newRefs, refsFile); refs.close(); refs = new FileReftableDatabase(this, refsFile); repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null, ConfigConstants.CONFIG_KEY_REFSTORAGE, ConfigConstants.CONFIG_REFSTORAGE_REFTABLE); repoConfig.save(); } /** * Converts between ref storage formats. * * @param format * the format to convert to, either "reftable" or "refdir" * @param writeLogs * whether to write reflogs * @param backup * whether to make a backup of the old data * @throws IOException * on I/O problems. */ public void convertRefStorage(String format, boolean writeLogs, boolean backup) throws IOException { if (format.equals("reftable")) { //$NON-NLS-1$ if (refs instanceof RefDirectory) { convertToReftable(writeLogs, backup); } } else if (format.equals("refdir")) {//$NON-NLS-1$ if (refs instanceof FileReftableDatabase) { convertToPackedRefs(backup); } } else { throw new IOException(MessageFormat.format(JGitText.get().unknownRefStorageFormat, format)); } } }