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 			assertEquals(1, refsChangedEvents);
194 		} else {
195 			assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
196 			assertRefs("refs/heads/master", B, "refs/heads/masters", B);
197 			assertEquals(2, refsChangedEvents);
198 		}
199 	}
200 
201 	@Test
202 	public void simpleForce() throws IOException {
203 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
204 
205 		List<ReceiveCommand> cmds = Arrays.asList(
206 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
207 				new ReceiveCommand(B, A, "refs/heads/masters",
208 						UPDATE_NONFASTFORWARD));
209 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
210 
211 		assertResults(cmds, OK, OK);
212 		assertRefs("refs/heads/master", B, "refs/heads/masters", A);
213 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
214 	}
215 
216 	@Test
217 	public void nonFastForwardDoesNotDoExpensiveMergeCheck()
218 			throws IOException {
219 		writeLooseRef("refs/heads/master", B);
220 
221 		List<ReceiveCommand> cmds = Arrays.asList(new ReceiveCommand(B, A,
222 				"refs/heads/master", UPDATE_NONFASTFORWARD));
223 		try (RevWalk rw = new RevWalk(diskRepo) {
224 			@Override
225 			public boolean isMergedInto(RevCommit base, RevCommit tip) {
226 				throw new AssertionError("isMergedInto() should not be called");
227 			}
228 		}) {
229 			newBatchUpdate(cmds).setAllowNonFastForwards(true).execute(rw,
230 					new StrictWorkMonitor());
231 		}
232 
233 		assertResults(cmds, OK);
234 		assertRefs("refs/heads/master", A);
235 		assertEquals(2, refsChangedEvents);
236 	}
237 
238 	@Test
239 	public void fileDirectoryConflict() throws IOException {
240 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
241 
242 		List<ReceiveCommand> cmds = Arrays.asList(
243 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
244 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
245 				new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
246 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
247 
248 		if (atomic) {
249 			// Atomic update sees that master and master/x are conflicting, then
250 			// marks the first one in the list as LOCK_FAILURE and aborts the rest.
251 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED,
252 					TRANSACTION_ABORTED);
253 			assertRefs("refs/heads/master", A, "refs/heads/masters", B);
254 			assertEquals(1, refsChangedEvents);
255 		} else {
256 			// Non-atomic updates are applied in order: master succeeds, then
257 			// master/x fails due to conflict.
258 			assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
259 			assertRefs("refs/heads/master", B, "refs/heads/masters", B);
260 			assertEquals(2, refsChangedEvents);
261 		}
262 	}
263 
264 	@Test
265 	public void conflictThanksToDelete() throws IOException {
266 		writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
267 
268 		List<ReceiveCommand> cmds = Arrays.asList(
269 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
270 				new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
271 				new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
272 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
273 
274 		assertResults(cmds, OK, OK, OK);
275 		assertRefs("refs/heads/master", B, "refs/heads/masters/x", A);
276 		if (atomic) {
277 			assertEquals(2, refsChangedEvents);
278 		} else if (!useReftable) {
279 			// The non-atomic case actually produces 5 events, but that's an
280 			// implementation detail. We expect at least 4 events, one for the
281 			// initial read due to writeLooseRef(), and then one for each
282 			// successful ref update.
283 			assertTrue(refsChangedEvents >= 4);
284 		}
285 	}
286 
287 	@Test
288 	public void updateToMissingObject() throws IOException {
289 		writeLooseRef("refs/heads/master", A);
290 
291 		ObjectId bad = ObjectId
292 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
293 		List<ReceiveCommand> cmds = Arrays.asList(
294 				new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
295 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
296 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
297 
298 		if (atomic) {
299 			assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
300 			assertRefs("refs/heads/master", A);
301 			assertEquals(1, refsChangedEvents);
302 		} else {
303 			assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
304 			assertRefs("refs/heads/master", A, "refs/heads/foo2", B);
305 			assertEquals(2, refsChangedEvents);
306 		}
307 	}
308 
309 	@Test
310 	public void addMissingObject() throws IOException {
311 		writeLooseRef("refs/heads/master", A);
312 
313 		ObjectId bad = ObjectId
314 				.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
315 		List<ReceiveCommand> cmds = Arrays.asList(
316 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
317 				new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
318 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
319 
320 		if (atomic) {
321 			assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT);
322 			assertRefs("refs/heads/master", A);
323 			assertEquals(1, refsChangedEvents);
324 		} else {
325 			assertResults(cmds, OK, REJECTED_MISSING_OBJECT);
326 			assertRefs("refs/heads/master", B);
327 			assertEquals(2, refsChangedEvents);
328 		}
329 	}
330 
331 	@Test
332 	public void oneNonExistentRef() throws IOException {
333 		List<ReceiveCommand> cmds = Arrays.asList(
334 				new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE),
335 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
336 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
337 
338 		if (atomic) {
339 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
340 			assertRefs();
341 			assertEquals(0, refsChangedEvents);
342 		} else {
343 			assertResults(cmds, LOCK_FAILURE, OK);
344 			assertRefs("refs/heads/foo2", B);
345 			assertEquals(1, refsChangedEvents);
346 		}
347 	}
348 
349 	@Test
350 	public void oneRefWrongOldValue() throws IOException {
351 		writeLooseRef("refs/heads/master", A);
352 
353 		List<ReceiveCommand> cmds = Arrays.asList(
354 				new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
355 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
356 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
357 
358 		if (atomic) {
359 			assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
360 			assertRefs("refs/heads/master", A);
361 			assertEquals(1, refsChangedEvents);
362 		} else {
363 			assertResults(cmds, LOCK_FAILURE, OK);
364 			assertRefs("refs/heads/master", A, "refs/heads/foo2", B);
365 			assertEquals(2, refsChangedEvents);
366 		}
367 	}
368 
369 	@Test
370 	public void nonExistentRef() throws IOException {
371 		writeLooseRef("refs/heads/master", A);
372 
373 		List<ReceiveCommand> cmds = Arrays.asList(
374 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
375 				new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
376 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
377 
378 		if (atomic) {
379 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
380 			assertRefs("refs/heads/master", A);
381 			assertEquals(1, refsChangedEvents);
382 		} else {
383 			assertResults(cmds, OK, LOCK_FAILURE);
384 			assertRefs("refs/heads/master", B);
385 			assertEquals(2, refsChangedEvents);
386 		}
387 	}
388 
389 	@Test
390 	public void noRefLog() throws IOException {
391 		writeRef("refs/heads/master", A);
392 
393 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
394 				"refs/heads/branch");
395 		assertEquals(Collections.singleton("refs/heads/master"),
396 				oldLogs.keySet());
397 
398 		List<ReceiveCommand> cmds = Arrays.asList(
399 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
400 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
401 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
402 
403 		assertResults(cmds, OK, OK);
404 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
405 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
406 		assertReflogUnchanged(oldLogs, "refs/heads/master");
407 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
408 	}
409 
410 	@Test
411 	public void reflogDefaultIdent() throws IOException {
412 		writeRef("refs/heads/master", A);
413 		writeRef("refs/heads/branch2", A);
414 
415 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
416 				"refs/heads/branch1", "refs/heads/branch2");
417 		List<ReceiveCommand> cmds = Arrays.asList(
418 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
419 				new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
420 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)
421 				.setRefLogMessage("a reflog", false));
422 
423 		assertResults(cmds, OK, OK);
424 		assertRefs("refs/heads/master", B, "refs/heads/branch1", B,
425 				"refs/heads/branch2", A);
426 		assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents);
427 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
428 				getLastReflog("refs/heads/master"));
429 		assertReflogEquals(
430 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
431 				getLastReflog("refs/heads/branch1"));
432 		assertReflogUnchanged(oldLogs, "refs/heads/branch2");
433 	}
434 
435 	@Test
436 	public void reflogAppendStatusNoMessage() throws IOException {
437 		writeRef("refs/heads/master", A);
438 		writeRef("refs/heads/branch1", B);
439 
440 		List<ReceiveCommand> cmds = Arrays.asList(
441 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
442 				new ReceiveCommand(B, A, "refs/heads/branch1",
443 						UPDATE_NONFASTFORWARD),
444 				new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
445 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)
446 				.setRefLogMessage(null, true));
447 
448 		assertResults(cmds, OK, OK, OK);
449 		assertRefs("refs/heads/master", B, "refs/heads/branch1", A,
450 				"refs/heads/branch2", A);
451 		assertEquals(batchesRefUpdates() ? 3 : 5, refsChangedEvents);
452 		assertReflogEquals(
453 				// Always forced; setAllowNonFastForwards(true) bypasses the
454 				// check.
455 				reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
456 				getLastReflog("refs/heads/master"));
457 		assertReflogEquals(
458 				reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
459 				getLastReflog("refs/heads/branch1"));
460 		assertReflogEquals(
461 				reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
462 				getLastReflog("refs/heads/branch2"));
463 	}
464 
465 	@Test
466 	public void reflogAppendStatusFastForward() throws IOException {
467 		writeRef("refs/heads/master", A);
468 
469 		List<ReceiveCommand> cmds = Arrays
470 				.asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
471 		execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
472 
473 		assertResults(cmds, OK);
474 		assertRefs("refs/heads/master", B);
475 		assertEquals(2, refsChangedEvents);
476 		assertReflogEquals(
477 				reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
478 				getLastReflog("refs/heads/master"));
479 	}
480 
481 	@Test
482 	public void reflogAppendStatusWithMessage() throws IOException {
483 		writeRef("refs/heads/master", A);
484 
485 		List<ReceiveCommand> cmds = Arrays.asList(
486 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
487 				new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
488 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
489 
490 		assertResults(cmds, OK, OK);
491 		assertRefs("refs/heads/master", B, "refs/heads/branch", A);
492 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
493 		assertReflogEquals(
494 				reflog(A, B, new PersonIdent(diskRepo),
495 						"a reflog: fast-forward"),
496 				getLastReflog("refs/heads/master"));
497 		assertReflogEquals(
498 				reflog(zeroId(), A, new PersonIdent(diskRepo),
499 						"a reflog: created"),
500 				getLastReflog("refs/heads/branch"));
501 	}
502 
503 	@Test
504 	public void reflogCustomIdent() throws IOException {
505 		writeRef("refs/heads/master", A);
506 
507 		List<ReceiveCommand> cmds = Arrays.asList(
508 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
509 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
510 		PersonIdent ident = new PersonIdent("A Reflog User",
511 				"reflog@example.com");
512 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)
513 				.setRefLogIdent(ident));
514 
515 		assertResults(cmds, OK, OK);
516 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
517 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
518 		assertReflogEquals(reflog(A, B, ident, "a reflog"),
519 				getLastReflog("refs/heads/master"), true);
520 		assertReflogEquals(reflog(zeroId(), B, ident, "a reflog"),
521 				getLastReflog("refs/heads/branch"), true);
522 	}
523 
524 	@Test
525 	public void reflogDelete() throws IOException {
526 		writeRef("refs/heads/master", A);
527 		writeRef("refs/heads/branch", A);
528 		assertEquals(2, getLastReflogs("refs/heads/master", "refs/heads/branch")
529 				.size());
530 
531 		List<ReceiveCommand> cmds = Arrays.asList(
532 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
533 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
534 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
535 
536 		assertResults(cmds, OK, OK);
537 		assertRefs("refs/heads/branch", B);
538 		assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents);
539 		if (useReftable) {
540 			// reftable retains reflog entries for deleted branches.
541 			assertReflogEquals(
542 					reflog(A, zeroId(), new PersonIdent(diskRepo), "a reflog"),
543 					getLastReflog("refs/heads/master"));
544 		} else {
545 			assertNull(getLastReflog("refs/heads/master"));
546 		}
547 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
548 				getLastReflog("refs/heads/branch"));
549 	}
550 
551 	@Test
552 	public void reflogFileDirectoryConflict() throws IOException {
553 		writeRef("refs/heads/master", A);
554 
555 		List<ReceiveCommand> cmds = Arrays.asList(
556 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
557 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
558 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
559 
560 		assertResults(cmds, OK, OK);
561 		assertRefs("refs/heads/master/x", A);
562 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
563 		if (!useReftable) {
564 			// reftable retains reflog entries for deleted branches.
565 			assertNull(getLastReflog("refs/heads/master"));
566 		}
567 		assertReflogEquals(
568 				reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
569 				getLastReflog("refs/heads/master/x"));
570 	}
571 
572 	@Test
573 	public void reflogOnLockFailure() throws IOException {
574 		writeRef("refs/heads/master", A);
575 
576 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
577 				"refs/heads/branch");
578 
579 		List<ReceiveCommand> cmds = Arrays.asList(
580 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
581 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
582 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
583 
584 		if (atomic) {
585 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
586 			assertEquals(1, refsChangedEvents);
587 			assertReflogUnchanged(oldLogs, "refs/heads/master");
588 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
589 		} else {
590 			assertResults(cmds, OK, LOCK_FAILURE);
591 			assertEquals(2, refsChangedEvents);
592 			assertReflogEquals(
593 					reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
594 					getLastReflog("refs/heads/master"));
595 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
596 		}
597 	}
598 
599 	@Test
600 	public void overrideRefLogMessage() throws Exception {
601 		writeRef("refs/heads/master", A);
602 
603 		List<ReceiveCommand> cmds = Arrays.asList(
604 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
605 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
606 		cmds.get(0).setRefLogMessage("custom log", false);
607 		PersonIdent ident = new PersonIdent(diskRepo);
608 		execute(newBatchUpdate(cmds).setRefLogIdent(ident)
609 				.setRefLogMessage("a reflog", true));
610 
611 		assertResults(cmds, OK, OK);
612 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
613 		assertReflogEquals(reflog(A, B, ident, "custom log"),
614 				getLastReflog("refs/heads/master"), true);
615 		assertReflogEquals(reflog(zeroId(), B, ident, "a reflog: created"),
616 				getLastReflog("refs/heads/branch"), true);
617 	}
618 
619 	@Test
620 	public void overrideDisableRefLog() throws Exception {
621 		writeRef("refs/heads/master", A);
622 
623 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
624 				"refs/heads/branch");
625 
626 		List<ReceiveCommand> cmds = Arrays.asList(
627 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
628 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
629 		cmds.get(0).disableRefLog();
630 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
631 
632 		assertResults(cmds, OK, OK);
633 		assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
634 		assertReflogUnchanged(oldLogs, "refs/heads/master");
635 		assertReflogEquals(
636 				reflog(zeroId(), B, new PersonIdent(diskRepo),
637 						"a reflog: created"),
638 				getLastReflog("refs/heads/branch"));
639 	}
640 
641 	@Test
642 	public void refLogNotWrittenWithoutConfigOption() throws Exception {
643 		assumeFalse(useReftable);
644 		setLogAllRefUpdates(false);
645 		writeRef("refs/heads/master", A);
646 
647 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
648 				"refs/heads/branch");
649 		assertTrue(oldLogs.isEmpty());
650 
651 		List<ReceiveCommand> cmds = Arrays.asList(
652 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
653 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
654 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
655 
656 		assertResults(cmds, OK, OK);
657 		assertReflogUnchanged(oldLogs, "refs/heads/master");
658 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
659 	}
660 
661 	@Test
662 	public void forceRefLogInUpdate() throws Exception {
663 		assumeFalse(useReftable);
664 		setLogAllRefUpdates(false);
665 		writeRef("refs/heads/master", A);
666 		assertTrue(getLastReflogs("refs/heads/master", "refs/heads/branch")
667 				.isEmpty());
668 
669 		List<ReceiveCommand> cmds = Arrays.asList(
670 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
671 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
672 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)
673 				.setForceRefLog(true));
674 
675 		assertResults(cmds, OK, OK);
676 		assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
677 				getLastReflog("refs/heads/master"));
678 		assertReflogEquals(
679 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
680 				getLastReflog("refs/heads/branch"));
681 	}
682 
683 	@Test
684 	public void forceRefLogInCommand() throws Exception {
685 		assumeFalse(useReftable);
686 		setLogAllRefUpdates(false);
687 		writeRef("refs/heads/master", A);
688 
689 		Map<String, ReflogEntry> oldLogs = getLastReflogs("refs/heads/master",
690 				"refs/heads/branch");
691 		assertTrue(oldLogs.isEmpty());
692 
693 		List<ReceiveCommand> cmds = Arrays.asList(
694 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
695 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
696 		cmds.get(1).setForceRefLog(true);
697 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
698 
699 		assertResults(cmds, OK, OK);
700 		assertReflogUnchanged(oldLogs, "refs/heads/master");
701 		assertReflogEquals(
702 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
703 				getLastReflog("refs/heads/branch"));
704 	}
705 
706 	@Test
707 	public void packedRefsLockFailure() throws Exception {
708 		assumeFalse(useReftable);
709 
710 		writeLooseRef("refs/heads/master", A);
711 
712 		List<ReceiveCommand> cmds = Arrays.asList(
713 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
714 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
715 
716 		LockFile myLock = refdir.lockPackedRefs();
717 		try {
718 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
719 
720 			assertFalse(getLockFile("refs/heads/master").exists());
721 			assertFalse(getLockFile("refs/heads/branch").exists());
722 
723 			if (atomic) {
724 				assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
725 				assertRefs("refs/heads/master", A);
726 				assertEquals(1, refsChangedEvents);
727 			} else {
728 				// Only operates on loose refs, doesn't care that packed-refs is
729 				// locked.
730 				assertResults(cmds, OK, OK);
731 				assertRefs("refs/heads/master", B, "refs/heads/branch", B);
732 				assertEquals(3, refsChangedEvents);
733 			}
734 		} finally {
735 			myLock.unlock();
736 		}
737 	}
738 
739 	@Test
740 	public void oneRefLockFailure() throws Exception {
741 		assumeFalse(useReftable);
742 
743 		writeLooseRef("refs/heads/master", A);
744 
745 		List<ReceiveCommand> cmds = Arrays.asList(
746 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
747 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
748 
749 		LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
750 		assertTrue(myLock.lock());
751 		try {
752 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
753 
754 			assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
755 			assertFalse(getLockFile("refs/heads/branch").exists());
756 
757 			if (atomic) {
758 				assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
759 				assertRefs("refs/heads/master", A);
760 				assertEquals(1, refsChangedEvents);
761 			} else {
762 				assertResults(cmds, OK, LOCK_FAILURE);
763 				assertRefs("refs/heads/branch", B, "refs/heads/master", A);
764 				assertEquals(2, refsChangedEvents);
765 			}
766 		} finally {
767 			myLock.unlock();
768 		}
769 	}
770 
771 	@Test
772 	public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
773 		assumeFalse(useReftable);
774 		writeLooseRef("refs/heads/master", A);
775 
776 		List<ReceiveCommand> cmds = Arrays
777 				.asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
778 
779 		LockFile myLock = refdir.lockPackedRefs();
780 		try {
781 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
782 
783 			assertFalse(getLockFile("refs/heads/master").exists());
784 			assertResults(cmds, OK);
785 			assertEquals(2, refsChangedEvents);
786 			assertRefs("refs/heads/master", B);
787 		} finally {
788 			myLock.unlock();
789 		}
790 	}
791 
792 	@Test
793 	public void atomicUpdateRespectsInProcessLock() throws Exception {
794 		assumeTrue(atomic);
795 		assumeFalse(useReftable);
796 
797 		writeLooseRef("refs/heads/master", A);
798 
799 		List<ReceiveCommand> cmds = Arrays.asList(
800 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
801 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
802 
803 		Thread t = new Thread(() -> {
804 			try {
805 				execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
806 			} catch (Exception e) {
807 				throw new RuntimeException(e);
808 			}
809 		});
810 
811 		ReentrantLock l = refdir.inProcessPackedRefsLock;
812 		l.lock();
813 		try {
814 			t.start();
815 			long timeoutSecs = 10;
816 			long startNanos = System.nanoTime();
817 
818 			// Hold onto the lock until we observe the worker thread has
819 			// attempted to
820 			// acquire it.
821 			while (l.getQueueLength() == 0) {
822 				long elapsedNanos = System.nanoTime() - startNanos;
823 				assertTrue(
824 						"timed out waiting for work thread to attempt to acquire lock",
825 						NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
826 				Thread.sleep(3);
827 			}
828 
829 			// Once we unlock, the worker thread should finish the update
830 			// promptly.
831 			l.unlock();
832 			t.join(SECONDS.toMillis(timeoutSecs));
833 			assertFalse(t.isAlive());
834 		} finally {
835 			if (l.isHeldByCurrentThread()) {
836 				l.unlock();
837 			}
838 		}
839 
840 		assertResults(cmds, OK, OK);
841 		assertEquals(2, refsChangedEvents);
842 		assertRefs("refs/heads/master", B, "refs/heads/branch", B);
843 	}
844 
845 	private void setLogAllRefUpdates(boolean enable) throws Exception {
846 		StoredConfig cfg = diskRepo.getConfig();
847 		cfg.load();
848 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
849 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
850 		cfg.save();
851 	}
852 
853 	private void writeLooseRef(String name, AnyObjectId id) throws IOException {
854 		if (useReftable) {
855 			writeRef(name, id);
856 		} else {
857 			write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
858 		}
859 	}
860 
861 	private void writeLooseRefs(String name1, AnyObjectId id1, String name2,
862 			AnyObjectId id2) throws IOException {
863 		if (useReftable) {
864 			BatchRefUpdate bru = diskRepo.getRefDatabase().newBatchUpdate();
865 
866 			Ref r1 = diskRepo.exactRef(name1);
867 			ReceiveCommand c1 = new ReceiveCommand(
868 					r1 != null ? r1.getObjectId() : ObjectId.zeroId(),
869 					id1.toObjectId(), name1, r1 == null ? CREATE : UPDATE);
870 
871 			Ref r2 = diskRepo.exactRef(name2);
872 			ReceiveCommand c2 = new ReceiveCommand(
873 					r2 != null ? r2.getObjectId() : ObjectId.zeroId(),
874 					id2.toObjectId(), name2, r2 == null ? CREATE : UPDATE);
875 
876 			bru.addCommand(c1, c2);
877 			try (RevWalk rw = new RevWalk(diskRepo)) {
878 				bru.execute(rw, NullProgressMonitor.INSTANCE);
879 			}
880 			assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
881 			assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
882 		} else {
883 			writeLooseRef(name1, id1);
884 			writeLooseRef(name2, id2);
885 		}
886 	}
887 
888 	private void writeRef(String name, AnyObjectId id) throws IOException {
889 		RefUpdate u = diskRepo.updateRef(name);
890 		u.setRefLogMessage(getClass().getSimpleName(), false);
891 		u.setForceUpdate(true);
892 		u.setNewObjectId(id);
893 		RefUpdate.Result r = u.update();
894 		switch (r) {
895 		case NEW:
896 		case FORCED:
897 			return;
898 		default:
899 			throw new IOException("Got " + r + " while updating " + name);
900 		}
901 	}
902 
903 	private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
904 		BatchRefUpdate u = diskRepo.getRefDatabase().newBatchUpdate();
905 		if (atomic) {
906 			assertTrue(u.isAtomic());
907 		} else {
908 			u.setAtomic(false);
909 		}
910 		u.addCommand(cmds);
911 		return u;
912 	}
913 
914 	private void execute(BatchRefUpdate u) throws IOException {
915 		execute(u, false);
916 	}
917 
918 	private void execute(BatchRefUpdate u, boolean strictWork)
919 			throws IOException {
920 		try (RevWalk rw = new RevWalk(diskRepo)) {
921 			u.execute(rw, strictWork ? new StrictWorkMonitor()
922 					: NullProgressMonitor.INSTANCE);
923 		}
924 	}
925 
926 	private void assertRefs(Object... args) throws IOException {
927 		if (args.length % 2 != 0) {
928 			throw new IllegalArgumentException(
929 					"expected even number of args: " + Arrays.toString(args));
930 		}
931 
932 		Map<String, AnyObjectId> expected = new LinkedHashMap<>();
933 		for (int i = 0; i < args.length; i += 2) {
934 			expected.put((String) args[i], (AnyObjectId) args[i + 1]);
935 		}
936 
937 		Map<String, Ref> refs = diskRepo.getRefDatabase()
938 				.getRefs(RefDatabase.ALL);
939 		Ref actualHead = refs.remove(Constants.HEAD);
940 		if (actualHead != null) {
941 			String actualLeafName = actualHead.getLeaf().getName();
942 			assertEquals(
943 					"expected HEAD to point to refs/heads/master, got: "
944 							+ actualLeafName,
945 					"refs/heads/master", actualLeafName);
946 			AnyObjectId expectedMaster = expected.get("refs/heads/master");
947 			assertNotNull("expected master ref since HEAD exists",
948 					expectedMaster);
949 			assertEquals(expectedMaster, actualHead.getObjectId());
950 		}
951 
952 		Map<String, AnyObjectId> actual = new LinkedHashMap<>();
953 		refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
954 
955 		assertEquals(expected.keySet(), actual.keySet());
956 		actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
957 	}
958 
959 	enum Result {
960 		OK(ReceiveCommand.Result.OK), LOCK_FAILURE(
961 				ReceiveCommand.Result.LOCK_FAILURE), REJECTED_NONFASTFORWARD(
962 						ReceiveCommand.Result.REJECTED_NONFASTFORWARD), REJECTED_MISSING_OBJECT(
963 								ReceiveCommand.Result.REJECTED_MISSING_OBJECT), TRANSACTION_ABORTED(
964 										ReceiveCommand::isTransactionAborted);
965 
966 		@SuppressWarnings("ImmutableEnumChecker")
967 		final Predicate<? super ReceiveCommand> p;
968 
969 		private Result(Predicate<? super ReceiveCommand> p) {
970 			this.p = p;
971 		}
972 
973 		private Result(ReceiveCommand.Result result) {
974 			this(c -> c.getResult() == result);
975 		}
976 	}
977 
978 	private void assertResults(List<ReceiveCommand> cmds, Result... expected) {
979 		if (expected.length != cmds.size()) {
980 			throw new IllegalArgumentException(
981 					"expected " + cmds.size() + " result args");
982 		}
983 		for (int i = 0; i < cmds.size(); i++) {
984 			ReceiveCommand c = cmds.get(i);
985 			Result r = expected[i];
986 			assertTrue(String.format(
987 					"result of command (%d) should be %s, got %s %s%s",
988 					Integer.valueOf(i), r, c, c.getResult(),
989 					c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
990 					r.p.test(c));
991 		}
992 	}
993 
994 	private Map<String, ReflogEntry> getLastReflogs(String... names)
995 			throws IOException {
996 		Map<String, ReflogEntry> result = new LinkedHashMap<>();
997 		for (String name : names) {
998 			ReflogEntry e = getLastReflog(name);
999 			if (e != null) {
1000 				result.put(name, e);
1001 			}
1002 		}
1003 		return result;
1004 	}
1005 
1006 	private ReflogEntry getLastReflog(String name) throws IOException {
1007 		ReflogReader r = diskRepo.getReflogReader(name);
1008 		if (r == null) {
1009 			return null;
1010 		}
1011 		return r.getLastEntry();
1012 	}
1013 
1014 	private File getLockFile(String refName) {
1015 		return LockFile.getLockFile(refdir.fileFor(refName));
1016 	}
1017 
1018 	private void assertReflogUnchanged(Map<String, ReflogEntry> old,
1019 			String name) throws IOException {
1020 		assertReflogEquals(old.get(name), getLastReflog(name), true);
1021 	}
1022 
1023 	private static void assertReflogEquals(ReflogEntry expected,
1024 			ReflogEntry actual) {
1025 		assertReflogEquals(expected, actual, false);
1026 	}
1027 
1028 	private static void assertReflogEquals(ReflogEntry expected,
1029 			ReflogEntry actual, boolean strictTime) {
1030 		if (expected == null) {
1031 			assertNull(actual);
1032 			return;
1033 		}
1034 		assertNotNull(actual);
1035 		assertEquals(expected.getOldId(), actual.getOldId());
1036 		assertEquals(expected.getNewId(), actual.getNewId());
1037 		if (strictTime) {
1038 			assertEquals(expected.getWho(), actual.getWho());
1039 		} else {
1040 			assertEquals(expected.getWho().getName(),
1041 					actual.getWho().getName());
1042 			assertEquals(expected.getWho().getEmailAddress(),
1043 					actual.getWho().getEmailAddress());
1044 		}
1045 		assertEquals(expected.getComment(), actual.getComment());
1046 	}
1047 
1048 	private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
1049 			PersonIdent who, String comment) {
1050 		return new ReflogEntry() {
1051 			@Override
1052 			public ObjectId getOldId() {
1053 				return oldId;
1054 			}
1055 
1056 			@Override
1057 			public ObjectId getNewId() {
1058 				return newId;
1059 			}
1060 
1061 			@Override
1062 			public PersonIdent getWho() {
1063 				return who;
1064 			}
1065 
1066 			@Override
1067 			public String getComment() {
1068 				return comment;
1069 			}
1070 
1071 			@Override
1072 			public CheckoutEntry parseCheckout() {
1073 				throw new UnsupportedOperationException();
1074 			}
1075 		};
1076 	}
1077 
1078 	private boolean batchesRefUpdates() {
1079 		return atomic || useReftable;
1080 	}
1081 }