Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

FileReftableDatabase.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /*
  2. * Copyright (C) 2019 Google LLC and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.storage.file;
  11. import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX;
  12. import static org.eclipse.jgit.lib.Ref.Storage.NEW;
  13. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.HashSet;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.TreeSet;
  22. import java.util.concurrent.locks.ReentrantLock;
  23. import java.util.stream.Collectors;
  24. import org.eclipse.jgit.annotations.NonNull;
  25. import org.eclipse.jgit.events.RefsChangedEvent;
  26. import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
  27. import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
  28. import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
  29. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
  30. import org.eclipse.jgit.lib.BatchRefUpdate;
  31. import org.eclipse.jgit.lib.Constants;
  32. import org.eclipse.jgit.lib.ObjectId;
  33. import org.eclipse.jgit.lib.ObjectIdRef;
  34. import org.eclipse.jgit.lib.PersonIdent;
  35. import org.eclipse.jgit.lib.Ref;
  36. import org.eclipse.jgit.lib.RefDatabase;
  37. import org.eclipse.jgit.lib.RefRename;
  38. import org.eclipse.jgit.lib.RefUpdate;
  39. import org.eclipse.jgit.lib.ReflogEntry;
  40. import org.eclipse.jgit.lib.ReflogReader;
  41. import org.eclipse.jgit.lib.Repository;
  42. import org.eclipse.jgit.lib.SymbolicRef;
  43. import org.eclipse.jgit.revwalk.RevObject;
  44. import org.eclipse.jgit.revwalk.RevTag;
  45. import org.eclipse.jgit.revwalk.RevWalk;
  46. import org.eclipse.jgit.transport.ReceiveCommand;
  47. import org.eclipse.jgit.util.FileUtils;
  48. import org.eclipse.jgit.util.RefList;
  49. import org.eclipse.jgit.util.RefMap;
  50. /**
  51. * Implements RefDatabase using reftable for storage.
  52. *
  53. * This class is threadsafe.
  54. */
  55. public class FileReftableDatabase extends RefDatabase {
  56. private final ReftableDatabase reftableDatabase;
  57. private final FileRepository fileRepository;
  58. private final FileReftableStack reftableStack;
  59. FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
  60. this.fileRepository = repo;
  61. this.reftableStack = new FileReftableStack(refstackName,
  62. new File(fileRepository.getDirectory(), Constants.REFTABLE),
  63. () -> fileRepository.fireEvent(new RefsChangedEvent()),
  64. () -> fileRepository.getConfig());
  65. this.reftableDatabase = new ReftableDatabase() {
  66. @Override
  67. public MergedReftable openMergedReftable() throws IOException {
  68. return reftableStack.getMergedReftable();
  69. }
  70. };
  71. }
  72. ReflogReader getReflogReader(String refname) throws IOException {
  73. return reftableDatabase.getReflogReader(refname);
  74. }
  75. /**
  76. * @param repoDir
  77. * @return whether the given repo uses reftable for refdb storage.
  78. */
  79. public static boolean isReftable(File repoDir) {
  80. return new File(repoDir, "refs").isFile() //$NON-NLS-1$
  81. && new File(repoDir, Constants.REFTABLE).isDirectory();
  82. }
  83. /** {@inheritDoc} */
  84. @Override
  85. public boolean hasFastTipsWithSha1() throws IOException {
  86. return reftableDatabase.hasFastTipsWithSha1();
  87. }
  88. /**
  89. * Runs a full compaction for GC purposes.
  90. * @throws IOException on I/O errors
  91. */
  92. public void compactFully() throws IOException {
  93. reftableDatabase.getLock().lock();
  94. try {
  95. reftableStack.compactFully();
  96. reftableDatabase.clearCache();
  97. } finally {
  98. reftableDatabase.getLock().unlock();
  99. }
  100. }
  101. private ReentrantLock getLock() {
  102. return reftableDatabase.getLock();
  103. }
  104. /** {@inheritDoc} */
  105. @Override
  106. public boolean performsAtomicTransactions() {
  107. return true;
  108. }
  109. /** {@inheritDoc} */
  110. @NonNull
  111. @Override
  112. public BatchRefUpdate newBatchUpdate() {
  113. return new FileReftableBatchRefUpdate(this, fileRepository);
  114. }
  115. /** {@inheritDoc} */
  116. @Override
  117. public RefUpdate newUpdate(String refName, boolean detach)
  118. throws IOException {
  119. boolean detachingSymbolicRef = false;
  120. Ref ref = exactRef(refName);
  121. if (ref == null) {
  122. ref = new ObjectIdRef.Unpeeled(NEW, refName, null);
  123. } else {
  124. detachingSymbolicRef = detach && ref.isSymbolic();
  125. }
  126. RefUpdate update = new FileReftableRefUpdate(ref);
  127. if (detachingSymbolicRef) {
  128. update.setDetachingSymbolicRef();
  129. }
  130. return update;
  131. }
  132. /** {@inheritDoc} */
  133. @Override
  134. public Ref exactRef(String name) throws IOException {
  135. return reftableDatabase.exactRef(name);
  136. }
  137. /** {@inheritDoc} */
  138. @Override
  139. public List<Ref> getRefs() throws IOException {
  140. return super.getRefs();
  141. }
  142. /** {@inheritDoc} */
  143. @Override
  144. public Map<String, Ref> getRefs(String prefix) throws IOException {
  145. List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
  146. RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
  147. for (Ref r : refs) {
  148. builder.add(r);
  149. }
  150. return new RefMap(prefix, builder.toRefList(), RefList.emptyList(),
  151. RefList.emptyList());
  152. }
  153. /** {@inheritDoc} */
  154. @Override
  155. public List<Ref> getAdditionalRefs() throws IOException {
  156. return Collections.emptyList();
  157. }
  158. /** {@inheritDoc} */
  159. @Override
  160. public Ref peel(Ref ref) throws IOException {
  161. Ref oldLeaf = ref.getLeaf();
  162. if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
  163. return ref;
  164. }
  165. return recreate(ref, doPeel(oldLeaf), hasVersioning());
  166. }
  167. private Ref doPeel(Ref leaf) throws IOException {
  168. try (RevWalk rw = new RevWalk(fileRepository)) {
  169. RevObject obj = rw.parseAny(leaf.getObjectId());
  170. if (obj instanceof RevTag) {
  171. return new ObjectIdRef.PeeledTag(leaf.getStorage(),
  172. leaf.getName(), leaf.getObjectId(), rw.peel(obj).copy(),
  173. hasVersioning() ? leaf.getUpdateIndex()
  174. : UNDEFINED_UPDATE_INDEX);
  175. }
  176. return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
  177. leaf.getName(), leaf.getObjectId(),
  178. hasVersioning() ? leaf.getUpdateIndex()
  179. : UNDEFINED_UPDATE_INDEX);
  180. }
  181. }
  182. private static Ref recreate(Ref old, Ref leaf, boolean hasVersioning) {
  183. if (old.isSymbolic()) {
  184. Ref dst = recreate(old.getTarget(), leaf, hasVersioning);
  185. return new SymbolicRef(old.getName(), dst,
  186. hasVersioning ? old.getUpdateIndex()
  187. : UNDEFINED_UPDATE_INDEX);
  188. }
  189. return leaf;
  190. }
  191. private class FileRefRename extends RefRename {
  192. FileRefRename(RefUpdate src, RefUpdate dst) {
  193. super(src, dst);
  194. }
  195. void writeRename(ReftableWriter w) throws IOException {
  196. long idx = reftableDatabase.nextUpdateIndex();
  197. w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin();
  198. List<Ref> refs = new ArrayList<>(3);
  199. Ref dest = destination.getRef();
  200. Ref head = exactRef(Constants.HEAD);
  201. if (head != null && head.isSymbolic()
  202. && head.getLeaf().getName().equals(source.getName())) {
  203. head = new SymbolicRef(Constants.HEAD, dest, idx);
  204. refs.add(head);
  205. }
  206. ObjectId objId = source.getRef().getObjectId();
  207. // XXX should we check if the source is a Tag vs. NonTag?
  208. refs.add(new ObjectIdRef.PeeledNonTag(Ref.Storage.NEW,
  209. destination.getName(), objId));
  210. refs.add(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, source.getName(),
  211. null));
  212. w.sortAndWriteRefs(refs);
  213. PersonIdent who = destination.getRefLogIdent();
  214. if (who == null) {
  215. who = new PersonIdent(fileRepository);
  216. }
  217. if (!destination.getRefLogMessage().isEmpty()) {
  218. List<String> refnames = refs.stream().map(r -> r.getName())
  219. .collect(Collectors.toList());
  220. Collections.sort(refnames);
  221. for (String s : refnames) {
  222. ObjectId old = (Constants.HEAD.equals(s)
  223. || s.equals(source.getName())) ? objId
  224. : ObjectId.zeroId();
  225. ObjectId newId = (Constants.HEAD.equals(s)
  226. || s.equals(destination.getName())) ? objId
  227. : ObjectId.zeroId();
  228. w.writeLog(s, idx, who, old, newId,
  229. destination.getRefLogMessage());
  230. }
  231. }
  232. }
  233. @Override
  234. protected RefUpdate.Result doRename() throws IOException {
  235. Ref src = exactRef(source.getName());
  236. if (exactRef(destination.getName()) != null || src == null
  237. || !source.getOldObjectId().equals(src.getObjectId())) {
  238. return RefUpdate.Result.LOCK_FAILURE;
  239. }
  240. if (src.isSymbolic()) {
  241. // We could support this, but this is easier and compatible.
  242. return RefUpdate.Result.IO_FAILURE;
  243. }
  244. if (!addReftable(this::writeRename)) {
  245. return RefUpdate.Result.LOCK_FAILURE;
  246. }
  247. return RefUpdate.Result.RENAMED;
  248. }
  249. }
  250. /** {@inheritDoc} */
  251. @Override
  252. public RefRename newRename(String fromName, String toName)
  253. throws IOException {
  254. RefUpdate src = newUpdate(fromName, true);
  255. RefUpdate dst = newUpdate(toName, true);
  256. return new FileRefRename(src, dst);
  257. }
  258. /** {@inheritDoc} */
  259. @Override
  260. public boolean isNameConflicting(String name) throws IOException {
  261. return reftableDatabase.isNameConflicting(name, new TreeSet<>(),
  262. new HashSet<>());
  263. }
  264. /** {@inheritDoc} */
  265. @Override
  266. public void close() {
  267. reftableStack.close();
  268. }
  269. /** {@inheritDoc} */
  270. @Override
  271. public void create() throws IOException {
  272. FileUtils.mkdir(
  273. new File(fileRepository.getDirectory(), Constants.REFTABLE),
  274. true);
  275. }
  276. private boolean addReftable(FileReftableStack.Writer w) throws IOException {
  277. if (!reftableStack.addReftable(w)) {
  278. reftableStack.reload();
  279. reftableDatabase.clearCache();
  280. return false;
  281. }
  282. reftableDatabase.clearCache();
  283. return true;
  284. }
  285. private class FileReftableBatchRefUpdate extends ReftableBatchRefUpdate {
  286. FileReftableBatchRefUpdate(FileReftableDatabase db,
  287. Repository repository) {
  288. super(db, db.reftableDatabase, db.getLock(), repository);
  289. }
  290. @Override
  291. protected void applyUpdates(List<Ref> newRefs,
  292. List<ReceiveCommand> pending) throws IOException {
  293. if (!addReftable(rw -> write(rw, newRefs, pending))) {
  294. for (ReceiveCommand c : pending) {
  295. if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
  296. c.setResult(RefUpdate.Result.LOCK_FAILURE);
  297. }
  298. }
  299. }
  300. }
  301. }
  302. private class FileReftableRefUpdate extends RefUpdate {
  303. FileReftableRefUpdate(Ref ref) {
  304. super(ref);
  305. }
  306. @Override
  307. protected RefDatabase getRefDatabase() {
  308. return FileReftableDatabase.this;
  309. }
  310. @Override
  311. protected Repository getRepository() {
  312. return FileReftableDatabase.this.fileRepository;
  313. }
  314. @Override
  315. protected void unlock() {
  316. // nop.
  317. }
  318. private RevWalk rw;
  319. private Ref dstRef;
  320. @Override
  321. public Result update(RevWalk walk) throws IOException {
  322. try {
  323. rw = walk;
  324. return super.update(walk);
  325. } finally {
  326. rw = null;
  327. }
  328. }
  329. @Override
  330. protected boolean tryLock(boolean deref) throws IOException {
  331. dstRef = getRef();
  332. if (deref) {
  333. dstRef = dstRef.getLeaf();
  334. }
  335. Ref derefed = exactRef(dstRef.getName());
  336. if (derefed != null) {
  337. setOldObjectId(derefed.getObjectId());
  338. }
  339. return true;
  340. }
  341. void writeUpdate(ReftableWriter w) throws IOException {
  342. Ref newRef = null;
  343. if (rw != null && !ObjectId.zeroId().equals(getNewObjectId())) {
  344. RevObject obj = rw.parseAny(getNewObjectId());
  345. if (obj instanceof RevTag) {
  346. newRef = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED,
  347. dstRef.getName(), getNewObjectId(),
  348. rw.peel(obj).copy());
  349. }
  350. }
  351. if (newRef == null) {
  352. newRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED,
  353. dstRef.getName(), getNewObjectId());
  354. }
  355. long idx = reftableDatabase.nextUpdateIndex();
  356. w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
  357. .writeRef(newRef);
  358. ObjectId oldId = getOldObjectId();
  359. if (oldId == null) {
  360. oldId = ObjectId.zeroId();
  361. }
  362. w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
  363. getNewObjectId(), getRefLogMessage());
  364. }
  365. @Override
  366. public PersonIdent getRefLogIdent() {
  367. PersonIdent who = super.getRefLogIdent();
  368. if (who == null) {
  369. who = new PersonIdent(getRepository());
  370. }
  371. return who;
  372. }
  373. void writeDelete(ReftableWriter w) throws IOException {
  374. Ref newRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW,
  375. dstRef.getName(), null);
  376. long idx = reftableDatabase.nextUpdateIndex();
  377. w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
  378. .writeRef(newRef);
  379. ObjectId oldId = ObjectId.zeroId();
  380. Ref old = exactRef(dstRef.getName());
  381. if (old != null) {
  382. old = old.getLeaf();
  383. if (old.getObjectId() != null) {
  384. oldId = old.getObjectId();
  385. }
  386. }
  387. w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
  388. ObjectId.zeroId(), getRefLogMessage());
  389. }
  390. @Override
  391. protected Result doUpdate(Result desiredResult) throws IOException {
  392. if (isRefLogIncludingResult()) {
  393. setRefLogMessage(
  394. getRefLogMessage() + ": " + desiredResult.toString(), //$NON-NLS-1$
  395. false);
  396. }
  397. if (!addReftable(this::writeUpdate)) {
  398. return Result.LOCK_FAILURE;
  399. }
  400. return desiredResult;
  401. }
  402. @Override
  403. protected Result doDelete(Result desiredResult) throws IOException {
  404. if (isRefLogIncludingResult()) {
  405. setRefLogMessage(
  406. getRefLogMessage() + ": " + desiredResult.toString(), //$NON-NLS-1$
  407. false);
  408. }
  409. if (!addReftable(this::writeDelete)) {
  410. return Result.LOCK_FAILURE;
  411. }
  412. return desiredResult;
  413. }
  414. void writeLink(ReftableWriter w) throws IOException {
  415. long idx = reftableDatabase.nextUpdateIndex();
  416. w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
  417. .writeRef(dstRef);
  418. ObjectId beforeId = ObjectId.zeroId();
  419. Ref before = exactRef(dstRef.getName());
  420. if (before != null) {
  421. before = before.getLeaf();
  422. if (before.getObjectId() != null) {
  423. beforeId = before.getObjectId();
  424. }
  425. }
  426. Ref after = dstRef.getLeaf();
  427. ObjectId afterId = ObjectId.zeroId();
  428. if (after.getObjectId() != null) {
  429. afterId = after.getObjectId();
  430. }
  431. w.writeLog(dstRef.getName(), idx, getRefLogIdent(), beforeId,
  432. afterId, getRefLogMessage());
  433. }
  434. @Override
  435. protected Result doLink(String target) throws IOException {
  436. if (isRefLogIncludingResult()) {
  437. setRefLogMessage(
  438. getRefLogMessage() + ": " + Result.FORCED.toString(), //$NON-NLS-1$
  439. false);
  440. }
  441. boolean exists = exactRef(getName()) != null;
  442. dstRef = new SymbolicRef(getName(),
  443. new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null),
  444. reftableDatabase.nextUpdateIndex());
  445. if (!addReftable(this::writeLink)) {
  446. return Result.LOCK_FAILURE;
  447. }
  448. // XXX unclear if we should support FORCED here. Baseclass says
  449. // NEW is OK ?
  450. return exists ? Result.FORCED : Result.NEW;
  451. }
  452. }
  453. private static void writeConvertTable(Repository repo, ReftableWriter w,
  454. boolean writeLogs) throws IOException {
  455. int size = 0;
  456. List<Ref> refs = repo.getRefDatabase().getRefs();
  457. if (writeLogs) {
  458. for (Ref r : refs) {
  459. ReflogReader rlr = repo.getReflogReader(r.getName());
  460. if (rlr != null) {
  461. size = Math.max(rlr.getReverseEntries().size(), size);
  462. }
  463. }
  464. }
  465. // We must use 1 here, nextUpdateIndex() on the empty stack is 1.
  466. w.setMinUpdateIndex(1).setMaxUpdateIndex(size + 1).begin();
  467. // The spec says to write the logs in the first table, and put refs in a
  468. // separate table, but this complicates the compaction (when we can we drop
  469. // deletions? Can we compact the .log table and the .ref table together?)
  470. try (RevWalk rw = new RevWalk(repo)) {
  471. List<Ref> toWrite = new ArrayList<>(refs.size());
  472. for (Ref r : refs) {
  473. toWrite.add(refForWrite(rw, r));
  474. }
  475. w.sortAndWriteRefs(toWrite);
  476. }
  477. if (writeLogs) {
  478. for (Ref r : refs) {
  479. long idx = size;
  480. ReflogReader reader = repo.getReflogReader(r.getName());
  481. if (reader == null) {
  482. continue;
  483. }
  484. for (ReflogEntry e : reader.getReverseEntries()) {
  485. w.writeLog(r.getName(), idx, e.getWho(), e.getOldId(),
  486. e.getNewId(), e.getComment());
  487. idx--;
  488. }
  489. }
  490. }
  491. }
  492. private static Ref refForWrite(RevWalk rw, Ref r) throws IOException {
  493. if (r.isSymbolic()) {
  494. return new SymbolicRef(r.getName(), new ObjectIdRef.Unpeeled(NEW,
  495. r.getTarget().getName(), null));
  496. }
  497. ObjectId newId = r.getObjectId();
  498. RevObject obj = rw.parseAny(newId);
  499. RevObject peel = null;
  500. if (obj instanceof RevTag) {
  501. peel = rw.peel(obj);
  502. }
  503. if (peel != null) {
  504. return new ObjectIdRef.PeeledTag(PACKED, r.getName(), newId,
  505. peel.copy());
  506. }
  507. return new ObjectIdRef.PeeledNonTag(PACKED, r.getName(), newId);
  508. }
  509. /**
  510. * @param repo
  511. * the repository
  512. * @param refstackName
  513. * the filename for the stack
  514. * @param writeLogs
  515. * whether to write reflogs
  516. * @return a reftable based RefDB from an existing repository.
  517. * @throws IOException
  518. * on IO error
  519. */
  520. public static FileReftableDatabase convertFrom(FileRepository repo,
  521. File refstackName, boolean writeLogs) throws IOException {
  522. FileReftableDatabase newDb = null;
  523. try {
  524. File reftableDir = new File(repo.getDirectory(), Constants.REFTABLE);
  525. if (!reftableDir.isDirectory()) {
  526. reftableDir.mkdir();
  527. }
  528. try (FileReftableStack stack = new FileReftableStack(refstackName,
  529. reftableDir, null, () -> repo.getConfig())) {
  530. stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
  531. }
  532. refstackName = null;
  533. } finally {
  534. if (refstackName != null) {
  535. refstackName.delete();
  536. }
  537. }
  538. return newDb;
  539. }
  540. }