Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

FileRepository.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  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 + Constants.MASTER);
  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);
  691. FileUtils.delete(logsDir, FileUtils.RECURSIVE);
  692. FileUtils.delete(refsFile, FileUtils.RECURSIVE);
  693. for (String r : additional) {
  694. new File(getDirectory(), r).delete();
  695. }
  696. }
  697. FileUtils.mkdir(refsFile, true);
  698. // By putting in a dummy HEAD, old versions of Git still detect a repo
  699. // (that they can't read)
  700. try (OutputStream os = new FileOutputStream(headFile)) {
  701. os.write(Constants.encodeASCII("ref: refs/heads/.invalid"));
  702. }
  703. // Some tools might write directly into .git/refs/heads/BRANCH. By
  704. // putting a file here, this fails spectacularly.
  705. FileUtils.createNewFile(new File(refsFile, "heads"));
  706. repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  707. ConfigConstants.CONFIG_KEY_REF_STORAGE,
  708. ConfigConstants.CONFIG_REF_STORAGE_REFTABLE);
  709. repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
  710. ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
  711. repoConfig.save();
  712. refs.close();
  713. refs = new FileReftableDatabase(this);
  714. }
  715. /**
  716. * Converts between ref storage formats.
  717. *
  718. * @param format
  719. * the format to convert to, either "reftable" or "refdir"
  720. * @param writeLogs
  721. * whether to write reflogs
  722. * @param backup
  723. * whether to make a backup of the old data
  724. * @throws IOException
  725. * on I/O problems.
  726. */
  727. public void convertRefStorage(String format, boolean writeLogs,
  728. boolean backup) throws IOException {
  729. if (format.equals("reftable")) { //$NON-NLS-1$
  730. if (refs instanceof RefDirectory) {
  731. convertToReftable(writeLogs, backup);
  732. }
  733. } else if (format.equals("refdir")) {//$NON-NLS-1$
  734. if (refs instanceof FileReftableDatabase) {
  735. convertToPackedRefs(writeLogs, backup);
  736. }
  737. } else {
  738. throw new IOException(MessageFormat
  739. .format(JGitText.get().unknownRefStorageFormat, format));
  740. }
  741. }
  742. }