View Javadoc
1   /*
2    * Copyright (C) 2017 Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static java.util.concurrent.TimeUnit.NANOSECONDS;
15  import static java.util.concurrent.TimeUnit.SECONDS;
16  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
17  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK;
18  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT;
19  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD;
20  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED;
21  import static org.eclipse.jgit.lib.ObjectId.zeroId;
22  import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
23  import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
24  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
25  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
26  import static org.junit.Assert.assertEquals;
27  import static org.junit.Assert.assertFalse;
28  import static org.junit.Assert.assertNotNull;
29  import static org.junit.Assert.assertNull;
30  import static org.junit.Assert.assertTrue;
31  import static org.junit.Assume.assumeFalse;
32  import static org.junit.Assume.assumeTrue;
33  
34  import java.io.File;
35  import java.io.IOException;
36  import java.nio.file.Files;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.Collections;
40  import java.util.LinkedHashMap;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.concurrent.locks.ReentrantLock;
44  import java.util.function.Predicate;
45  
46  import org.eclipse.jgit.events.ListenerHandle;
47  import org.eclipse.jgit.events.RefsChangedListener;
48  import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
49  import org.eclipse.jgit.junit.StrictWorkMonitor;
50  import org.eclipse.jgit.junit.TestRepository;
51  import org.eclipse.jgit.lib.AnyObjectId;
52  import org.eclipse.jgit.lib.BatchRefUpdate;
53  import org.eclipse.jgit.lib.CheckoutEntry;
54  import org.eclipse.jgit.lib.ConfigConstants;
55  import org.eclipse.jgit.lib.Constants;
56  import org.eclipse.jgit.lib.NullProgressMonitor;
57  import org.eclipse.jgit.lib.ObjectId;
58  import org.eclipse.jgit.lib.PersonIdent;
59  import org.eclipse.jgit.lib.Ref;
60  import org.eclipse.jgit.lib.RefDatabase;
61  import org.eclipse.jgit.lib.RefUpdate;
62  import org.eclipse.jgit.lib.ReflogEntry;
63  import org.eclipse.jgit.lib.ReflogReader;
64  import org.eclipse.jgit.lib.Repository;
65  import org.eclipse.jgit.lib.StoredConfig;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.revwalk.RevWalk;
68  import org.eclipse.jgit.transport.ReceiveCommand;
69  import org.junit.After;
70  import org.junit.Before;
71  import org.junit.Test;
72  import org.junit.runner.RunWith;
73  import org.junit.runners.Parameterized;
74  import org.junit.runners.Parameterized.Parameter;
75  import org.junit.runners.Parameterized.Parameters;
76  
77  @SuppressWarnings("boxing")
78  @RunWith(Parameterized.class)
79  public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
80  	@Parameter(0)
81  	public boolean atomic;
82  
83  	@Parameter(1)
84  	public boolean useReftable;
85  
86  	@Parameters(name = "atomic={0} reftable={1}")
87  	public static Collection<Object[]> data() {
88  		return Arrays.asList(new Object[][] { { Boolean.FALSE, Boolean.FALSE },
89  				{ Boolean.TRUE, Boolean.FALSE },
90  				{ Boolean.FALSE, Boolean.TRUE },
91  				{ Boolean.TRUE, Boolean.TRUE }, });
92  	}
93  
94  	private Repository diskRepo;
95  
96  	private TestRepository<Repository> repo;
97  
98  	private RefDirectory refdir;
99  
100 	private RevCommit A;
101 
102 	private RevCommit B; // B descends from A.
103 
104 	/**
105 	 * When asserting the number of RefsChangedEvents you must account for one
106 	 * additional event due to the initial ref setup via a number of calls to
107 	 * {@link #writeLooseRef(String, AnyObjectId)} (will be fired in execute()
108 	 * when it is detected that the on-disk loose refs have changed), or for one
109 	 * additional event per {@link #writeRef(String, AnyObjectId)}.
110 	 */
111 	private int refsChangedEvents;
112 
113 	private ListenerHandle handle;
114 
115 	private RefsChangedListener refsChangedListener = event -> {
116 		refsChangedEvents++;
117 	};
118 
119 	@Override
120 	@Before
121 	public void setUp() throws Exception {
122 		super.setUp();
123 
124 		FileRepository fileRepo = createBareRepository();
125 		if (useReftable) {
126 			fileRepo.convertToReftable(false, false);
127 		}
128 
129 		diskRepo = fileRepo;
130 		setLogAllRefUpdates(true);
131 
132 		if (!useReftable) {
133 			refdir = (RefDirectory) diskRepo.getRefDatabase();
134 			refdir.setRetrySleepMs(Arrays.asList(0, 0));
135 		}
136 
137 		repo = new TestRepository<>(diskRepo);
138 		A = repo.commit().create();
139 		B = repo.commit(repo.getRevWalk().parseCommit(A));
140 		refsChangedEvents = 0;
141 		handle = diskRepo.getListenerList()
142 				.addRefsChangedListener(refsChangedListener);
143 	}
144 
145 	@After
146 	public void removeListener() {
147 		handle.remove();
148 		refsChangedEvents = 0;
149 	}
150 
151 	@Test
152 	public void packedRefsFileIsSorted() throws IOException {
153 		assumeTrue(atomic);
154 		assumeFalse(useReftable);
155 
156 		for (int i = 0; i < 2; i++) {
157 			BatchRefUpdate bu = diskRepo.getRefDatabase().newBatchUpdate();
158 			String b1 = String.format("refs/heads/a%d", i);
159 			String b2 = String.format("refs/heads/b%d", i);
160 			bu.setAtomic(atomic);
161 			ReceiveCommand c1 = new ReceiveCommand(ObjectId.zeroId(), A, b1);
162 			ReceiveCommand c2 = new ReceiveCommand(ObjectId.zeroId(), B, b2);
163 			bu.addCommand(c1, c2);
164 			try (RevWalk rw = new RevWalk(diskRepo)) {
165 				bu.execute(rw, NullProgressMonitor.INSTANCE);
166 			}
167 			assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
168 			assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
169 		}
170 
171 		File packed = new File(diskRepo.getDirectory(), "packed-refs");
172 		String packedStr = new String(Files.readAllBytes(packed.toPath()),
173 				UTF_8);
174 
175 		int a2 = packedStr.indexOf("refs/heads/a1");
176 		int b1 = packedStr.indexOf("refs/heads/b0");
177 		assertTrue(a2 < b1);
178 	}
179 
180 	@Test
181 	public void simpleNoForce() throws IOException {
182 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
183 
184 		List<ReceiveCommand> cmds = Arrays.asList(
185 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
186 				new ReceiveCommand(B, A, "refs/heads/masters",
187 						UPDATE_NONFASTFORWARD));
188 		execute(newBatchUpdate(cmds));
189 
190 		if (atomic) {
191 			assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD);
192 			assertRefs("refs/heads/master", A, "refs/heads/masters", B);
193 		} else {
194 			assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
195 			assertRefs("refs/heads/master", B, "refs/heads/masters", B);
196 		}
197 	}
198 
199 	@Test
200 	public void simpleNoForceRefsChangedEvents() throws IOException {
201 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
202 		int initialRefsChangedEvents = refsChangedEvents;
203 
204 		List<ReceiveCommand> cmds = Arrays.asList(
205 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
206 				new ReceiveCommand(B, A, "refs/heads/masters",
207 						UPDATE_NONFASTFORWARD));
208 		execute(newBatchUpdate(cmds));
209 
210 		assertEquals(atomic ? initialRefsChangedEvents
211 				: initialRefsChangedEvents + 1, refsChangedEvents);
212 	}
213 
214 	@Test
215 	public void simpleForce() throws IOException {
216 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
217 
218 		List<ReceiveCommand> cmds = Arrays.asList(
219 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
220 				new ReceiveCommand(B, A, "refs/heads/masters",
221 						UPDATE_NONFASTFORWARD));
222 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
223 
224 		assertResults(cmds, OK, OK);
225 		assertRefs("refs/heads/master", B, "refs/heads/masters", A);
226 	}
227 
228 	@Test
229 	public void simpleForceRefsChangedEvents() throws IOException {
230 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
231 		int initialRefsChangedEvents = refsChangedEvents;
232 
233 		List<ReceiveCommand> cmds = Arrays.asList(
234 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
235 				new ReceiveCommand(B, A, "refs/heads/masters",
236 						UPDATE_NONFASTFORWARD));
237 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
238 
239 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
240 				: initialRefsChangedEvents + 2, refsChangedEvents);
241 	}
242 
243 	@Test
244 	public void nonFastForwardDoesNotDoExpensiveMergeCheck()
245 			throws IOException {
246 		writeLooseRef("refs/heads/master", B);
247 
248 		List<ReceiveCommand> cmds = Arrays.asList(new ReceiveCommand(B, A,
249 				"refs/heads/master", UPDATE_NONFASTFORWARD));
250 		try (RevWalk rw = new RevWalk(diskRepo) {
251 			@Override
252 			public boolean isMergedInto(RevCommit base, RevCommit tip) {
253 				throw new AssertionError("isMergedInto() should not be called");
254 			}
255 		}) {
256 			newBatchUpdate(cmds).setAllowNonFastForwards(true).execute(rw,
257 					new StrictWorkMonitor());
258 		}
259 
260 		assertResults(cmds, OK);
261 		assertRefs("refs/heads/master", A);
262 	}
263 
264 	@Test
265 	public void nonFastForwardDoesNotDoExpensiveMergeCheckRefsChangedEvents()
266 			throws IOException {
267 		writeLooseRef("refs/heads/master", B);
268 		int initialRefsChangedEvents = refsChangedEvents;
269 
270 		List<ReceiveCommand> cmds = Arrays.asList(new ReceiveCommand(B, A,
271 				"refs/heads/master", UPDATE_NONFASTFORWARD));
272 		try (RevWalk rw = new RevWalk(diskRepo) {
273 			@Override
274 			public boolean isMergedInto(RevCommit base, RevCommit tip) {
275 				throw new AssertionError("isMergedInto() should not be called");
276 			}
277 		}) {
278 			newBatchUpdate(cmds).setAllowNonFastForwards(true).execute(rw,
279 					new StrictWorkMonitor());
280 		}
281 
282 		assertEquals(initialRefsChangedEvents + 1, refsChangedEvents);
283 	}
284 
285 	@Test
286 	public void fileDirectoryConflict() throws IOException {
287 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
288 
289 		List<ReceiveCommand> cmds = Arrays.asList(
290 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
291 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
292 				new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
293 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
294 
295 		if (atomic) {
296 			// Atomic update sees that master and master/x are conflicting, then
297 			// marks the first one in the list as LOCK_FAILURE and aborts the rest.
298 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED,
299 					TRANSACTION_ABORTED);
300 			assertRefs("refs/heads/master", A, "refs/heads/masters", B);
301 		} else {
302 			// Non-atomic updates are applied in order: master succeeds, then
303 			// master/x fails due to conflict.
304 			assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
305 			assertRefs("refs/heads/master", B, "refs/heads/masters", B);
306 		}
307 	}
308 
309 	@Test
310 	public void fileDirectoryConflictRefsChangedEvents() throws IOException {
311 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
312 		int initialRefsChangedEvents = refsChangedEvents;
313 
314 		List<ReceiveCommand> cmds = Arrays.asList(
315 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
316 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
317 				new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
318 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
319 
320 		assertEquals(atomic ? initialRefsChangedEvents
321 				: initialRefsChangedEvents + 1, refsChangedEvents);
322 	}
323 
324 	@Test
325 	public void conflictThanksToDelete() throws IOException {
326 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
327 
328 		List<ReceiveCommand> cmds = Arrays.asList(
329 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
330 				new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
331 				new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
332 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
333 
334 		assertResults(cmds, OK, OK, OK);
335 		assertRefs("refs/heads/master", B, "refs/heads/masters/x", A);
336 	}
337 
338 	@Test
339 	public void conflictThanksToDeleteRefsChangedEvents() throws IOException {
340 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
341 		int initialRefsChangedEvents = refsChangedEvents;
342 
343 		List<ReceiveCommand> cmds = Arrays.asList(
344 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
345 				new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
346 				new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
347 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
348 
349 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
350 				: initialRefsChangedEvents + 3, refsChangedEvents);
351 	}
352 
353 	@Test
354 	public void updateToMissingObject() throws IOException {
355 		writeLooseRef("refs/heads/master", A);
356 
357 		ObjectId bad = ObjectId
358 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
359 		List<ReceiveCommand> cmds = Arrays.asList(
360 				new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
361 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
362 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
363 
364 		if (atomic) {
365 			assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
366 			assertRefs("refs/heads/master", A);
367 		} else {
368 			assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
369 			assertRefs("refs/heads/master", A, "refs/heads/foo2", B);
370 		}
371 	}
372 
373 	@Test
374 	public void updateToMissingObjectRefsChangedEvents() throws IOException {
375 		writeLooseRef("refs/heads/master", A);
376 		int initialRefsChangedEvents = refsChangedEvents;
377 
378 		ObjectId bad = ObjectId
379 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
380 		List<ReceiveCommand> cmds = Arrays.asList(
381 				new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
382 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
383 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
384 
385 		assertEquals(atomic ? initialRefsChangedEvents
386 				: initialRefsChangedEvents + 1, refsChangedEvents);
387 	}
388 
389 	@Test
390 	public void addMissingObject() throws IOException {
391 		writeLooseRef("refs/heads/master", A);
392 
393 		ObjectId bad = ObjectId
394 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
395 		List<ReceiveCommand> cmds = Arrays.asList(
396 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
397 				new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
398 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
399 
400 		if (atomic) {
401 			assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT);
402 			assertRefs("refs/heads/master", A);
403 		} else {
404 			assertResults(cmds, OK, REJECTED_MISSING_OBJECT);
405 			assertRefs("refs/heads/master", B);
406 		}
407 	}
408 
409 	@Test
410 	public void addMissingObjectRefsChangedEvents() throws IOException {
411 		writeLooseRef("refs/heads/master", A);
412 		int initialRefsChangedEvents = refsChangedEvents;
413 
414 		ObjectId bad = ObjectId
415 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
416 		List<ReceiveCommand> cmds = Arrays.asList(
417 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
418 				new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
419 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
420 
421 		assertEquals(atomic ? initialRefsChangedEvents
422 				: initialRefsChangedEvents + 1, refsChangedEvents);
423 	}
424 
425 	@Test
426 	public void oneNonExistentRef() throws IOException {
427 		List<ReceiveCommand> cmds = Arrays.asList(
428 				new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE),
429 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
430 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
431 
432 		if (atomic) {
433 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
434 			assertRefs();
435 			assertEquals(0, refsChangedEvents);
436 		} else {
437 			assertResults(cmds, LOCK_FAILURE, OK);
438 			assertRefs("refs/heads/foo2", B);
439 			assertEquals(1, refsChangedEvents);
440 		}
441 	}
442 
443 	@Test
444 	public void oneRefWrongOldValue() throws IOException {
445 		writeLooseRef("refs/heads/master", A);
446 
447 		List<ReceiveCommand> cmds = Arrays.asList(
448 				new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
449 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
450 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
451 
452 		if (atomic) {
453 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
454 			assertRefs("refs/heads/master", A);
455 		} else {
456 			assertResults(cmds, LOCK_FAILURE, OK);
457 			assertRefs("refs/heads/master", A, "refs/heads/foo2", B);
458 		}
459 	}
460 
461 	@Test
462 	public void oneRefWrongOldValueRefsChangedEvents() throws IOException {
463 		writeLooseRef("refs/heads/master", A);
464 		int initialRefsChangedEvents = refsChangedEvents;
465 
466 		List<ReceiveCommand> cmds = Arrays.asList(
467 				new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
468 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
469 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
470 
471 		assertEquals(atomic ? initialRefsChangedEvents
472 				: initialRefsChangedEvents + 1, refsChangedEvents);
473 	}
474 
475 	@Test
476 	public void nonExistentRef() throws IOException {
477 		writeLooseRef("refs/heads/master", A);
478 
479 		List<ReceiveCommand> cmds = Arrays.asList(
480 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
481 				new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
482 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
483 
484 		if (atomic) {
485 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
486 			assertRefs("refs/heads/master", A);
487 		} else {
488 			assertResults(cmds, OK, LOCK_FAILURE);
489 			assertRefs("refs/heads/master", B);
490 		}
491 	}
492 
493 	@Test
494 	public void nonExistentRefRefsChangedEvents() throws IOException {
495 		writeLooseRef("refs/heads/master", A);
496 
497 		int initialRefsChangedEvents = refsChangedEvents;
498 
499 		List<ReceiveCommand> cmds = Arrays.asList(
500 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
501 				new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
502 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
503 
504 		assertEquals(atomic ? initialRefsChangedEvents
505 				: initialRefsChangedEvents + 1, refsChangedEvents);
506 	}
507 
508 	@Test
509 	public void noRefLog() throws IOException {
510 		writeRef("refs/heads/master", A);
511 		int initialRefsChangedEvents = refsChangedEvents;
512 
513 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
514 				"refs/heads/branch");
515 		assertEquals(Collections.singleton("refs/heads/master"),
516 				oldLogs.keySet());
517 
518 		List<ReceiveCommand> cmds = Arrays.asList(
519 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
520 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
521 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
522 
523 		assertResults(cmds, OK, OK);
524 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
525 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
526 				: initialRefsChangedEvents + 2, refsChangedEvents);
527 		assertReflogUnchanged(oldLogs, "refs/heads/master");
528 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
529 	}
530 
531 	@Test
532 	public void reflogDefaultIdent() throws IOException {
533 		writeRef("refs/heads/master", A);
534 		writeRef("refs/heads/branch2", A);
535 		int initialRefsChangedEvents = refsChangedEvents;
536 
537 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
538 				"refs/heads/branch1", "refs/heads/branch2");
539 		List<ReceiveCommand> cmds = Arrays.asList(
540 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
541 				new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
542 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)
543 				.setRefLogMessage("a reflog", false));
544 
545 		assertResults(cmds, OK, OK);
546 		assertRefs("refs/heads/master", B, "refs/heads/branch1", B,
547 				"refs/heads/branch2", A);
548 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
549 				: initialRefsChangedEvents + 2, refsChangedEvents);
550 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
551 				getLastReflog("refs/heads/master"));
552 		assertReflogEquals(
553 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
554 				getLastReflog("refs/heads/branch1"));
555 		assertReflogUnchanged(oldLogs, "refs/heads/branch2");
556 	}
557 
558 	@Test
559 	public void reflogAppendStatusNoMessage() throws IOException {
560 		writeRef("refs/heads/master", A);
561 		writeRef("refs/heads/branch1", B);
562 		int initialRefsChangedEvents = refsChangedEvents;
563 
564 		List<ReceiveCommand> cmds = Arrays.asList(
565 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
566 				new ReceiveCommand(B, A, "refs/heads/branch1",
567 						UPDATE_NONFASTFORWARD),
568 				new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
569 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)
570 				.setRefLogMessage(null, true));
571 
572 		assertResults(cmds, OK, OK, OK);
573 		assertRefs("refs/heads/master", B, "refs/heads/branch1", A,
574 				"refs/heads/branch2", A);
575 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
576 				: initialRefsChangedEvents + 3, refsChangedEvents);
577 		assertReflogEquals(
578 				// Always forced; setAllowNonFastForwards(true) bypasses the
579 				// check.
580 				reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
581 				getLastReflog("refs/heads/master"));
582 		assertReflogEquals(
583 				reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
584 				getLastReflog("refs/heads/branch1"));
585 		assertReflogEquals(
586 				reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
587 				getLastReflog("refs/heads/branch2"));
588 	}
589 
590 	@Test
591 	public void reflogAppendStatusFastForward() throws IOException {
592 		writeRef("refs/heads/master", A);
593 		int initialRefsChangedEvents = refsChangedEvents;
594 
595 		List<ReceiveCommand> cmds = Arrays
596 				.asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
597 		execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
598 
599 		assertResults(cmds, OK);
600 		assertRefs("refs/heads/master", B);
601 		assertEquals(initialRefsChangedEvents + 1, refsChangedEvents);
602 		assertReflogEquals(
603 				reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
604 				getLastReflog("refs/heads/master"));
605 	}
606 
607 	@Test
608 	public void reflogAppendStatusWithMessage() throws IOException {
609 		writeRef("refs/heads/master", A);
610 		int initialRefsChangedEvents = refsChangedEvents;
611 
612 		List<ReceiveCommand> cmds = Arrays.asList(
613 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
614 				new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
615 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
616 
617 		assertResults(cmds, OK, OK);
618 		assertRefs("refs/heads/master", B, "refs/heads/branch", A);
619 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
620 				: initialRefsChangedEvents + 2, refsChangedEvents);
621 		assertReflogEquals(
622 				reflog(A, B, new PersonIdent(diskRepo),
623 						"a reflog: fast-forward"),
624 				getLastReflog("refs/heads/master"));
625 		assertReflogEquals(
626 				reflog(zeroId(), A, new PersonIdent(diskRepo),
627 						"a reflog: created"),
628 				getLastReflog("refs/heads/branch"));
629 	}
630 
631 	@Test
632 	public void reflogCustomIdent() throws IOException {
633 		writeRef("refs/heads/master", A);
634 		int initialRefsChangedEvents = refsChangedEvents;
635 
636 		List<ReceiveCommand> cmds = Arrays.asList(
637 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
638 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
639 		PersonIdent ident = new PersonIdent("A Reflog User",
640 				"reflog@example.com");
641 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)
642 				.setRefLogIdent(ident));
643 
644 		assertResults(cmds, OK, OK);
645 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
646 				: initialRefsChangedEvents + 2, refsChangedEvents);
647 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
648 		assertReflogEquals(reflog(A, B, ident, "a reflog"),
649 				getLastReflog("refs/heads/master"), true);
650 		assertReflogEquals(reflog(zeroId(), B, ident, "a reflog"),
651 				getLastReflog("refs/heads/branch"), true);
652 	}
653 
654 	@Test
655 	public void reflogDelete() throws IOException {
656 		writeRef("refs/heads/master", A);
657 		writeRef("refs/heads/branch", A);
658 		int initialRefsChangedEvents = refsChangedEvents;
659 		assertEquals(2, getLastReflogs("refs/heads/master", "refs/heads/branch")
660 				.size());
661 
662 		List<ReceiveCommand> cmds = Arrays.asList(
663 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
664 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
665 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
666 
667 		assertResults(cmds, OK, OK);
668 		assertRefs("refs/heads/branch", B);
669 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
670 				: initialRefsChangedEvents + 2, refsChangedEvents);
671 		if (useReftable) {
672 			// reftable retains reflog entries for deleted branches.
673 			assertReflogEquals(
674 					reflog(A, zeroId(), new PersonIdent(diskRepo), "a reflog"),
675 					getLastReflog("refs/heads/master"));
676 		} else {
677 			assertNull(getLastReflog("refs/heads/master"));
678 		}
679 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
680 				getLastReflog("refs/heads/branch"));
681 	}
682 
683 	@Test
684 	public void reflogFileDirectoryConflict() throws IOException {
685 		writeRef("refs/heads/master", A);
686 		int initialRefsChangedEvents = refsChangedEvents;
687 
688 		List<ReceiveCommand> cmds = Arrays.asList(
689 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
690 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
691 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
692 
693 		assertResults(cmds, OK, OK);
694 		assertRefs("refs/heads/master/x", A);
695 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
696 				: initialRefsChangedEvents + 2, refsChangedEvents);
697 		if (!useReftable) {
698 			// reftable retains reflog entries for deleted branches.
699 			assertNull(getLastReflog("refs/heads/master"));
700 		}
701 		assertReflogEquals(
702 				reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
703 				getLastReflog("refs/heads/master/x"));
704 	}
705 
706 	@Test
707 	public void reflogOnLockFailure() throws IOException {
708 		writeRef("refs/heads/master", A);
709 		int initialRefsChangedEvents = refsChangedEvents;
710 
711 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
712 				"refs/heads/branch");
713 
714 		List<ReceiveCommand> cmds = Arrays.asList(
715 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
716 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
717 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
718 
719 		if (atomic) {
720 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
721 			assertEquals(initialRefsChangedEvents, refsChangedEvents);
722 			assertReflogUnchanged(oldLogs, "refs/heads/master");
723 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
724 		} else {
725 			assertResults(cmds, OK, LOCK_FAILURE);
726 			assertEquals(initialRefsChangedEvents + 1, refsChangedEvents);
727 			assertReflogEquals(
728 					reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
729 					getLastReflog("refs/heads/master"));
730 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
731 		}
732 	}
733 
734 	@Test
735 	public void overrideRefLogMessage() throws Exception {
736 		writeRef("refs/heads/master", A);
737 		int initialRefsChangedEvents = refsChangedEvents;
738 
739 		List<ReceiveCommand> cmds = Arrays.asList(
740 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
741 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
742 		cmds.get(0).setRefLogMessage("custom log", false);
743 		PersonIdent ident = new PersonIdent(diskRepo);
744 		execute(newBatchUpdate(cmds).setRefLogIdent(ident)
745 				.setRefLogMessage("a reflog", true));
746 
747 		assertResults(cmds, OK, OK);
748 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
749 				: initialRefsChangedEvents + 2, refsChangedEvents);
750 		assertReflogEquals(reflog(A, B, ident, "custom log"),
751 				getLastReflog("refs/heads/master"), true);
752 		assertReflogEquals(reflog(zeroId(), B, ident, "a reflog: created"),
753 				getLastReflog("refs/heads/branch"), true);
754 	}
755 
756 	@Test
757 	public void overrideDisableRefLog() throws Exception {
758 		writeRef("refs/heads/master", A);
759 		int initialRefsChangedEvents = refsChangedEvents;
760 
761 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
762 				"refs/heads/branch");
763 
764 		List<ReceiveCommand> cmds = Arrays.asList(
765 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
766 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
767 		cmds.get(0).disableRefLog();
768 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
769 
770 		assertResults(cmds, OK, OK);
771 		assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1
772 				: initialRefsChangedEvents + 2, refsChangedEvents);
773 		assertReflogUnchanged(oldLogs, "refs/heads/master");
774 		assertReflogEquals(
775 				reflog(zeroId(), B, new PersonIdent(diskRepo),
776 						"a reflog: created"),
777 				getLastReflog("refs/heads/branch"));
778 	}
779 
780 	@Test
781 	public void refLogNotWrittenWithoutConfigOption() throws Exception {
782 		assumeFalse(useReftable);
783 
784 		setLogAllRefUpdates(false);
785 		writeRef("refs/heads/master", A);
786 
787 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
788 				"refs/heads/branch");
789 		assertTrue(oldLogs.isEmpty());
790 
791 		List<ReceiveCommand> cmds = Arrays.asList(
792 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
793 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
794 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
795 
796 		assertResults(cmds, OK, OK);
797 		assertReflogUnchanged(oldLogs, "refs/heads/master");
798 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
799 	}
800 
801 	@Test
802 	public void forceRefLogInUpdate() throws Exception {
803 		assumeFalse(useReftable);
804 
805 		setLogAllRefUpdates(false);
806 		writeRef("refs/heads/master", A);
807 		assertTrue(getLastReflogs("refs/heads/master", "refs/heads/branch")
808 				.isEmpty());
809 
810 		List<ReceiveCommand> cmds = Arrays.asList(
811 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
812 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
813 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)
814 				.setForceRefLog(true));
815 
816 		assertResults(cmds, OK, OK);
817 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
818 				getLastReflog("refs/heads/master"));
819 		assertReflogEquals(
820 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
821 				getLastReflog("refs/heads/branch"));
822 	}
823 
824 	@Test
825 	public void forceRefLogInCommand() throws Exception {
826 		assumeFalse(useReftable);
827 
828 		setLogAllRefUpdates(false);
829 		writeRef("refs/heads/master", A);
830 
831 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
832 				"refs/heads/branch");
833 		assertTrue(oldLogs.isEmpty());
834 
835 		List<ReceiveCommand> cmds = Arrays.asList(
836 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
837 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
838 		cmds.get(1).setForceRefLog(true);
839 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
840 
841 		assertResults(cmds, OK, OK);
842 		assertReflogUnchanged(oldLogs, "refs/heads/master");
843 		assertReflogEquals(
844 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
845 				getLastReflog("refs/heads/branch"));
846 	}
847 
848 	@Test
849 	public void packedRefsLockFailure() throws Exception {
850 		assumeFalse(useReftable);
851 
852 		writeLooseRef("refs/heads/master", A);
853 
854 		List<ReceiveCommand> cmds = Arrays.asList(
855 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
856 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
857 
858 		LockFile myLock = refdir.lockPackedRefs();
859 		try {
860 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
861 
862 			assertFalse(getLockFile("refs/heads/master").exists());
863 			assertFalse(getLockFile("refs/heads/branch").exists());
864 
865 			if (atomic) {
866 				assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
867 				assertRefs("refs/heads/master", A);
868 			} else {
869 				// Only operates on loose refs, doesn't care that packed-refs is
870 				// locked.
871 				assertResults(cmds, OK, OK);
872 				assertRefs("refs/heads/master", B, "refs/heads/branch", B);
873 			}
874 		} finally {
875 			myLock.unlock();
876 		}
877 	}
878 
879 	@Test
880 	public void packedRefsLockFailureRefsChangedEvents() throws Exception {
881 		assumeFalse(useReftable);
882 
883 		writeLooseRef("refs/heads/master", A);
884 		int initialRefsChangedEvents = refsChangedEvents;
885 
886 		List<ReceiveCommand> cmds = Arrays.asList(
887 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
888 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
889 
890 		LockFile myLock = refdir.lockPackedRefs();
891 		try {
892 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
893 
894 			assertEquals(atomic ? initialRefsChangedEvents
895 					: initialRefsChangedEvents + 2, refsChangedEvents);
896 		} finally {
897 			myLock.unlock();
898 		}
899 	}
900 
901 	@Test
902 	public void oneRefLockFailure() throws Exception {
903 		assumeFalse(useReftable);
904 
905 		writeLooseRef("refs/heads/master", A);
906 
907 		List<ReceiveCommand> cmds = Arrays.asList(
908 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
909 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
910 
911 		LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
912 		assertTrue(myLock.lock());
913 		try {
914 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
915 
916 			assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
917 			assertFalse(getLockFile("refs/heads/branch").exists());
918 
919 			if (atomic) {
920 				assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
921 				assertRefs("refs/heads/master", A);
922 			} else {
923 				assertResults(cmds, OK, LOCK_FAILURE);
924 				assertRefs("refs/heads/branch", B, "refs/heads/master", A);
925 			}
926 		} finally {
927 			myLock.unlock();
928 		}
929 	}
930 
931 	@Test
932 	public void oneRefLockFailureRefsChangedEvents() throws Exception {
933 		assumeFalse(useReftable);
934 
935 		writeLooseRef("refs/heads/master", A);
936 		int initialRefsChangedEvents = refsChangedEvents;
937 
938 		List<ReceiveCommand> cmds = Arrays.asList(
939 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
940 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
941 
942 		LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
943 		assertTrue(myLock.lock());
944 		try {
945 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
946 
947 			assertEquals(atomic ? initialRefsChangedEvents
948 					: initialRefsChangedEvents + 1, refsChangedEvents);
949 		} finally {
950 			myLock.unlock();
951 		}
952 	}
953 
954 	@Test
955 	public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
956 		assumeFalse(useReftable);
957 
958 		writeLooseRef("refs/heads/master", A);
959 
960 		List<ReceiveCommand> cmds = Arrays
961 				.asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
962 
963 		LockFile myLock = refdir.lockPackedRefs();
964 		try {
965 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
966 
967 			assertFalse(getLockFile("refs/heads/master").exists());
968 			assertResults(cmds, OK);
969 			assertRefs("refs/heads/master", B);
970 		} finally {
971 			myLock.unlock();
972 		}
973 	}
974 
975 	@Test
976 	public void singleRefUpdateDoesNotRequirePackedRefsLockRefsChangedEvents()
977 			throws Exception {
978 		assumeFalse(useReftable);
979 
980 		writeLooseRef("refs/heads/master", A);
981 		int initialRefsChangedEvents = refsChangedEvents;
982 
983 		List<ReceiveCommand> cmds = Arrays
984 				.asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
985 
986 		LockFile myLock = refdir.lockPackedRefs();
987 		try {
988 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
989 
990 			assertEquals(initialRefsChangedEvents + 1, refsChangedEvents);
991 		} finally {
992 			myLock.unlock();
993 		}
994 	}
995 
996 	@Test
997 	public void atomicUpdateRespectsInProcessLock() throws Exception {
998 		assumeTrue(atomic);
999 		assumeFalse(useReftable);
1000 
1001 		writeLooseRef("refs/heads/master", A);
1002 
1003 		List<ReceiveCommand> cmds = Arrays.asList(
1004 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
1005 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
1006 
1007 		Thread t = new Thread(() -> {
1008 			try {
1009 				execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
1010 			} catch (Exception e) {
1011 				throw new RuntimeException(e);
1012 			}
1013 		});
1014 
1015 		ReentrantLock l = refdir.inProcessPackedRefsLock;
1016 		l.lock();
1017 		try {
1018 			t.start();
1019 			long timeoutSecs = 10;
1020 			long startNanos = System.nanoTime();
1021 
1022 			// Hold onto the lock until we observe the worker thread has
1023 			// attempted to
1024 			// acquire it.
1025 			while (l.getQueueLength() == 0) {
1026 				long elapsedNanos = System.nanoTime() - startNanos;
1027 				assertTrue(
1028 						"timed out waiting for work thread to attempt to acquire lock",
1029 						NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
1030 				Thread.sleep(3);
1031 			}
1032 
1033 			// Once we unlock, the worker thread should finish the update
1034 			// promptly.
1035 			l.unlock();
1036 			t.join(SECONDS.toMillis(timeoutSecs));
1037 			assertFalse(t.isAlive());
1038 		} finally {
1039 			if (l.isHeldByCurrentThread()) {
1040 				l.unlock();
1041 			}
1042 		}
1043 
1044 		assertResults(cmds, OK, OK);
1045 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
1046 	}
1047 
1048 	@Test
1049 	public void atomicUpdateRespectsInProcessLockRefsChangedEvents()
1050 			throws Exception {
1051 		assumeTrue(atomic);
1052 		assumeFalse(useReftable);
1053 
1054 		writeLooseRef("refs/heads/master", A);
1055 		int initialRefsChangedEvents = refsChangedEvents;
1056 
1057 		List<ReceiveCommand> cmds = Arrays.asList(
1058 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
1059 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
1060 
1061 		Thread t = new Thread(() -> {
1062 			try {
1063 				execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
1064 			} catch (Exception e) {
1065 				throw new RuntimeException(e);
1066 			}
1067 		});
1068 
1069 		ReentrantLock l = refdir.inProcessPackedRefsLock;
1070 		l.lock();
1071 		try {
1072 			t.start();
1073 			long timeoutSecs = 10;
1074 
1075 			// Hold onto the lock until we observe the worker thread has
1076 			// attempted to
1077 			// acquire it.
1078 			while (l.getQueueLength() == 0) {
1079 				Thread.sleep(3);
1080 			}
1081 
1082 			// Once we unlock, the worker thread should finish the update
1083 			// promptly.
1084 			l.unlock();
1085 			t.join(SECONDS.toMillis(timeoutSecs));
1086 		} finally {
1087 			if (l.isHeldByCurrentThread()) {
1088 				l.unlock();
1089 			}
1090 		}
1091 
1092 		assertEquals(initialRefsChangedEvents + 1, refsChangedEvents);
1093 	}
1094 
1095 	private void setLogAllRefUpdates(boolean enable) throws Exception {
1096 		StoredConfig cfg = diskRepo.getConfig();
1097 		cfg.load();
1098 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1099 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
1100 		cfg.save();
1101 	}
1102 
1103 	private void writeLooseRef(String name, AnyObjectId id) throws IOException {
1104 		if (useReftable) {
1105 			writeRef(name, id);
1106 		} else {
1107 			write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
1108 			// force the refs-changed event to be fired for the loose ref that
1109 			// was created. We do this to get the events fired during the test
1110 			// 'setup' out of the way and this allows us to now accurately
1111 			// assert only for the new events fired during the BatchRefUpdate.
1112 			refdir.exactRef(name);
1113 		}
1114 	}
1115 
1116 	private void writeLooseRefs(String name1, AnyObjectId id1, String name2,
1117 			AnyObjectId id2) throws IOException {
1118 		if (useReftable) {
1119 			BatchRefUpdate bru = diskRepo.getRefDatabase().newBatchUpdate();
1120 
1121 			Ref r1 = diskRepo.exactRef(name1);
1122 			ReceiveCommand c1 = new ReceiveCommand(
1123 					r1 != null ? r1.getObjectId() : ObjectId.zeroId(),
1124 					id1.toObjectId(), name1, r1 == null ? CREATE : UPDATE);
1125 
1126 			Ref r2 = diskRepo.exactRef(name2);
1127 			ReceiveCommand c2 = new ReceiveCommand(
1128 					r2 != null ? r2.getObjectId() : ObjectId.zeroId(),
1129 					id2.toObjectId(), name2, r2 == null ? CREATE : UPDATE);
1130 
1131 			bru.addCommand(c1, c2);
1132 			try (RevWalk rw = new RevWalk(diskRepo)) {
1133 				bru.execute(rw, NullProgressMonitor.INSTANCE);
1134 			}
1135 			assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
1136 			assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
1137 		} else {
1138 			writeLooseRef(name1, id1);
1139 			writeLooseRef(name2, id2);
1140 		}
1141 	}
1142 
1143 	private void writeRef(String name, AnyObjectId id) throws IOException {
1144 		RefUpdate u = diskRepo.updateRef(name);
1145 		u.setRefLogMessage(getClass().getSimpleName(), false);
1146 		u.setForceUpdate(true);
1147 		u.setNewObjectId(id);
1148 		RefUpdate.Result r = u.update();
1149 		switch (r) {
1150 		case NEW:
1151 		case FORCED:
1152 			return;
1153 		default:
1154 			throw new IOException("Got " + r + " while updating " + name);
1155 		}
1156 	}
1157 
1158 	private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
1159 		BatchRefUpdate u = diskRepo.getRefDatabase().newBatchUpdate();
1160 		if (atomic) {
1161 			assertTrue(u.isAtomic());
1162 		} else {
1163 			u.setAtomic(false);
1164 		}
1165 		u.addCommand(cmds);
1166 		return u;
1167 	}
1168 
1169 	private void execute(BatchRefUpdate u) throws IOException {
1170 		execute(u, false);
1171 	}
1172 
1173 	private void execute(BatchRefUpdate u, boolean strictWork)
1174 			throws IOException {
1175 		try (RevWalk rw = new RevWalk(diskRepo)) {
1176 			u.execute(rw, strictWork ? new StrictWorkMonitor()
1177 					: NullProgressMonitor.INSTANCE);
1178 		}
1179 	}
1180 
1181 	private void assertRefs(Object... args) throws IOException {
1182 		if (args.length % 2 != 0) {
1183 			throw new IllegalArgumentException(
1184 					"expected even number of args: " + Arrays.toString(args));
1185 		}
1186 
1187 		Map<String, AnyObjectId> expected = new LinkedHashMap<>();
1188 		for (int i = 0; i < args.length; i += 2) {
1189 			expected.put((String) args[i], (AnyObjectId) args[i + 1]);
1190 		}
1191 
1192 		Map<String, Ref> refs = diskRepo.getRefDatabase()
1193 				.getRefs(RefDatabase.ALL);
1194 		Ref actualHead = refs.remove(Constants.HEAD);
1195 		if (actualHead != null) {
1196 			String actualLeafName = actualHead.getLeaf().getName();
1197 			assertEquals(
1198 					"expected HEAD to point to refs/heads/master, got: "
1199 							+ actualLeafName,
1200 					"refs/heads/master", actualLeafName);
1201 			AnyObjectId expectedMaster = expected.get("refs/heads/master");
1202 			assertNotNull("expected master ref since HEAD exists",
1203 					expectedMaster);
1204 			assertEquals(expectedMaster, actualHead.getObjectId());
1205 		}
1206 
1207 		Map<String, AnyObjectId> actual = new LinkedHashMap<>();
1208 		refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
1209 
1210 		assertEquals(expected.keySet(), actual.keySet());
1211 		actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
1212 	}
1213 
1214 	enum Result {
1215 		OK(ReceiveCommand.Result.OK),
1216 		LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE),
1217 		REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD),
1218 		REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
1219 		TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
1220 
1221 		@SuppressWarnings("ImmutableEnumChecker")
1222 		final Predicate<? super ReceiveCommand> p;
1223 
1224 		private Result(Predicate<? super ReceiveCommand> p) {
1225 			this.p = p;
1226 		}
1227 
1228 		private Result(ReceiveCommand.Result result) {
1229 			this(c -> c.getResult() == result);
1230 		}
1231 	}
1232 
1233 	private void assertResults(List<ReceiveCommand> cmds, Result... expected) {
1234 		if (expected.length != cmds.size()) {
1235 			throw new IllegalArgumentException(
1236 					"expected " + cmds.size() + " result args");
1237 		}
1238 		for (int i = 0; i < cmds.size(); i++) {
1239 			ReceiveCommand c = cmds.get(i);
1240 			Result r = expected[i];
1241 			assertTrue(String.format(
1242 					"result of command (%d) should be %s, got %s %s%s",
1243 					Integer.valueOf(i), r, c, c.getResult(),
1244 					c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
1245 					r.p.test(c));
1246 		}
1247 	}
1248 
1249 	private Map<String, ReflogEntry> getLastReflogs(String... names)
1250 			throws IOException {
1251 		Map<String, ReflogEntry> result = new LinkedHashMap<>();
1252 		for (String name : names) {
1253 			ReflogEntry e = getLastReflog(name);
1254 			if (e != null) {
1255 				result.put(name, e);
1256 			}
1257 		}
1258 		return result;
1259 	}
1260 
1261 	private ReflogEntry getLastReflog(String name) throws IOException {
1262 		ReflogReader r = diskRepo.getReflogReader(name);
1263 		if (r == null) {
1264 			return null;
1265 		}
1266 		return r.getLastEntry();
1267 	}
1268 
1269 	private File getLockFile(String refName) {
1270 		return LockFile.getLockFile(refdir.fileFor(refName));
1271 	}
1272 
1273 	private void assertReflogUnchanged(Map<String, ReflogEntry> old,
1274 			String name) throws IOException {
1275 		assertReflogEquals(old.get(name), getLastReflog(name), true);
1276 	}
1277 
1278 	private static void assertReflogEquals(ReflogEntry expected,
1279 			ReflogEntry actual) {
1280 		assertReflogEquals(expected, actual, false);
1281 	}
1282 
1283 	private static void assertReflogEquals(ReflogEntry expected,
1284 			ReflogEntry actual, boolean strictTime) {
1285 		if (expected == null) {
1286 			assertNull(actual);
1287 			return;
1288 		}
1289 		assertNotNull(actual);
1290 		assertEquals(expected.getOldId(), actual.getOldId());
1291 		assertEquals(expected.getNewId(), actual.getNewId());
1292 		if (strictTime) {
1293 			assertEquals(expected.getWho(), actual.getWho());
1294 		} else {
1295 			assertEquals(expected.getWho().getName(),
1296 					actual.getWho().getName());
1297 			assertEquals(expected.getWho().getEmailAddress(),
1298 					actual.getWho().getEmailAddress());
1299 		}
1300 		assertEquals(expected.getComment(), actual.getComment());
1301 	}
1302 
1303 	private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
1304 			PersonIdent who, String comment) {
1305 		return new ReflogEntry() {
1306 			@Override
1307 			public ObjectId getOldId() {
1308 				return oldId;
1309 			}
1310 
1311 			@Override
1312 			public ObjectId getNewId() {
1313 				return newId;
1314 			}
1315 
1316 			@Override
1317 			public PersonIdent getWho() {
1318 				return who;
1319 			}
1320 
1321 			@Override
1322 			public String getComment() {
1323 				return comment;
1324 			}
1325 
1326 			@Override
1327 			public CheckoutEntry parseCheckout() {
1328 				throw new UnsupportedOperationException();
1329 			}
1330 		};
1331 	}
1332 
1333 	private boolean batchesRefUpdates() {
1334 		return atomic || useReftable;
1335 	}
1336 }