View Javadoc
1   /*
2    * Copyright (C) 2010, 2013, 2016 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.reftree;
12  
13  import static org.eclipse.jgit.lib.Constants.HEAD;
14  import static org.eclipse.jgit.lib.Constants.ORIG_HEAD;
15  import static org.eclipse.jgit.lib.Constants.R_HEADS;
16  import static org.eclipse.jgit.lib.Constants.R_TAGS;
17  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
18  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
19  import static org.eclipse.jgit.lib.RefDatabase.ALL;
20  import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
21  import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
22  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
23  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertFalse;
26  import static org.junit.Assert.assertNotEquals;
27  import static org.junit.Assert.assertNotNull;
28  import static org.junit.Assert.assertNull;
29  import static org.junit.Assert.assertSame;
30  import static org.junit.Assert.assertTrue;
31  import static org.junit.Assert.fail;
32  
33  import java.io.IOException;
34  import java.text.MessageFormat;
35  import java.util.Arrays;
36  import java.util.Collections;
37  import java.util.List;
38  import java.util.Map;
39  
40  import org.eclipse.jgit.internal.JGitText;
41  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
42  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
43  import org.eclipse.jgit.junit.TestRepository;
44  import org.eclipse.jgit.lib.AnyObjectId;
45  import org.eclipse.jgit.lib.BatchRefUpdate;
46  import org.eclipse.jgit.lib.CommitBuilder;
47  import org.eclipse.jgit.lib.NullProgressMonitor;
48  import org.eclipse.jgit.lib.ObjectId;
49  import org.eclipse.jgit.lib.ObjectIdRef;
50  import org.eclipse.jgit.lib.ObjectInserter;
51  import org.eclipse.jgit.lib.ObjectReader;
52  import org.eclipse.jgit.lib.Ref;
53  import org.eclipse.jgit.lib.RefDatabase;
54  import org.eclipse.jgit.lib.RefUpdate;
55  import org.eclipse.jgit.lib.SymbolicRef;
56  import org.eclipse.jgit.revwalk.RevCommit;
57  import org.eclipse.jgit.revwalk.RevTag;
58  import org.eclipse.jgit.revwalk.RevWalk;
59  import org.eclipse.jgit.transport.ReceiveCommand;
60  import org.junit.Before;
61  import org.junit.Test;
62  
63  public class RefTreeDatabaseTest {
64  	private InMemRefTreeRepo repo;
65  	private RefTreeDatabase refdb;
66  	private RefDatabase bootstrap;
67  
68  	private TestRepository<InMemRefTreeRepo> testRepo;
69  	private RevCommit A;
70  	private RevCommit B;
71  	private RevTag v1_0;
72  
73  	@Before
74  	public void setUp() throws Exception {
75  		repo = new InMemRefTreeRepo(new DfsRepositoryDescription("test"));
76  		bootstrap = refdb.getBootstrap();
77  
78  		testRepo = new TestRepository<>(repo);
79  		A = testRepo.commit().create();
80  		B = testRepo.commit(testRepo.getRevWalk().parseCommit(A));
81  		v1_0 = testRepo.tag("v1_0", B);
82  		testRepo.getRevWalk().parseBody(v1_0);
83  	}
84  
85  	@Test
86  	public void testSupportsAtomic() {
87  		assertTrue(refdb.performsAtomicTransactions());
88  	}
89  
90  	@Test
91  	public void testGetRefs_EmptyDatabase() throws IOException {
92  		assertTrue("no references", refdb.getRefs(ALL).isEmpty());
93  		assertTrue("no references", refdb.getRefs(R_HEADS).isEmpty());
94  		assertTrue("no references", refdb.getRefs(R_TAGS).isEmpty());
95  		assertTrue("no references", refdb.getAdditionalRefs().isEmpty());
96  	}
97  
98  	@Test
99  	public void testGetAdditionalRefs() throws IOException {
100 		update("refs/heads/master", A);
101 
102 		List<Ref> addl = refdb.getAdditionalRefs();
103 		assertEquals(1, addl.size());
104 		assertEquals("refs/txn/committed", addl.get(0).getName());
105 		assertEquals(getTxnCommitted(), addl.get(0).getObjectId());
106 	}
107 
108 	@Test
109 	public void testGetRefs_HeadOnOneBranch() throws IOException {
110 		symref(HEAD, "refs/heads/master");
111 		update("refs/heads/master", A);
112 
113 		Map<String, Ref> all = refdb.getRefs(ALL);
114 		assertEquals(2, all.size());
115 		assertTrue("has HEAD", all.containsKey(HEAD));
116 		assertTrue("has master", all.containsKey("refs/heads/master"));
117 
118 		Ref head = all.get(HEAD);
119 		Ref master = all.get("refs/heads/master");
120 
121 		assertEquals(HEAD, head.getName());
122 		assertTrue(head.isSymbolic());
123 		assertSame(LOOSE, head.getStorage());
124 		assertSame("uses same ref as target", master, head.getTarget());
125 
126 		assertEquals("refs/heads/master", master.getName());
127 		assertFalse(master.isSymbolic());
128 		assertSame(PACKED, master.getStorage());
129 		assertEquals(A, master.getObjectId());
130 	}
131 
132 	@Test
133 	public void testGetRefs_DetachedHead() throws IOException {
134 		update(HEAD, A);
135 
136 		Map<String, Ref> all = refdb.getRefs(ALL);
137 		assertEquals(1, all.size());
138 		assertTrue("has HEAD", all.containsKey(HEAD));
139 
140 		Ref head = all.get(HEAD);
141 		assertEquals(HEAD, head.getName());
142 		assertFalse(head.isSymbolic());
143 		assertSame(PACKED, head.getStorage());
144 		assertEquals(A, head.getObjectId());
145 	}
146 
147 	@Test
148 	public void testGetRefs_DeeplyNestedBranch() throws IOException {
149 		String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k";
150 		update(name, A);
151 
152 		Map<String, Ref> all = refdb.getRefs(ALL);
153 		assertEquals(1, all.size());
154 
155 		Ref r = all.get(name);
156 		assertEquals(name, r.getName());
157 		assertFalse(r.isSymbolic());
158 		assertSame(PACKED, r.getStorage());
159 		assertEquals(A, r.getObjectId());
160 	}
161 
162 	@Test
163 	public void testGetRefs_HeadBranchNotBorn() throws IOException {
164 		update("refs/heads/A", A);
165 		update("refs/heads/B", B);
166 
167 		Map<String, Ref> all = refdb.getRefs(ALL);
168 		assertEquals(2, all.size());
169 		assertFalse("no HEAD", all.containsKey(HEAD));
170 
171 		Ref a = all.get("refs/heads/A");
172 		Ref b = all.get("refs/heads/B");
173 
174 		assertEquals(A, a.getObjectId());
175 		assertEquals(B, b.getObjectId());
176 
177 		assertEquals("refs/heads/A", a.getName());
178 		assertEquals("refs/heads/B", b.getName());
179 	}
180 
181 	@Test
182 	public void testGetRefs_HeadsOnly() throws IOException {
183 		update("refs/heads/A", A);
184 		update("refs/heads/B", B);
185 		update("refs/tags/v1.0", v1_0);
186 
187 		Map<String, Ref> heads = refdb.getRefs(R_HEADS);
188 		assertEquals(2, heads.size());
189 
190 		Ref a = heads.get("A");
191 		Ref b = heads.get("B");
192 
193 		assertEquals("refs/heads/A", a.getName());
194 		assertEquals("refs/heads/B", b.getName());
195 
196 		assertEquals(A, a.getObjectId());
197 		assertEquals(B, b.getObjectId());
198 	}
199 
200 	@Test
201 	public void testGetRefs_TagsOnly() throws IOException {
202 		update("refs/heads/A", A);
203 		update("refs/heads/B", B);
204 		update("refs/tags/v1.0", v1_0);
205 
206 		Map<String, Ref> tags = refdb.getRefs(R_TAGS);
207 		assertEquals(1, tags.size());
208 
209 		Ref a = tags.get("v1.0");
210 		assertEquals("refs/tags/v1.0", a.getName());
211 		assertEquals(v1_0, a.getObjectId());
212 		assertTrue(a.isPeeled());
213 		assertEquals(v1_0.getObject(), a.getPeeledObjectId());
214 	}
215 
216 	@Test
217 	public void testGetRefs_HeadsSymref() throws IOException {
218 		symref("refs/heads/other", "refs/heads/master");
219 		update("refs/heads/master", A);
220 
221 		Map<String, Ref> heads = refdb.getRefs(R_HEADS);
222 		assertEquals(2, heads.size());
223 
224 		Ref master = heads.get("master");
225 		Ref other = heads.get("other");
226 
227 		assertEquals("refs/heads/master", master.getName());
228 		assertEquals(A, master.getObjectId());
229 
230 		assertEquals("refs/heads/other", other.getName());
231 		assertEquals(A, other.getObjectId());
232 		assertSame(master, other.getTarget());
233 	}
234 
235 	@Test
236 	public void testGetRefs_InvalidPrefixes() throws IOException {
237 		update("refs/heads/A", A);
238 
239 		assertTrue("empty refs/heads", refdb.getRefs("refs/heads").isEmpty());
240 		assertTrue("empty objects", refdb.getRefs("objects").isEmpty());
241 		assertTrue("empty objects/", refdb.getRefs("objects/").isEmpty());
242 	}
243 
244 	@Test
245 	public void testGetRefs_DiscoversNew() throws IOException {
246 		update("refs/heads/master", A);
247 		Map<String, Ref> orig = refdb.getRefs(ALL);
248 
249 		update("refs/heads/next", B);
250 		Map<String, Ref> next = refdb.getRefs(ALL);
251 
252 		assertEquals(1, orig.size());
253 		assertEquals(2, next.size());
254 
255 		assertFalse(orig.containsKey("refs/heads/next"));
256 		assertTrue(next.containsKey("refs/heads/next"));
257 
258 		assertEquals(A, next.get("refs/heads/master").getObjectId());
259 		assertEquals(B, next.get("refs/heads/next").getObjectId());
260 	}
261 
262 	@Test
263 	public void testGetRefs_DiscoversModified() throws IOException {
264 		symref(HEAD, "refs/heads/master");
265 		update("refs/heads/master", A);
266 
267 		Map<String, Ref> all = refdb.getRefs(ALL);
268 		assertEquals(A, all.get(HEAD).getObjectId());
269 
270 		update("refs/heads/master", B);
271 		all = refdb.getRefs(ALL);
272 		assertEquals(B, all.get(HEAD).getObjectId());
273 		assertEquals(B, refdb.exactRef(HEAD).getObjectId());
274 	}
275 
276 	@Test
277 	public void testGetRefs_CycleInSymbolicRef() throws IOException {
278 		symref("refs/1", "refs/2");
279 		symref("refs/2", "refs/3");
280 		symref("refs/3", "refs/4");
281 		symref("refs/4", "refs/5");
282 		symref("refs/5", "refs/end");
283 		update("refs/end", A);
284 
285 		Map<String, Ref> all = refdb.getRefs(ALL);
286 		Ref r = all.get("refs/1");
287 		assertNotNull("has 1", r);
288 
289 		assertEquals("refs/1", r.getName());
290 		assertEquals(A, r.getObjectId());
291 		assertTrue(r.isSymbolic());
292 
293 		r = r.getTarget();
294 		assertEquals("refs/2", r.getName());
295 		assertEquals(A, r.getObjectId());
296 		assertTrue(r.isSymbolic());
297 
298 		r = r.getTarget();
299 		assertEquals("refs/3", r.getName());
300 		assertEquals(A, r.getObjectId());
301 		assertTrue(r.isSymbolic());
302 
303 		r = r.getTarget();
304 		assertEquals("refs/4", r.getName());
305 		assertEquals(A, r.getObjectId());
306 		assertTrue(r.isSymbolic());
307 
308 		r = r.getTarget();
309 		assertEquals("refs/5", r.getName());
310 		assertEquals(A, r.getObjectId());
311 		assertTrue(r.isSymbolic());
312 
313 		r = r.getTarget();
314 		assertEquals("refs/end", r.getName());
315 		assertEquals(A, r.getObjectId());
316 		assertFalse(r.isSymbolic());
317 
318 		symref("refs/5", "refs/6");
319 		symref("refs/6", "refs/end");
320 		all = refdb.getRefs(ALL);
321 		assertNull("mising 1 due to cycle", all.get("refs/1"));
322 		assertEquals(A, all.get("refs/2").getObjectId());
323 		assertEquals(A, all.get("refs/3").getObjectId());
324 		assertEquals(A, all.get("refs/4").getObjectId());
325 		assertEquals(A, all.get("refs/5").getObjectId());
326 		assertEquals(A, all.get("refs/6").getObjectId());
327 		assertEquals(A, all.get("refs/end").getObjectId());
328 	}
329 
330 	@Test
331 	public void testGetRef_NonExistingBranchConfig() throws IOException {
332 		assertNull("find branch config", refdb.findRef("config"));
333 		assertNull("find branch config", refdb.findRef("refs/heads/config"));
334 	}
335 
336 	@Test
337 	public void testGetRef_FindBranchConfig() throws IOException {
338 		update("refs/heads/config", A);
339 
340 		for (String t : new String[] { "config", "refs/heads/config" }) {
341 			Ref r = refdb.findRef(t);
342 			assertNotNull("find branch config (" + t + ")", r);
343 			assertEquals("for " + t, "refs/heads/config", r.getName());
344 			assertEquals("for " + t, A, r.getObjectId());
345 		}
346 	}
347 
348 	@Test
349 	public void testFirstExactRef() throws IOException {
350 		update("refs/heads/A", A);
351 		update("refs/tags/v1.0", v1_0);
352 
353 		Ref a = refdb.firstExactRef("refs/heads/A", "refs/tags/v1.0");
354 		Ref one = refdb.firstExactRef("refs/tags/v1.0", "refs/heads/A");
355 
356 		assertEquals("refs/heads/A", a.getName());
357 		assertEquals("refs/tags/v1.0", one.getName());
358 
359 		assertEquals(A, a.getObjectId());
360 		assertEquals(v1_0, one.getObjectId());
361 	}
362 
363 	@Test
364 	public void testExactRef_DiscoversModified() throws IOException {
365 		symref(HEAD, "refs/heads/master");
366 		update("refs/heads/master", A);
367 		assertEquals(A, refdb.exactRef(HEAD).getObjectId());
368 
369 		update("refs/heads/master", B);
370 		assertEquals(B, refdb.exactRef(HEAD).getObjectId());
371 	}
372 
373 	@Test
374 	public void testIsNameConflicting() throws IOException {
375 		update("refs/heads/a/b", A);
376 		update("refs/heads/q", B);
377 
378 		// new references cannot replace an existing container
379 		assertTrue(refdb.isNameConflicting("refs"));
380 		assertTrue(refdb.isNameConflicting("refs/heads"));
381 		assertTrue(refdb.isNameConflicting("refs/heads/a"));
382 
383 		// existing reference is not conflicting
384 		assertFalse(refdb.isNameConflicting("refs/heads/a/b"));
385 
386 		// new references are not conflicting
387 		assertFalse(refdb.isNameConflicting("refs/heads/a/d"));
388 		assertFalse(refdb.isNameConflicting("refs/heads/master"));
389 
390 		// existing reference must not be used as a container
391 		assertTrue(refdb.isNameConflicting("refs/heads/a/b/c"));
392 		assertTrue(refdb.isNameConflicting("refs/heads/q/master"));
393 
394 		// refs/txn/ names always conflict.
395 		assertTrue(refdb.isNameConflicting(refdb.getTxnCommitted()));
396 		assertTrue(refdb.isNameConflicting("refs/txn/foo"));
397 	}
398 
399 	@Test
400 	public void testUpdate_RefusesRefsTxnNamespace() throws IOException {
401 		ObjectId txnId = getTxnCommitted();
402 
403 		RefUpdate u = refdb.newUpdate("refs/txn/tmp", false);
404 		u.setNewObjectId(B);
405 		assertEquals(RefUpdate.Result.LOCK_FAILURE, u.update());
406 		assertEquals(txnId, getTxnCommitted());
407 
408 		ReceiveCommand cmd = command(null, B, "refs/txn/tmp");
409 		BatchRefUpdate batch = refdb.newBatchUpdate();
410 		batch.addCommand(cmd);
411 		try (RevWalk rw = new RevWalk(repo)) {
412 			batch.execute(rw, NullProgressMonitor.INSTANCE);
413 		}
414 		assertEquals(REJECTED_OTHER_REASON, cmd.getResult());
415 		assertEquals(MessageFormat.format(JGitText.get().invalidRefName,
416 				"refs/txn/tmp"), cmd.getMessage());
417 		assertEquals(txnId, getTxnCommitted());
418 	}
419 
420 	@Test
421 	public void testUpdate_RefusesDotLockInRefName() throws IOException {
422 		ObjectId txnId = getTxnCommitted();
423 
424 		RefUpdate u = refdb.newUpdate("refs/heads/pu.lock", false);
425 		u.setNewObjectId(B);
426 		assertEquals(RefUpdate.Result.REJECTED, u.update());
427 		assertEquals(txnId, getTxnCommitted());
428 
429 		ReceiveCommand cmd = command(null, B, "refs/heads/pu.lock");
430 		BatchRefUpdate batch = refdb.newBatchUpdate();
431 		batch.addCommand(cmd);
432 		try (RevWalk rw = new RevWalk(repo)) {
433 			batch.execute(rw, NullProgressMonitor.INSTANCE);
434 		}
435 		assertEquals(REJECTED_OTHER_REASON, cmd.getResult());
436 		assertEquals(JGitText.get().funnyRefname, cmd.getMessage());
437 		assertEquals(txnId, getTxnCommitted());
438 	}
439 
440 	@Test
441 	public void testUpdate_RefusesOrigHeadOnBare() throws IOException {
442 		assertTrue(refdb.getRepository().isBare());
443 		ObjectId txnId = getTxnCommitted();
444 
445 		RefUpdate orig = refdb.newUpdate(ORIG_HEAD, true);
446 		orig.setNewObjectId(B);
447 		assertEquals(RefUpdate.Result.LOCK_FAILURE, orig.update());
448 		assertEquals(txnId, getTxnCommitted());
449 
450 		ReceiveCommand cmd = command(null, B, ORIG_HEAD);
451 		BatchRefUpdate batch = refdb.newBatchUpdate();
452 		batch.addCommand(cmd);
453 		try (RevWalk rw = new RevWalk(repo)) {
454 			batch.execute(rw, NullProgressMonitor.INSTANCE);
455 		}
456 		assertEquals(REJECTED_OTHER_REASON, cmd.getResult());
457 		assertEquals(
458 				MessageFormat.format(JGitText.get().invalidRefName, ORIG_HEAD),
459 				cmd.getMessage());
460 		assertEquals(txnId, getTxnCommitted());
461 	}
462 
463 	@Test
464 	public void testBatchRefUpdate_NonFastForwardAborts() throws IOException {
465 		update("refs/heads/master", A);
466 		update("refs/heads/masters", B);
467 		ObjectId txnId = getTxnCommitted();
468 
469 		List<ReceiveCommand> commands = Arrays.asList(
470 				command(A, B, "refs/heads/master"),
471 				command(B, A, "refs/heads/masters"));
472 		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
473 		batchUpdate.addCommand(commands);
474 		try (RevWalk rw = new RevWalk(repo)) {
475 			batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
476 		}
477 		assertEquals(txnId, getTxnCommitted());
478 
479 		assertEquals(REJECTED_NONFASTFORWARD,
480 				commands.get(1).getResult());
481 		assertEquals(REJECTED_OTHER_REASON,
482 				commands.get(0).getResult());
483 		assertEquals(JGitText.get().transactionAborted,
484 				commands.get(0).getMessage());
485 	}
486 
487 	@Test
488 	public void testBatchRefUpdate_ForceUpdate() throws IOException {
489 		update("refs/heads/master", A);
490 		update("refs/heads/masters", B);
491 		ObjectId txnId = getTxnCommitted();
492 
493 		List<ReceiveCommand> commands = Arrays.asList(
494 				command(A, B, "refs/heads/master"),
495 				command(B, A, "refs/heads/masters"));
496 		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
497 		batchUpdate.setAllowNonFastForwards(true);
498 		batchUpdate.addCommand(commands);
499 		try (RevWalk rw = new RevWalk(repo)) {
500 			batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
501 		}
502 		assertNotEquals(txnId, getTxnCommitted());
503 
504 		Map<String, Ref> refs = refdb.getRefs(ALL);
505 		assertEquals(OK, commands.get(0).getResult());
506 		assertEquals(OK, commands.get(1).getResult());
507 		assertEquals(
508 				"[refs/heads/master, refs/heads/masters]",
509 				refs.keySet().toString());
510 		assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
511 		assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId());
512 	}
513 
514 	@Test
515 	public void testBatchRefUpdate_NonFastForwardDoesNotDoExpensiveMergeCheck()
516 			throws IOException {
517 		update("refs/heads/master", B);
518 		ObjectId txnId = getTxnCommitted();
519 
520 		List<ReceiveCommand> commands = Arrays.asList(
521 				command(B, A, "refs/heads/master"));
522 		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
523 		batchUpdate.setAllowNonFastForwards(true);
524 		batchUpdate.addCommand(commands);
525 		try (RevWalk rw = new RevWalk(repo) {
526 			@Override
527 			public boolean isMergedInto(RevCommit base, RevCommit tip) {
528 				fail("isMergedInto() should not be called");
529 				return false;
530 			}
531 		}) {
532 			batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
533 		}
534 		assertNotEquals(txnId, getTxnCommitted());
535 
536 		Map<String, Ref> refs = refdb.getRefs(ALL);
537 		assertEquals(OK, commands.get(0).getResult());
538 		assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId());
539 	}
540 
541 	@Test
542 	public void testBatchRefUpdate_ConflictCausesAbort() throws IOException {
543 		update("refs/heads/master", A);
544 		update("refs/heads/masters", B);
545 		ObjectId txnId = getTxnCommitted();
546 
547 		List<ReceiveCommand> commands = Arrays.asList(
548 				command(A, B, "refs/heads/master"),
549 				command(null, A, "refs/heads/master/x"),
550 				command(null, A, "refs/heads"));
551 		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
552 		batchUpdate.setAllowNonFastForwards(true);
553 		batchUpdate.addCommand(commands);
554 		try (RevWalk rw = new RevWalk(repo)) {
555 			batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
556 		}
557 		assertEquals(txnId, getTxnCommitted());
558 
559 		assertEquals(LOCK_FAILURE, commands.get(0).getResult());
560 
561 		assertEquals(REJECTED_OTHER_REASON, commands.get(1).getResult());
562 		assertEquals(JGitText.get().transactionAborted,
563 				commands.get(1).getMessage());
564 
565 		assertEquals(REJECTED_OTHER_REASON, commands.get(2).getResult());
566 		assertEquals(JGitText.get().transactionAborted,
567 				commands.get(2).getMessage());
568 	}
569 
570 	@Test
571 	public void testBatchRefUpdate_NoConflictIfDeleted() throws IOException {
572 		update("refs/heads/master", A);
573 		update("refs/heads/masters", B);
574 		ObjectId txnId = getTxnCommitted();
575 
576 		List<ReceiveCommand> commands = Arrays.asList(
577 				command(A, B, "refs/heads/master"),
578 				command(null, A, "refs/heads/masters/x"),
579 				command(B, null, "refs/heads/masters"));
580 		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
581 		batchUpdate.setAllowNonFastForwards(true);
582 		batchUpdate.addCommand(commands);
583 		try (RevWalk rw = new RevWalk(repo)) {
584 			batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
585 		}
586 		assertNotEquals(txnId, getTxnCommitted());
587 
588 		assertEquals(OK, commands.get(0).getResult());
589 		assertEquals(OK, commands.get(1).getResult());
590 		assertEquals(OK, commands.get(2).getResult());
591 
592 		Map<String, Ref> refs = refdb.getRefs(ALL);
593 		assertEquals(
594 				"[refs/heads/master, refs/heads/masters/x]",
595 				refs.keySet().toString());
596 		assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId());
597 	}
598 
599 	private ObjectId getTxnCommitted() throws IOException {
600 		Ref r = bootstrap.exactRef(refdb.getTxnCommitted());
601 		if (r != null && r.getObjectId() != null) {
602 			return r.getObjectId();
603 		}
604 		return ObjectId.zeroId();
605 	}
606 
607 	private static ReceiveCommand command(AnyObjectId a, AnyObjectId b,
608 			String name) {
609 		return new ReceiveCommand(
610 				a != null ? a.copy() : ObjectId.zeroId(),
611 				b != null ? b.copy() : ObjectId.zeroId(),
612 				name);
613 	}
614 
615 	private void symref(String name, String dst)
616 			throws IOException {
617 		commit((ObjectReader reader, RefTree tree) -> {
618 			Ref old = tree.exactRef(reader, name);
619 			Command n = new Command(old, new SymbolicRef(name,
620 					new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null)));
621 			return tree.apply(Collections.singleton(n));
622 		});
623 	}
624 
625 	private void update(String name, ObjectId id)
626 			throws IOException {
627 		commit((ObjectReader reader, RefTree tree) -> {
628 			Ref old = tree.exactRef(reader, name);
629 			Command n;
630 			try (RevWalk rw = new RevWalk(repo)) {
631 				n = new Command(old, Command.toRef(rw, id, null, name, true));
632 			}
633 			return tree.apply(Collections.singleton(n));
634 		});
635 	}
636 
637 	interface Function {
638 		boolean apply(ObjectReader reader, RefTree tree) throws IOException;
639 	}
640 
641 	private void commit(Function fun) throws IOException {
642 		try (ObjectReader reader = repo.newObjectReader();
643 				ObjectInserter inserter = repo.newObjectInserter();
644 				RevWalk rw = new RevWalk(reader)) {
645 			RefUpdate u = bootstrap.newUpdate(refdb.getTxnCommitted(), false);
646 			CommitBuilder cb = new CommitBuilder();
647 			testRepo.setAuthorAndCommitter(cb);
648 
649 			Ref ref = bootstrap.exactRef(refdb.getTxnCommitted());
650 			RefTree tree;
651 			if (ref != null && ref.getObjectId() != null) {
652 				tree = RefTree.read(reader, rw.parseTree(ref.getObjectId()));
653 				cb.setParentId(ref.getObjectId());
654 				u.setExpectedOldObjectId(ref.getObjectId());
655 			} else {
656 				tree = RefTree.newEmptyTree();
657 				u.setExpectedOldObjectId(ObjectId.zeroId());
658 			}
659 
660 			assertTrue(fun.apply(reader, tree));
661 			cb.setTreeId(tree.writeTree(inserter));
662 			u.setNewObjectId(inserter.insert(cb));
663 			inserter.flush();
664 			switch (u.update(rw)) {
665 			case NEW:
666 			case FAST_FORWARD:
667 				break;
668 			default:
669 				fail("Expected " + u.getName() + " to update");
670 			}
671 		}
672 	}
673 
674 	private class InMemRefTreeRepo extends InMemoryRepository {
675 		private final RefTreeDatabase refs;
676 
677 		InMemRefTreeRepo(DfsRepositoryDescription repoDesc) {
678 			super(repoDesc);
679 			refs = new RefTreeDatabase(this, super.getRefDatabase(),
680 					"refs/txn/committed");
681 			RefTreeDatabaseTest.this.refdb = refs;
682 		}
683 
684 		@Override
685 		public RefDatabase getRefDatabase() {
686 			return refs;
687 		}
688 	}
689 }