1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
126
127
128
129
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
241
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
250
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
278
279
280
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
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
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
829
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
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 }