View Javadoc
1   /*
2    * Copyright (C) 2008-2012, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
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   * Batch of reference updates to be applied to a repository.
40   * <p>
41   * The batch update is primarily useful in the transport code, where a client or
42   * server is making changes to more than one reference at a time.
43   */
44  public class BatchRefUpdate {
45  	/**
46  	 * Maximum delay the calling thread will tolerate while waiting for a
47  	 * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
48  	 * <p>
49  	 * A default of 5 seconds was chosen by guessing. A common assumption is
50  	 * clock skew between machines on the same LAN using an NTP server also on
51  	 * the same LAN should be under 5 seconds. 5 seconds is also not that long
52  	 * for a large `git push` operation to complete.
53  	 *
54  	 * @since 4.9
55  	 */
56  	protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
57  
58  	private final RefDatabase refdb;
59  
60  	/** Commands to apply during this batch. */
61  	private final List<ReceiveCommand> commands;
62  
63  	/** Does the caller permit a forced update on a reference? */
64  	private boolean allowNonFastForwards;
65  
66  	/** Identity to record action as within the reflog. */
67  	private PersonIdent refLogIdent;
68  
69  	/** Message the caller wants included in the reflog. */
70  	private String refLogMessage;
71  
72  	/** Should the result value be appended to {@link #refLogMessage}. */
73  	private boolean refLogIncludeResult;
74  
75  	/**
76  	 * Should reflogs be written even if the configured default for this ref is
77  	 * not to write it.
78  	 */
79  	private boolean forceRefLog;
80  
81  	/** Push certificate associated with this update. */
82  	private PushCertificate pushCert;
83  
84  	/** Whether updates should be atomic. */
85  	private boolean atomic;
86  
87  	/** Push options associated with this update. */
88  	private List<String> pushOptions;
89  
90  	/** Associated timestamps that should be blocked on before update. */
91  	private List<ProposedTimestamp> timestamps;
92  
93  	/**
94  	 * Initialize a new batch update.
95  	 *
96  	 * @param refdb
97  	 *            the reference database of the repository to be updated.
98  	 */
99  	protected BatchRefUpdate(RefDatabase refdb) {
100 		this.refdb = refdb;
101 		this.commands = new ArrayList<>();
102 		this.atomic = refdb.performsAtomicTransactions();
103 	}
104 
105 	/**
106 	 * Whether the batch update will permit a non-fast-forward update to an
107 	 * existing reference.
108 	 *
109 	 * @return true if the batch update will permit a non-fast-forward update to
110 	 *         an existing reference.
111 	 */
112 	public boolean isAllowNonFastForwards() {
113 		return allowNonFastForwards;
114 	}
115 
116 	/**
117 	 * Set if this update wants to permit a forced update.
118 	 *
119 	 * @param allow
120 	 *            true if this update batch should ignore merge tests.
121 	 * @return {@code this}.
122 	 */
123 	public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
124 		allowNonFastForwards = allow;
125 		return this;
126 	}
127 
128 	/**
129 	 * Get identity of the user making the change in the reflog.
130 	 *
131 	 * @return identity of the user making the change in the reflog.
132 	 */
133 	public PersonIdent getRefLogIdent() {
134 		return refLogIdent;
135 	}
136 
137 	/**
138 	 * Set the identity of the user appearing in the reflog.
139 	 * <p>
140 	 * The timestamp portion of the identity is ignored. A new identity with the
141 	 * current timestamp will be created automatically when the update occurs
142 	 * and the log record is written.
143 	 *
144 	 * @param pi
145 	 *            identity of the user. If null the identity will be
146 	 *            automatically determined based on the repository
147 	 *            configuration.
148 	 * @return {@code this}.
149 	 */
150 	public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
151 		refLogIdent = pi;
152 		return this;
153 	}
154 
155 	/**
156 	 * Get the message to include in the reflog.
157 	 *
158 	 * @return message the caller wants to include in the reflog; null if the
159 	 *         update should not be logged.
160 	 */
161 	@Nullable
162 	public String getRefLogMessage() {
163 		return refLogMessage;
164 	}
165 
166 	/**
167 	 * Check whether the reflog message should include the result of the update,
168 	 * such as fast-forward or force-update.
169 	 * <p>
170 	 * Describes the default for commands in this batch that do not override it
171 	 * with
172 	 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
173 	 *
174 	 * @return true if the message should include the result.
175 	 */
176 	public boolean isRefLogIncludingResult() {
177 		return refLogIncludeResult;
178 	}
179 
180 	/**
181 	 * Set the message to include in the reflog.
182 	 * <p>
183 	 * Repository implementations may limit which reflogs are written by
184 	 * default, based on the project configuration. If a repo is not configured
185 	 * to write logs for this ref by default, setting the message alone may have
186 	 * no effect. To indicate that the repo should write logs for this update in
187 	 * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
188 	 * <p>
189 	 * Describes the default for commands in this batch that do not override it
190 	 * with
191 	 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
192 	 *
193 	 * @param msg
194 	 *            the message to describe this change. If null and appendStatus
195 	 *            is false, the reflog will not be updated.
196 	 * @param appendStatus
197 	 *            true if the status of the ref change (fast-forward or
198 	 *            forced-update) should be appended to the user supplied
199 	 *            message.
200 	 * @return {@code this}.
201 	 */
202 	public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
203 		if (msg == null && !appendStatus)
204 			disableRefLog();
205 		else if (msg == null && appendStatus) {
206 			refLogMessage = ""; //$NON-NLS-1$
207 			refLogIncludeResult = true;
208 		} else {
209 			refLogMessage = msg;
210 			refLogIncludeResult = appendStatus;
211 		}
212 		return this;
213 	}
214 
215 	/**
216 	 * Don't record this update in the ref's associated reflog.
217 	 * <p>
218 	 * Equivalent to {@code setRefLogMessage(null, false)}.
219 	 *
220 	 * @return {@code this}.
221 	 */
222 	public BatchRefUpdate disableRefLog() {
223 		refLogMessage = null;
224 		refLogIncludeResult = false;
225 		return this;
226 	}
227 
228 	/**
229 	 * Force writing a reflog for the updated ref.
230 	 *
231 	 * @param force whether to force.
232 	 * @return {@code this}
233 	 * @since 4.9
234 	 */
235 	public BatchRefUpdate setForceRefLog(boolean force) {
236 		forceRefLog = force;
237 		return this;
238 	}
239 
240 	/**
241 	 * Check whether log has been disabled by {@link #disableRefLog()}.
242 	 *
243 	 * @return true if disabled.
244 	 */
245 	public boolean isRefLogDisabled() {
246 		return refLogMessage == null;
247 	}
248 
249 	/**
250 	 * Check whether the reflog should be written regardless of repo defaults.
251 	 *
252 	 * @return whether force writing is enabled.
253 	 * @since 4.9
254 	 */
255 	protected boolean isForceRefLog() {
256 		return forceRefLog;
257 	}
258 
259 	/**
260 	 * Request that all updates in this batch be performed atomically.
261 	 * <p>
262 	 * When atomic updates are used, either all commands apply successfully, or
263 	 * none do. Commands that might have otherwise succeeded are rejected with
264 	 * {@code REJECTED_OTHER_REASON}.
265 	 * <p>
266 	 * This method only works if the underlying ref database supports atomic
267 	 * transactions, i.e.
268 	 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
269 	 * returns true. Calling this method with true if the underlying ref
270 	 * database does not support atomic transactions will cause all commands to
271 	 * fail with {@code
272 	 * REJECTED_OTHER_REASON}.
273 	 *
274 	 * @param atomic
275 	 *            whether updates should be atomic.
276 	 * @return {@code this}
277 	 * @since 4.4
278 	 */
279 	public BatchRefUpdate setAtomic(boolean atomic) {
280 		this.atomic = atomic;
281 		return this;
282 	}
283 
284 	/**
285 	 * Whether updates should be atomic.
286 	 *
287 	 * @return atomic whether updates should be atomic.
288 	 * @since 4.4
289 	 */
290 	public boolean isAtomic() {
291 		return atomic;
292 	}
293 
294 	/**
295 	 * Set a push certificate associated with this update.
296 	 * <p>
297 	 * This usually includes commands to update the refs in this batch, but is not
298 	 * required to.
299 	 *
300 	 * @param cert
301 	 *            push certificate, may be null.
302 	 * @since 4.1
303 	 */
304 	public void setPushCertificate(PushCertificate cert) {
305 		pushCert = cert;
306 	}
307 
308 	/**
309 	 * Set the push certificate associated with this update.
310 	 * <p>
311 	 * This usually includes commands to update the refs in this batch, but is not
312 	 * required to.
313 	 *
314 	 * @return push certificate, may be null.
315 	 * @since 4.1
316 	 */
317 	protected PushCertificate getPushCertificate() {
318 		return pushCert;
319 	}
320 
321 	/**
322 	 * Get commands this update will process.
323 	 *
324 	 * @return commands this update will process.
325 	 */
326 	public List<ReceiveCommand> getCommands() {
327 		return Collections.unmodifiableList(commands);
328 	}
329 
330 	/**
331 	 * Add a single command to this batch update.
332 	 *
333 	 * @param cmd
334 	 *            the command to add, must not be null.
335 	 * @return {@code this}.
336 	 */
337 	public BatchRefUpdate addCommand(ReceiveCommand cmd) {
338 		commands.add(cmd);
339 		return this;
340 	}
341 
342 	/**
343 	 * Add commands to this batch update.
344 	 *
345 	 * @param cmd
346 	 *            the commands to add, must not be null.
347 	 * @return {@code this}.
348 	 */
349 	public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
350 		return addCommand(Arrays.asList(cmd));
351 	}
352 
353 	/**
354 	 * Add commands to this batch update.
355 	 *
356 	 * @param cmd
357 	 *            the commands to add, must not be null.
358 	 * @return {@code this}.
359 	 */
360 	public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
361 		commands.addAll(cmd);
362 		return this;
363 	}
364 
365 	/**
366 	 * Gets the list of option strings associated with this update.
367 	 *
368 	 * @return push options that were passed to {@link #execute}; prior to calling
369 	 *         {@link #execute}, always returns null.
370 	 * @since 4.5
371 	 */
372 	@Nullable
373 	public List<String> getPushOptions() {
374 		return pushOptions;
375 	}
376 
377 	/**
378 	 * Set push options associated with this update.
379 	 * <p>
380 	 * Implementations must call this at the top of {@link #execute(RevWalk,
381 	 * ProgressMonitor, List)}.
382 	 *
383 	 * @param options options passed to {@code execute}.
384 	 * @since 4.9
385 	 */
386 	protected void setPushOptions(List<String> options) {
387 		pushOptions = options;
388 	}
389 
390 	/**
391 	 * Get list of timestamps the batch must wait for.
392 	 *
393 	 * @return list of timestamps the batch must wait for.
394 	 * @since 4.6
395 	 */
396 	public List<ProposedTimestamp> getProposedTimestamps() {
397 		if (timestamps != null) {
398 			return Collections.unmodifiableList(timestamps);
399 		}
400 		return Collections.emptyList();
401 	}
402 
403 	/**
404 	 * Request the batch to wait for the affected timestamps to resolve.
405 	 *
406 	 * @param ts
407 	 *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
408 	 * @return {@code this}.
409 	 * @since 4.6
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 	 * Execute this batch update.
421 	 * <p>
422 	 * The default implementation of this method performs a sequential reference
423 	 * update over each reference.
424 	 * <p>
425 	 * Implementations must respect the atomicity requirements of the underlying
426 	 * database as described in {@link #setAtomic(boolean)} and
427 	 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
428 	 *
429 	 * @param walk
430 	 *            a RevWalk to parse tags in case the storage system wants to
431 	 *            store them pre-peeled, a common performance optimization.
432 	 * @param monitor
433 	 *            progress monitor to receive update status on.
434 	 * @param options
435 	 *            a list of option strings; set null to execute without
436 	 * @throws java.io.IOException
437 	 *             the database is unable to accept the update. Individual
438 	 *             command status must be tested to determine if there is a
439 	 *             partial failure, or a total failure.
440 	 * @since 4.5
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 		// First delete refs. This may free the name space for some of the
466 		// updates.
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 			// What part of the name space is already taken
499 			Collection<String> takenNames = refdb.getRefs().stream()
500 					.map(Ref::getName)
501 					.collect(toCollection(HashSet::new));
502 			Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
503 
504 			// Now to the update that may require more room in the name space
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 							// Performed in the first phase
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; // Explicit add or delete is not missing.
551 		}
552 		try {
553 			walk.parseAny(id);
554 			return false;
555 		} catch (MissingObjectException e) {
556 			return true;
557 		}
558 	}
559 
560 	/**
561 	 * Wait for timestamps to be in the past, aborting commands on timeout.
562 	 *
563 	 * @param maxWait
564 	 *            maximum amount of time to wait for timestamps to resolve.
565 	 * @return true if timestamps were successfully waited for; false if
566 	 *         commands were aborted.
567 	 * @since 4.6
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 	 * Execute this batch update without option strings.
589 	 *
590 	 * @param walk
591 	 *            a RevWalk to parse tags in case the storage system wants to
592 	 *            store them pre-peeled, a common performance optimization.
593 	 * @param monitor
594 	 *            progress monitor to receive update status on.
595 	 * @throws java.io.IOException
596 	 *             the database is unable to accept the update. Individual
597 	 *             command status must be tested to determine if there is a
598 	 *             partial failure, or a total failure.
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 	 * Get all path prefixes of a ref name.
615 	 *
616 	 * @param name
617 	 *            ref name.
618 	 * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
619 	 *         {@code refs} and {@code refs/heads}.
620 	 * @since 4.9
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 	 * Add prefixes of a ref name to an existing collection.
630 	 *
631 	 * @param name
632 	 *            ref name.
633 	 * @param out
634 	 *            path prefixes of the ref name. For {@code refs/heads/foo},
635 	 *            returns {@code refs} and {@code refs/heads}.
636 	 * @since 4.9
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 	 * Create a new RefUpdate copying the batch settings.
648 	 *
649 	 * @param cmd
650 	 *            specific command the update should be created to copy.
651 	 * @return a single reference update command.
652 	 * @throws java.io.IOException
653 	 *             the reference database cannot make a new update object for
654 	 *             the given reference.
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 	 * Check whether reflog is disabled for a command.
686 	 *
687 	 * @param cmd
688 	 *            specific command.
689 	 * @return whether the reflog is disabled, taking into account the state from
690 	 *         this instance as well as overrides in the given command.
691 	 * @since 4.9
692 	 */
693 	protected boolean isRefLogDisabled(ReceiveCommand cmd) {
694 		return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
695 	}
696 
697 	/**
698 	 * Get reflog message for a command.
699 	 *
700 	 * @param cmd
701 	 *            specific command.
702 	 * @return reflog message, taking into account the state from this instance as
703 	 *         well as overrides in the given command.
704 	 * @since 4.9
705 	 */
706 	protected String getRefLogMessage(ReceiveCommand cmd) {
707 		return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
708 	}
709 
710 	/**
711 	 * Check whether the reflog message for a command should include the result.
712 	 *
713 	 * @param cmd
714 	 *            specific command.
715 	 * @return whether the reflog message should show the result, taking into
716 	 *         account the state from this instance as well as overrides in the
717 	 *         given command.
718 	 * @since 4.9
719 	 */
720 	protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
721 		return cmd.hasCustomRefLog()
722 				? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
723 	}
724 
725 	/**
726 	 * Check whether the reflog for a command should be written regardless of repo
727 	 * defaults.
728 	 *
729 	 * @param cmd
730 	 *            specific command.
731 	 * @return whether force writing is enabled.
732 	 * @since 4.9
733 	 */
734 	protected boolean isForceRefLog(ReceiveCommand cmd) {
735 		Boolean isForceRefLog = cmd.isForceRefLog();
736 		return isForceRefLog != null ? isForceRefLog.booleanValue()
737 				: isForceRefLog();
738 	}
739 
740 	/** {@inheritDoc} */
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("  "); //$NON-NLS-1$
751 			r.append(cmd);
752 			r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
753 			if (cmd.getMessage() != null) {
754 				r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
755 			}
756 			r.append(")\n"); //$NON-NLS-1$
757 		}
758 		return r.append(']').toString();
759 	}
760 }