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