1
2
3
4
5
6
7
8
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;
103
104
105
106
107
108
109
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
250
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
257
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
280
281
282
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
454
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
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
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
729
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
819
820
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
830
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 }