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
45 package org.eclipse.jgit.lib;
46
47 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
48 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
49 import static java.util.stream.Collectors.toCollection;
50
51 import java.io.IOException;
52 import java.text.MessageFormat;
53 import java.time.Duration;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.HashSet;
59 import java.util.List;
60 import java.util.concurrent.TimeoutException;
61
62 import org.eclipse.jgit.annotations.Nullable;
63 import org.eclipse.jgit.errors.MissingObjectException;
64 import org.eclipse.jgit.internal.JGitText;
65 import org.eclipse.jgit.lib.RefUpdate.Result;
66 import org.eclipse.jgit.revwalk.RevWalk;
67 import org.eclipse.jgit.transport.PushCertificate;
68 import org.eclipse.jgit.transport.ReceiveCommand;
69 import org.eclipse.jgit.util.time.ProposedTimestamp;
70
71
72
73
74
75
76
77 public class BatchRefUpdate {
78
79
80
81
82
83
84
85
86
87
88
89 protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
90
91 private final RefDatabase refdb;
92
93
94 private final List<ReceiveCommand> commands;
95
96
97 private boolean allowNonFastForwards;
98
99
100 private PersonIdent refLogIdent;
101
102
103 private String refLogMessage;
104
105
106 private boolean refLogIncludeResult;
107
108
109
110
111
112 private boolean forceRefLog;
113
114
115 private PushCertificate pushCert;
116
117
118 private boolean atomic;
119
120
121 private List<String> pushOptions;
122
123
124 private List<ProposedTimestamp> timestamps;
125
126
127
128
129
130
131
132 protected BatchRefUpdate(RefDatabase refdb) {
133 this.refdb = refdb;
134 this.commands = new ArrayList<>();
135 this.atomic = refdb.performsAtomicTransactions();
136 }
137
138
139
140
141
142
143
144
145 public boolean isAllowNonFastForwards() {
146 return allowNonFastForwards;
147 }
148
149
150
151
152
153
154
155
156 public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
157 allowNonFastForwards = allow;
158 return this;
159 }
160
161
162
163
164
165
166 public PersonIdent getRefLogIdent() {
167 return refLogIdent;
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
184 refLogIdent = pi;
185 return this;
186 }
187
188
189
190
191
192
193
194 @Nullable
195 public String getRefLogMessage() {
196 return refLogMessage;
197 }
198
199
200
201
202
203
204
205
206
207
208
209 public boolean isRefLogIncludingResult() {
210 return refLogIncludeResult;
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
236 if (msg == null && !appendStatus)
237 disableRefLog();
238 else if (msg == null && appendStatus) {
239 refLogMessage = "";
240 refLogIncludeResult = true;
241 } else {
242 refLogMessage = msg;
243 refLogIncludeResult = appendStatus;
244 }
245 return this;
246 }
247
248
249
250
251
252
253
254
255 public BatchRefUpdate disableRefLog() {
256 refLogMessage = null;
257 refLogIncludeResult = false;
258 return this;
259 }
260
261
262
263
264
265
266
267
268 public BatchRefUpdate setForceRefLog(boolean force) {
269 forceRefLog = force;
270 return this;
271 }
272
273
274
275
276
277
278 public boolean isRefLogDisabled() {
279 return refLogMessage == null;
280 }
281
282
283
284
285
286
287
288 protected boolean isForceRefLog() {
289 return forceRefLog;
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 public BatchRefUpdate setAtomic(boolean atomic) {
313 this.atomic = atomic;
314 return this;
315 }
316
317
318
319
320
321
322
323 public boolean isAtomic() {
324 return atomic;
325 }
326
327
328
329
330
331
332
333
334
335
336
337 public void setPushCertificate(PushCertificate cert) {
338 pushCert = cert;
339 }
340
341
342
343
344
345
346
347
348
349
350 protected PushCertificate getPushCertificate() {
351 return pushCert;
352 }
353
354
355
356
357
358
359 public List<ReceiveCommand> getCommands() {
360 return Collections.unmodifiableList(commands);
361 }
362
363
364
365
366
367
368
369
370 public BatchRefUpdate addCommand(ReceiveCommand cmd) {
371 commands.add(cmd);
372 return this;
373 }
374
375
376
377
378
379
380
381
382 public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
383 return addCommand(Arrays.asList(cmd));
384 }
385
386
387
388
389
390
391
392
393 public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
394 commands.addAll(cmd);
395 return this;
396 }
397
398
399
400
401
402
403
404
405 @Nullable
406 public List<String> getPushOptions() {
407 return pushOptions;
408 }
409
410
411
412
413
414
415
416
417
418
419 protected void setPushOptions(List<String> options) {
420 pushOptions = options;
421 }
422
423
424
425
426
427
428
429 public List<ProposedTimestamp> getProposedTimestamps() {
430 if (timestamps != null) {
431 return Collections.unmodifiableList(timestamps);
432 }
433 return Collections.emptyList();
434 }
435
436
437
438
439
440
441
442
443
444 public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
445 if (timestamps == null) {
446 timestamps = new ArrayList<>(4);
447 }
448 timestamps.add(ts);
449 return this;
450 }
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 public void execute(RevWalk walk, ProgressMonitor monitor,
476 List<String> options) throws IOException {
477
478 if (atomic && !refdb.performsAtomicTransactions()) {
479 for (ReceiveCommand c : commands) {
480 if (c.getResult() == NOT_ATTEMPTED) {
481 c.setResult(REJECTED_OTHER_REASON,
482 JGitText.get().atomicRefUpdatesNotSupported);
483 }
484 }
485 return;
486 }
487 if (!blockUntilTimestamps(MAX_WAIT)) {
488 return;
489 }
490
491 if (options != null) {
492 setPushOptions(options);
493 }
494
495 monitor.beginTask(JGitText.get().updatingReferences, commands.size());
496 List<ReceiveCommand> commands2 = new ArrayList<>(
497 commands.size());
498
499
500 for (ReceiveCommand cmd : commands) {
501 try {
502 if (cmd.getResult() == NOT_ATTEMPTED) {
503 if (isMissing(walk, cmd.getOldId())
504 || isMissing(walk, cmd.getNewId())) {
505 cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
506 continue;
507 }
508 cmd.updateType(walk);
509 switch (cmd.getType()) {
510 case CREATE:
511 commands2.add(cmd);
512 break;
513 case UPDATE:
514 case UPDATE_NONFASTFORWARD:
515 commands2.add(cmd);
516 break;
517 case DELETE:
518 RefUpdate rud = newUpdate(cmd);
519 monitor.update(1);
520 cmd.setResult(rud.delete(walk));
521 }
522 }
523 } catch (IOException err) {
524 cmd.setResult(
525 REJECTED_OTHER_REASON,
526 MessageFormat.format(JGitText.get().lockError,
527 err.getMessage()));
528 }
529 }
530 if (!commands2.isEmpty()) {
531
532 Collection<String> takenNames = refdb.getRefs().stream()
533 .map(Ref::getName)
534 .collect(toCollection(HashSet::new));
535 Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
536
537
538 for (ReceiveCommand cmd : commands2) {
539 try {
540 if (cmd.getResult() == NOT_ATTEMPTED) {
541 cmd.updateType(walk);
542 RefUpdate ru = newUpdate(cmd);
543 SWITCH: switch (cmd.getType()) {
544 case DELETE:
545
546 break;
547 case UPDATE:
548 case UPDATE_NONFASTFORWARD:
549 RefUpdate ruu = newUpdate(cmd);
550 cmd.setResult(ruu.update(walk));
551 break;
552 case CREATE:
553 for (String prefix : getPrefixes(cmd.getRefName())) {
554 if (takenNames.contains(prefix)) {
555 cmd.setResult(Result.LOCK_FAILURE);
556 break SWITCH;
557 }
558 }
559 if (takenPrefixes.contains(cmd.getRefName())) {
560 cmd.setResult(Result.LOCK_FAILURE);
561 break SWITCH;
562 }
563 ru.setCheckConflicting(false);
564 takenPrefixes.addAll(getPrefixes(cmd.getRefName()));
565 takenNames.add(cmd.getRefName());
566 cmd.setResult(ru.update(walk));
567 }
568 }
569 } catch (IOException err) {
570 cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
571 JGitText.get().lockError, err.getMessage()));
572 } finally {
573 monitor.update(1);
574 }
575 }
576 }
577 monitor.endTask();
578 }
579
580 private static boolean isMissing(RevWalk walk, ObjectId id)
581 throws IOException {
582 if (id.equals(ObjectId.zeroId())) {
583 return false;
584 }
585 try {
586 walk.parseAny(id);
587 return false;
588 } catch (MissingObjectException e) {
589 return true;
590 }
591 }
592
593
594
595
596
597
598
599
600
601
602 protected boolean blockUntilTimestamps(Duration maxWait) {
603 if (timestamps == null) {
604 return true;
605 }
606 try {
607 ProposedTimestamp.blockUntil(timestamps, maxWait);
608 return true;
609 } catch (TimeoutException | InterruptedException e) {
610 String msg = JGitText.get().timeIsUncertain;
611 for (ReceiveCommand c : commands) {
612 if (c.getResult() == NOT_ATTEMPTED) {
613 c.setResult(REJECTED_OTHER_REASON, msg);
614 }
615 }
616 return false;
617 }
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633 public void execute(RevWalk walk, ProgressMonitor monitor)
634 throws IOException {
635 execute(walk, monitor, null);
636 }
637
638 private static Collection<String> getTakenPrefixes(Collection<String> names) {
639 Collection<String> ref = new HashSet<>();
640 for (String name : names) {
641 addPrefixesTo(name, ref);
642 }
643 return ref;
644 }
645
646
647
648
649
650
651
652
653
654
655 protected static Collection<String> getPrefixes(String name) {
656 Collection<String> ret = new HashSet<>();
657 addPrefixesTo(name, ret);
658 return ret;
659 }
660
661
662
663
664
665
666
667
668
669
670
671 protected static void addPrefixesTo(String name, Collection<String> out) {
672 int p1 = name.indexOf('/');
673 while (p1 > 0) {
674 out.add(name.substring(0, p1));
675 p1 = name.indexOf('/', p1 + 1);
676 }
677 }
678
679
680
681
682
683
684
685
686
687
688
689 protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
690 RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
691 if (isRefLogDisabled(cmd)) {
692 ru.disableRefLog();
693 } else {
694 ru.setRefLogIdent(refLogIdent);
695 ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
696 ru.setForceRefLog(isForceRefLog(cmd));
697 }
698 ru.setPushCertificate(pushCert);
699 switch (cmd.getType()) {
700 case DELETE:
701 if (!ObjectId.zeroId().equals(cmd.getOldId()))
702 ru.setExpectedOldObjectId(cmd.getOldId());
703 ru.setForceUpdate(true);
704 return ru;
705
706 case CREATE:
707 case UPDATE:
708 case UPDATE_NONFASTFORWARD:
709 default:
710 ru.setForceUpdate(isAllowNonFastForwards());
711 ru.setExpectedOldObjectId(cmd.getOldId());
712 ru.setNewObjectId(cmd.getNewId());
713 return ru;
714 }
715 }
716
717
718
719
720
721
722
723
724
725
726 protected boolean isRefLogDisabled(ReceiveCommand cmd) {
727 return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
728 }
729
730
731
732
733
734
735
736
737
738
739 protected String getRefLogMessage(ReceiveCommand cmd) {
740 return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
741 }
742
743
744
745
746
747
748
749
750
751
752
753 protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
754 return cmd.hasCustomRefLog()
755 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
756 }
757
758
759
760
761
762
763
764
765
766
767 protected boolean isForceRefLog(ReceiveCommand cmd) {
768 Boolean isForceRefLog = cmd.isForceRefLog();
769 return isForceRefLog != null ? isForceRefLog.booleanValue()
770 : isForceRefLog();
771 }
772
773
774 @Override
775 public String toString() {
776 StringBuilder r = new StringBuilder();
777 r.append(getClass().getSimpleName()).append('[');
778 if (commands.isEmpty())
779 return r.append(']').toString();
780
781 r.append('\n');
782 for (ReceiveCommand cmd : commands) {
783 r.append(" ");
784 r.append(cmd);
785 r.append(" (").append(cmd.getResult());
786 if (cmd.getMessage() != null) {
787 r.append(": ").append(cmd.getMessage());
788 }
789 r.append(")\n");
790 }
791 return r.append(']').toString();
792 }
793 }