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.

MergedReftableTest.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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 org.eclipse.jgit.lib.Constants.HEAD;
  12. import static org.eclipse.jgit.lib.Constants.MASTER;
  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.junit.Assert.assertEquals;
  18. import static org.junit.Assert.assertFalse;
  19. import static org.junit.Assert.assertNull;
  20. import static org.junit.Assert.assertTrue;
  21. import java.io.ByteArrayOutputStream;
  22. import java.io.IOException;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.Collection;
  26. import java.util.Collections;
  27. import java.util.HashMap;
  28. import java.util.List;
  29. import java.util.Map;
  30. import org.eclipse.jgit.internal.storage.io.BlockSource;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.ObjectIdRef;
  33. import org.eclipse.jgit.lib.Ref;
  34. import org.eclipse.jgit.lib.RefComparator;
  35. import org.eclipse.jgit.lib.SymbolicRef;
  36. import org.junit.Test;
  37. public class MergedReftableTest {
  38. @Test
  39. public void noTables() throws IOException {
  40. MergedReftable mr = merge(new byte[0][]);
  41. try (RefCursor rc = mr.allRefs()) {
  42. assertFalse(rc.next());
  43. }
  44. try (RefCursor rc = mr.seekRef(HEAD)) {
  45. assertFalse(rc.next());
  46. }
  47. try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
  48. assertFalse(rc.next());
  49. }
  50. }
  51. @Test
  52. public void oneEmptyTable() throws IOException {
  53. MergedReftable mr = merge(write());
  54. try (RefCursor rc = mr.allRefs()) {
  55. assertFalse(rc.next());
  56. }
  57. try (RefCursor rc = mr.seekRef(HEAD)) {
  58. assertFalse(rc.next());
  59. }
  60. try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
  61. assertFalse(rc.next());
  62. }
  63. }
  64. @Test
  65. public void twoEmptyTables() throws IOException {
  66. MergedReftable mr = merge(write(), write());
  67. try (RefCursor rc = mr.allRefs()) {
  68. assertFalse(rc.next());
  69. }
  70. try (RefCursor rc = mr.seekRef(HEAD)) {
  71. assertFalse(rc.next());
  72. }
  73. try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
  74. assertFalse(rc.next());
  75. }
  76. }
  77. @SuppressWarnings("boxing")
  78. @Test
  79. public void oneTableScan() throws IOException {
  80. List<Ref> refs = new ArrayList<>();
  81. for (int i = 1; i <= 567; i++) {
  82. refs.add(ref(String.format("refs/heads/%03d", i), i));
  83. }
  84. MergedReftable mr = merge(write(refs));
  85. try (RefCursor rc = mr.allRefs()) {
  86. for (Ref exp : refs) {
  87. assertTrue("has " + exp.getName(), rc.next());
  88. Ref act = rc.getRef();
  89. assertEquals(exp.getName(), act.getName());
  90. assertEquals(exp.getObjectId(), act.getObjectId());
  91. assertEquals(1, act.getUpdateIndex());
  92. }
  93. assertFalse(rc.next());
  94. }
  95. }
  96. @Test
  97. public void deleteIsHidden() throws IOException {
  98. List<Ref> delta1 = Arrays.asList(
  99. ref("refs/heads/apple", 1),
  100. ref("refs/heads/master", 2));
  101. List<Ref> delta2 = Arrays.asList(delete("refs/heads/apple"));
  102. MergedReftable mr = merge(write(delta1), write(delta2));
  103. try (RefCursor rc = mr.allRefs()) {
  104. assertTrue(rc.next());
  105. assertEquals("refs/heads/master", rc.getRef().getName());
  106. assertEquals(id(2), rc.getRef().getObjectId());
  107. assertEquals(1, rc.getRef().getUpdateIndex());
  108. assertFalse(rc.next());
  109. }
  110. }
  111. @Test
  112. public void twoTableSeek() throws IOException {
  113. List<Ref> delta1 = Arrays.asList(
  114. ref("refs/heads/apple", 1),
  115. ref("refs/heads/master", 2));
  116. List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
  117. MergedReftable mr = merge(write(delta1), write(delta2));
  118. try (RefCursor rc = mr.seekRef("refs/heads/master")) {
  119. assertTrue(rc.next());
  120. assertEquals("refs/heads/master", rc.getRef().getName());
  121. assertEquals(id(2), rc.getRef().getObjectId());
  122. assertFalse(rc.next());
  123. assertEquals(1, rc.getRef().getUpdateIndex());
  124. }
  125. }
  126. @Test
  127. public void twoTableById() throws IOException {
  128. List<Ref> delta1 = Arrays.asList(
  129. ref("refs/heads/apple", 1),
  130. ref("refs/heads/master", 2));
  131. List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
  132. MergedReftable mr = merge(write(delta1), write(delta2));
  133. try (RefCursor rc = mr.byObjectId(id(2))) {
  134. assertTrue(rc.next());
  135. assertEquals("refs/heads/master", rc.getRef().getName());
  136. assertEquals(id(2), rc.getRef().getObjectId());
  137. assertEquals(1, rc.getRef().getUpdateIndex());
  138. assertFalse(rc.next());
  139. }
  140. }
  141. @Test
  142. public void tableByIDDeletion() throws IOException {
  143. List<Ref> delta1 = Arrays.asList(
  144. ref("refs/heads/apple", 1),
  145. ref("refs/heads/master", 2));
  146. List<Ref> delta2 = Arrays.asList(ref("refs/heads/master", 3));
  147. MergedReftable mr = merge(write(delta1), write(delta2));
  148. try (RefCursor rc = mr.byObjectId(id(2))) {
  149. assertFalse(rc.next());
  150. }
  151. }
  152. @SuppressWarnings("boxing")
  153. @Test
  154. public void fourTableScan() throws IOException {
  155. List<Ref> base = new ArrayList<>();
  156. for (int i = 1; i <= 567; i++) {
  157. base.add(ref(String.format("refs/heads/%03d", i), i));
  158. }
  159. List<Ref> delta1 = Arrays.asList(
  160. ref("refs/heads/next", 4),
  161. ref(String.format("refs/heads/%03d", 55), 4096));
  162. List<Ref> delta2 = Arrays.asList(
  163. delete("refs/heads/next"),
  164. ref(String.format("refs/heads/%03d", 55), 8192));
  165. List<Ref> delta3 = Arrays.asList(
  166. ref("refs/heads/master", 4242),
  167. ref(String.format("refs/heads/%03d", 42), 5120),
  168. ref(String.format("refs/heads/%03d", 98), 6120));
  169. List<Ref> expected = merge(base, delta1, delta2, delta3);
  170. MergedReftable mr = merge(
  171. write(base),
  172. write(delta1),
  173. write(delta2),
  174. write(delta3));
  175. try (RefCursor rc = mr.allRefs()) {
  176. for (Ref exp : expected) {
  177. assertTrue("has " + exp.getName(), rc.next());
  178. Ref act = rc.getRef();
  179. assertEquals(exp.getName(), act.getName());
  180. assertEquals(exp.getObjectId(), act.getObjectId());
  181. assertEquals(1, rc.getRef().getUpdateIndex());
  182. }
  183. assertFalse(rc.next());
  184. }
  185. }
  186. @Test
  187. public void scanIncludeDeletes() throws IOException {
  188. List<Ref> delta1 = Arrays.asList(ref("refs/heads/next", 4));
  189. List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
  190. List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
  191. MergedReftable mr = merge(write(delta1), write(delta2), write(delta3));
  192. mr.setIncludeDeletes(true);
  193. try (RefCursor rc = mr.allRefs()) {
  194. assertTrue(rc.next());
  195. Ref r = rc.getRef();
  196. assertEquals("refs/heads/master", r.getName());
  197. assertEquals(id(8), r.getObjectId());
  198. assertEquals(1, rc.getRef().getUpdateIndex());
  199. assertTrue(rc.next());
  200. r = rc.getRef();
  201. assertEquals("refs/heads/next", r.getName());
  202. assertEquals(NEW, r.getStorage());
  203. assertNull(r.getObjectId());
  204. assertEquals(1, rc.getRef().getUpdateIndex());
  205. assertFalse(rc.next());
  206. }
  207. }
  208. @SuppressWarnings("boxing")
  209. @Test
  210. public void oneTableSeek() throws IOException {
  211. List<Ref> refs = new ArrayList<>();
  212. for (int i = 1; i <= 567; i++) {
  213. refs.add(ref(String.format("refs/heads/%03d", i), i));
  214. }
  215. MergedReftable mr = merge(write(refs));
  216. for (Ref exp : refs) {
  217. try (RefCursor rc = mr.seekRef(exp.getName())) {
  218. assertTrue("has " + exp.getName(), rc.next());
  219. Ref act = rc.getRef();
  220. assertEquals(exp.getName(), act.getName());
  221. assertEquals(exp.getObjectId(), act.getObjectId());
  222. assertEquals(1, act.getUpdateIndex());
  223. assertFalse(rc.next());
  224. }
  225. }
  226. }
  227. @Test
  228. public void missedUpdate() throws IOException {
  229. ByteArrayOutputStream buf = new ByteArrayOutputStream();
  230. ReftableWriter writer = new ReftableWriter(buf)
  231. .setMinUpdateIndex(1)
  232. .setMaxUpdateIndex(3)
  233. .begin();
  234. writer.writeRef(ref("refs/heads/a", 1), 1);
  235. writer.writeRef(ref("refs/heads/c", 3), 3);
  236. writer.finish();
  237. byte[] base = buf.toByteArray();
  238. byte[] delta = write(Arrays.asList(
  239. ref("refs/heads/b", 2),
  240. ref("refs/heads/c", 4)),
  241. 2);
  242. MergedReftable mr = merge(base, delta);
  243. try (RefCursor rc = mr.allRefs()) {
  244. assertTrue(rc.next());
  245. assertEquals("refs/heads/a", rc.getRef().getName());
  246. assertEquals(id(1), rc.getRef().getObjectId());
  247. assertEquals(1, rc.getRef().getUpdateIndex());
  248. assertTrue(rc.next());
  249. assertEquals("refs/heads/b", rc.getRef().getName());
  250. assertEquals(id(2), rc.getRef().getObjectId());
  251. assertEquals(2, rc.getRef().getUpdateIndex());
  252. assertTrue(rc.next());
  253. assertEquals("refs/heads/c", rc.getRef().getName());
  254. assertEquals(id(3), rc.getRef().getObjectId());
  255. assertEquals(3, rc.getRef().getUpdateIndex());
  256. }
  257. }
  258. @Test
  259. public void nonOverlappedUpdateIndices() throws IOException {
  260. ByteArrayOutputStream buf = new ByteArrayOutputStream();
  261. ReftableWriter writer = new ReftableWriter(buf)
  262. .setMinUpdateIndex(1)
  263. .setMaxUpdateIndex(2)
  264. .begin();
  265. writer.writeRef(ref("refs/heads/a", 1), 1);
  266. writer.writeRef(ref("refs/heads/b", 2), 2);
  267. writer.finish();
  268. byte[] base = buf.toByteArray();
  269. buf = new ByteArrayOutputStream();
  270. writer = new ReftableWriter(buf)
  271. .setMinUpdateIndex(3)
  272. .setMaxUpdateIndex(4)
  273. .begin();
  274. writer.writeRef(ref("refs/heads/a", 10), 3);
  275. writer.writeRef(ref("refs/heads/b", 20), 4);
  276. writer.finish();
  277. byte[] delta = buf.toByteArray();
  278. MergedReftable mr = merge(base, delta);
  279. assertEquals(1, mr.minUpdateIndex());
  280. assertEquals(4, mr.maxUpdateIndex());
  281. try (RefCursor rc = mr.allRefs()) {
  282. assertTrue(rc.next());
  283. assertEquals("refs/heads/a", rc.getRef().getName());
  284. assertEquals(id(10), rc.getRef().getObjectId());
  285. assertEquals(3, rc.getRef().getUpdateIndex());
  286. assertTrue(rc.next());
  287. assertEquals("refs/heads/b", rc.getRef().getName());
  288. assertEquals(id(20), rc.getRef().getObjectId());
  289. assertEquals(4, rc.getRef().getUpdateIndex());
  290. }
  291. }
  292. @Test
  293. public void overlappedUpdateIndices() throws IOException {
  294. ByteArrayOutputStream buf = new ByteArrayOutputStream();
  295. ReftableWriter writer = new ReftableWriter(buf)
  296. .setMinUpdateIndex(1)
  297. .setMaxUpdateIndex(3)
  298. .begin();
  299. writer.writeRef(ref("refs/heads/a", 1), 1);
  300. writer.writeRef(ref("refs/heads/b", 2), 3);
  301. writer.finish();
  302. byte[] base = buf.toByteArray();
  303. buf = new ByteArrayOutputStream();
  304. writer = new ReftableWriter(buf)
  305. .setMinUpdateIndex(2)
  306. .setMaxUpdateIndex(4)
  307. .begin();
  308. writer.writeRef(ref("refs/heads/a", 10), 2);
  309. writer.writeRef(ref("refs/heads/b", 20), 4);
  310. writer.finish();
  311. byte[] delta = buf.toByteArray();
  312. MergedReftable mr = merge(base, delta);
  313. assertEquals(1, mr.minUpdateIndex());
  314. assertEquals(4, mr.maxUpdateIndex());
  315. try (RefCursor rc = mr.allRefs()) {
  316. assertTrue(rc.next());
  317. assertEquals("refs/heads/a", rc.getRef().getName());
  318. assertEquals(id(10), rc.getRef().getObjectId());
  319. assertEquals(2, rc.getRef().getUpdateIndex());
  320. assertTrue(rc.next());
  321. assertEquals("refs/heads/b", rc.getRef().getName());
  322. assertEquals(id(20), rc.getRef().getObjectId());
  323. assertEquals(4, rc.getRef().getUpdateIndex());
  324. }
  325. }
  326. @Test
  327. public void enclosedUpdateIndices() throws IOException {
  328. ByteArrayOutputStream buf = new ByteArrayOutputStream();
  329. ReftableWriter writer = new ReftableWriter(buf)
  330. .setMinUpdateIndex(1)
  331. .setMaxUpdateIndex(4)
  332. .begin();
  333. writer.writeRef(ref("refs/heads/a", 1), 1);
  334. writer.writeRef(ref("refs/heads/b", 20), 4);
  335. writer.finish();
  336. byte[] base = buf.toByteArray();
  337. buf = new ByteArrayOutputStream();
  338. writer = new ReftableWriter(buf)
  339. .setMinUpdateIndex(2)
  340. .setMaxUpdateIndex(3)
  341. .begin();
  342. writer.writeRef(ref("refs/heads/a", 10), 2);
  343. writer.writeRef(ref("refs/heads/b", 2), 3);
  344. writer.finish();
  345. byte[] delta = buf.toByteArray();
  346. MergedReftable mr = merge(base, delta);
  347. assertEquals(1, mr.minUpdateIndex());
  348. assertEquals(4, mr.maxUpdateIndex());
  349. try (RefCursor rc = mr.allRefs()) {
  350. assertTrue(rc.next());
  351. assertEquals("refs/heads/a", rc.getRef().getName());
  352. assertEquals(id(10), rc.getRef().getObjectId());
  353. assertEquals(2, rc.getRef().getUpdateIndex());
  354. assertTrue(rc.next());
  355. assertEquals("refs/heads/b", rc.getRef().getName());
  356. assertEquals(id(20), rc.getRef().getObjectId());
  357. assertEquals(4, rc.getRef().getUpdateIndex());
  358. }
  359. }
  360. @Test
  361. public void compaction() throws IOException {
  362. List<Ref> delta1 = Arrays.asList(
  363. ref("refs/heads/next", 4),
  364. ref("refs/heads/master", 1));
  365. List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
  366. List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
  367. ByteArrayOutputStream out = new ByteArrayOutputStream();
  368. ReftableCompactor compactor = new ReftableCompactor(out);
  369. compactor.addAll(Arrays.asList(
  370. read(write(delta1)),
  371. read(write(delta2)),
  372. read(write(delta3))));
  373. compactor.compact();
  374. byte[] table = out.toByteArray();
  375. ReftableReader reader = read(table);
  376. try (RefCursor rc = reader.allRefs()) {
  377. assertTrue(rc.next());
  378. Ref r = rc.getRef();
  379. assertEquals("refs/heads/master", r.getName());
  380. assertEquals(id(8), r.getObjectId());
  381. assertFalse(rc.next());
  382. }
  383. }
  384. @Test
  385. public void versioningSymbolicReftargetMoves() throws IOException {
  386. Ref master = ref(MASTER, 100);
  387. List<Ref> delta1 = Arrays.asList(master, sym(HEAD, MASTER));
  388. List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
  389. MergedReftable mr = merge(write(delta1, 1), write(delta2, 2));
  390. Ref head = mr.exactRef(HEAD);
  391. assertEquals(head.getUpdateIndex(), 1);
  392. Ref masterRef = mr.exactRef(MASTER);
  393. assertEquals(masterRef.getUpdateIndex(), 2);
  394. }
  395. @Test
  396. public void versioningSymbolicRefMoves() throws IOException {
  397. Ref branchX = ref("refs/heads/branchX", 200);
  398. List<Ref> delta1 = Arrays.asList(ref(MASTER, 100), branchX,
  399. sym(HEAD, MASTER));
  400. List<Ref> delta2 = Arrays.asList(sym(HEAD, "refs/heads/branchX"));
  401. List<Ref> delta3 = Arrays.asList(sym(HEAD, MASTER));
  402. MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
  403. write(delta3, 3));
  404. Ref head = mr.exactRef(HEAD);
  405. assertEquals(head.getUpdateIndex(), 3);
  406. Ref masterRef = mr.exactRef(MASTER);
  407. assertEquals(masterRef.getUpdateIndex(), 1);
  408. Ref branchRef = mr.exactRef(MASTER);
  409. assertEquals(branchRef.getUpdateIndex(), 1);
  410. }
  411. @Test
  412. public void versioningResolveRef() throws IOException {
  413. List<Ref> delta1 = Arrays.asList(sym(HEAD, "refs/heads/tmp"),
  414. sym("refs/heads/tmp", MASTER), ref(MASTER, 100));
  415. List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
  416. List<Ref> delta3 = Arrays.asList(ref(MASTER, 300));
  417. MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
  418. write(delta3, 3));
  419. Ref head = mr.exactRef(HEAD);
  420. Ref resolvedHead = mr.resolve(head);
  421. assertEquals(resolvedHead.getObjectId(), id(300));
  422. assertEquals("HEAD has not moved", resolvedHead.getUpdateIndex(), 1);
  423. Ref master = mr.exactRef(MASTER);
  424. Ref resolvedMaster = mr.resolve(master);
  425. assertEquals(resolvedMaster.getObjectId(), id(300));
  426. assertEquals("master also has update index",
  427. resolvedMaster.getUpdateIndex(), 3);
  428. }
  429. private static MergedReftable merge(byte[]... table) {
  430. List<ReftableReader> stack = new ArrayList<>(table.length);
  431. for (byte[] b : table) {
  432. stack.add(read(b));
  433. }
  434. return new MergedReftable(stack);
  435. }
  436. private static ReftableReader read(byte[] table) {
  437. return new ReftableReader(BlockSource.from(table));
  438. }
  439. private static Ref ref(String name, int id) {
  440. return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
  441. }
  442. private static Ref sym(String name, String target) {
  443. return new SymbolicRef(name, newRef(target));
  444. }
  445. private static Ref newRef(String name) {
  446. return new ObjectIdRef.Unpeeled(NEW, name, null);
  447. }
  448. private static Ref delete(String name) {
  449. return new ObjectIdRef.Unpeeled(NEW, name, null);
  450. }
  451. private static ObjectId id(int i) {
  452. byte[] buf = new byte[OBJECT_ID_LENGTH];
  453. buf[0] = (byte) (i & 0xff);
  454. buf[1] = (byte) ((i >>> 8) & 0xff);
  455. buf[2] = (byte) ((i >>> 16) & 0xff);
  456. buf[3] = (byte) (i >>> 24);
  457. return ObjectId.fromRaw(buf);
  458. }
  459. private byte[] write(Ref... refs) throws IOException {
  460. return write(Arrays.asList(refs));
  461. }
  462. private byte[] write(Collection<Ref> refs) throws IOException {
  463. return write(refs, 1);
  464. }
  465. private byte[] write(Collection<Ref> refs, long updateIndex)
  466. throws IOException {
  467. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  468. new ReftableWriter(buffer)
  469. .setMinUpdateIndex(updateIndex)
  470. .setMaxUpdateIndex(updateIndex)
  471. .begin()
  472. .sortAndWriteRefs(refs)
  473. .finish();
  474. return buffer.toByteArray();
  475. }
  476. @SafeVarargs
  477. private static List<Ref> merge(List<Ref>... tables) {
  478. Map<String, Ref> expect = new HashMap<>();
  479. for (List<Ref> t : tables) {
  480. for (Ref r : t) {
  481. if (r.getStorage() == NEW && r.getObjectId() == null) {
  482. expect.remove(r.getName());
  483. } else {
  484. expect.put(r.getName(), r);
  485. }
  486. }
  487. }
  488. List<Ref> expected = new ArrayList<>(expect.values());
  489. Collections.sort(expected, RefComparator.INSTANCE);
  490. return expected;
  491. }
  492. }