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