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