1 /*
2 * Copyright (C) 2008-2012, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 * Batch of reference updates to be applied to a repository.
73 * <p>
74 * The batch update is primarily useful in the transport code, where a client or
75 * server is making changes to more than one reference at a time.
76 */
77 public class BatchRefUpdate {
78 /**
79 * Maximum delay the calling thread will tolerate while waiting for a
80 * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
81 * <p>
82 * A default of 5 seconds was chosen by guessing. A common assumption is
83 * clock skew between machines on the same LAN using an NTP server also on
84 * the same LAN should be under 5 seconds. 5 seconds is also not that long
85 * for a large `git push` operation to complete.
86 *
87 * @since 4.9
88 */
89 protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
90
91 private final RefDatabase refdb;
92
93 /** Commands to apply during this batch. */
94 private final List<ReceiveCommand> commands;
95
96 /** Does the caller permit a forced update on a reference? */
97 private boolean allowNonFastForwards;
98
99 /** Identity to record action as within the reflog. */
100 private PersonIdent refLogIdent;
101
102 /** Message the caller wants included in the reflog. */
103 private String refLogMessage;
104
105 /** Should the result value be appended to {@link #refLogMessage}. */
106 private boolean refLogIncludeResult;
107
108 /**
109 * Should reflogs be written even if the configured default for this ref is
110 * not to write it.
111 */
112 private boolean forceRefLog;
113
114 /** Push certificate associated with this update. */
115 private PushCertificate pushCert;
116
117 /** Whether updates should be atomic. */
118 private boolean atomic;
119
120 /** Push options associated with this update. */
121 private List<String> pushOptions;
122
123 /** Associated timestamps that should be blocked on before update. */
124 private List<ProposedTimestamp> timestamps;
125
126 /**
127 * Initialize a new batch update.
128 *
129 * @param refdb
130 * the reference database of the repository to be updated.
131 */
132 protected BatchRefUpdate(RefDatabase refdb) {
133 this.refdb = refdb;
134 this.commands = new ArrayList<>();
135 this.atomic = refdb.performsAtomicTransactions();
136 }
137
138 /**
139 * Whether the batch update will permit a non-fast-forward update to an
140 * existing reference.
141 *
142 * @return true if the batch update will permit a non-fast-forward update to
143 * an existing reference.
144 */
145 public boolean isAllowNonFastForwards() {
146 return allowNonFastForwards;
147 }
148
149 /**
150 * Set if this update wants to permit a forced update.
151 *
152 * @param allow
153 * true if this update batch should ignore merge tests.
154 * @return {@code this}.
155 */
156 public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
157 allowNonFastForwards = allow;
158 return this;
159 }
160
161 /**
162 * Get identity of the user making the change in the reflog.
163 *
164 * @return identity of the user making the change in the reflog.
165 */
166 public PersonIdent getRefLogIdent() {
167 return refLogIdent;
168 }
169
170 /**
171 * Set the identity of the user appearing in the reflog.
172 * <p>
173 * The timestamp portion of the identity is ignored. A new identity with the
174 * current timestamp will be created automatically when the update occurs
175 * and the log record is written.
176 *
177 * @param pi
178 * identity of the user. If null the identity will be
179 * automatically determined based on the repository
180 * configuration.
181 * @return {@code this}.
182 */
183 public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
184 refLogIdent = pi;
185 return this;
186 }
187
188 /**
189 * Get the message to include in the reflog.
190 *
191 * @return message the caller wants to include in the reflog; null if the
192 * update should not be logged.
193 */
194 @Nullable
195 public String getRefLogMessage() {
196 return refLogMessage;
197 }
198
199 /**
200 * Check whether the reflog message should include the result of the update,
201 * such as fast-forward or force-update.
202 * <p>
203 * Describes the default for commands in this batch that do not override it
204 * with
205 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
206 *
207 * @return true if the message should include the result.
208 */
209 public boolean isRefLogIncludingResult() {
210 return refLogIncludeResult;
211 }
212
213 /**
214 * Set the message to include in the reflog.
215 * <p>
216 * Repository implementations may limit which reflogs are written by
217 * default, based on the project configuration. If a repo is not configured
218 * to write logs for this ref by default, setting the message alone may have
219 * no effect. To indicate that the repo should write logs for this update in
220 * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
221 * <p>
222 * Describes the default for commands in this batch that do not override it
223 * with
224 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
225 *
226 * @param msg
227 * the message to describe this change. If null and appendStatus
228 * is false, the reflog will not be updated.
229 * @param appendStatus
230 * true if the status of the ref change (fast-forward or
231 * forced-update) should be appended to the user supplied
232 * message.
233 * @return {@code this}.
234 */
235 public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
236 if (msg == null && !appendStatus)
237 disableRefLog();
238 else if (msg == null && appendStatus) {
239 refLogMessage = ""; //$NON-NLS-1$
240 refLogIncludeResult = true;
241 } else {
242 refLogMessage = msg;
243 refLogIncludeResult = appendStatus;
244 }
245 return this;
246 }
247
248 /**
249 * Don't record this update in the ref's associated reflog.
250 * <p>
251 * Equivalent to {@code setRefLogMessage(null, false)}.
252 *
253 * @return {@code this}.
254 */
255 public BatchRefUpdate disableRefLog() {
256 refLogMessage = null;
257 refLogIncludeResult = false;
258 return this;
259 }
260
261 /**
262 * Force writing a reflog for the updated ref.
263 *
264 * @param force whether to force.
265 * @return {@code this}
266 * @since 4.9
267 */
268 public BatchRefUpdate setForceRefLog(boolean force) {
269 forceRefLog = force;
270 return this;
271 }
272
273 /**
274 * Check whether log has been disabled by {@link #disableRefLog()}.
275 *
276 * @return true if disabled.
277 */
278 public boolean isRefLogDisabled() {
279 return refLogMessage == null;
280 }
281
282 /**
283 * Check whether the reflog should be written regardless of repo defaults.
284 *
285 * @return whether force writing is enabled.
286 * @since 4.9
287 */
288 protected boolean isForceRefLog() {
289 return forceRefLog;
290 }
291
292 /**
293 * Request that all updates in this batch be performed atomically.
294 * <p>
295 * When atomic updates are used, either all commands apply successfully, or
296 * none do. Commands that might have otherwise succeeded are rejected with
297 * {@code REJECTED_OTHER_REASON}.
298 * <p>
299 * This method only works if the underlying ref database supports atomic
300 * transactions, i.e.
301 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
302 * returns true. Calling this method with true if the underlying ref
303 * database does not support atomic transactions will cause all commands to
304 * fail with {@code
305 * REJECTED_OTHER_REASON}.
306 *
307 * @param atomic
308 * whether updates should be atomic.
309 * @return {@code this}
310 * @since 4.4
311 */
312 public BatchRefUpdate setAtomic(boolean atomic) {
313 this.atomic = atomic;
314 return this;
315 }
316
317 /**
318 * Whether updates should be atomic.
319 *
320 * @return atomic whether updates should be atomic.
321 * @since 4.4
322 */
323 public boolean isAtomic() {
324 return atomic;
325 }
326
327 /**
328 * Set a push certificate associated with this update.
329 * <p>
330 * This usually includes commands to update the refs in this batch, but is not
331 * required to.
332 *
333 * @param cert
334 * push certificate, may be null.
335 * @since 4.1
336 */
337 public void setPushCertificate(PushCertificate cert) {
338 pushCert = cert;
339 }
340
341 /**
342 * Set the push certificate associated with this update.
343 * <p>
344 * This usually includes commands to update the refs in this batch, but is not
345 * required to.
346 *
347 * @return push certificate, may be null.
348 * @since 4.1
349 */
350 protected PushCertificate getPushCertificate() {
351 return pushCert;
352 }
353
354 /**
355 * Get commands this update will process.
356 *
357 * @return commands this update will process.
358 */
359 public List<ReceiveCommand> getCommands() {
360 return Collections.unmodifiableList(commands);
361 }
362
363 /**
364 * Add a single command to this batch update.
365 *
366 * @param cmd
367 * the command to add, must not be null.
368 * @return {@code this}.
369 */
370 public BatchRefUpdate addCommand(ReceiveCommand cmd) {
371 commands.add(cmd);
372 return this;
373 }
374
375 /**
376 * Add commands to this batch update.
377 *
378 * @param cmd
379 * the commands to add, must not be null.
380 * @return {@code this}.
381 */
382 public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
383 return addCommand(Arrays.asList(cmd));
384 }
385
386 /**
387 * Add commands to this batch update.
388 *
389 * @param cmd
390 * the commands to add, must not be null.
391 * @return {@code this}.
392 */
393 public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
394 commands.addAll(cmd);
395 return this;
396 }
397
398 /**
399 * Gets the list of option strings associated with this update.
400 *
401 * @return push options that were passed to {@link #execute}; prior to calling
402 * {@link #execute}, always returns null.
403 * @since 4.5
404 */
405 @Nullable
406 public List<String> getPushOptions() {
407 return pushOptions;
408 }
409
410 /**
411 * Set push options associated with this update.
412 * <p>
413 * Implementations must call this at the top of {@link #execute(RevWalk,
414 * ProgressMonitor, List)}.
415 *
416 * @param options options passed to {@code execute}.
417 * @since 4.9
418 */
419 protected void setPushOptions(List<String> options) {
420 pushOptions = options;
421 }
422
423 /**
424 * Get list of timestamps the batch must wait for.
425 *
426 * @return list of timestamps the batch must wait for.
427 * @since 4.6
428 */
429 public List<ProposedTimestamp> getProposedTimestamps() {
430 if (timestamps != null) {
431 return Collections.unmodifiableList(timestamps);
432 }
433 return Collections.emptyList();
434 }
435
436 /**
437 * Request the batch to wait for the affected timestamps to resolve.
438 *
439 * @param ts
440 * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
441 * @return {@code this}.
442 * @since 4.6
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 * Execute this batch update.
454 * <p>
455 * The default implementation of this method performs a sequential reference
456 * update over each reference.
457 * <p>
458 * Implementations must respect the atomicity requirements of the underlying
459 * database as described in {@link #setAtomic(boolean)} and
460 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
461 *
462 * @param walk
463 * a RevWalk to parse tags in case the storage system wants to
464 * store them pre-peeled, a common performance optimization.
465 * @param monitor
466 * progress monitor to receive update status on.
467 * @param options
468 * a list of option strings; set null to execute without
469 * @throws java.io.IOException
470 * the database is unable to accept the update. Individual
471 * command status must be tested to determine if there is a
472 * partial failure, or a total failure.
473 * @since 4.5
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 // First delete refs. This may free the name space for some of the
499 // updates.
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 // What part of the name space is already taken
532 Collection<String> takenNames = refdb.getRefs().stream()
533 .map(Ref::getName)
534 .collect(toCollection(HashSet::new));
535 Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
536
537 // Now to the update that may require more room in the name space
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 // Performed in the first phase
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; // Explicit add or delete is not missing.
584 }
585 try {
586 walk.parseAny(id);
587 return false;
588 } catch (MissingObjectException e) {
589 return true;
590 }
591 }
592
593 /**
594 * Wait for timestamps to be in the past, aborting commands on timeout.
595 *
596 * @param maxWait
597 * maximum amount of time to wait for timestamps to resolve.
598 * @return true if timestamps were successfully waited for; false if
599 * commands were aborted.
600 * @since 4.6
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 * Execute this batch update without option strings.
622 *
623 * @param walk
624 * a RevWalk to parse tags in case the storage system wants to
625 * store them pre-peeled, a common performance optimization.
626 * @param monitor
627 * progress monitor to receive update status on.
628 * @throws java.io.IOException
629 * the database is unable to accept the update. Individual
630 * command status must be tested to determine if there is a
631 * partial failure, or a total failure.
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 * Get all path prefixes of a ref name.
648 *
649 * @param name
650 * ref name.
651 * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
652 * {@code refs} and {@code refs/heads}.
653 * @since 4.9
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 * Add prefixes of a ref name to an existing collection.
663 *
664 * @param name
665 * ref name.
666 * @param out
667 * path prefixes of the ref name. For {@code refs/heads/foo},
668 * returns {@code refs} and {@code refs/heads}.
669 * @since 4.9
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 * Create a new RefUpdate copying the batch settings.
681 *
682 * @param cmd
683 * specific command the update should be created to copy.
684 * @return a single reference update command.
685 * @throws java.io.IOException
686 * the reference database cannot make a new update object for
687 * the given reference.
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 * Check whether reflog is disabled for a command.
719 *
720 * @param cmd
721 * specific command.
722 * @return whether the reflog is disabled, taking into account the state from
723 * this instance as well as overrides in the given command.
724 * @since 4.9
725 */
726 protected boolean isRefLogDisabled(ReceiveCommand cmd) {
727 return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
728 }
729
730 /**
731 * Get reflog message for a command.
732 *
733 * @param cmd
734 * specific command.
735 * @return reflog message, taking into account the state from this instance as
736 * well as overrides in the given command.
737 * @since 4.9
738 */
739 protected String getRefLogMessage(ReceiveCommand cmd) {
740 return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
741 }
742
743 /**
744 * Check whether the reflog message for a command should include the result.
745 *
746 * @param cmd
747 * specific command.
748 * @return whether the reflog message should show the result, taking into
749 * account the state from this instance as well as overrides in the
750 * given command.
751 * @since 4.9
752 */
753 protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
754 return cmd.hasCustomRefLog()
755 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
756 }
757
758 /**
759 * Check whether the reflog for a command should be written regardless of repo
760 * defaults.
761 *
762 * @param cmd
763 * specific command.
764 * @return whether force writing is enabled.
765 * @since 4.9
766 */
767 protected boolean isForceRefLog(ReceiveCommand cmd) {
768 Boolean isForceRefLog = cmd.isForceRefLog();
769 return isForceRefLog != null ? isForceRefLog.booleanValue()
770 : isForceRefLog();
771 }
772
773 /** {@inheritDoc} */
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(" "); //$NON-NLS-1$
784 r.append(cmd);
785 r.append(" (").append(cmd.getResult()); //$NON-NLS-1$
786 if (cmd.getMessage() != null) {
787 r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
788 }
789 r.append(")\n"); //$NON-NLS-1$
790 }
791 return r.append(']').toString();
792 }
793 }