You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileRepository.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  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.internal.storage.reftree.RefTreeDatabase;
  40. import org.eclipse.jgit.lib.BaseRepositoryBuilder;
  41. import org.eclipse.jgit.lib.BatchRefUpdate;
  42. import org.eclipse.jgit.lib.ConfigConstants;
  43. import org.eclipse.jgit.lib.Constants;
  44. import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
  45. import org.eclipse.jgit.lib.CoreConfig.SymLinks;
  46. import org.eclipse.jgit.lib.NullProgressMonitor;
  47. import org.eclipse.jgit.lib.ObjectId;
  48. import org.eclipse.jgit.lib.ProgressMonitor;
  49. import org.eclipse.jgit.lib.Ref;
  50. import org.eclipse.jgit.lib.RefDatabase;
  51. import org.eclipse.jgit.lib.RefUpdate;
  52. import org.eclipse.jgit.lib.ReflogEntry;
  53. import org.eclipse.jgit.lib.ReflogReader;
  54. import org.eclipse.jgit.lib.Repository;
  55. import org.eclipse.jgit.lib.StoredConfig;
  56. import org.eclipse.jgit.revwalk.RevWalk;
  57. import org.eclipse.jgit.storage.file.FileBasedConfig;
  58. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  59. import org.eclipse.jgit.storage.pack.PackConfig;
  60. import org.eclipse.jgit.transport.ReceiveCommand;
  61. import org.eclipse.jgit.util.FileUtils;
  62. import org.eclipse.jgit.util.IO;
  63. import org.eclipse.jgit.util.RawParseUtils;
  64. import org.eclipse.jgit.util.StringUtils;
  65. import org.eclipse.jgit.util.SystemReader;
  66. import org.slf4j.Logger;
  67. import org.slf4j.LoggerFactory;
  68. /**
  69. * Represents a Git repository. A repository holds all objects and refs used for
  70. * managing source code (could by any type of file, but source code is what
  71. * SCM's are typically used for).
  72. *
  73. * In Git terms all data is stored in GIT_DIR, typically a directory called
  74. * .git. A work tree is maintained unless the repository is a bare repository.
  75. * Typically the .git directory is located at the root of the work dir.
  76. *
  77. * <ul>
  78. * <li>GIT_DIR
  79. * <ul>
  80. * <li>objects/ - objects</li>
  81. * <li>refs/ - tags and heads</li>
  82. * <li>config - configuration</li>
  83. * <li>info/ - more configurations</li>
  84. * </ul>
  85. * </li>
  86. * </ul>
  87. * <p>
  88. * This class is thread-safe.
  89. * <p>
  90. * This implementation only handles a subtly undocumented subset of git features.
  91. */
  92. public class FileRepository extends Repository {
  93. private static final Logger LOG = LoggerFactory
  94. .getLogger(FileRepository.class);
  95. private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$
  96. private final FileBasedConfig repoConfig;
  97. private RefDatabase refs;
  98. private final ObjectDirectory objectDatabase;
  99. private final Object snapshotLock = new Object();
  100. // protected by snapshotLock
  101. private FileSnapshot snapshot;
  102. /**
  103. * Construct a representation of a Git repository.
  104. * <p>
  105. * The work tree, object directory, alternate object directories and index
  106. * file locations are deduced from the given git directory and the default
  107. * rules by running
  108. * {@link org.eclipse.jgit.storage.file.FileRepositoryBuilder}. This
  109. * constructor is the same as saying:
  110. *
  111. * <pre>
  112. * new FileRepositoryBuilder().setGitDir(gitDir).build()
  113. * </pre>
  114. *
  115. * @param gitDir
  116. * GIT_DIR (the location of the repository metadata).
  117. * @throws java.io.IOException
  118. * the repository appears to already exist but cannot be
  119. * accessed.
  120. * @see FileRepositoryBuilder
  121. */
  122. public FileRepository(File gitDir) throws IOException {
  123. this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
  124. }
  125. /**
  126. * A convenience API for {@link #FileRepository(File)}.
  127. *
  128. * @param gitDir
  129. * GIT_DIR (the location of the repository metadata).
  130. * @throws java.io.IOException
  131. * the repository appears to already exist but cannot be
  132. * accessed.
  133. * @see FileRepositoryBuilder
  134. */
  135. public FileRepository(String gitDir) throws IOException {
  136. this(new File(gitDir));
  137. }
  138. /**
  139. * Create a repository using the local file system.
  140. *
  141. * @param options
  142. * description of the repository's important paths.
  143. * @throws java.io.IOException
  144. * the user configuration file or repository configuration file
  145. * cannot be accessed.
  146. */
  147. public FileRepository(BaseRepositoryBuilder options) throws IOException {
  148. super(options);
  149. StoredConfig userConfig = null;
  150. try {
  151. userConfig = SystemReader.getInstance().getUserConfig();
  152. } catch (ConfigInvalidException e) {
  153. LOG.error(e.getMessage(), e);
  154. throw new IOException(e.getMessage(), e);
  155. }
  156. repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
  157. getDirectory(), Constants.CONFIG),
  158. getFS());
  159. loadRepoConfig();
  160. repoConfig.addChangeListener(this::fireEvent);
  161. final long repositoryFormatVersion = getConfig().getLong(
  162. ConfigConstants.CONFIG_CORE_SECTION, null,
  163. ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
  164. String reftype = repoConfig.getString(
  165. ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  166. ConfigConstants.CONFIG_KEY_REF_STORAGE);
  167. if (repositoryFormatVersion >= 1 && reftype != null) {
  168. if (StringUtils.equalsIgnoreCase(reftype,
  169. ConfigConstants.CONFIG_REF_STORAGE_REFTABLE)) {
  170. refs = new FileReftableDatabase(this);
  171. } else if (StringUtils.equalsIgnoreCase(reftype,
  172. ConfigConstants.CONFIG_REFSTORAGE_REFTREE)) {
  173. refs = new RefTreeDatabase(this, new RefDirectory(this));
  174. } else {
  175. throw new IOException(JGitText.get().unknownRepositoryFormat);
  176. }
  177. } else {
  178. refs = new RefDirectory(this);
  179. }
  180. objectDatabase = new ObjectDirectory(repoConfig, //
  181. options.getObjectDirectory(), //
  182. options.getAlternateObjectDirectories(), //
  183. getFS(), //
  184. new File(getDirectory(), Constants.SHALLOW));
  185. if (objectDatabase.exists()) {
  186. if (repositoryFormatVersion > 1)
  187. throw new IOException(MessageFormat.format(
  188. JGitText.get().unknownRepositoryFormat2,
  189. Long.valueOf(repositoryFormatVersion)));
  190. }
  191. if (!isBare()) {
  192. snapshot = FileSnapshot.save(getIndexFile());
  193. }
  194. }
  195. private void loadRepoConfig() throws IOException {
  196. try {
  197. repoConfig.load();
  198. } catch (ConfigInvalidException e) {
  199. throw new IOException(JGitText.get().unknownRepositoryFormat, e);
  200. }
  201. }
  202. /**
  203. * {@inheritDoc}
  204. * <p>
  205. * Create a new Git repository initializing the necessary files and
  206. * directories.
  207. */
  208. @Override
  209. public void create(boolean bare) throws IOException {
  210. final FileBasedConfig cfg = getConfig();
  211. if (cfg.getFile().exists()) {
  212. throw new IllegalStateException(MessageFormat.format(
  213. JGitText.get().repositoryAlreadyExists, getDirectory()));
  214. }
  215. FileUtils.mkdirs(getDirectory(), true);
  216. HideDotFiles hideDotFiles = getConfig().getEnum(
  217. ConfigConstants.CONFIG_CORE_SECTION, null,
  218. ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
  219. HideDotFiles.DOTGITONLY);
  220. if (hideDotFiles != HideDotFiles.FALSE && !isBare()
  221. && getDirectory().getName().startsWith(".")) //$NON-NLS-1$
  222. getFS().setHidden(getDirectory(), true);
  223. refs.create();
  224. objectDatabase.create();
  225. FileUtils.mkdir(new File(getDirectory(), "branches")); //$NON-NLS-1$
  226. FileUtils.mkdir(new File(getDirectory(), "hooks")); //$NON-NLS-1$
  227. RefUpdate head = updateRef(Constants.HEAD);
  228. head.disableRefLog();
  229. head.link(Constants.R_HEADS + Constants.MASTER);
  230. final boolean fileMode;
  231. if (getFS().supportsExecute()) {
  232. File tmp = File.createTempFile("try", "execute", getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
  233. getFS().setExecute(tmp, true);
  234. final boolean on = getFS().canExecute(tmp);
  235. getFS().setExecute(tmp, false);
  236. final boolean off = getFS().canExecute(tmp);
  237. FileUtils.delete(tmp);
  238. fileMode = on && !off;
  239. } else {
  240. fileMode = false;
  241. }
  242. SymLinks symLinks = SymLinks.FALSE;
  243. if (getFS().supportsSymlinks()) {
  244. File tmp = new File(getDirectory(), "tmplink"); //$NON-NLS-1$
  245. try {
  246. getFS().createSymLink(tmp, "target"); //$NON-NLS-1$
  247. symLinks = null;
  248. FileUtils.delete(tmp);
  249. } catch (IOException e) {
  250. // Normally a java.nio.file.FileSystemException
  251. }
  252. }
  253. if (symLinks != null)
  254. cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
  255. ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
  256. .toLowerCase(Locale.ROOT));
  257. cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
  258. ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
  259. cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  260. ConfigConstants.CONFIG_KEY_FILEMODE, fileMode);
  261. if (bare)
  262. cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  263. ConfigConstants.CONFIG_KEY_BARE, true);
  264. cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  265. ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
  266. if (SystemReader.getInstance().isMacOS())
  267. // Java has no other way
  268. cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  269. ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
  270. if (!bare) {
  271. File workTree = getWorkTree();
  272. if (!getDirectory().getParentFile().equals(workTree)) {
  273. cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
  274. ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
  275. .getAbsolutePath());
  276. LockFile dotGitLockFile = new LockFile(new File(workTree,
  277. Constants.DOT_GIT));
  278. try {
  279. if (dotGitLockFile.lock()) {
  280. dotGitLockFile.write(Constants.encode(Constants.GITDIR
  281. + getDirectory().getAbsolutePath()));
  282. dotGitLockFile.commit();
  283. }
  284. } finally {
  285. dotGitLockFile.unlock();
  286. }
  287. }
  288. }
  289. cfg.save();
  290. }
  291. /**
  292. * Get the directory containing the objects owned by this repository
  293. *
  294. * @return the directory containing the objects owned by this repository.
  295. */
  296. public File getObjectsDirectory() {
  297. return objectDatabase.getDirectory();
  298. }
  299. /** {@inheritDoc} */
  300. @Override
  301. public ObjectDirectory getObjectDatabase() {
  302. return objectDatabase;
  303. }
  304. /** {@inheritDoc} */
  305. @Override
  306. public RefDatabase getRefDatabase() {
  307. return refs;
  308. }
  309. /** {@inheritDoc} */
  310. @Override
  311. public String getIdentifier() {
  312. File directory = getDirectory();
  313. if (directory != null) {
  314. return directory.getPath();
  315. }
  316. throw new IllegalStateException();
  317. }
  318. /** {@inheritDoc} */
  319. @Override
  320. public FileBasedConfig getConfig() {
  321. try {
  322. SystemReader.getInstance().getUserConfig();
  323. if (repoConfig.isOutdated()) {
  324. loadRepoConfig();
  325. }
  326. } catch (IOException | ConfigInvalidException e) {
  327. throw new RuntimeException(e);
  328. }
  329. return repoConfig;
  330. }
  331. /** {@inheritDoc} */
  332. @Override
  333. @Nullable
  334. public String getGitwebDescription() throws IOException {
  335. String d;
  336. try {
  337. d = RawParseUtils.decode(IO.readFully(descriptionFile()));
  338. } catch (FileNotFoundException err) {
  339. return null;
  340. }
  341. if (d != null) {
  342. d = d.trim();
  343. if (d.isEmpty() || UNNAMED.equals(d)) {
  344. return null;
  345. }
  346. }
  347. return d;
  348. }
  349. /** {@inheritDoc} */
  350. @Override
  351. public void setGitwebDescription(@Nullable String description)
  352. throws IOException {
  353. String old = getGitwebDescription();
  354. if (Objects.equals(old, description)) {
  355. return;
  356. }
  357. File path = descriptionFile();
  358. LockFile lock = new LockFile(path);
  359. if (!lock.lock()) {
  360. throw new IOException(MessageFormat.format(JGitText.get().lockError,
  361. path.getAbsolutePath()));
  362. }
  363. try {
  364. String d = description;
  365. if (d != null) {
  366. d = d.trim();
  367. if (!d.isEmpty()) {
  368. d += '\n';
  369. }
  370. } else {
  371. d = ""; //$NON-NLS-1$
  372. }
  373. lock.write(Constants.encode(d));
  374. lock.commit();
  375. } finally {
  376. lock.unlock();
  377. }
  378. }
  379. private File descriptionFile() {
  380. return new File(getDirectory(), "description"); //$NON-NLS-1$
  381. }
  382. /**
  383. * {@inheritDoc}
  384. * <p>
  385. * Objects known to exist but not expressed by {@code #getAllRefs()}.
  386. * <p>
  387. * When a repository borrows objects from another repository, it can
  388. * advertise that it safely has that other repository's references, without
  389. * exposing any other details about the other repository. This may help a
  390. * client trying to push changes avoid pushing more than it needs to.
  391. */
  392. @Override
  393. public Set<ObjectId> getAdditionalHaves() {
  394. return getAdditionalHaves(null);
  395. }
  396. /**
  397. * Objects known to exist but not expressed by {@code #getAllRefs()}.
  398. * <p>
  399. * When a repository borrows objects from another repository, it can
  400. * advertise that it safely has that other repository's references, without
  401. * exposing any other details about the other repository. This may help a
  402. * client trying to push changes avoid pushing more than it needs to.
  403. *
  404. * @param skips
  405. * Set of AlternateHandle Ids already seen
  406. *
  407. * @return unmodifiable collection of other known objects.
  408. */
  409. private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) {
  410. HashSet<ObjectId> r = new HashSet<>();
  411. skips = objectDatabase.addMe(skips);
  412. for (AlternateHandle d : objectDatabase.myAlternates()) {
  413. if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
  414. FileRepository repo;
  415. repo = ((AlternateRepository) d).repository;
  416. for (Ref ref : repo.getAllRefs().values()) {
  417. if (ref.getObjectId() != null)
  418. r.add(ref.getObjectId());
  419. if (ref.getPeeledObjectId() != null)
  420. r.add(ref.getPeeledObjectId());
  421. }
  422. r.addAll(repo.getAdditionalHaves(skips));
  423. }
  424. }
  425. return r;
  426. }
  427. /**
  428. * Add a single existing pack to the list of available pack files.
  429. *
  430. * @param pack
  431. * path of the pack file to open.
  432. * @throws java.io.IOException
  433. * index file could not be opened, read, or is not recognized as
  434. * a Git pack file index.
  435. */
  436. public void openPack(File pack) throws IOException {
  437. objectDatabase.openPack(pack);
  438. }
  439. /** {@inheritDoc} */
  440. @Override
  441. public void scanForRepoChanges() throws IOException {
  442. getRefDatabase().getRefs(); // This will look for changes to refs
  443. detectIndexChanges();
  444. }
  445. /** Detect index changes. */
  446. private void detectIndexChanges() {
  447. if (isBare()) {
  448. return;
  449. }
  450. File indexFile = getIndexFile();
  451. synchronized (snapshotLock) {
  452. if (snapshot == null) {
  453. snapshot = FileSnapshot.save(indexFile);
  454. return;
  455. }
  456. if (!snapshot.isModified(indexFile)) {
  457. return;
  458. }
  459. }
  460. notifyIndexChanged(false);
  461. }
  462. /** {@inheritDoc} */
  463. @Override
  464. public void notifyIndexChanged(boolean internal) {
  465. synchronized (snapshotLock) {
  466. snapshot = FileSnapshot.save(getIndexFile());
  467. }
  468. fireEvent(new IndexChangedEvent(internal));
  469. }
  470. /** {@inheritDoc} */
  471. @Override
  472. public ReflogReader getReflogReader(String refName) throws IOException {
  473. if (refs instanceof FileReftableDatabase) {
  474. // Cannot use findRef: reftable stores log data for deleted or renamed
  475. // branches.
  476. return ((FileReftableDatabase)refs).getReflogReader(refName);
  477. }
  478. // TODO: use exactRef here, which offers more predictable and therefore preferable
  479. // behavior.
  480. Ref ref = findRef(refName);
  481. if (ref == null) {
  482. return null;
  483. }
  484. return new ReflogReaderImpl(this, ref.getName());
  485. }
  486. /** {@inheritDoc} */
  487. @Override
  488. public AttributesNodeProvider createAttributesNodeProvider() {
  489. return new AttributesNodeProviderImpl(this);
  490. }
  491. /**
  492. * Implementation a {@link AttributesNodeProvider} for a
  493. * {@link FileRepository}.
  494. *
  495. * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
  496. *
  497. */
  498. static class AttributesNodeProviderImpl implements
  499. AttributesNodeProvider {
  500. private AttributesNode infoAttributesNode;
  501. private AttributesNode globalAttributesNode;
  502. /**
  503. * Constructor.
  504. *
  505. * @param repo
  506. * {@link Repository} that will provide the attribute nodes.
  507. */
  508. protected AttributesNodeProviderImpl(Repository repo) {
  509. infoAttributesNode = new InfoAttributesNode(repo);
  510. globalAttributesNode = new GlobalAttributesNode(repo);
  511. }
  512. @Override
  513. public AttributesNode getInfoAttributesNode() throws IOException {
  514. if (infoAttributesNode instanceof InfoAttributesNode)
  515. infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
  516. .load();
  517. return infoAttributesNode;
  518. }
  519. @Override
  520. public AttributesNode getGlobalAttributesNode() throws IOException {
  521. if (globalAttributesNode instanceof GlobalAttributesNode)
  522. globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
  523. .load();
  524. return globalAttributesNode;
  525. }
  526. static void loadRulesFromFile(AttributesNode r, File attrs)
  527. throws FileNotFoundException, IOException {
  528. if (attrs.exists()) {
  529. try (FileInputStream in = new FileInputStream(attrs)) {
  530. r.parse(in);
  531. }
  532. }
  533. }
  534. }
  535. private boolean shouldAutoDetach() {
  536. return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
  537. ConfigConstants.CONFIG_KEY_AUTODETACH, true);
  538. }
  539. /** {@inheritDoc} */
  540. @Override
  541. public void autoGC(ProgressMonitor monitor) {
  542. GC gc = new GC(this);
  543. gc.setPackConfig(new PackConfig(this));
  544. gc.setProgressMonitor(monitor);
  545. gc.setAuto(true);
  546. gc.setBackground(shouldAutoDetach());
  547. try {
  548. gc.gc();
  549. } catch (ParseException | IOException e) {
  550. throw new JGitInternalException(JGitText.get().gcFailed, e);
  551. }
  552. }
  553. /**
  554. * Converts the RefDatabase from reftable to RefDirectory. This operation is
  555. * not atomic.
  556. *
  557. * @param writeLogs
  558. * whether to write reflogs
  559. * @param backup
  560. * whether to rename or delete the old storage files. If set to
  561. * {@code true}, the reftable list is left in {@code refs.old},
  562. * and the {@code reftable/} dir is left alone. If set to
  563. * {@code false}, the {@code reftable/} dir is removed, and
  564. * {@code refs} file is removed.
  565. * @throws IOException
  566. * on IO problem
  567. */
  568. void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
  569. List<Ref> all = refs.getRefs();
  570. File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
  571. if (packedRefs.exists()) {
  572. throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists,
  573. packedRefs.getName()));
  574. }
  575. File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$
  576. File refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
  577. File headFile = new File(getDirectory(), Constants.HEAD);
  578. FileReftableDatabase oldDb = (FileReftableDatabase) refs;
  579. // Remove the dummy files that ensure compatibility with older git
  580. // versions (see convertToReftable). First make room for refs/heads/
  581. refsHeadsFile.delete();
  582. // RefDirectory wants to create the refs/ directory from scratch, so
  583. // remove that too.
  584. refsFile.delete();
  585. // remove HEAD so its previous invalid value doesn't cause issues.
  586. headFile.delete();
  587. // This is not atomic, but there is no way to instantiate a RefDirectory
  588. // that is disconnected from the current repo.
  589. RefDirectory refDir = new RefDirectory(this);
  590. refs = refDir;
  591. refs.create();
  592. ReflogWriter logWriter = refDir.newLogWriter(true);
  593. List<Ref> symrefs = new ArrayList<>();
  594. BatchRefUpdate bru = refs.newBatchUpdate();
  595. for (Ref r : all) {
  596. if (r.isSymbolic()) {
  597. symrefs.add(r);
  598. } else {
  599. bru.addCommand(new ReceiveCommand(ObjectId.zeroId(),
  600. r.getObjectId(), r.getName()));
  601. }
  602. if (writeLogs) {
  603. List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
  604. .getReverseEntries();
  605. Collections.reverse(logs);
  606. for (ReflogEntry e : logs) {
  607. logWriter.log(r.getName(), e);
  608. }
  609. }
  610. }
  611. try (RevWalk rw = new RevWalk(this)) {
  612. bru.execute(rw, NullProgressMonitor.INSTANCE);
  613. }
  614. List<String> failed = new ArrayList<>();
  615. for (ReceiveCommand cmd : bru.getCommands()) {
  616. if (cmd.getResult() != ReceiveCommand.Result.OK) {
  617. failed.add(cmd.getRefName() + ": " + cmd.getResult()); //$NON-NLS-1$
  618. }
  619. }
  620. if (!failed.isEmpty()) {
  621. throw new IOException(String.format("%s: %s", //$NON-NLS-1$
  622. JGitText.get().failedToConvert,
  623. StringUtils.join(failed, ", "))); //$NON-NLS-1$
  624. }
  625. for (Ref s : symrefs) {
  626. RefUpdate up = refs.newUpdate(s.getName(), false);
  627. up.setForceUpdate(true);
  628. RefUpdate.Result res = up.link(s.getTarget().getName());
  629. if (res != RefUpdate.Result.NEW
  630. && res != RefUpdate.Result.NO_CHANGE) {
  631. throw new IOException(
  632. String.format("ref %s: %s", s.getName(), res)); //$NON-NLS-1$
  633. }
  634. }
  635. if (!backup) {
  636. File reftableDir = new File(getDirectory(), Constants.REFTABLE);
  637. FileUtils.delete(reftableDir,
  638. FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
  639. }
  640. repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  641. ConfigConstants.CONFIG_KEY_REF_STORAGE);
  642. repoConfig.save();
  643. }
  644. /**
  645. * Converts the RefDatabase from RefDirectory to reftable. This operation is
  646. * not atomic.
  647. *
  648. * @param writeLogs
  649. * whether to write reflogs
  650. * @param backup
  651. * whether to rename or delete the old storage files. If set to
  652. * {@code true}, the loose refs are left in {@code refs.old}, the
  653. * packed-refs in {@code packed-refs.old} and reflogs in
  654. * {@code refs.old/}. HEAD is left in {@code HEAD.old} and also
  655. * {@code .log} is appended to additional refs. If set to
  656. * {@code false}, the {@code refs/} and {@code logs/} directories
  657. * and {@code HEAD} and additional symbolic refs are removed.
  658. * @throws IOException
  659. * on IO problem
  660. */
  661. @SuppressWarnings("nls")
  662. void convertToReftable(boolean writeLogs, boolean backup)
  663. throws IOException {
  664. File reftableDir = new File(getDirectory(), Constants.REFTABLE);
  665. File headFile = new File(getDirectory(), Constants.HEAD);
  666. if (reftableDir.exists() && reftableDir.listFiles().length > 0) {
  667. throw new IOException(JGitText.get().reftableDirExists);
  668. }
  669. // Ignore return value, as it is tied to temporary newRefs file.
  670. FileReftableDatabase.convertFrom(this, writeLogs);
  671. File refsFile = new File(getDirectory(), "refs");
  672. // non-atomic: remove old data.
  673. File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
  674. File logsDir = new File(getDirectory(), Constants.LOGS);
  675. List<String> additional = getRefDatabase().getAdditionalRefs().stream()
  676. .map(Ref::getName).collect(toList());
  677. additional.add(Constants.HEAD);
  678. if (backup) {
  679. FileUtils.rename(refsFile, new File(getDirectory(), "refs.old"));
  680. if (packedRefs.exists()) {
  681. FileUtils.rename(packedRefs, new File(getDirectory(),
  682. Constants.PACKED_REFS + ".old"));
  683. }
  684. if (logsDir.exists()) {
  685. FileUtils.rename(logsDir,
  686. new File(getDirectory(), Constants.LOGS + ".old"));
  687. }
  688. for (String r : additional) {
  689. FileUtils.rename(new File(getDirectory(), r),
  690. new File(getDirectory(), r + ".old"));
  691. }
  692. } else {
  693. FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING);
  694. FileUtils.delete(headFile);
  695. FileUtils.delete(logsDir, FileUtils.RECURSIVE);
  696. FileUtils.delete(refsFile, FileUtils.RECURSIVE);
  697. for (String r : additional) {
  698. new File(getDirectory(), r).delete();
  699. }
  700. }
  701. FileUtils.mkdir(refsFile, true);
  702. // By putting in a dummy HEAD, old versions of Git still detect a repo
  703. // (that they can't read)
  704. try (OutputStream os = new FileOutputStream(headFile)) {
  705. os.write(Constants.encodeASCII("ref: refs/heads/.invalid"));
  706. }
  707. // Some tools might write directly into .git/refs/heads/BRANCH. By
  708. // putting a file here, this fails spectacularly.
  709. FileUtils.createNewFile(new File(refsFile, "heads"));
  710. repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
  711. ConfigConstants.CONFIG_KEY_REF_STORAGE,
  712. ConfigConstants.CONFIG_REF_STORAGE_REFTABLE);
  713. repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
  714. ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
  715. repoConfig.save();
  716. refs.close();
  717. refs = new FileReftableDatabase(this);
  718. }
  719. /**
  720. * Converts between ref storage formats.
  721. *
  722. * @param format
  723. * the format to convert to, either "reftable" or "refdir"
  724. * @param writeLogs
  725. * whether to write reflogs
  726. * @param backup
  727. * whether to make a backup of the old data
  728. * @throws IOException
  729. * on I/O problems.
  730. */
  731. public void convertRefStorage(String format, boolean writeLogs,
  732. boolean backup) throws IOException {
  733. if (format.equals("reftable")) { //$NON-NLS-1$
  734. if (refs instanceof RefDirectory) {
  735. convertToReftable(writeLogs, backup);
  736. }
  737. } else if (format.equals("refdir")) {//$NON-NLS-1$
  738. if (refs instanceof FileReftableDatabase) {
  739. convertToPackedRefs(writeLogs, backup);
  740. }
  741. } else {
  742. throw new IOException(MessageFormat
  743. .format(JGitText.get().unknownRefStorageFormat, format));
  744. }
  745. }
  746. }