View Javadoc
1   /*
2    * Copyright (C) 2019, 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  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
14  import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
15  import static org.eclipse.jgit.lib.RefUpdate.Result.IO_FAILURE;
16  import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
17  import static org.junit.Assert.assertEquals;
18  import static org.junit.Assert.assertFalse;
19  import static org.junit.Assert.assertNotEquals;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNotSame;
22  import static org.junit.Assert.assertNull;
23  import static org.junit.Assert.assertSame;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.File;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.OutputStream;
31  import java.security.SecureRandom;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  import java.util.HashSet;
35  import java.util.List;
36  
37  import java.util.Set;
38  import org.eclipse.jgit.lib.AnyObjectId;
39  import org.eclipse.jgit.lib.Constants;
40  import org.eclipse.jgit.lib.NullProgressMonitor;
41  import org.eclipse.jgit.lib.ObjectId;
42  import org.eclipse.jgit.lib.PersonIdent;
43  import org.eclipse.jgit.lib.Ref;
44  import org.eclipse.jgit.lib.RefDatabase;
45  import org.eclipse.jgit.lib.RefRename;
46  import org.eclipse.jgit.lib.RefUpdate;
47  import org.eclipse.jgit.lib.RefUpdate.Result;
48  import org.eclipse.jgit.lib.ReflogEntry;
49  import org.eclipse.jgit.lib.ReflogReader;
50  import org.eclipse.jgit.lib.RepositoryCache;
51  import org.eclipse.jgit.revwalk.RevWalk;
52  import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
53  import org.eclipse.jgit.transport.ReceiveCommand;
54  import org.junit.Test;
55  
56  public class FileReftableTest extends SampleDataRepositoryTestCase {
57  	String bCommit;
58  
59  	@Override
60  	public void setUp() throws Exception {
61  		super.setUp();
62  		Ref b = db.exactRef("refs/heads/b");
63  		bCommit = b.getObjectId().getName();
64  		db.convertToReftable(false, false);
65  	}
66  
67  	@SuppressWarnings("boxing")
68  	@Test
69  	public void testRacyReload() throws Exception {
70  		ObjectId id = db.resolve("master");
71  		int retry = 0;
72  		try (FileRepository repo1 = new FileRepository(db.getDirectory());
73  				FileRepository repo2 = new FileRepository(db.getDirectory())) {
74  			FileRepository repos[] = { repo1, repo2 };
75  			for (int i = 0; i < 10; i++) {
76  				for (int j = 0; j < 2; j++) {
77  					FileRepository repo = repos[j];
78  					RefUpdate u = repo.getRefDatabase().newUpdate(
79  							String.format("branch%d", i * 10 + j), false);
80  
81  					u.setNewObjectId(id);
82  					RefUpdate.Result r = u.update();
83  					if (!r.equals(Result.NEW)) {
84  						retry++;
85  						u = repo.getRefDatabase().newUpdate(
86  								String.format("branch%d", i * 10 + j), false);
87  
88  						u.setNewObjectId(id);
89  						r = u.update();
90  						assertEquals(r, Result.NEW);
91  					}
92  				}
93  			}
94  
95  			// only the first one succeeds
96  			assertEquals(retry, 19);
97  		}
98  	}
99  
100 	@Test
101 	public void testCompactFully() throws Exception {
102 		ObjectId c1 = db.resolve("master^^");
103 		ObjectId c2 = db.resolve("master^");
104 		for (int i = 0; i < 5; i++) {
105 			RefUpdate u = db.updateRef("refs/heads/master");
106 			u.setForceUpdate(true);
107 			u.setNewObjectId((i%2) == 0 ? c1 : c2);
108 			assertEquals(u.update(), FORCED);
109 		}
110 
111 		File tableDir = new File(db.getDirectory(), Constants.REFTABLE);
112 		assertTrue(tableDir.listFiles().length > 2);
113 		((FileReftableDatabase)db.getRefDatabase()).compactFully();
114 		assertEquals(tableDir.listFiles().length,2);
115 	}
116 
117 	@Test
118 	public void testOpenConvert() throws Exception {
119 		try (FileRepository repo = new FileRepository(db.getDirectory())) {
120 			assertTrue(repo.getRefDatabase() instanceof FileReftableDatabase);
121 		}
122 	}
123 
124 	@Test
125 	public void testConvert() throws Exception {
126 		Ref h = db.exactRef("HEAD");
127 		assertTrue(h.isSymbolic());
128 		assertEquals("refs/heads/master", h.getTarget().getName());
129 
130 		Ref b = db.exactRef("refs/heads/b");
131 		assertFalse(b.isSymbolic());
132 		assertTrue(b.isPeeled());
133 		assertEquals(bCommit, b.getObjectId().name());
134 
135 		assertTrue(db.getRefDatabase().hasFastTipsWithSha1());
136 	}
137 
138 
139 	@Test
140 	public void testConvertBrokenObjectId() throws Exception {
141 		db.convertToPackedRefs(false, false);
142 		new File(db.getDirectory(), "refs/heads").mkdirs();
143 
144 		String invalidId = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
145 		File headFile = new File(db.getDirectory(), "refs/heads/broken");
146 		try (OutputStream os = new FileOutputStream(headFile)) {
147 			os.write(Constants.encodeASCII(invalidId + "\n"));
148 		}
149 
150 		Ref r = db.exactRef("refs/heads/broken");
151 		assertNotNull(r);
152 		db.convertToReftable(true, false);
153 	}
154 
155 	@Test
156 	public void testConvertToRefdirReflog() throws Exception {
157 		Ref a = db.exactRef("refs/heads/a");
158 		String aCommit = a.getObjectId().getName();
159 		RefUpdate u = db.updateRef("refs/heads/master");
160 		u.setForceUpdate(true);
161 		u.setNewObjectId(ObjectId.fromString(aCommit));
162 		u.setForceRefLog(true);
163 		u.setRefLogMessage("apple", false);
164 		u.update();
165 
166 		RefUpdate v = db.updateRef("refs/heads/master");
167 		v.setForceUpdate(true);
168 		v.setNewObjectId(ObjectId.fromString(bCommit));
169 		v.setForceRefLog(true);
170 		v.setRefLogMessage("banana", false);
171 		v.update();
172 
173 		db.convertToPackedRefs(true, false);
174 		List<ReflogEntry> logs = db.getReflogReader("refs/heads/master").getReverseEntries(2);
175 		assertEquals(logs.get(0).getComment(), "banana");
176 		assertEquals(logs.get(1).getComment(), "apple");
177 	}
178 
179 	@Test
180 	public void testBatchrefUpdate() throws Exception {
181 		ObjectId cur = db.resolve("master");
182 		ObjectId prev = db.resolve("master^");
183 
184 		PersonIdent person = new PersonIdent("name", "mail@example.com");
185 		ReceiveCommand rc1 = new ReceiveCommand(ObjectId.zeroId(), cur, "refs/heads/batch1");
186 		ReceiveCommand rc2 = new ReceiveCommand(ObjectId.zeroId(), prev, "refs/heads/batch2");
187 		String msg =  "message";
188 		try (RevWalk rw = new RevWalk(db)) {
189 			db.getRefDatabase().newBatchUpdate()
190 					.addCommand(rc1, rc2)
191 					.setAtomic(true)
192 					.setRefLogIdent(person)
193 					.setRefLogMessage(msg, false)
194 					.execute(rw, NullProgressMonitor.INSTANCE);
195 		}
196 
197 		assertEquals(rc1.getResult(), ReceiveCommand.Result.OK);
198 		assertEquals(rc2.getResult(), ReceiveCommand.Result.OK);
199 
200 		ReflogEntry e = db.getReflogReader("refs/heads/batch1").getLastEntry();
201 		assertEquals(msg, e.getComment());
202 		assertEquals(person, e.getWho());
203 		assertEquals(cur, e.getNewId());
204 
205 		e = db.getReflogReader("refs/heads/batch2").getLastEntry();
206 		assertEquals(msg, e.getComment());
207 		assertEquals(person, e.getWho());
208 		assertEquals(prev, e.getNewId());
209 
210 		assertEquals(cur, db.exactRef("refs/heads/batch1").getObjectId());
211 		assertEquals(prev, db.exactRef("refs/heads/batch2").getObjectId());
212 	}
213 
214 	@Test
215 	public void testFastforwardStatus() throws Exception {
216 		ObjectId cur = db.resolve("master");
217 		ObjectId prev = db.resolve("master^");
218 		RefUpdate u = db.updateRef("refs/heads/master");
219 
220 		u.setNewObjectId(prev);
221 		u.setForceUpdate(true);
222 		assertEquals(FORCED, u.update());
223 
224 		RefUpdate u2 = db.updateRef("refs/heads/master");
225 
226 		u2.setNewObjectId(cur);
227 		assertEquals(FAST_FORWARD, u2.update());
228 	}
229 
230 	@Test
231 	public void testUpdateChecksOldValue() throws Exception {
232 		ObjectId cur = db.resolve("master");
233 		ObjectId prev = db.resolve("master^");
234 		RefUpdate u1 = db.updateRef("refs/heads/master");
235 		RefUpdate u2 = db.updateRef("refs/heads/master");
236 
237 		u1.setExpectedOldObjectId(cur);
238 		u1.setNewObjectId(prev);
239 		u1.setForceUpdate(true);
240 
241 		u2.setExpectedOldObjectId(cur);
242 		u2.setNewObjectId(prev);
243 		u2.setForceUpdate(true);
244 
245 		assertEquals(FORCED, u1.update());
246 		assertEquals(LOCK_FAILURE, u2.update());
247 	}
248 
249 	@Test
250 	public void testWritesymref() throws Exception {
251 		writeSymref(Constants.HEAD, "refs/heads/a");
252 		assertNotNull(db.exactRef("refs/heads/b"));
253 	}
254 
255 	@Test
256 	public void testFastforwardStatus2() throws Exception {
257 		writeSymref(Constants.HEAD, "refs/heads/a");
258 		ObjectId bId = db.exactRef("refs/heads/b").getObjectId();
259 		RefUpdate u = db.updateRef("refs/heads/a");
260 		u.setNewObjectId(bId);
261 		u.setRefLogMessage("Setup", false);
262 		assertEquals(FAST_FORWARD, u.update());
263 	}
264 
265 	@Test
266 	public void testDelete() throws Exception {
267 		RefUpdate up = db.getRefDatabase().newUpdate("refs/heads/a", false);
268 		up.setForceUpdate(true);
269 		RefUpdate.Result res = up.delete();
270 		assertEquals(res, FORCED);
271 		assertNull(db.exactRef("refs/heads/a"));
272 	}
273 
274 	@Test
275 	public void testDeleteWithoutHead() throws IOException {
276 		// Prepare repository without HEAD
277 		RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
278 		refUpdate.setForceUpdate(true);
279 		refUpdate.setNewObjectId(ObjectId.zeroId());
280 
281 		RefUpdate.Result updateResult = refUpdate.update();
282 		assertEquals(FORCED, updateResult);
283 
284 		Ref r = db.exactRef("HEAD");
285 		assertEquals(ObjectId.zeroId(), r.getObjectId());
286 		RefUpdate.Result deleteHeadResult = db.updateRef(Constants.HEAD)
287 				.delete();
288 
289 		// why does doDelete say NEW ?
290 		assertEquals(RefUpdate.Result.NO_CHANGE, deleteHeadResult);
291 
292 		// Any result is ok as long as it's not an NPE
293 		db.updateRef(Constants.R_HEADS + "master").delete();
294 	}
295 
296 	@Test
297 	public void testUpdateRefDetached() throws Exception {
298 		ObjectId pid = db.resolve("refs/heads/master");
299 		ObjectId ppid = db.resolve("refs/heads/master^");
300 		RefUpdate updateRef = db.updateRef("HEAD", true);
301 		updateRef.setForceUpdate(true);
302 		updateRef.setNewObjectId(ppid);
303 		RefUpdate.Result update = updateRef.update();
304 		assertEquals(FORCED, update);
305 		assertEquals(ppid, db.resolve("HEAD"));
306 		Ref ref = db.exactRef("HEAD");
307 		assertEquals("HEAD", ref.getName());
308 		assertTrue("is detached", !ref.isSymbolic());
309 
310 		// the branch HEAD referred to is left untouched
311 		assertEquals(pid, db.resolve("refs/heads/master"));
312 		ReflogReader reflogReader = db.getReflogReader("HEAD");
313 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
314 		assertEquals(ppid, e.getNewId());
315 		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
316 		assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
317 		assertEquals(1250379778000L, e.getWho().getWhen().getTime());
318 		assertEquals(pid, e.getOldId());
319 	}
320 
321 	@Test
322 	public void testWriteReflog() throws Exception {
323 		ObjectId pid = db.resolve("refs/heads/master^");
324 		RefUpdate updateRef = db.updateRef("refs/heads/master");
325 		updateRef.setNewObjectId(pid);
326 		String msg = "REFLOG!";
327 		updateRef.setRefLogMessage(msg, true);
328 		PersonIdent person = new PersonIdent("name", "mail@example.com");
329 		updateRef.setRefLogIdent(person);
330 		updateRef.setForceUpdate(true);
331 		RefUpdate.Result update = updateRef.update();
332 		assertEquals(FORCED, update); // internal
333 		ReflogReader r = db.getReflogReader("refs/heads/master");
334 
335 		ReflogEntry e = r.getLastEntry();
336 		assertEquals(e.getNewId(), pid);
337 		assertEquals(e.getComment(), "REFLOG!: FORCED");
338 		assertEquals(e.getWho(), person);
339 	}
340 
341 	@Test
342 	public void testLooseDelete() throws IOException {
343 		final String newRef = "refs/heads/abc";
344 		assertNull(db.exactRef(newRef));
345 
346 		RefUpdate ref = db.updateRef(newRef);
347 		ObjectId nonZero = db.resolve(Constants.HEAD);
348 		assertNotEquals(nonZero, ObjectId.zeroId());
349 		ref.setNewObjectId(nonZero);
350 		assertEquals(RefUpdate.Result.NEW, ref.update());
351 
352 		ref = db.updateRef(newRef);
353 		ref.setNewObjectId(db.resolve(Constants.HEAD));
354 
355 		assertEquals(ref.delete(), RefUpdate.Result.NO_CHANGE);
356 
357 		// Differs from RefupdateTest. Deleting a loose ref leaves reflog trail.
358 		ReflogReader reader = db.getReflogReader("refs/heads/abc");
359 		assertEquals(ObjectId.zeroId(), reader.getReverseEntry(1).getOldId());
360 		assertEquals(nonZero, reader.getReverseEntry(1).getNewId());
361 		assertEquals(nonZero, reader.getReverseEntry(0).getOldId());
362 		assertEquals(ObjectId.zeroId(), reader.getReverseEntry(0).getNewId());
363 	}
364 
365 	private static class SubclassedId extends ObjectId {
366 		SubclassedId(AnyObjectId src) {
367 			super(src);
368 		}
369 	}
370 
371 	@Test
372 	public void testNoCacheObjectIdSubclass() throws IOException {
373 		final String newRef = "refs/heads/abc";
374 		final RefUpdate ru = updateRef(newRef);
375 		final SubclassedId newid = new SubclassedId(ru.getNewObjectId());
376 		ru.setNewObjectId(newid);
377 		RefUpdate.Result update = ru.update();
378 		assertEquals(RefUpdate.Result.NEW, update);
379 		Ref r = db.exactRef(newRef);
380 		assertEquals(newRef, r.getName());
381 		assertNotNull(r.getObjectId());
382 		assertNotSame(newid, r.getObjectId());
383 		assertSame(ObjectId.class, r.getObjectId().getClass());
384 		assertEquals(newid, r.getObjectId());
385 		List<ReflogEntry> reverseEntries1 = db.getReflogReader("refs/heads/abc")
386 				.getReverseEntries();
387 		ReflogEntry entry1 = reverseEntries1.get(0);
388 		assertEquals(1, reverseEntries1.size());
389 		assertEquals(ObjectId.zeroId(), entry1.getOldId());
390 		assertEquals(r.getObjectId(), entry1.getNewId());
391 
392 		assertEquals(new PersonIdent(db).toString(),
393 				entry1.getWho().toString());
394 		assertEquals("", entry1.getComment());
395 		List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
396 				.getReverseEntries();
397 		assertEquals(0, reverseEntries2.size());
398 	}
399 
400 	@Test
401 	public void testDeleteSymref() throws IOException {
402 		RefUpdate dst = updateRef("refs/heads/abc");
403 		assertEquals(RefUpdate.Result.NEW, dst.update());
404 		ObjectId id = dst.getNewObjectId();
405 
406 		RefUpdate u = db.updateRef("refs/symref");
407 		assertEquals(RefUpdate.Result.NEW, u.link(dst.getName()));
408 
409 		Ref ref = db.exactRef(u.getName());
410 		assertNotNull(ref);
411 		assertTrue(ref.isSymbolic());
412 		assertEquals(dst.getName(), ref.getLeaf().getName());
413 		assertEquals(id, ref.getLeaf().getObjectId());
414 
415 		u = db.updateRef(u.getName());
416 		u.setDetachingSymbolicRef();
417 		u.setForceUpdate(true);
418 		assertEquals(FORCED, u.delete());
419 
420 		assertNull(db.exactRef(u.getName()));
421 		ref = db.exactRef(dst.getName());
422 		assertNotNull(ref);
423 		assertFalse(ref.isSymbolic());
424 		assertEquals(id, ref.getObjectId());
425 	}
426 
427 	@Test
428 	public void writeUnbornHead() throws Exception {
429 		RefUpdate.Result r = db.updateRef("HEAD").link("refs/heads/unborn");
430 		assertEquals(FORCED, r);
431 
432 		Ref head = db.exactRef("HEAD");
433 		assertTrue(head.isSymbolic());
434 		assertEquals(head.getTarget().getName(), "refs/heads/unborn");
435 	}
436 
437 	/**
438 	 * Update the HEAD ref when the referenced branch is unborn
439 	 *
440 	 * @throws Exception
441 	 */
442 	@Test
443 	public void testUpdateRefDetachedUnbornHead() throws Exception {
444 		ObjectId ppid = db.resolve("refs/heads/master^");
445 		writeSymref("HEAD", "refs/heads/unborn");
446 		RefUpdate updateRef = db.updateRef("HEAD", true);
447 		updateRef.setForceUpdate(true);
448 		updateRef.setNewObjectId(ppid);
449 		RefUpdate.Result update = updateRef.update();
450 		assertEquals(RefUpdate.Result.NEW, update);
451 		assertEquals(ppid, db.resolve("HEAD"));
452 		Ref ref = db.exactRef("HEAD");
453 		assertEquals("HEAD", ref.getName());
454 		assertTrue("is detached", !ref.isSymbolic());
455 
456 		// the branch HEAD referred to is left untouched
457 		assertNull(db.resolve("refs/heads/unborn"));
458 		ReflogReader reflogReader = db.getReflogReader("HEAD");
459 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
460 		assertEquals(ObjectId.zeroId(), e.getOldId());
461 		assertEquals(ppid, e.getNewId());
462 		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
463 		assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
464 		assertEquals(1250379778000L, e.getWho().getWhen().getTime());
465 	}
466 
467 	@Test
468 	public void testDeleteNotFound() throws IOException {
469 		RefUpdate ref = updateRef("refs/heads/doesnotexist");
470 		assertNull(db.exactRef(ref.getName()));
471 		assertEquals(RefUpdate.Result.NEW, ref.delete());
472 		assertNull(db.exactRef(ref.getName()));
473 	}
474 
475 	@Test
476 	public void testRenameSymref() throws IOException {
477 		db.resolve("HEAD");
478 		RefRename r = db.renameRef("HEAD", "KOPF");
479 		assertEquals(IO_FAILURE, r.rename());
480 	}
481 
482 	@Test
483 	public void testRenameCurrentBranch() throws IOException {
484 		ObjectId rb = db.resolve("refs/heads/b");
485 		writeSymref(Constants.HEAD, "refs/heads/b");
486 		ObjectId oldHead = db.resolve(Constants.HEAD);
487 		assertEquals("internal test condition, b == HEAD", oldHead, rb);
488 		RefRename renameRef = db.renameRef("refs/heads/b",
489 				"refs/heads/new/name");
490 		RefUpdate.Result result = renameRef.rename();
491 		assertEquals(RefUpdate.Result.RENAMED, result);
492 		assertEquals(rb, db.resolve("refs/heads/new/name"));
493 		assertNull(db.resolve("refs/heads/b"));
494 		assertEquals(rb, db.resolve(Constants.HEAD));
495 
496 		List<String> names = new ArrayList<>();
497 		names.add("HEAD");
498 		names.add("refs/heads/b");
499 		names.add("refs/heads/new/name");
500 
501 		for (String nm : names) {
502 			ReflogReader rd = db.getReflogReader(nm);
503 			assertNotNull(rd);
504 			ReflogEntry last = rd.getLastEntry();
505 			ObjectId id = last.getNewId();
506 			assertTrue(ObjectId.zeroId().equals(id) || rb.equals(id));
507 
508 			id = last.getNewId();
509 			assertTrue(ObjectId.zeroId().equals(id) || rb.equals(id));
510 
511 			String want = "Branch: renamed b to new/name";
512 			assertEquals(want, last.getComment());
513 		}
514 	}
515 
516 	@Test
517 	public void isGitRepository() {
518 		assertTrue(RepositoryCache.FileKey.isGitRepository(db.getDirectory(), db.getFS()));
519 	}
520 
521 	@Test
522 	public void testRenameDestExists() throws IOException {
523 		ObjectId rb = db.resolve("refs/heads/b");
524 		writeSymref(Constants.HEAD, "refs/heads/b");
525 		ObjectId oldHead = db.resolve(Constants.HEAD);
526 		assertEquals("internal test condition, b == HEAD", oldHead, rb);
527 		RefRename renameRef = db.renameRef("refs/heads/b", "refs/heads/a");
528 		RefUpdate.Result result = renameRef.rename();
529 		assertEquals(RefUpdate.Result.LOCK_FAILURE, result);
530 	}
531 
532 	@Test
533 	public void testRenameAtomic() throws IOException {
534 		ObjectId prevId = db.resolve("refs/heads/master^");
535 
536 		RefRename rename = db.renameRef("refs/heads/master",
537 				"refs/heads/newmaster");
538 
539 		RefUpdate updateRef = db.updateRef("refs/heads/master");
540 		updateRef.setNewObjectId(prevId);
541 		updateRef.setForceUpdate(true);
542 		assertEquals(FORCED, updateRef.update());
543 		assertEquals(RefUpdate.Result.LOCK_FAILURE, rename.rename());
544 	}
545 
546 	@Test
547 	public void compactFully() throws Exception {
548 		FileReftableDatabase refDb = (FileReftableDatabase) db.getRefDatabase();
549 		PersonIdent person = new PersonIdent("jane", "jane@invalid");
550 
551 		ObjectId aId  = db.exactRef("refs/heads/a").getObjectId();
552 		ObjectId bId  = db.exactRef("refs/heads/b").getObjectId();
553 
554 		SecureRandom random = new SecureRandom();
555 		List<String> strs = new ArrayList<>();
556 		for (int i = 0; i < 1024; i++) {
557 			strs.add(String.format("%02x",
558 					Integer.valueOf(random.nextInt(256))));
559 		}
560 
561 		String randomStr = String.join("", strs);
562 		String refName = "branch";
563 		for (long i = 0; i < 2; i++) {
564 			RefUpdate ru = refDb.newUpdate(refName, false);
565 			ru.setNewObjectId(i % 2 == 0 ? aId : bId);
566 			ru.setForceUpdate(true);
567 			// Only write a large string in the first table, so it becomes much larger
568 			// than the second, and the result is not autocompacted.
569 			ru.setRefLogMessage(i == 0 ? randomStr : "short", false);
570 			ru.setRefLogIdent(person);
571 
572 			RefUpdate.Result res = ru.update();
573 			assertTrue(res == Result.NEW || res == FORCED);
574 		}
575 
576 		assertEquals(refDb.exactRef(refName).getObjectId(), bId);
577 		assertTrue(randomStr.equals(refDb.getReflogReader(refName).getReverseEntry(1).getComment()));
578 		refDb.compactFully();
579 		assertEquals(refDb.exactRef(refName).getObjectId(), bId);
580 		assertTrue(randomStr.equals(refDb.getReflogReader(refName).getReverseEntry(1).getComment()));
581 	}
582 
583 	@Test
584 	public void reftableRefsStorageClass() throws IOException {
585 		Ref b = db.exactRef("refs/heads/b");
586 		assertEquals(Ref.Storage.PACKED, b.getStorage());
587 	}
588 
589 	@Test
590 	public void testGetRefsExcludingPrefix() throws IOException {
591 		Set<String> prefixes = new HashSet<>();
592 		prefixes.add("refs/tags");
593 		// HEAD + 12 refs/heads are present here.
594 		List<Ref> refs =
595 				db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, prefixes);
596 		assertEquals(13, refs.size());
597 		checkContainsRef(refs, db.exactRef("HEAD"));
598 		checkContainsRef(refs, db.exactRef("refs/heads/a"));
599 		for (Ref notInResult : db.getRefDatabase().getRefsByPrefix("refs/tags")) {
600 			assertFalse(refs.contains(notInResult));
601 		}
602 	}
603 
604 	@Test
605 	public void testGetRefsExcludingPrefixes() throws IOException {
606 		Set<String> exclude = new HashSet<>();
607 		exclude.add("refs/tags/");
608 		exclude.add("refs/heads/");
609 		List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
610 		assertEquals(1, refs.size());
611 		checkContainsRef(refs, db.exactRef("HEAD"));
612 	}
613 
614 	@Test
615 	public void testGetRefsExcludingNonExistingPrefixes() throws IOException {
616 		Set<String> exclude = new HashSet<>();
617 		exclude.add("refs/tags/");
618 		exclude.add("refs/heads/");
619 		exclude.add("refs/nonexistent/");
620 		List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
621 		assertEquals(1, refs.size());
622 		checkContainsRef(refs, db.exactRef("HEAD"));
623 	}
624 
625 	@Test
626 	public void testGetRefsWithPrefixExcludingPrefixes() throws IOException {
627 		Set<String> exclude = new HashSet<>();
628 		exclude.add("refs/heads/pa");
629 		String include = "refs/heads/p";
630 		List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(include, exclude);
631 		assertEquals(1, refs.size());
632 		checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
633 	}
634 
635 	@Test
636 	public void testGetRefsWithPrefixExcludingOverlappingPrefixes() throws IOException {
637 		Set<String> exclude = new HashSet<>();
638 		exclude.add("refs/heads/pa");
639 		exclude.add("refs/heads/");
640 		exclude.add("refs/heads/p");
641 		exclude.add("refs/tags/");
642 		List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
643 		assertEquals(1, refs.size());
644 		checkContainsRef(refs, db.exactRef("HEAD"));
645 	}
646 
647 	private RefUpdate updateRef(String name) throws IOException {
648 		final RefUpdate ref = db.updateRef(name);
649 		ref.setNewObjectId(db.resolve(Constants.HEAD));
650 		return ref;
651 	}
652 
653 	private void writeSymref(String src, String dst) throws IOException {
654 		RefUpdate u = db.updateRef(src);
655 		switch (u.link(dst)) {
656 		case NEW:
657 		case FORCED:
658 		case NO_CHANGE:
659 			break;
660 		default:
661 			fail("link " + src + " to " + dst);
662 		}
663 	}
664 
665 	private static void checkContainsRef(Collection<Ref> haystack, Ref needle) {
666 		for (Ref ref : haystack) {
667 			if (ref.getName().equals(needle.getName()) &&
668 					ref.getObjectId().equals(needle.getObjectId())) {
669 				return;
670 			}
671 		}
672 		fail("list " + haystack + " does not contain ref " + needle);
673 	}
674 }