View Javadoc
1   /*
2    * Copyright (C) 2017 Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.internal.storage.file;
45  
46  import static java.util.concurrent.TimeUnit.NANOSECONDS;
47  import static java.util.concurrent.TimeUnit.SECONDS;
48  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
49  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK;
50  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT;
51  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD;
52  import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED;
53  import static org.eclipse.jgit.lib.ObjectId.zeroId;
54  import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
55  import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
56  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
57  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
58  import static org.junit.Assert.assertEquals;
59  import static org.junit.Assert.assertFalse;
60  import static org.junit.Assert.assertNotNull;
61  import static org.junit.Assert.assertNull;
62  import static org.junit.Assert.assertTrue;
63  import static org.junit.Assume.assumeTrue;
64  
65  import java.io.File;
66  import java.io.IOException;
67  import java.util.Arrays;
68  import java.util.Collection;
69  import java.util.Collections;
70  import java.util.LinkedHashMap;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.concurrent.locks.ReentrantLock;
74  import java.util.function.Predicate;
75  
76  import org.eclipse.jgit.events.ListenerHandle;
77  import org.eclipse.jgit.events.RefsChangedListener;
78  import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
79  import org.eclipse.jgit.junit.StrictWorkMonitor;
80  import org.eclipse.jgit.junit.TestRepository;
81  import org.eclipse.jgit.lib.AnyObjectId;
82  import org.eclipse.jgit.lib.BatchRefUpdate;
83  import org.eclipse.jgit.lib.CheckoutEntry;
84  import org.eclipse.jgit.lib.ConfigConstants;
85  import org.eclipse.jgit.lib.Constants;
86  import org.eclipse.jgit.lib.NullProgressMonitor;
87  import org.eclipse.jgit.lib.ObjectId;
88  import org.eclipse.jgit.lib.PersonIdent;
89  import org.eclipse.jgit.lib.Ref;
90  import org.eclipse.jgit.lib.RefDatabase;
91  import org.eclipse.jgit.lib.RefUpdate;
92  import org.eclipse.jgit.lib.ReflogEntry;
93  import org.eclipse.jgit.lib.ReflogReader;
94  import org.eclipse.jgit.lib.Repository;
95  import org.eclipse.jgit.lib.StoredConfig;
96  import org.eclipse.jgit.revwalk.RevCommit;
97  import org.eclipse.jgit.revwalk.RevWalk;
98  import org.eclipse.jgit.transport.ReceiveCommand;
99  import org.junit.After;
100 import org.junit.Before;
101 import org.junit.Test;
102 import org.junit.runner.RunWith;
103 import org.junit.runners.Parameterized;
104 import org.junit.runners.Parameterized.Parameter;
105 import org.junit.runners.Parameterized.Parameters;
106 
107 @SuppressWarnings("boxing")
108 @RunWith(Parameterized.class)
109 public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
110 	@Parameter
111 	public boolean atomic;
112 
113 	@Parameters(name = "atomic={0}")
114 	public static Collection<Object[]> data() {
115 		return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} });
116 	}
117 
118 	private Repository diskRepo;
119 	private TestRepository<Repository> repo;
120 	private RefDirectory refdir;
121 	private RevCommit A;
122 	private RevCommit B;
123 
124 	/**
125 	 * When asserting the number of RefsChangedEvents you must account for one
126 	 * additional event due to the initial ref setup via a number of calls to
127 	 * {@link #writeLooseRef(String, AnyObjectId)} (will be fired in execute()
128 	 * when it is detected that the on-disk loose refs have changed), or for one
129 	 * additional event per {@link #writeRef(String, AnyObjectId)}.
130 	 */
131 	private int refsChangedEvents;
132 
133 	private ListenerHandle handle;
134 
135 	private RefsChangedListener refsChangedListener = event -> {
136 		refsChangedEvents++;
137 	};
138 
139 	@Override
140 	@Before
141 	public void setUp() throws Exception {
142 		super.setUp();
143 
144 		diskRepo = createBareRepository();
145 		setLogAllRefUpdates(true);
146 
147 		refdir = (RefDirectory) diskRepo.getRefDatabase();
148 		refdir.setRetrySleepMs(Arrays.asList(0, 0));
149 
150 		repo = new TestRepository<>(diskRepo);
151 		A = repo.commit().create();
152 		B = repo.commit(repo.getRevWalk().parseCommit(A));
153 		refsChangedEvents = 0;
154 		handle = diskRepo.getListenerList()
155 				.addRefsChangedListener(refsChangedListener);
156 	}
157 
158 	@After
159 	public void removeListener() {
160 		handle.remove();
161 		refsChangedEvents = 0;
162 	}
163 
164 	@Test
165 	public void simpleNoForce() throws IOException {
166 		writeLooseRef("refs/heads/master", A);
167 		writeLooseRef("refs/heads/masters", B);
168 
169 		List<ReceiveCommand> cmds = Arrays.asList(
170 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
171 				new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
172 		execute(newBatchUpdate(cmds));
173 
174 		if (atomic) {
175 			assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD);
176 			assertRefs(
177 					"refs/heads/master", A,
178 					"refs/heads/masters", B);
179 			assertEquals(1, refsChangedEvents);
180 		} else {
181 			assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
182 			assertRefs(
183 					"refs/heads/master", B,
184 					"refs/heads/masters", B);
185 			assertEquals(2, refsChangedEvents);
186 		}
187 	}
188 
189 	@Test
190 	public void simpleForce() throws IOException {
191 		writeLooseRef("refs/heads/master", A);
192 		writeLooseRef("refs/heads/masters", B);
193 
194 		List<ReceiveCommand> cmds = Arrays.asList(
195 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
196 				new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
197 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
198 
199 		assertResults(cmds, OK, OK);
200 		assertRefs(
201 				"refs/heads/master", B,
202 				"refs/heads/masters", A);
203 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
204 	}
205 
206 	@Test
207 	public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException {
208 		writeLooseRef("refs/heads/master", B);
209 
210 		List<ReceiveCommand> cmds = Arrays.asList(
211 				new ReceiveCommand(B, A, "refs/heads/master", UPDATE_NONFASTFORWARD));
212 		try (RevWalk rw = new RevWalk(diskRepo) {
213 					@Override
214 					public boolean isMergedInto(RevCommit base, RevCommit tip) {
215 						throw new AssertionError("isMergedInto() should not be called");
216 					}
217 				}) {
218 			newBatchUpdate(cmds)
219 					.setAllowNonFastForwards(true)
220 					.execute(rw, new StrictWorkMonitor());
221 		}
222 
223 		assertResults(cmds, OK);
224 		assertRefs("refs/heads/master", A);
225 		assertEquals(2, refsChangedEvents);
226 	}
227 
228 	@Test
229 	public void fileDirectoryConflict() throws IOException {
230 		writeLooseRef("refs/heads/master", A);
231 		writeLooseRef("refs/heads/masters", B);
232 
233 		List<ReceiveCommand> cmds = Arrays.asList(
234 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
235 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
236 				new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
237 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
238 
239 		if (atomic) {
240 			// Atomic update sees that master and master/x are conflicting, then marks
241 			// the first one in the list as LOCK_FAILURE and aborts the rest.
242 			assertResults(cmds,
243 					LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED);
244 			assertRefs(
245 					"refs/heads/master", A,
246 					"refs/heads/masters", B);
247 			assertEquals(1, refsChangedEvents);
248 		} else {
249 			// Non-atomic updates are applied in order: master succeeds, then master/x
250 			// fails due to conflict.
251 			assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
252 			assertRefs(
253 					"refs/heads/master", B,
254 					"refs/heads/masters", B);
255 			assertEquals(2, refsChangedEvents);
256 		}
257 	}
258 
259 	@Test
260 	public void conflictThanksToDelete() throws IOException {
261 		writeLooseRef("refs/heads/master", A);
262 		writeLooseRef("refs/heads/masters", B);
263 
264 		List<ReceiveCommand> cmds = Arrays.asList(
265 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
266 				new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
267 				new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
268 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
269 
270 		assertResults(cmds, OK, OK, OK);
271 		assertRefs(
272 				"refs/heads/master", B,
273 				"refs/heads/masters/x", A);
274 		if (atomic) {
275 			assertEquals(2, refsChangedEvents);
276 		} else {
277 			// The non-atomic case actually produces 5 events, but that's an
278 			// implementation detail. We expect at least 4 events, one for the
279 			// initial read due to writeLooseRef(), and then one for each
280 			// successful ref update.
281 			assertTrue(refsChangedEvents >= 4);
282 		}
283 	}
284 
285 	@Test
286 	public void updateToMissingObject() throws IOException {
287 		writeLooseRef("refs/heads/master", A);
288 
289 		ObjectId bad =
290 				ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
291 		List<ReceiveCommand> cmds = Arrays.asList(
292 				new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
293 				new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
294 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
295 
296 		if (atomic) {
297 			assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
298 			assertRefs("refs/heads/master", A);
299 			assertEquals(1, refsChangedEvents);
300 		} else {
301 			assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
302 			assertRefs(
303 					"refs/heads/master", A,
304 					"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 =
314 				ObjectId.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(
365 					"refs/heads/master", A,
366 					"refs/heads/foo2", B);
367 			assertEquals(2, refsChangedEvents);
368 		}
369 	}
370 
371 	@Test
372 	public void nonExistentRef() throws IOException {
373 		writeLooseRef("refs/heads/master", A);
374 
375 		List<ReceiveCommand> cmds = Arrays.asList(
376 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
377 				new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
378 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
379 
380 		if (atomic) {
381 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
382 			assertRefs("refs/heads/master", A);
383 			assertEquals(1, refsChangedEvents);
384 		} else {
385 			assertResults(cmds, OK, LOCK_FAILURE);
386 			assertRefs("refs/heads/master", B);
387 			assertEquals(2, refsChangedEvents);
388 		}
389 	}
390 
391 	@Test
392 	public void noRefLog() throws IOException {
393 		writeRef("refs/heads/master", A);
394 
395 		Map<String, ReflogEntry> oldLogs =
396 				getLastReflogs("refs/heads/master", "refs/heads/branch");
397 		assertEquals(Collections.singleton("refs/heads/master"), oldLogs.keySet());
398 
399 		List<ReceiveCommand> cmds = Arrays.asList(
400 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
401 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
402 		execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
403 
404 		assertResults(cmds, OK, OK);
405 		assertRefs(
406 				"refs/heads/master", B,
407 				"refs/heads/branch", B);
408 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
409 		assertReflogUnchanged(oldLogs, "refs/heads/master");
410 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
411 	}
412 
413 	@Test
414 	public void reflogDefaultIdent() throws IOException {
415 		writeRef("refs/heads/master", A);
416 		writeRef("refs/heads/branch2", A);
417 
418 		Map<String, ReflogEntry> oldLogs = getLastReflogs(
419 				"refs/heads/master", "refs/heads/branch1", "refs/heads/branch2");
420 		List<ReceiveCommand> cmds = Arrays.asList(
421 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
422 				new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
423 		execute(
424 				newBatchUpdate(cmds)
425 						.setAllowNonFastForwards(true)
426 						.setRefLogMessage("a reflog", false));
427 
428 		assertResults(cmds, OK, OK);
429 		assertRefs(
430 				"refs/heads/master", B,
431 				"refs/heads/branch1", B,
432 				"refs/heads/branch2", A);
433 		assertEquals(atomic ? 3 : 4, refsChangedEvents);
434 		assertReflogEquals(
435 				reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
436 				getLastReflog("refs/heads/master"));
437 		assertReflogEquals(
438 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
439 				getLastReflog("refs/heads/branch1"));
440 		assertReflogUnchanged(oldLogs, "refs/heads/branch2");
441 	}
442 
443 	@Test
444 	public void reflogAppendStatusNoMessage() throws IOException {
445 		writeRef("refs/heads/master", A);
446 		writeRef("refs/heads/branch1", B);
447 
448 		List<ReceiveCommand> cmds = Arrays.asList(
449 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
450 				new ReceiveCommand(B, A, "refs/heads/branch1", UPDATE_NONFASTFORWARD),
451 				new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
452 		execute(
453 				newBatchUpdate(cmds)
454 						.setAllowNonFastForwards(true)
455 						.setRefLogMessage(null, true));
456 
457 		assertResults(cmds, OK, OK, OK);
458 		assertRefs(
459 				"refs/heads/master", B,
460 				"refs/heads/branch1", A,
461 				"refs/heads/branch2", A);
462 		assertEquals(atomic ? 3 : 5, refsChangedEvents);
463 		assertReflogEquals(
464 				// Always forced; setAllowNonFastForwards(true) bypasses the check.
465 				reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
466 				getLastReflog("refs/heads/master"));
467 		assertReflogEquals(
468 				reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
469 				getLastReflog("refs/heads/branch1"));
470 		assertReflogEquals(
471 				reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
472 				getLastReflog("refs/heads/branch2"));
473 	}
474 
475 	@Test
476 	public void reflogAppendStatusFastForward() throws IOException {
477 		writeRef("refs/heads/master", A);
478 
479 		List<ReceiveCommand> cmds = Arrays.asList(
480 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
481 		execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
482 
483 		assertResults(cmds, OK);
484 		assertRefs("refs/heads/master", B);
485 		assertEquals(2, refsChangedEvents);
486 		assertReflogEquals(
487 				reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
488 				getLastReflog("refs/heads/master"));
489 	}
490 
491 	@Test
492 	public void reflogAppendStatusWithMessage() throws IOException {
493 		writeRef("refs/heads/master", A);
494 
495 		List<ReceiveCommand> cmds = Arrays.asList(
496 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
497 				new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
498 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
499 
500 		assertResults(cmds, OK, OK);
501 		assertRefs(
502 				"refs/heads/master", B,
503 				"refs/heads/branch", A);
504 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
505 		assertReflogEquals(
506 				reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"),
507 				getLastReflog("refs/heads/master"));
508 		assertReflogEquals(
509 				reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog: created"),
510 				getLastReflog("refs/heads/branch"));
511 	}
512 
513 	@Test
514 	public void reflogCustomIdent() throws IOException {
515 		writeRef("refs/heads/master", A);
516 
517 		List<ReceiveCommand> cmds = Arrays.asList(
518 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
519 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
520 		PersonIdent ident = new PersonIdent("A Reflog User", "reflog@example.com");
521 		execute(
522 				newBatchUpdate(cmds)
523 						.setRefLogMessage("a reflog", false)
524 						.setRefLogIdent(ident));
525 
526 		assertResults(cmds, OK, OK);
527 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
528 		assertRefs(
529 				"refs/heads/master", B,
530 				"refs/heads/branch", B);
531 		assertReflogEquals(
532 				reflog(A, B, ident, "a reflog"),
533 				getLastReflog("refs/heads/master"),
534 				true);
535 		assertReflogEquals(
536 				reflog(zeroId(), B, ident, "a reflog"),
537 				getLastReflog("refs/heads/branch"),
538 				true);
539 	}
540 
541 	@Test
542 	public void reflogDelete() throws IOException {
543 		writeRef("refs/heads/master", A);
544 		writeRef("refs/heads/branch", A);
545 		assertEquals(
546 				2, getLastReflogs("refs/heads/master", "refs/heads/branch").size());
547 
548 		List<ReceiveCommand> cmds = Arrays.asList(
549 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
550 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
551 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
552 
553 		assertResults(cmds, OK, OK);
554 		assertRefs("refs/heads/branch", B);
555 		assertEquals(atomic ? 3 : 4, refsChangedEvents);
556 		assertNull(getLastReflog("refs/heads/master"));
557 		assertReflogEquals(
558 				reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
559 				getLastReflog("refs/heads/branch"));
560 	}
561 
562 	@Test
563 	public void reflogFileDirectoryConflict() throws IOException {
564 		writeRef("refs/heads/master", A);
565 
566 		List<ReceiveCommand> cmds = Arrays.asList(
567 				new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
568 				new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
569 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
570 
571 		assertResults(cmds, OK, OK);
572 		assertRefs("refs/heads/master/x", A);
573 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
574 		assertNull(getLastReflog("refs/heads/master"));
575 		assertReflogEquals(
576 				reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
577 				getLastReflog("refs/heads/master/x"));
578 	}
579 
580 	@Test
581 	public void reflogOnLockFailure() throws IOException {
582 		writeRef("refs/heads/master", A);
583 
584 		Map<String, ReflogEntry> oldLogs =
585 				getLastReflogs("refs/heads/master", "refs/heads/branch");
586 
587 		List<ReceiveCommand> cmds = Arrays.asList(
588 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
589 				new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
590 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
591 
592 		if (atomic) {
593 			assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
594 			assertEquals(1, refsChangedEvents);
595 			assertReflogUnchanged(oldLogs, "refs/heads/master");
596 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
597 		} else {
598 			assertResults(cmds, OK, LOCK_FAILURE);
599 			assertEquals(2, refsChangedEvents);
600 			assertReflogEquals(
601 					reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
602 					getLastReflog("refs/heads/master"));
603 			assertReflogUnchanged(oldLogs, "refs/heads/branch");
604 		}
605 	}
606 
607 	@Test
608 	public void overrideRefLogMessage() throws Exception {
609 		writeRef("refs/heads/master", A);
610 
611 		List<ReceiveCommand> cmds = Arrays.asList(
612 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
613 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
614 		cmds.get(0).setRefLogMessage("custom log", false);
615 		PersonIdent ident = new PersonIdent(diskRepo);
616 		execute(
617 				newBatchUpdate(cmds)
618 						.setRefLogIdent(ident)
619 						.setRefLogMessage("a reflog", true));
620 
621 		assertResults(cmds, OK, OK);
622 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
623 		assertReflogEquals(
624 				reflog(A, B, ident, "custom log"),
625 				getLastReflog("refs/heads/master"),
626 				true);
627 		assertReflogEquals(
628 				reflog(zeroId(), B, ident, "a reflog: created"),
629 				getLastReflog("refs/heads/branch"),
630 				true);
631 	}
632 
633 	@Test
634 	public void overrideDisableRefLog() throws Exception {
635 		writeRef("refs/heads/master", A);
636 
637 		Map<String, ReflogEntry> oldLogs =
638 				getLastReflogs("refs/heads/master", "refs/heads/branch");
639 
640 		List<ReceiveCommand> cmds = Arrays.asList(
641 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
642 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
643 		cmds.get(0).disableRefLog();
644 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
645 
646 		assertResults(cmds, OK, OK);
647 		assertEquals(atomic ? 2 : 3, refsChangedEvents);
648 		assertReflogUnchanged(oldLogs, "refs/heads/master");
649 		assertReflogEquals(
650 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog: created"),
651 				getLastReflog("refs/heads/branch"));
652 	}
653 
654 	@Test
655 	public void refLogNotWrittenWithoutConfigOption() throws Exception {
656 		setLogAllRefUpdates(false);
657 		writeRef("refs/heads/master", A);
658 
659 		Map<String, ReflogEntry> oldLogs =
660 				getLastReflogs("refs/heads/master", "refs/heads/branch");
661 		assertTrue(oldLogs.isEmpty());
662 
663 		List<ReceiveCommand> cmds = Arrays.asList(
664 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
665 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
666 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
667 
668 		assertResults(cmds, OK, OK);
669 		assertReflogUnchanged(oldLogs, "refs/heads/master");
670 		assertReflogUnchanged(oldLogs, "refs/heads/branch");
671 	}
672 
673 	@Test
674 	public void forceRefLogInUpdate() throws Exception {
675 		setLogAllRefUpdates(false);
676 		writeRef("refs/heads/master", A);
677 		assertTrue(
678 				getLastReflogs("refs/heads/master", "refs/heads/branch").isEmpty());
679 
680 		List<ReceiveCommand> cmds = Arrays.asList(
681 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
682 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
683 		execute(
684 				newBatchUpdate(cmds)
685 						.setRefLogMessage("a reflog", false)
686 						.setForceRefLog(true));
687 
688 		assertResults(cmds, OK, OK);
689 		assertReflogEquals(
690 				reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
691 				getLastReflog("refs/heads/master"));
692 		assertReflogEquals(
693 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
694 				getLastReflog("refs/heads/branch"));
695 	}
696 
697 	@Test
698 	public void forceRefLogInCommand() throws Exception {
699 		setLogAllRefUpdates(false);
700 		writeRef("refs/heads/master", A);
701 
702 		Map<String, ReflogEntry> oldLogs =
703 				getLastReflogs("refs/heads/master", "refs/heads/branch");
704 		assertTrue(oldLogs.isEmpty());
705 
706 		List<ReceiveCommand> cmds = Arrays.asList(
707 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
708 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
709 		cmds.get(1).setForceRefLog(true);
710 		execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
711 
712 		assertResults(cmds, OK, OK);
713 		assertReflogUnchanged(oldLogs, "refs/heads/master");
714 		assertReflogEquals(
715 				reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
716 				getLastReflog("refs/heads/branch"));
717 	}
718 
719 	@Test
720 	public void packedRefsLockFailure() throws Exception {
721 		writeLooseRef("refs/heads/master", A);
722 
723 		List<ReceiveCommand> cmds = Arrays.asList(
724 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
725 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
726 
727 		LockFile myLock = refdir.lockPackedRefs();
728 		try {
729 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
730 
731 			assertFalse(getLockFile("refs/heads/master").exists());
732 			assertFalse(getLockFile("refs/heads/branch").exists());
733 
734 			if (atomic) {
735 				assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
736 				assertRefs("refs/heads/master", A);
737 				assertEquals(1, refsChangedEvents);
738 			} else {
739 				// Only operates on loose refs, doesn't care that packed-refs is locked.
740 				assertResults(cmds, OK, OK);
741 				assertRefs(
742 						"refs/heads/master", B,
743 						"refs/heads/branch", B);
744 				assertEquals(3, refsChangedEvents);
745 			}
746 		} finally {
747 			myLock.unlock();
748 		}
749 	}
750 
751 	@Test
752 	public void oneRefLockFailure() throws Exception {
753 		writeLooseRef("refs/heads/master", A);
754 
755 		List<ReceiveCommand> cmds = Arrays.asList(
756 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
757 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
758 
759 		LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
760 		assertTrue(myLock.lock());
761 		try {
762 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
763 
764 			assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
765 			assertFalse(getLockFile("refs/heads/branch").exists());
766 
767 			if (atomic) {
768 				assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
769 				assertRefs("refs/heads/master", A);
770 				assertEquals(1, refsChangedEvents);
771 			} else {
772 				assertResults(cmds, OK, LOCK_FAILURE);
773 				assertRefs(
774 						"refs/heads/branch", B,
775 						"refs/heads/master", A);
776 				assertEquals(2, refsChangedEvents);
777 			}
778 		} finally {
779 			myLock.unlock();
780 		}
781 	}
782 
783 	@Test
784 	public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
785 		writeLooseRef("refs/heads/master", A);
786 
787 		List<ReceiveCommand> cmds = Arrays.asList(
788 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
789 
790 		LockFile myLock = refdir.lockPackedRefs();
791 		try {
792 			execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
793 
794 			assertFalse(getLockFile("refs/heads/master").exists());
795 			assertResults(cmds, OK);
796 			assertEquals(2, refsChangedEvents);
797 			assertRefs("refs/heads/master", B);
798 		} finally {
799 			myLock.unlock();
800 		}
801 	}
802 
803 	@Test
804 	public void atomicUpdateRespectsInProcessLock() throws Exception {
805 		assumeTrue(atomic);
806 
807 		writeLooseRef("refs/heads/master", A);
808 
809 		List<ReceiveCommand> cmds = Arrays.asList(
810 				new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
811 				new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
812 
813 		Thread t = new Thread(() -> {
814 			try {
815 				execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
816 			} catch (Exception e) {
817 				throw new RuntimeException(e);
818 			}
819 		});
820 
821 		ReentrantLock l = refdir.inProcessPackedRefsLock;
822 		l.lock();
823 		try {
824 			t.start();
825 			long timeoutSecs = 10;
826 			long startNanos = System.nanoTime();
827 
828 			// Hold onto the lock until we observe the worker thread has attempted to
829 			// acquire it.
830 			while (l.getQueueLength() == 0) {
831 				long elapsedNanos = System.nanoTime() - startNanos;
832 				assertTrue(
833 						"timed out waiting for work thread to attempt to acquire lock",
834 						NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
835 				Thread.sleep(3);
836 			}
837 
838 			// Once we unlock, the worker thread should finish the update promptly.
839 			l.unlock();
840 			t.join(SECONDS.toMillis(timeoutSecs));
841 			assertFalse(t.isAlive());
842 		} finally {
843 			if (l.isHeldByCurrentThread()) {
844 				l.unlock();
845 			}
846 		}
847 
848 		assertResults(cmds, OK, OK);
849 		assertEquals(2, refsChangedEvents);
850 		assertRefs(
851 				"refs/heads/master", B,
852 				"refs/heads/branch", B);
853 	}
854 
855 	private void setLogAllRefUpdates(boolean enable) throws Exception {
856 		StoredConfig cfg = diskRepo.getConfig();
857 		cfg.load();
858 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
859 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
860 		cfg.save();
861 	}
862 
863 	private void writeLooseRef(String name, AnyObjectId id) throws IOException {
864 		write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
865 	}
866 
867 	private void writeRef(String name, AnyObjectId id) throws IOException {
868 		RefUpdate u = diskRepo.updateRef(name);
869 		u.setRefLogMessage(getClass().getSimpleName(), false);
870 		u.setForceUpdate(true);
871 		u.setNewObjectId(id);
872 		RefUpdate.Result r = u.update();
873 		switch (r) {
874 			case NEW:
875 			case FORCED:
876 				return;
877 			default:
878 				throw new IOException("Got " + r + " while updating " + name);
879 		}
880 	}
881 
882 	private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
883 		BatchRefUpdate u = refdir.newBatchUpdate();
884 		if (atomic) {
885 			assertTrue(u.isAtomic());
886 		} else {
887 			u.setAtomic(false);
888 		}
889 		u.addCommand(cmds);
890 		return u;
891 	}
892 
893 	private void execute(BatchRefUpdate u) throws IOException {
894 		execute(u, false);
895 	}
896 
897 	private void execute(BatchRefUpdate u, boolean strictWork) throws IOException {
898 		try (RevWalk rw = new RevWalk(diskRepo)) {
899 			u.execute(rw,
900 					strictWork ? new StrictWorkMonitor() : NullProgressMonitor.INSTANCE);
901 		}
902 	}
903 
904 	private void assertRefs(Object... args) throws IOException {
905 		if (args.length % 2 != 0) {
906 			throw new IllegalArgumentException(
907 					"expected even number of args: " + Arrays.toString(args));
908 		}
909 
910 		Map<String, AnyObjectId> expected = new LinkedHashMap<>();
911 		for (int i = 0; i < args.length; i += 2) {
912 			expected.put((String) args[i], (AnyObjectId) args[i + 1]);
913 		}
914 
915 		Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
916 		Ref actualHead = refs.remove(Constants.HEAD);
917 		if (actualHead != null) {
918 			String actualLeafName = actualHead.getLeaf().getName();
919 			assertEquals(
920 					"expected HEAD to point to refs/heads/master, got: " + actualLeafName,
921 					"refs/heads/master", actualLeafName);
922 			AnyObjectId expectedMaster = expected.get("refs/heads/master");
923 			assertNotNull("expected master ref since HEAD exists", expectedMaster);
924 			assertEquals(expectedMaster, actualHead.getObjectId());
925 		}
926 
927 		Map<String, AnyObjectId> actual = new LinkedHashMap<>();
928 		refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
929 
930 		assertEquals(expected.keySet(), actual.keySet());
931 		actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
932 	}
933 
934 	enum Result {
935 		OK(ReceiveCommand.Result.OK),
936 		LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE),
937 		REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD),
938 		REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
939 		TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
940 
941 		final Predicate<? super ReceiveCommand> p;
942 
943 		private Result(Predicate<? super ReceiveCommand> p) {
944 			this.p = p;
945 		}
946 
947 		private Result(ReceiveCommand.Result result) {
948 			this(c -> c.getResult() == result);
949 		}
950 	}
951 
952 	private void assertResults(
953 			List<ReceiveCommand> cmds, Result... expected) {
954 		if (expected.length != cmds.size()) {
955 			throw new IllegalArgumentException(
956 					"expected " + cmds.size() + " result args");
957 		}
958 		for (int i = 0; i < cmds.size(); i++) {
959 			ReceiveCommand c = cmds.get(i);
960 			Result r = expected[i];
961 			assertTrue(
962 					String.format(
963 							"result of command (%d) should be %s: %s %s%s",
964 							Integer.valueOf(i), r, c,
965 							c.getResult(),
966 							c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
967 					r.p.test(c));
968 		}
969 	}
970 
971 	private Map<String, ReflogEntry> getLastReflogs(String... names)
972 			throws IOException {
973 		Map<String, ReflogEntry> result = new LinkedHashMap<>();
974 		for (String name : names) {
975 			ReflogEntry e = getLastReflog(name);
976 			if (e != null) {
977 				result.put(name, e);
978 			}
979 		}
980 		return result;
981 	}
982 
983 	private ReflogEntry getLastReflog(String name) throws IOException {
984 		ReflogReader r = diskRepo.getReflogReader(name);
985 		if (r == null) {
986 			return null;
987 		}
988 		return r.getLastEntry();
989 	}
990 
991 	private File getLockFile(String refName) {
992 		return LockFile.getLockFile(refdir.fileFor(refName));
993 	}
994 
995 	private void assertReflogUnchanged(
996 			Map<String, ReflogEntry> old, String name) throws IOException {
997 		assertReflogEquals(old.get(name), getLastReflog(name), true);
998 	}
999 
1000 	private static void assertReflogEquals(
1001 			ReflogEntry expected, ReflogEntry actual) {
1002 		assertReflogEquals(expected, actual, false);
1003 	}
1004 
1005 	private static void assertReflogEquals(
1006 			ReflogEntry expected, ReflogEntry actual, boolean strictTime) {
1007 		if (expected == null) {
1008 			assertNull(actual);
1009 			return;
1010 		}
1011 		assertNotNull(actual);
1012 		assertEquals(expected.getOldId(), actual.getOldId());
1013 		assertEquals(expected.getNewId(), actual.getNewId());
1014 		if (strictTime) {
1015 			assertEquals(expected.getWho(), actual.getWho());
1016 		} else {
1017 			assertEquals(expected.getWho().getName(), actual.getWho().getName());
1018 			assertEquals(
1019 					expected.getWho().getEmailAddress(),
1020 					actual.getWho().getEmailAddress());
1021 		}
1022 		assertEquals(expected.getComment(), actual.getComment());
1023 	}
1024 
1025 	private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
1026 			PersonIdent who, String comment) {
1027 		return new ReflogEntry() {
1028 			@Override
1029 			public ObjectId getOldId() {
1030 				return oldId;
1031 			}
1032 
1033 			@Override
1034 			public ObjectId getNewId() {
1035 				return newId;
1036 			}
1037 
1038 			@Override
1039 			public PersonIdent getWho() {
1040 				return who;
1041 			}
1042 
1043 			@Override
1044 			public String getComment() {
1045 				return comment;
1046 			}
1047 
1048 			@Override
1049 			public CheckoutEntry parseCheckout() {
1050 				throw new UnsupportedOperationException();
1051 			}
1052 		};
1053 	}
1054 }