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