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.

ReftableTest.java 34KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. /*
  2. * Copyright (C) 2017, Google Inc. 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.reftable;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.lib.Constants.HEAD;
  13. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  14. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  15. import static org.eclipse.jgit.lib.Ref.Storage.NEW;
  16. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
  17. import static org.hamcrest.CoreMatchers.containsString;
  18. import static org.hamcrest.MatcherAssert.assertThat;
  19. import static org.junit.Assert.assertEquals;
  20. import static org.junit.Assert.assertFalse;
  21. import static org.junit.Assert.assertNotNull;
  22. import static org.junit.Assert.assertNull;
  23. import static org.junit.Assert.assertSame;
  24. import static org.junit.Assert.assertThrows;
  25. import static org.junit.Assert.assertTrue;
  26. import static org.junit.Assert.fail;
  27. import java.io.ByteArrayOutputStream;
  28. import java.io.IOException;
  29. import java.util.ArrayList;
  30. import java.util.Arrays;
  31. import java.util.Collection;
  32. import java.util.Collections;
  33. import java.util.List;
  34. import java.util.concurrent.locks.ReentrantLock;
  35. import java.util.stream.Collectors;
  36. import org.eclipse.jgit.internal.JGitText;
  37. import org.eclipse.jgit.internal.storage.io.BlockSource;
  38. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
  39. import org.eclipse.jgit.lib.ObjectId;
  40. import org.eclipse.jgit.lib.ObjectIdRef;
  41. import org.eclipse.jgit.lib.PersonIdent;
  42. import org.eclipse.jgit.lib.Ref;
  43. import org.eclipse.jgit.lib.ReflogEntry;
  44. import org.eclipse.jgit.lib.SymbolicRef;
  45. import org.hamcrest.Matchers;
  46. import org.junit.Test;
  47. public class ReftableTest {
  48. private static final byte[] LAST_UTF8_CHAR = new byte[] {
  49. (byte)0x10,
  50. (byte)0xFF,
  51. (byte)0xFF};
  52. private static final String MASTER = "refs/heads/master";
  53. private static final String NEXT = "refs/heads/next";
  54. private static final String AFTER_NEXT = "refs/heads/nextnext";
  55. private static final String LAST = "refs/heads/nextnextnext";
  56. private static final String NOT_REF_HEADS = "refs/zzz/zzz";
  57. private static final String V1_0 = "refs/tags/v1.0";
  58. private Stats stats;
  59. @Test
  60. public void emptyTable() throws IOException {
  61. byte[] table = write();
  62. assertEquals(92 /* header, footer */, table.length);
  63. assertEquals('R', table[0]);
  64. assertEquals('E', table[1]);
  65. assertEquals('F', table[2]);
  66. assertEquals('T', table[3]);
  67. assertEquals(0x01, table[4]);
  68. assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8));
  69. assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92));
  70. Reftable t = read(table);
  71. try (RefCursor rc = t.allRefs()) {
  72. assertFalse(rc.next());
  73. }
  74. try (RefCursor rc = t.seekRef(HEAD)) {
  75. assertFalse(rc.next());
  76. }
  77. try (RefCursor rc = t.seekRefsWithPrefix(R_HEADS)) {
  78. assertFalse(rc.next());
  79. }
  80. try (LogCursor rc = t.allLogs()) {
  81. assertFalse(rc.next());
  82. }
  83. }
  84. @Test
  85. public void emptyVirtualTableFromRefs() throws IOException {
  86. Reftable t = Reftable.from(Collections.emptyList());
  87. try (RefCursor rc = t.allRefs()) {
  88. assertFalse(rc.next());
  89. }
  90. try (RefCursor rc = t.seekRef(HEAD)) {
  91. assertFalse(rc.next());
  92. }
  93. try (LogCursor rc = t.allLogs()) {
  94. assertFalse(rc.next());
  95. }
  96. }
  97. @Test
  98. public void estimateCurrentBytesOneRef() throws IOException {
  99. Ref exp = ref(MASTER, 1);
  100. int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68;
  101. byte[] table;
  102. ReftableConfig cfg = new ReftableConfig();
  103. cfg.setIndexObjects(false);
  104. try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
  105. ReftableWriter writer = new ReftableWriter(buf).setConfig(cfg);
  106. writer.begin();
  107. assertEquals(92, writer.estimateTotalBytes());
  108. writer.writeRef(exp);
  109. assertEquals(expBytes, writer.estimateTotalBytes());
  110. writer.finish();
  111. table = buf.toByteArray();
  112. }
  113. assertEquals(expBytes, table.length);
  114. }
  115. @Test
  116. public void estimateCurrentBytesWithIndex() throws IOException {
  117. List<Ref> refs = new ArrayList<>();
  118. for (int i = 1; i <= 5670; i++) {
  119. @SuppressWarnings("boxing")
  120. Ref ref = ref(String.format("refs/heads/%04d", i), i);
  121. refs.add(ref);
  122. }
  123. ReftableConfig cfg = new ReftableConfig();
  124. cfg.setIndexObjects(false);
  125. cfg.setMaxIndexLevels(1);
  126. int expBytes = 147860;
  127. byte[] table;
  128. try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
  129. ReftableWriter writer = new ReftableWriter(buf).setConfig(cfg);
  130. writer.begin();
  131. writer.sortAndWriteRefs(refs);
  132. assertEquals(expBytes, writer.estimateTotalBytes());
  133. writer.finish();
  134. stats = writer.getStats();
  135. table = buf.toByteArray();
  136. }
  137. assertEquals(1, stats.refIndexLevels());
  138. assertEquals(expBytes, table.length);
  139. }
  140. @Test
  141. public void hasObjMapRefs() throws IOException {
  142. ArrayList<Ref> refs = new ArrayList<>();
  143. refs.add(ref(MASTER, 1));
  144. byte[] table = write(refs);
  145. ReftableReader t = read(table);
  146. assertTrue(t.hasObjectMap());
  147. }
  148. @Test
  149. public void hasObjMapRefsSmallTable() throws IOException {
  150. ArrayList<Ref> refs = new ArrayList<>();
  151. ReftableConfig cfg = new ReftableConfig();
  152. cfg.setIndexObjects(false);
  153. refs.add(ref(MASTER, 1));
  154. byte[] table = write(refs);
  155. ReftableReader t = read(table);
  156. assertTrue(t.hasObjectMap());
  157. }
  158. @Test
  159. public void hasObjLogs() throws IOException {
  160. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  161. String msg = "test";
  162. ReftableConfig cfg = new ReftableConfig();
  163. cfg.setIndexObjects(false);
  164. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  165. ReftableWriter writer = new ReftableWriter(buffer)
  166. .setMinUpdateIndex(1)
  167. .setConfig(cfg)
  168. .setMaxUpdateIndex(1)
  169. .begin();
  170. writer.writeLog("master", 1, who, ObjectId.zeroId(), id(1), msg);
  171. writer.finish();
  172. byte[] table = buffer.toByteArray();
  173. ReftableReader t = read(table);
  174. assertTrue(t.hasObjectMap());
  175. }
  176. @Test
  177. public void hasObjMapRefsNoIndexObjects() throws IOException {
  178. ArrayList<Ref> refs = new ArrayList<>();
  179. ReftableConfig cfg = new ReftableConfig();
  180. cfg.setIndexObjects(false);
  181. cfg.setRefBlockSize(256);
  182. cfg.setAlignBlocks(true);
  183. // Fill up 5 blocks.
  184. int N = 256 * 5 / 25;
  185. for (int i= 0; i < N; i++) {
  186. @SuppressWarnings("boxing")
  187. Ref ref = ref(String.format("%02d/xxxxxxxxxx", i), i);
  188. refs.add(ref);
  189. }
  190. byte[] table = write(refs, cfg);
  191. ReftableReader t = read(table);
  192. assertFalse(t.hasObjectMap());
  193. }
  194. @Test
  195. public void oneIdRef() throws IOException {
  196. Ref exp = ref(MASTER, 1);
  197. byte[] table = write(exp);
  198. assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length);
  199. ReftableReader t = read(table);
  200. try (RefCursor rc = t.allRefs()) {
  201. assertTrue(rc.next());
  202. Ref act = rc.getRef();
  203. assertNotNull(act);
  204. assertEquals(PACKED, act.getStorage());
  205. assertTrue(act.isPeeled());
  206. assertFalse(act.isSymbolic());
  207. assertEquals(exp.getName(), act.getName());
  208. assertEquals(exp.getObjectId(), act.getObjectId());
  209. assertEquals(0, act.getUpdateIndex());
  210. assertNull(act.getPeeledObjectId());
  211. assertFalse(rc.wasDeleted());
  212. assertFalse(rc.next());
  213. }
  214. try (RefCursor rc = t.seekRef(MASTER)) {
  215. assertTrue(rc.next());
  216. Ref act = rc.getRef();
  217. assertNotNull(act);
  218. assertEquals(exp.getName(), act.getName());
  219. assertEquals(0, act.getUpdateIndex());
  220. assertFalse(rc.next());
  221. }
  222. }
  223. @Test
  224. public void oneTagRef() throws IOException {
  225. Ref exp = tag(V1_0, 1, 2);
  226. byte[] table = write(exp);
  227. assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length);
  228. ReftableReader t = read(table);
  229. try (RefCursor rc = t.allRefs()) {
  230. assertTrue(rc.next());
  231. Ref act = rc.getRef();
  232. assertNotNull(act);
  233. assertEquals(PACKED, act.getStorage());
  234. assertTrue(act.isPeeled());
  235. assertFalse(act.isSymbolic());
  236. assertEquals(exp.getName(), act.getName());
  237. assertEquals(exp.getObjectId(), act.getObjectId());
  238. assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
  239. assertEquals(0, act.getUpdateIndex());
  240. }
  241. }
  242. @Test
  243. public void oneSymbolicRef() throws IOException {
  244. Ref exp = sym(HEAD, MASTER);
  245. byte[] table = write(exp);
  246. assertEquals(
  247. 24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68,
  248. table.length);
  249. ReftableReader t = read(table);
  250. try (RefCursor rc = t.allRefs()) {
  251. assertTrue(rc.next());
  252. Ref act = rc.getRef();
  253. assertNotNull(act);
  254. assertTrue(act.isSymbolic());
  255. assertEquals(exp.getName(), act.getName());
  256. assertNotNull(act.getLeaf());
  257. assertEquals(MASTER, act.getTarget().getName());
  258. assertNull(act.getObjectId());
  259. assertEquals(0, act.getUpdateIndex());
  260. }
  261. }
  262. @Test
  263. public void resolveSymbolicRef() throws IOException {
  264. Reftable t = read(write(
  265. sym(HEAD, "refs/heads/tmp"),
  266. sym("refs/heads/tmp", MASTER),
  267. ref(MASTER, 1)));
  268. Ref head = t.exactRef(HEAD);
  269. assertNull(head.getObjectId());
  270. assertEquals("refs/heads/tmp", head.getTarget().getName());
  271. assertEquals(0, head.getUpdateIndex());
  272. head = t.resolve(head);
  273. assertNotNull(head);
  274. assertEquals(id(1), head.getObjectId());
  275. assertEquals(0, head.getUpdateIndex());
  276. Ref master = t.exactRef(MASTER);
  277. assertNotNull(master);
  278. assertSame(master, t.resolve(master));
  279. assertEquals(0, master.getUpdateIndex());
  280. }
  281. @Test
  282. public void failDeepChainOfSymbolicRef() throws IOException {
  283. Reftable t = read(write(
  284. sym(HEAD, "refs/heads/1"),
  285. sym("refs/heads/1", "refs/heads/2"),
  286. sym("refs/heads/2", "refs/heads/3"),
  287. sym("refs/heads/3", "refs/heads/4"),
  288. sym("refs/heads/4", "refs/heads/5"),
  289. sym("refs/heads/5", MASTER),
  290. ref(MASTER, 1)));
  291. Ref head = t.exactRef(HEAD);
  292. assertNull(head.getObjectId());
  293. assertNull(t.resolve(head));
  294. }
  295. @Test
  296. public void oneDeletedRef() throws IOException {
  297. String name = "refs/heads/gone";
  298. Ref exp = newRef(name);
  299. byte[] table = write(exp);
  300. assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length);
  301. ReftableReader t = read(table);
  302. try (RefCursor rc = t.allRefs()) {
  303. assertFalse(rc.next());
  304. }
  305. t.setIncludeDeletes(true);
  306. try (RefCursor rc = t.allRefs()) {
  307. assertTrue(rc.next());
  308. Ref act = rc.getRef();
  309. assertNotNull(act);
  310. assertFalse(act.isSymbolic());
  311. assertEquals(name, act.getName());
  312. assertEquals(NEW, act.getStorage());
  313. assertNull(act.getObjectId());
  314. assertTrue(rc.wasDeleted());
  315. }
  316. }
  317. @Test
  318. public void seekNotFound() throws IOException {
  319. Ref exp = ref(MASTER, 1);
  320. ReftableReader t = read(write(exp));
  321. try (RefCursor rc = t.seekRef("refs/heads/a")) {
  322. assertFalse(rc.next());
  323. }
  324. try (RefCursor rc = t.seekRef("refs/heads/n")) {
  325. assertFalse(rc.next());
  326. }
  327. }
  328. @Test
  329. public void namespaceNotFound() throws IOException {
  330. Ref exp = ref(MASTER, 1);
  331. ReftableReader t = read(write(exp));
  332. try (RefCursor rc = t.seekRefsWithPrefix("refs/changes/")) {
  333. assertFalse(rc.next());
  334. }
  335. try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
  336. assertFalse(rc.next());
  337. }
  338. }
  339. @Test
  340. public void namespaceHeads() throws IOException {
  341. Ref master = ref(MASTER, 1);
  342. Ref next = ref(NEXT, 2);
  343. Ref v1 = tag(V1_0, 3, 4);
  344. ReftableReader t = read(write(master, next, v1));
  345. try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
  346. assertTrue(rc.next());
  347. assertEquals(V1_0, rc.getRef().getName());
  348. assertEquals(0, rc.getRef().getUpdateIndex());
  349. assertFalse(rc.next());
  350. }
  351. try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
  352. assertTrue(rc.next());
  353. assertEquals(MASTER, rc.getRef().getName());
  354. assertEquals(0, rc.getRef().getUpdateIndex());
  355. assertTrue(rc.next());
  356. assertEquals(NEXT, rc.getRef().getName());
  357. assertEquals(0, rc.getRef().getUpdateIndex());
  358. assertFalse(rc.next());
  359. }
  360. }
  361. @Test
  362. public void seekPastRefWithRefCursor() throws IOException {
  363. Ref exp = ref(MASTER, 1);
  364. Ref next = ref(NEXT, 2);
  365. Ref afterNext = ref(AFTER_NEXT, 3);
  366. Ref afterNextNext = ref(LAST, 4);
  367. ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
  368. try (RefCursor rc = t.seekRefsWithPrefix("")) {
  369. assertTrue(rc.next());
  370. assertEquals(MASTER, rc.getRef().getName());
  371. rc.seekPastPrefix("refs/heads/next/");
  372. assertTrue(rc.next());
  373. assertEquals(AFTER_NEXT, rc.getRef().getName());
  374. assertTrue(rc.next());
  375. assertEquals(LAST, rc.getRef().getName());
  376. assertFalse(rc.next());
  377. }
  378. }
  379. @Test
  380. public void seekPastToNonExistentPrefixToTheMiddle() throws IOException {
  381. Ref exp = ref(MASTER, 1);
  382. Ref next = ref(NEXT, 2);
  383. Ref afterNext = ref(AFTER_NEXT, 3);
  384. Ref afterNextNext = ref(LAST, 4);
  385. ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
  386. try (RefCursor rc = t.seekRefsWithPrefix("")) {
  387. rc.seekPastPrefix("refs/heads/master_non_existent");
  388. assertTrue(rc.next());
  389. assertEquals(NEXT, rc.getRef().getName());
  390. assertTrue(rc.next());
  391. assertEquals(AFTER_NEXT, rc.getRef().getName());
  392. assertTrue(rc.next());
  393. assertEquals(LAST, rc.getRef().getName());
  394. assertFalse(rc.next());
  395. }
  396. }
  397. @Test
  398. public void seekPastToNonExistentPrefixToTheEnd() throws IOException {
  399. Ref exp = ref(MASTER, 1);
  400. Ref next = ref(NEXT, 2);
  401. Ref afterNext = ref(AFTER_NEXT, 3);
  402. Ref afterNextNext = ref(LAST, 4);
  403. ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
  404. try (RefCursor rc = t.seekRefsWithPrefix("")) {
  405. rc.seekPastPrefix("refs/heads/nextnon_existent_end");
  406. assertFalse(rc.next());
  407. }
  408. }
  409. @Test
  410. public void seekPastWithSeekRefsWithPrefix() throws IOException {
  411. Ref exp = ref(MASTER, 1);
  412. Ref next = ref(NEXT, 2);
  413. Ref afterNext = ref(AFTER_NEXT, 3);
  414. Ref afterNextNext = ref(LAST, 4);
  415. Ref notRefsHeads = ref(NOT_REF_HEADS, 5);
  416. ReftableReader t = read(write(exp, next, afterNext, afterNextNext, notRefsHeads));
  417. try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
  418. rc.seekPastPrefix("refs/heads/next/");
  419. assertTrue(rc.next());
  420. assertEquals(AFTER_NEXT, rc.getRef().getName());
  421. assertTrue(rc.next());
  422. assertEquals(LAST, rc.getRef().getName());
  423. // NOT_REF_HEADS is next, but it's omitted because of
  424. // seekRefsWithPrefix("refs/heads/").
  425. assertFalse(rc.next());
  426. }
  427. }
  428. @Test
  429. public void seekPastWithLotsOfRefs() throws IOException {
  430. Ref[] refs = new Ref[500];
  431. for (int i = 1; i <= 500; i++) {
  432. refs[i - 1] = ref(String.format("refs/%d", i), i);
  433. }
  434. ReftableReader t = read(write(refs));
  435. try (RefCursor rc = t.allRefs()) {
  436. rc.seekPastPrefix("refs/3");
  437. assertTrue(rc.next());
  438. assertEquals("refs/4", rc.getRef().getName());
  439. assertTrue(rc.next());
  440. assertEquals("refs/40", rc.getRef().getName());
  441. rc.seekPastPrefix("refs/8");
  442. assertTrue(rc.next());
  443. assertEquals("refs/9", rc.getRef().getName());
  444. assertTrue(rc.next());
  445. assertEquals("refs/90", rc.getRef().getName());
  446. assertTrue(rc.next());
  447. assertEquals("refs/91", rc.getRef().getName());
  448. }
  449. }
  450. @Test
  451. public void seekPastManyTimes() throws IOException {
  452. Ref exp = ref(MASTER, 1);
  453. Ref next = ref(NEXT, 2);
  454. Ref afterNext = ref(AFTER_NEXT, 3);
  455. Ref afterNextNext = ref(LAST, 4);
  456. ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
  457. try (RefCursor rc = t.seekRefsWithPrefix("")) {
  458. rc.seekPastPrefix("refs/heads/master");
  459. rc.seekPastPrefix("refs/heads/next");
  460. rc.seekPastPrefix("refs/heads/nextnext");
  461. rc.seekPastPrefix("refs/heads/nextnextnext");
  462. assertFalse(rc.next());
  463. }
  464. }
  465. @Test
  466. public void seekPastOnEmptyTable() throws IOException {
  467. ReftableReader t = read(write());
  468. try (RefCursor rc = t.seekRefsWithPrefix("")) {
  469. rc.seekPastPrefix("refs/");
  470. assertFalse(rc.next());
  471. }
  472. }
  473. @Test
  474. public void indexScan() throws IOException {
  475. List<Ref> refs = new ArrayList<>();
  476. for (int i = 1; i <= 5670; i++) {
  477. @SuppressWarnings("boxing")
  478. Ref ref = ref(String.format("refs/heads/%04d", i), i);
  479. refs.add(ref);
  480. }
  481. byte[] table = write(refs);
  482. assertTrue(stats.refIndexLevels() > 0);
  483. assertTrue(stats.refIndexSize() > 0);
  484. assertScan(refs, read(table));
  485. }
  486. @Test
  487. public void indexSeek() throws IOException {
  488. List<Ref> refs = new ArrayList<>();
  489. for (int i = 1; i <= 5670; i++) {
  490. @SuppressWarnings("boxing")
  491. Ref ref = ref(String.format("refs/heads/%04d", i), i);
  492. refs.add(ref);
  493. }
  494. byte[] table = write(refs);
  495. assertTrue(stats.refIndexLevels() > 0);
  496. assertTrue(stats.refIndexSize() > 0);
  497. assertSeek(refs, read(table));
  498. }
  499. @Test
  500. public void noIndexScan() throws IOException {
  501. List<Ref> refs = new ArrayList<>();
  502. for (int i = 1; i <= 567; i++) {
  503. @SuppressWarnings("boxing")
  504. Ref ref = ref(String.format("refs/heads/%03d", i), i);
  505. refs.add(ref);
  506. }
  507. byte[] table = write(refs);
  508. assertEquals(0, stats.refIndexLevels());
  509. assertEquals(0, stats.refIndexSize());
  510. assertEquals(table.length, stats.totalBytes());
  511. assertScan(refs, read(table));
  512. }
  513. @Test
  514. public void noIndexSeek() throws IOException {
  515. List<Ref> refs = new ArrayList<>();
  516. for (int i = 1; i <= 567; i++) {
  517. @SuppressWarnings("boxing")
  518. Ref ref = ref(String.format("refs/heads/%03d", i), i);
  519. refs.add(ref);
  520. }
  521. byte[] table = write(refs);
  522. assertEquals(0, stats.refIndexLevels());
  523. assertSeek(refs, read(table));
  524. }
  525. @Test
  526. public void invalidRefWriteOrderSortAndWrite() {
  527. Ref master = ref(MASTER, 1);
  528. ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
  529. .setMinUpdateIndex(1)
  530. .setMaxUpdateIndex(1)
  531. .begin();
  532. List<Ref> refs = new ArrayList<>();
  533. refs.add(master);
  534. refs.add(master);
  535. IllegalArgumentException e = assertThrows(
  536. IllegalArgumentException.class,
  537. () -> writer.sortAndWriteRefs(refs));
  538. assertThat(e.getMessage(), containsString("records must be increasing"));
  539. }
  540. @Test
  541. public void invalidReflogWriteOrderUpdateIndex() throws IOException {
  542. ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
  543. .setMinUpdateIndex(1)
  544. .setMaxUpdateIndex(2)
  545. .begin();
  546. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  547. String msg = "test";
  548. writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
  549. IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
  550. () -> writer.writeLog(
  551. MASTER, 2, who, ObjectId.zeroId(), id(2), msg));
  552. assertThat(e.getMessage(), containsString("records must be increasing"));
  553. }
  554. @Test
  555. public void invalidReflogWriteOrderName() throws IOException {
  556. ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
  557. .setMinUpdateIndex(1)
  558. .setMaxUpdateIndex(1)
  559. .begin();
  560. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  561. String msg = "test";
  562. writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(1), msg);
  563. IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
  564. () -> writer.writeLog(
  565. MASTER, 1, who, ObjectId.zeroId(), id(2), msg));
  566. assertThat(e.getMessage(), containsString("records must be increasing"));
  567. }
  568. @Test
  569. public void withReflog() throws IOException {
  570. Ref master = ref(MASTER, 1);
  571. Ref next = ref(NEXT, 2);
  572. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  573. String msg = "test";
  574. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  575. ReftableWriter writer = new ReftableWriter(buffer)
  576. .setMinUpdateIndex(1)
  577. .setMaxUpdateIndex(1)
  578. .begin();
  579. writer.writeRef(master);
  580. writer.writeRef(next);
  581. writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
  582. writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
  583. writer.finish();
  584. byte[] table = buffer.toByteArray();
  585. assertEquals(247, table.length);
  586. ReftableReader t = read(table);
  587. try (RefCursor rc = t.allRefs()) {
  588. assertTrue(rc.next());
  589. assertEquals(MASTER, rc.getRef().getName());
  590. assertEquals(id(1), rc.getRef().getObjectId());
  591. assertEquals(1, rc.getRef().getUpdateIndex());
  592. assertTrue(rc.next());
  593. assertEquals(NEXT, rc.getRef().getName());
  594. assertEquals(id(2), rc.getRef().getObjectId());
  595. assertEquals(1, rc.getRef().getUpdateIndex());
  596. assertFalse(rc.next());
  597. }
  598. try (LogCursor lc = t.allLogs()) {
  599. assertTrue(lc.next());
  600. assertEquals(MASTER, lc.getRefName());
  601. assertEquals(1, lc.getUpdateIndex());
  602. assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
  603. assertEquals(id(1), lc.getReflogEntry().getNewId());
  604. assertEquals(who, lc.getReflogEntry().getWho());
  605. assertEquals(msg, lc.getReflogEntry().getComment());
  606. assertTrue(lc.next());
  607. assertEquals(NEXT, lc.getRefName());
  608. assertEquals(1, lc.getUpdateIndex());
  609. assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
  610. assertEquals(id(2), lc.getReflogEntry().getNewId());
  611. assertEquals(who, lc.getReflogEntry().getWho());
  612. assertEquals(msg, lc.getReflogEntry().getComment());
  613. assertFalse(lc.next());
  614. }
  615. }
  616. @Test
  617. public void reflogReader() throws IOException {
  618. Ref master = ref(MASTER, 1);
  619. Ref next = ref(NEXT, 2);
  620. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  621. ReftableWriter writer = new ReftableWriter(buffer).setMinUpdateIndex(1)
  622. .setMaxUpdateIndex(1).begin();
  623. writer.writeRef(master);
  624. writer.writeRef(next);
  625. PersonIdent who1 = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  626. writer.writeLog(MASTER, 3, who1, ObjectId.zeroId(), id(1), "1");
  627. PersonIdent who2 = new PersonIdent("Log", "Ger", 1500079710, -8 * 60);
  628. writer.writeLog(MASTER, 2, who2, id(1), id(2), "2");
  629. PersonIdent who3 = new PersonIdent("Log", "Ger", 1500079711, -8 * 60);
  630. writer.writeLog(MASTER, 1, who3, id(2), id(3), "3");
  631. writer.finish();
  632. byte[] table = buffer.toByteArray();
  633. ReentrantLock lock = new ReentrantLock();
  634. ReftableReader t = read(table);
  635. ReftableReflogReader rlr = new ReftableReflogReader(lock, t, MASTER);
  636. assertEquals(rlr.getLastEntry().getWho(), who1);
  637. List<PersonIdent> all = rlr.getReverseEntries().stream()
  638. .map(x -> x.getWho()).collect(Collectors.toList());
  639. Matchers.contains(all, who3, who2, who1);
  640. assertEquals(rlr.getReverseEntry(1).getWho(), who2);
  641. List<ReflogEntry> reverse2 = rlr.getReverseEntries(2);
  642. Matchers.contains(reverse2, who3, who2);
  643. List<PersonIdent> more = rlr.getReverseEntries(4).stream()
  644. .map(x -> x.getWho()).collect(Collectors.toList());
  645. assertEquals(all, more);
  646. }
  647. @Test
  648. public void allRefs() throws IOException {
  649. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  650. ReftableConfig cfg = new ReftableConfig();
  651. cfg.setRefBlockSize(1024);
  652. cfg.setLogBlockSize(1024);
  653. cfg.setAlignBlocks(true);
  654. ReftableWriter writer = new ReftableWriter(buffer)
  655. .setMinUpdateIndex(1)
  656. .setMaxUpdateIndex(1)
  657. .setConfig(cfg)
  658. .begin();
  659. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  660. // Fill out the 1st ref block.
  661. List<String> names = new ArrayList<>();
  662. for (int i = 0; i < 4; i++) {
  663. @SuppressWarnings("boxing")
  664. String name = new String(new char[220]).replace("\0", String.format("%c", i + 'a'));
  665. names.add(name);
  666. writer.writeRef(ref(name, i));
  667. }
  668. // Add some log data.
  669. writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), "msg");
  670. writer.finish();
  671. byte[] table = buffer.toByteArray();
  672. ReftableReader t = read(table);
  673. RefCursor c = t.allRefs();
  674. int j = 0;
  675. while (c.next()) {
  676. assertEquals(names.get(j), c.getRef().getName());
  677. j++;
  678. }
  679. }
  680. @Test
  681. public void reflogSeek() throws IOException {
  682. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  683. String msg = "test";
  684. String msgNext = "test next";
  685. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  686. ReftableWriter writer = new ReftableWriter(buffer)
  687. .setMinUpdateIndex(1)
  688. .setMaxUpdateIndex(1)
  689. .begin();
  690. writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
  691. writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msgNext);
  692. writer.finish();
  693. byte[] table = buffer.toByteArray();
  694. ReftableReader t = read(table);
  695. try (LogCursor c = t.seekLog(MASTER, Long.MAX_VALUE)) {
  696. assertTrue(c.next());
  697. assertEquals(c.getReflogEntry().getComment(), msg);
  698. }
  699. try (LogCursor c = t.seekLog(MASTER, 0)) {
  700. assertFalse(c.next());
  701. }
  702. try (LogCursor c = t.seekLog(MASTER, 1)) {
  703. assertTrue(c.next());
  704. assertEquals(c.getUpdateIndex(), 1);
  705. assertEquals(c.getReflogEntry().getComment(), msg);
  706. }
  707. try (LogCursor c = t.seekLog(NEXT, Long.MAX_VALUE)) {
  708. assertTrue(c.next());
  709. assertEquals(c.getReflogEntry().getComment(), msgNext);
  710. }
  711. try (LogCursor c = t.seekLog(NEXT, 0)) {
  712. assertFalse(c.next());
  713. }
  714. try (LogCursor c = t.seekLog(NEXT, 1)) {
  715. assertTrue(c.next());
  716. assertEquals(c.getUpdateIndex(), 1);
  717. assertEquals(c.getReflogEntry().getComment(), msgNext);
  718. }
  719. }
  720. @Test
  721. public void reflogSeekPrefix() throws IOException {
  722. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  723. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  724. ReftableWriter writer = new ReftableWriter(buffer)
  725. .setMinUpdateIndex(1)
  726. .setMaxUpdateIndex(1)
  727. .begin();
  728. writer.writeLog("branchname", 1, who, ObjectId.zeroId(), id(1), "branchname");
  729. writer.finish();
  730. byte[] table = buffer.toByteArray();
  731. ReftableReader t = read(table);
  732. try (LogCursor c = t.seekLog("branch", Long.MAX_VALUE)) {
  733. // We find a reflog block, but the iteration won't confuse branchname
  734. // and branch.
  735. assertFalse(c.next());
  736. }
  737. }
  738. @Test
  739. public void onlyReflog() throws IOException {
  740. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  741. String msg = "test";
  742. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  743. ReftableWriter writer = new ReftableWriter(buffer)
  744. .setMinUpdateIndex(1)
  745. .setMaxUpdateIndex(1)
  746. .begin();
  747. writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
  748. writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
  749. writer.finish();
  750. byte[] table = buffer.toByteArray();
  751. stats = writer.getStats();
  752. assertEquals(170, table.length);
  753. assertEquals(0, stats.refCount());
  754. assertEquals(0, stats.refBytes());
  755. assertEquals(0, stats.refIndexLevels());
  756. ReftableReader t = read(table);
  757. try (RefCursor rc = t.allRefs()) {
  758. assertFalse(rc.next());
  759. }
  760. try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
  761. assertFalse(rc.next());
  762. }
  763. try (LogCursor lc = t.allLogs()) {
  764. assertTrue(lc.next());
  765. assertEquals(MASTER, lc.getRefName());
  766. assertEquals(1, lc.getUpdateIndex());
  767. assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
  768. assertEquals(id(1), lc.getReflogEntry().getNewId());
  769. assertEquals(who, lc.getReflogEntry().getWho());
  770. // compare string too, to catch tz differences.
  771. assertEquals(who.toExternalString(), lc.getReflogEntry().getWho().toExternalString());
  772. assertEquals(msg, lc.getReflogEntry().getComment());
  773. assertTrue(lc.next());
  774. assertEquals(NEXT, lc.getRefName());
  775. assertEquals(1, lc.getUpdateIndex());
  776. assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
  777. assertEquals(id(2), lc.getReflogEntry().getNewId());
  778. assertEquals(who, lc.getReflogEntry().getWho());
  779. assertEquals(msg, lc.getReflogEntry().getComment());
  780. assertFalse(lc.next());
  781. }
  782. }
  783. @Test
  784. public void logScan() throws IOException {
  785. ReftableConfig cfg = new ReftableConfig();
  786. cfg.setRefBlockSize(256);
  787. cfg.setLogBlockSize(2048);
  788. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  789. ReftableWriter writer = new ReftableWriter(cfg, buffer);
  790. writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin();
  791. List<Ref> refs = new ArrayList<>();
  792. for (int i = 1; i <= 5670; i++) {
  793. @SuppressWarnings("boxing")
  794. Ref ref = ref(String.format("refs/heads/%04d", i), i);
  795. refs.add(ref);
  796. writer.writeRef(ref);
  797. }
  798. PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
  799. for (Ref ref : refs) {
  800. writer.writeLog(ref.getName(), 1, who,
  801. ObjectId.zeroId(), ref.getObjectId(),
  802. "create " + ref.getName());
  803. }
  804. writer.finish();
  805. stats = writer.getStats();
  806. assertTrue(stats.logBytes() > 4096);
  807. byte[] table = buffer.toByteArray();
  808. ReftableReader t = read(table);
  809. try (LogCursor lc = t.allLogs()) {
  810. for (Ref exp : refs) {
  811. assertTrue("has " + exp.getName(), lc.next());
  812. assertEquals(exp.getName(), lc.getRefName());
  813. ReflogEntry entry = lc.getReflogEntry();
  814. assertNotNull(entry);
  815. assertEquals(who, entry.getWho());
  816. assertEquals(ObjectId.zeroId(), entry.getOldId());
  817. assertEquals(exp.getObjectId(), entry.getNewId());
  818. assertEquals("create " + exp.getName(), entry.getComment());
  819. }
  820. assertFalse(lc.next());
  821. }
  822. }
  823. @Test
  824. public void byObjectIdOneRefNoIndex() throws IOException {
  825. List<Ref> refs = new ArrayList<>();
  826. for (int i = 1; i <= 200; i++) {
  827. @SuppressWarnings("boxing")
  828. Ref ref = ref(String.format("refs/heads/%02d", i), i);
  829. refs.add(ref);
  830. }
  831. refs.add(ref("refs/heads/master", 100));
  832. ReftableReader t = read(write(refs));
  833. assertEquals(0, stats.objIndexSize());
  834. try (RefCursor rc = t.byObjectId(id(42))) {
  835. assertTrue("has 42", rc.next());
  836. assertEquals("refs/heads/42", rc.getRef().getName());
  837. assertEquals(id(42), rc.getRef().getObjectId());
  838. assertEquals(0, rc.getRef().getUpdateIndex());
  839. assertFalse(rc.next());
  840. }
  841. try (RefCursor rc = t.byObjectId(id(100))) {
  842. assertTrue("has 100", rc.next());
  843. assertEquals("refs/heads/100", rc.getRef().getName());
  844. assertEquals(id(100), rc.getRef().getObjectId());
  845. assertTrue("has master", rc.next());
  846. assertEquals("refs/heads/master", rc.getRef().getName());
  847. assertEquals(id(100), rc.getRef().getObjectId());
  848. assertEquals(0, rc.getRef().getUpdateIndex());
  849. assertFalse(rc.next());
  850. }
  851. }
  852. @Test
  853. public void byObjectIdOneRefWithIndex() throws IOException {
  854. List<Ref> refs = new ArrayList<>();
  855. for (int i = 1; i <= 5200; i++) {
  856. @SuppressWarnings("boxing")
  857. Ref ref = ref(String.format("refs/heads/%02d", i), i);
  858. refs.add(ref);
  859. }
  860. refs.add(ref("refs/heads/master", 100));
  861. ReftableReader t = read(write(refs));
  862. assertTrue(stats.objIndexSize() > 0);
  863. try (RefCursor rc = t.byObjectId(id(42))) {
  864. assertTrue("has 42", rc.next());
  865. assertEquals("refs/heads/42", rc.getRef().getName());
  866. assertEquals(id(42), rc.getRef().getObjectId());
  867. assertEquals(0, rc.getRef().getUpdateIndex());
  868. assertFalse(rc.next());
  869. }
  870. try (RefCursor rc = t.byObjectId(id(100))) {
  871. assertTrue("has 100", rc.next());
  872. assertEquals("refs/heads/100", rc.getRef().getName());
  873. assertEquals(id(100), rc.getRef().getObjectId());
  874. assertTrue("has master", rc.next());
  875. assertEquals("refs/heads/master", rc.getRef().getName());
  876. assertEquals(id(100), rc.getRef().getObjectId());
  877. assertEquals(0, rc.getRef().getUpdateIndex());
  878. assertFalse(rc.next());
  879. }
  880. }
  881. @Test
  882. public void byObjectIdSkipPastPrefix() throws IOException {
  883. ReftableReader t = read(write());
  884. try (RefCursor rc = t.byObjectId(id(2))) {
  885. assertThrows(UnsupportedOperationException.class, () -> rc.seekPastPrefix("refs/heads/"));
  886. }
  887. }
  888. @Test
  889. public void unpeeledDoesNotWrite() {
  890. try {
  891. write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
  892. fail("expected IOException");
  893. } catch (IOException e) {
  894. assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage());
  895. }
  896. }
  897. @Test
  898. public void skipPastRefWithLastUTF8() throws IOException {
  899. ReftableReader t = read(write(ref(String.format("refs/heads/%sbla", new String(LAST_UTF8_CHAR
  900. , UTF_8)), 1)));
  901. try (RefCursor rc = t.allRefs()) {
  902. rc.seekPastPrefix("refs/heads/");
  903. assertFalse(rc.next());
  904. }
  905. }
  906. @Test
  907. public void nameTooLongDoesNotWrite() throws IOException {
  908. try {
  909. ReftableConfig cfg = new ReftableConfig();
  910. cfg.setRefBlockSize(64);
  911. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  912. ReftableWriter writer = new ReftableWriter(cfg, buffer).begin();
  913. writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1));
  914. writer.finish();
  915. fail("expected BlockSizeTooSmallException");
  916. } catch (BlockSizeTooSmallException e) {
  917. assertEquals(85, e.getMinimumBlockSize());
  918. }
  919. }
  920. @Test
  921. public void badCrc32() throws IOException {
  922. byte[] table = write();
  923. table[table.length - 1] = 0x42;
  924. try {
  925. read(table).seekRef(HEAD);
  926. fail("expected IOException");
  927. } catch (IOException e) {
  928. assertEquals(JGitText.get().invalidReftableCRC, e.getMessage());
  929. }
  930. }
  931. private static void assertScan(List<Ref> refs, Reftable t)
  932. throws IOException {
  933. try (RefCursor rc = t.allRefs()) {
  934. for (Ref exp : refs) {
  935. assertTrue("has " + exp.getName(), rc.next());
  936. Ref act = rc.getRef();
  937. assertEquals(exp.getName(), act.getName());
  938. assertEquals(exp.getObjectId(), act.getObjectId());
  939. assertEquals(0, rc.getRef().getUpdateIndex());
  940. }
  941. assertFalse(rc.next());
  942. }
  943. }
  944. private static void assertSeek(List<Ref> refs, Reftable t)
  945. throws IOException {
  946. for (Ref exp : refs) {
  947. try (RefCursor rc = t.seekRef(exp.getName())) {
  948. assertTrue("has " + exp.getName(), rc.next());
  949. Ref act = rc.getRef();
  950. assertEquals(exp.getName(), act.getName());
  951. assertEquals(exp.getObjectId(), act.getObjectId());
  952. assertEquals(0, rc.getRef().getUpdateIndex());
  953. assertFalse(rc.next());
  954. }
  955. }
  956. }
  957. private static Ref ref(String name, int id) {
  958. return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
  959. }
  960. private static Ref tag(String name, int id1, int id2) {
  961. return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2));
  962. }
  963. private static Ref sym(String name, String target) {
  964. return new SymbolicRef(name, newRef(target));
  965. }
  966. private static Ref newRef(String name) {
  967. return new ObjectIdRef.Unpeeled(NEW, name, null);
  968. }
  969. private static ObjectId id(int i) {
  970. byte[] buf = new byte[OBJECT_ID_LENGTH];
  971. buf[0] = (byte) (i & 0xff);
  972. buf[1] = (byte) ((i >>> 8) & 0xff);
  973. buf[2] = (byte) ((i >>> 16) & 0xff);
  974. buf[3] = (byte) (i >>> 24);
  975. return ObjectId.fromRaw(buf);
  976. }
  977. private static ReftableReader read(byte[] table) {
  978. return new ReftableReader(BlockSource.from(table));
  979. }
  980. private byte[] write(Ref... refs) throws IOException {
  981. return write(Arrays.asList(refs));
  982. }
  983. private byte[] write(Collection<Ref> refs) throws IOException {
  984. return write(refs, new ReftableConfig());
  985. }
  986. private byte[] write(Collection<Ref> refs, ReftableConfig cfg) throws IOException {
  987. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  988. stats = new ReftableWriter(buffer)
  989. .setConfig(cfg)
  990. .begin()
  991. .sortAndWriteRefs(refs)
  992. .finish()
  993. .getStats();
  994. return buffer.toByteArray();
  995. }
  996. }