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