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.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
128
129
130
131
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
270
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
279
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
307
308
309
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
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
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
858
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
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 @SuppressWarnings("ImmutableEnumChecker")
971 final Predicate<? super ReceiveCommand> p;
972
973 private Result(Predicate<? super ReceiveCommand> p) {
974 this.p = p;
975 }
976
977 private Result(ReceiveCommand.Result result) {
978 this(c -> c.getResult() == result);
979 }
980 }
981
982 private void assertResults(
983 List<ReceiveCommand> cmds, Result... expected) {
984 if (expected.length != cmds.size()) {
985 throw new IllegalArgumentException(
986 "expected " + cmds.size() + " result args");
987 }
988 for (int i = 0; i < cmds.size(); i++) {
989 ReceiveCommand c = cmds.get(i);
990 Result r = expected[i];
991 assertTrue(
992 String.format(
993 "result of command (%d) should be %s: %s %s%s",
994 Integer.valueOf(i), r, c,
995 c.getResult(),
996 c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
997 r.p.test(c));
998 }
999 }
1000
1001 private Map<String, ReflogEntry> getLastReflogs(String... names)
1002 throws IOException {
1003 Map<String, ReflogEntry> result = new LinkedHashMap<>();
1004 for (String name : names) {
1005 ReflogEntry e = getLastReflog(name);
1006 if (e != null) {
1007 result.put(name, e);
1008 }
1009 }
1010 return result;
1011 }
1012
1013 private ReflogEntry getLastReflog(String name) throws IOException {
1014 ReflogReader r = diskRepo.getReflogReader(name);
1015 if (r == null) {
1016 return null;
1017 }
1018 return r.getLastEntry();
1019 }
1020
1021 private File getLockFile(String refName) {
1022 return LockFile.getLockFile(refdir.fileFor(refName));
1023 }
1024
1025 private void assertReflogUnchanged(
1026 Map<String, ReflogEntry> old, String name) throws IOException {
1027 assertReflogEquals(old.get(name), getLastReflog(name), true);
1028 }
1029
1030 private static void assertReflogEquals(
1031 ReflogEntry expected, ReflogEntry actual) {
1032 assertReflogEquals(expected, actual, false);
1033 }
1034
1035 private static void assertReflogEquals(
1036 ReflogEntry expected, ReflogEntry actual, boolean strictTime) {
1037 if (expected == null) {
1038 assertNull(actual);
1039 return;
1040 }
1041 assertNotNull(actual);
1042 assertEquals(expected.getOldId(), actual.getOldId());
1043 assertEquals(expected.getNewId(), actual.getNewId());
1044 if (strictTime) {
1045 assertEquals(expected.getWho(), actual.getWho());
1046 } else {
1047 assertEquals(expected.getWho().getName(), actual.getWho().getName());
1048 assertEquals(
1049 expected.getWho().getEmailAddress(),
1050 actual.getWho().getEmailAddress());
1051 }
1052 assertEquals(expected.getComment(), actual.getComment());
1053 }
1054
1055 private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
1056 PersonIdent who, String comment) {
1057 return new ReflogEntry() {
1058 @Override
1059 public ObjectId getOldId() {
1060 return oldId;
1061 }
1062
1063 @Override
1064 public ObjectId getNewId() {
1065 return newId;
1066 }
1067
1068 @Override
1069 public PersonIdent getWho() {
1070 return who;
1071 }
1072
1073 @Override
1074 public String getComment() {
1075 return comment;
1076 }
1077
1078 @Override
1079 public CheckoutEntry parseCheckout() {
1080 throw new UnsupportedOperationException();
1081 }
1082 };
1083 }
1084 }