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
50 import java.io.IOException;
51 import java.text.MessageFormat;
52 import java.time.Duration;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collection;
56 import java.util.Collections;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.concurrent.TimeoutException;
60
61 import org.eclipse.jgit.internal.JGitText;
62 import org.eclipse.jgit.lib.RefUpdate.Result;
63 import org.eclipse.jgit.revwalk.RevWalk;
64 import org.eclipse.jgit.transport.PushCertificate;
65 import org.eclipse.jgit.transport.ReceiveCommand;
66 import org.eclipse.jgit.util.time.ProposedTimestamp;
67
68
69
70
71
72
73
74 public class BatchRefUpdate {
75
76
77
78
79
80
81
82
83
84 private static final Duration MAX_WAIT = Duration.ofSeconds(5);
85
86 private final RefDatabase refdb;
87
88
89 private final List<ReceiveCommand> commands;
90
91
92 private boolean allowNonFastForwards;
93
94
95 private PersonIdent refLogIdent;
96
97
98 private String refLogMessage;
99
100
101 private boolean refLogIncludeResult;
102
103
104 private PushCertificate pushCert;
105
106
107 private boolean atomic;
108
109
110 private List<String> pushOptions;
111
112
113 private List<ProposedTimestamp> timestamps;
114
115
116
117
118
119
120
121 protected BatchRefUpdate(RefDatabase refdb) {
122 this.refdb = refdb;
123 this.commands = new ArrayList<>();
124 this.atomic = refdb.performsAtomicTransactions();
125 }
126
127
128
129
130
131 public boolean isAllowNonFastForwards() {
132 return allowNonFastForwards;
133 }
134
135
136
137
138
139
140
141
142 public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
143 allowNonFastForwards = allow;
144 return this;
145 }
146
147
148 public PersonIdent getRefLogIdent() {
149 return refLogIdent;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public BatchRefUpdate setRefLogIdent(final PersonIdent pi) {
166 refLogIdent = pi;
167 return this;
168 }
169
170
171
172
173
174
175
176 public String getRefLogMessage() {
177 return refLogMessage;
178 }
179
180
181 public boolean isRefLogIncludingResult() {
182 return refLogIncludeResult;
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197 public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
198 if (msg == null && !appendStatus)
199 disableRefLog();
200 else if (msg == null && appendStatus) {
201 refLogMessage = "";
202 refLogIncludeResult = true;
203 } else {
204 refLogMessage = msg;
205 refLogIncludeResult = appendStatus;
206 }
207 return this;
208 }
209
210
211
212
213
214
215 public BatchRefUpdate disableRefLog() {
216 refLogMessage = null;
217 refLogIncludeResult = false;
218 return this;
219 }
220
221
222 public boolean isRefLogDisabled() {
223 return refLogMessage == null;
224 }
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 public BatchRefUpdate setAtomic(boolean atomic) {
244 this.atomic = atomic;
245 return this;
246 }
247
248
249
250
251
252 public boolean isAtomic() {
253 return atomic;
254 }
255
256
257
258
259
260
261
262
263
264
265
266 public void setPushCertificate(PushCertificate cert) {
267 pushCert = cert;
268 }
269
270
271
272
273
274
275
276
277
278
279 protected PushCertificate getPushCertificate() {
280 return pushCert;
281 }
282
283
284 public List<ReceiveCommand> getCommands() {
285 return Collections.unmodifiableList(commands);
286 }
287
288
289
290
291
292
293
294
295 public BatchRefUpdate addCommand(ReceiveCommand cmd) {
296 commands.add(cmd);
297 return this;
298 }
299
300
301
302
303
304
305
306
307 public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
308 return addCommand(Arrays.asList(cmd));
309 }
310
311
312
313
314
315
316
317
318 public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
319 commands.addAll(cmd);
320 return this;
321 }
322
323
324
325
326
327
328
329 public List<String> getPushOptions() {
330 return pushOptions;
331 }
332
333
334
335
336
337 public List<ProposedTimestamp> getProposedTimestamps() {
338 if (timestamps != null) {
339 return Collections.unmodifiableList(timestamps);
340 }
341 return Collections.emptyList();
342 }
343
344
345
346
347
348
349
350
351 public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
352 if (timestamps == null) {
353 timestamps = new ArrayList<>(4);
354 }
355 timestamps.add(ts);
356 return this;
357 }
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382 public void execute(RevWalk walk, ProgressMonitor monitor,
383 List<String> options) throws IOException {
384
385 if (atomic && !refdb.performsAtomicTransactions()) {
386 for (ReceiveCommand c : commands) {
387 if (c.getResult() == NOT_ATTEMPTED) {
388 c.setResult(REJECTED_OTHER_REASON,
389 JGitText.get().atomicRefUpdatesNotSupported);
390 }
391 }
392 return;
393 }
394 if (!blockUntilTimestamps(MAX_WAIT)) {
395 return;
396 }
397
398 if (options != null) {
399 pushOptions = options;
400 }
401
402 monitor.beginTask(JGitText.get().updatingReferences, commands.size());
403 List<ReceiveCommand> commands2 = new ArrayList<>(
404 commands.size());
405
406
407 for (ReceiveCommand cmd : commands) {
408 try {
409 if (cmd.getResult() == NOT_ATTEMPTED) {
410 cmd.updateType(walk);
411 switch (cmd.getType()) {
412 case CREATE:
413 commands2.add(cmd);
414 break;
415 case UPDATE:
416 case UPDATE_NONFASTFORWARD:
417 commands2.add(cmd);
418 break;
419 case DELETE:
420 RefUpdate rud = newUpdate(cmd);
421 monitor.update(1);
422 cmd.setResult(rud.delete(walk));
423 }
424 }
425 } catch (IOException err) {
426 cmd.setResult(
427 REJECTED_OTHER_REASON,
428 MessageFormat.format(JGitText.get().lockError,
429 err.getMessage()));
430 }
431 }
432 if (!commands2.isEmpty()) {
433
434 Collection<String> takenNames = new HashSet<>(refdb.getRefs(
435 RefDatabase.ALL).keySet());
436 Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
437
438
439 for (ReceiveCommand cmd : commands2) {
440 try {
441 if (cmd.getResult() == NOT_ATTEMPTED) {
442 cmd.updateType(walk);
443 RefUpdate ru = newUpdate(cmd);
444 SWITCH: switch (cmd.getType()) {
445 case DELETE:
446
447 break;
448 case UPDATE:
449 case UPDATE_NONFASTFORWARD:
450 RefUpdate ruu = newUpdate(cmd);
451 cmd.setResult(ruu.update(walk));
452 break;
453 case CREATE:
454 for (String prefix : getPrefixes(cmd.getRefName())) {
455 if (takenNames.contains(prefix)) {
456 cmd.setResult(Result.LOCK_FAILURE);
457 break SWITCH;
458 }
459 }
460 if (takenPrefixes.contains(cmd.getRefName())) {
461 cmd.setResult(Result.LOCK_FAILURE);
462 break SWITCH;
463 }
464 ru.setCheckConflicting(false);
465 addRefToPrefixes(takenPrefixes, cmd.getRefName());
466 takenNames.add(cmd.getRefName());
467 cmd.setResult(ru.update(walk));
468 }
469 }
470 } catch (IOException err) {
471 cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
472 JGitText.get().lockError, err.getMessage()));
473 } finally {
474 monitor.update(1);
475 }
476 }
477 }
478 monitor.endTask();
479 }
480
481
482
483
484
485
486
487
488
489
490 protected boolean blockUntilTimestamps(Duration maxWait) {
491 if (timestamps == null) {
492 return true;
493 }
494 try {
495 ProposedTimestamp.blockUntil(timestamps, maxWait);
496 return true;
497 } catch (TimeoutException | InterruptedException e) {
498 String msg = JGitText.get().timeIsUncertain;
499 for (ReceiveCommand c : commands) {
500 if (c.getResult() == NOT_ATTEMPTED) {
501 c.setResult(REJECTED_OTHER_REASON, msg);
502 }
503 }
504 return false;
505 }
506 }
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521 public void execute(RevWalk walk, ProgressMonitor monitor)
522 throws IOException {
523 execute(walk, monitor, null);
524 }
525
526 private static Collection<String> getTakenPrefixes(
527 final Collection<String> names) {
528 Collection<String> ref = new HashSet<>();
529 for (String name : names)
530 ref.addAll(getPrefixes(name));
531 return ref;
532 }
533
534 private static void addRefToPrefixes(Collection<String> prefixes,
535 String name) {
536 for (String prefix : getPrefixes(name)) {
537 prefixes.add(prefix);
538 }
539 }
540
541 static Collection<String> getPrefixes(String s) {
542 Collection<String> ret = new HashSet<>();
543 int p1 = s.indexOf('/');
544 while (p1 > 0) {
545 ret.add(s.substring(0, p1));
546 p1 = s.indexOf('/', p1 + 1);
547 }
548 return ret;
549 }
550
551
552
553
554
555
556
557
558
559
560
561 protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
562 RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
563 if (isRefLogDisabled())
564 ru.disableRefLog();
565 else {
566 ru.setRefLogIdent(refLogIdent);
567 ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
568 }
569 ru.setPushCertificate(pushCert);
570 switch (cmd.getType()) {
571 case DELETE:
572 if (!ObjectId.zeroId().equals(cmd.getOldId()))
573 ru.setExpectedOldObjectId(cmd.getOldId());
574 ru.setForceUpdate(true);
575 return ru;
576
577 case CREATE:
578 case UPDATE:
579 case UPDATE_NONFASTFORWARD:
580 default:
581 ru.setForceUpdate(isAllowNonFastForwards());
582 ru.setExpectedOldObjectId(cmd.getOldId());
583 ru.setNewObjectId(cmd.getNewId());
584 return ru;
585 }
586 }
587
588 @Override
589 public String toString() {
590 StringBuilder r = new StringBuilder();
591 r.append(getClass().getSimpleName()).append('[');
592 if (commands.isEmpty())
593 return r.append(']').toString();
594
595 r.append('\n');
596 for (ReceiveCommand cmd : commands) {
597 r.append(" ");
598 r.append(cmd);
599 r.append(" (").append(cmd.getResult());
600 if (cmd.getMessage() != null) {
601 r.append(": ").append(cmd.getMessage());
602 }
603 r.append(")\n");
604 }
605 return r.append(']').toString();
606 }
607 }