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