View Javadoc
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 }