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  
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.annotations.Nullable;
62  import org.eclipse.jgit.errors.MissingObjectException;
63  import org.eclipse.jgit.internal.JGitText;
64  import org.eclipse.jgit.lib.RefUpdate.Result;
65  import org.eclipse.jgit.revwalk.RevWalk;
66  import org.eclipse.jgit.transport.PushCertificate;
67  import org.eclipse.jgit.transport.ReceiveCommand;
68  import org.eclipse.jgit.util.time.ProposedTimestamp;
69  
70  /**
71   * Batch of reference updates to be applied to a repository.
72   * <p>
73   * The batch update is primarily useful in the transport code, where a client or
74   * server is making changes to more than one reference at a time.
75   */
76  public class BatchRefUpdate {
77  	/**
78  	 * Maximum delay the calling thread will tolerate while waiting for a
79  	 * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
80  	 * <p>
81  	 * A default of 5 seconds was chosen by guessing. A common assumption is
82  	 * clock skew between machines on the same LAN using an NTP server also on
83  	 * the same LAN should be under 5 seconds. 5 seconds is also not that long
84  	 * for a large `git push` operation to complete.
85  	 *
86  	 * @since 4.9
87  	 */
88  	protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
89  
90  	private final RefDatabase refdb;
91  
92  	/** Commands to apply during this batch. */
93  	private final List<ReceiveCommand> commands;
94  
95  	/** Does the caller permit a forced update on a reference? */
96  	private boolean allowNonFastForwards;
97  
98  	/** Identity to record action as within the reflog. */
99  	private PersonIdent refLogIdent;
100 
101 	/** Message the caller wants included in the reflog. */
102 	private String refLogMessage;
103 
104 	/** Should the result value be appended to {@link #refLogMessage}. */
105 	private boolean refLogIncludeResult;
106 
107 	/**
108 	 * Should reflogs be written even if the configured default for this ref is
109 	 * not to write it.
110 	 */
111 	private boolean forceRefLog;
112 
113 	/** Push certificate associated with this update. */
114 	private PushCertificate pushCert;
115 
116 	/** Whether updates should be atomic. */
117 	private boolean atomic;
118 
119 	/** Push options associated with this update. */
120 	private List<String> pushOptions;
121 
122 	/** Associated timestamps that should be blocked on before update. */
123 	private List<ProposedTimestamp> timestamps;
124 
125 	/**
126 	 * Initialize a new batch update.
127 	 *
128 	 * @param refdb
129 	 *            the reference database of the repository to be updated.
130 	 */
131 	protected BatchRefUpdate(RefDatabase refdb) {
132 		this.refdb = refdb;
133 		this.commands = new ArrayList<>();
134 		this.atomic = refdb.performsAtomicTransactions();
135 	}
136 
137 	/**
138 	 * Whether the batch update will permit a non-fast-forward update to an
139 	 * existing reference.
140 	 *
141 	 * @return true if the batch update will permit a non-fast-forward update to
142 	 *         an existing reference.
143 	 */
144 	public boolean isAllowNonFastForwards() {
145 		return allowNonFastForwards;
146 	}
147 
148 	/**
149 	 * Set if this update wants to permit a forced update.
150 	 *
151 	 * @param allow
152 	 *            true if this update batch should ignore merge tests.
153 	 * @return {@code this}.
154 	 */
155 	public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
156 		allowNonFastForwards = allow;
157 		return this;
158 	}
159 
160 	/**
161 	 * Get identity of the user making the change in the reflog.
162 	 *
163 	 * @return identity of the user making the change in the reflog.
164 	 */
165 	public PersonIdent getRefLogIdent() {
166 		return refLogIdent;
167 	}
168 
169 	/**
170 	 * Set the identity of the user appearing in the reflog.
171 	 * <p>
172 	 * The timestamp portion of the identity is ignored. A new identity with the
173 	 * current timestamp will be created automatically when the update occurs
174 	 * and the log record is written.
175 	 *
176 	 * @param pi
177 	 *            identity of the user. If null the identity will be
178 	 *            automatically determined based on the repository
179 	 *            configuration.
180 	 * @return {@code this}.
181 	 */
182 	public BatchRefUpdate setRefLogIdent(final PersonIdent pi) {
183 		refLogIdent = pi;
184 		return this;
185 	}
186 
187 	/**
188 	 * Get the message to include in the reflog.
189 	 *
190 	 * @return message the caller wants to include in the reflog; null if the
191 	 *         update should not be logged.
192 	 */
193 	@Nullable
194 	public String getRefLogMessage() {
195 		return refLogMessage;
196 	}
197 
198 	/**
199 	 * Check whether the reflog message should include the result of the update,
200 	 * such as fast-forward or force-update.
201 	 * <p>
202 	 * Describes the default for commands in this batch that do not override it
203 	 * with
204 	 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
205 	 *
206 	 * @return true if the message should include the result.
207 	 */
208 	public boolean isRefLogIncludingResult() {
209 		return refLogIncludeResult;
210 	}
211 
212 	/**
213 	 * Set the message to include in the reflog.
214 	 * <p>
215 	 * Repository implementations may limit which reflogs are written by
216 	 * default, based on the project configuration. If a repo is not configured
217 	 * to write logs for this ref by default, setting the message alone may have
218 	 * no effect. To indicate that the repo should write logs for this update in
219 	 * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
220 	 * <p>
221 	 * Describes the default for commands in this batch that do not override it
222 	 * with
223 	 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
224 	 *
225 	 * @param msg
226 	 *            the message to describe this change. If null and appendStatus
227 	 *            is false, the reflog will not be updated.
228 	 * @param appendStatus
229 	 *            true if the status of the ref change (fast-forward or
230 	 *            forced-update) should be appended to the user supplied
231 	 *            message.
232 	 * @return {@code this}.
233 	 */
234 	public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
235 		if (msg == null && !appendStatus)
236 			disableRefLog();
237 		else if (msg == null && appendStatus) {
238 			refLogMessage = ""; //$NON-NLS-1$
239 			refLogIncludeResult = true;
240 		} else {
241 			refLogMessage = msg;
242 			refLogIncludeResult = appendStatus;
243 		}
244 		return this;
245 	}
246 
247 	/**
248 	 * Don't record this update in the ref's associated reflog.
249 	 * <p>
250 	 * Equivalent to {@code setRefLogMessage(null, false)}.
251 	 *
252 	 * @return {@code this}.
253 	 */
254 	public BatchRefUpdate disableRefLog() {
255 		refLogMessage = null;
256 		refLogIncludeResult = false;
257 		return this;
258 	}
259 
260 	/**
261 	 * Force writing a reflog for the updated ref.
262 	 *
263 	 * @param force whether to force.
264 	 * @return {@code this}
265 	 * @since 4.9
266 	 */
267 	public BatchRefUpdate setForceRefLog(boolean force) {
268 		forceRefLog = force;
269 		return this;
270 	}
271 
272 	/**
273 	 * Check whether log has been disabled by {@link #disableRefLog()}.
274 	 *
275 	 * @return true if disabled.
276 	 */
277 	public boolean isRefLogDisabled() {
278 		return refLogMessage == null;
279 	}
280 
281 	/**
282 	 * Check whether the reflog should be written regardless of repo defaults.
283 	 *
284 	 * @return whether force writing is enabled.
285 	 * @since 4.9
286 	 */
287 	protected boolean isForceRefLog() {
288 		return forceRefLog;
289 	}
290 
291 	/**
292 	 * Request that all updates in this batch be performed atomically.
293 	 * <p>
294 	 * When atomic updates are used, either all commands apply successfully, or
295 	 * none do. Commands that might have otherwise succeeded are rejected with
296 	 * {@code REJECTED_OTHER_REASON}.
297 	 * <p>
298 	 * This method only works if the underlying ref database supports atomic
299 	 * transactions, i.e.
300 	 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
301 	 * returns true. Calling this method with true if the underlying ref
302 	 * database does not support atomic transactions will cause all commands to
303 	 * fail with {@code
304 	 * REJECTED_OTHER_REASON}.
305 	 *
306 	 * @param atomic
307 	 *            whether updates should be atomic.
308 	 * @return {@code this}
309 	 * @since 4.4
310 	 */
311 	public BatchRefUpdate setAtomic(boolean atomic) {
312 		this.atomic = atomic;
313 		return this;
314 	}
315 
316 	/**
317 	 * Whether updates should be atomic.
318 	 *
319 	 * @return atomic whether updates should be atomic.
320 	 * @since 4.4
321 	 */
322 	public boolean isAtomic() {
323 		return atomic;
324 	}
325 
326 	/**
327 	 * Set a push certificate associated with this update.
328 	 * <p>
329 	 * This usually includes commands to update the refs in this batch, but is not
330 	 * required to.
331 	 *
332 	 * @param cert
333 	 *            push certificate, may be null.
334 	 * @since 4.1
335 	 */
336 	public void setPushCertificate(PushCertificate cert) {
337 		pushCert = cert;
338 	}
339 
340 	/**
341 	 * Set the push certificate associated with this update.
342 	 * <p>
343 	 * This usually includes commands to update the refs in this batch, but is not
344 	 * required to.
345 	 *
346 	 * @return push certificate, may be null.
347 	 * @since 4.1
348 	 */
349 	protected PushCertificate getPushCertificate() {
350 		return pushCert;
351 	}
352 
353 	/**
354 	 * Get commands this update will process.
355 	 *
356 	 * @return commands this update will process.
357 	 */
358 	public List<ReceiveCommand> getCommands() {
359 		return Collections.unmodifiableList(commands);
360 	}
361 
362 	/**
363 	 * Add a single command to this batch update.
364 	 *
365 	 * @param cmd
366 	 *            the command to add, must not be null.
367 	 * @return {@code this}.
368 	 */
369 	public BatchRefUpdate addCommand(ReceiveCommand cmd) {
370 		commands.add(cmd);
371 		return this;
372 	}
373 
374 	/**
375 	 * Add commands to this batch update.
376 	 *
377 	 * @param cmd
378 	 *            the commands to add, must not be null.
379 	 * @return {@code this}.
380 	 */
381 	public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
382 		return addCommand(Arrays.asList(cmd));
383 	}
384 
385 	/**
386 	 * Add commands to this batch update.
387 	 *
388 	 * @param cmd
389 	 *            the commands to add, must not be null.
390 	 * @return {@code this}.
391 	 */
392 	public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
393 		commands.addAll(cmd);
394 		return this;
395 	}
396 
397 	/**
398 	 * Gets the list of option strings associated with this update.
399 	 *
400 	 * @return push options that were passed to {@link #execute}; prior to calling
401 	 *         {@link #execute}, always returns null.
402 	 * @since 4.5
403 	 */
404 	@Nullable
405 	public List<String> getPushOptions() {
406 		return pushOptions;
407 	}
408 
409 	/**
410 	 * Set push options associated with this update.
411 	 * <p>
412 	 * Implementations must call this at the top of {@link #execute(RevWalk,
413 	 * ProgressMonitor, List)}.
414 	 *
415 	 * @param options options passed to {@code execute}.
416 	 * @since 4.9
417 	 */
418 	protected void setPushOptions(List<String> options) {
419 		pushOptions = options;
420 	}
421 
422 	/**
423 	 * Get list of timestamps the batch must wait for.
424 	 *
425 	 * @return list of timestamps the batch must wait for.
426 	 * @since 4.6
427 	 */
428 	public List<ProposedTimestamp> getProposedTimestamps() {
429 		if (timestamps != null) {
430 			return Collections.unmodifiableList(timestamps);
431 		}
432 		return Collections.emptyList();
433 	}
434 
435 	/**
436 	 * Request the batch to wait for the affected timestamps to resolve.
437 	 *
438 	 * @param ts
439 	 *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
440 	 * @return {@code this}.
441 	 * @since 4.6
442 	 */
443 	public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
444 		if (timestamps == null) {
445 			timestamps = new ArrayList<>(4);
446 		}
447 		timestamps.add(ts);
448 		return this;
449 	}
450 
451 	/**
452 	 * Execute this batch update.
453 	 * <p>
454 	 * The default implementation of this method performs a sequential reference
455 	 * update over each reference.
456 	 * <p>
457 	 * Implementations must respect the atomicity requirements of the underlying
458 	 * database as described in {@link #setAtomic(boolean)} and
459 	 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
460 	 *
461 	 * @param walk
462 	 *            a RevWalk to parse tags in case the storage system wants to
463 	 *            store them pre-peeled, a common performance optimization.
464 	 * @param monitor
465 	 *            progress monitor to receive update status on.
466 	 * @param options
467 	 *            a list of option strings; set null to execute without
468 	 * @throws java.io.IOException
469 	 *             the database is unable to accept the update. Individual
470 	 *             command status must be tested to determine if there is a
471 	 *             partial failure, or a total failure.
472 	 * @since 4.5
473 	 */
474 	public void execute(RevWalk walk, ProgressMonitor monitor,
475 			List<String> options) throws IOException {
476 
477 		if (atomic && !refdb.performsAtomicTransactions()) {
478 			for (ReceiveCommand c : commands) {
479 				if (c.getResult() == NOT_ATTEMPTED) {
480 					c.setResult(REJECTED_OTHER_REASON,
481 							JGitText.get().atomicRefUpdatesNotSupported);
482 				}
483 			}
484 			return;
485 		}
486 		if (!blockUntilTimestamps(MAX_WAIT)) {
487 			return;
488 		}
489 
490 		if (options != null) {
491 			setPushOptions(options);
492 		}
493 
494 		monitor.beginTask(JGitText.get().updatingReferences, commands.size());
495 		List<ReceiveCommand> commands2 = new ArrayList<>(
496 				commands.size());
497 		// First delete refs. This may free the name space for some of the
498 		// updates.
499 		for (ReceiveCommand cmd : commands) {
500 			try {
501 				if (cmd.getResult() == NOT_ATTEMPTED) {
502 					if (isMissing(walk, cmd.getOldId())
503 							|| isMissing(walk, cmd.getNewId())) {
504 						cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
505 						continue;
506 					}
507 					cmd.updateType(walk);
508 					switch (cmd.getType()) {
509 					case CREATE:
510 						commands2.add(cmd);
511 						break;
512 					case UPDATE:
513 					case UPDATE_NONFASTFORWARD:
514 						commands2.add(cmd);
515 						break;
516 					case DELETE:
517 						RefUpdate rud = newUpdate(cmd);
518 						monitor.update(1);
519 						cmd.setResult(rud.delete(walk));
520 					}
521 				}
522 			} catch (IOException err) {
523 				cmd.setResult(
524 						REJECTED_OTHER_REASON,
525 						MessageFormat.format(JGitText.get().lockError,
526 								err.getMessage()));
527 			}
528 		}
529 		if (!commands2.isEmpty()) {
530 			// What part of the name space is already taken
531 			Collection<String> takenNames = new HashSet<>(refdb.getRefs(
532 					RefDatabase.ALL).keySet());
533 			Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
534 
535 			// Now to the update that may require more room in the name space
536 			for (ReceiveCommand cmd : commands2) {
537 				try {
538 					if (cmd.getResult() == NOT_ATTEMPTED) {
539 						cmd.updateType(walk);
540 						RefUpdate ru = newUpdate(cmd);
541 						SWITCH: switch (cmd.getType()) {
542 						case DELETE:
543 							// Performed in the first phase
544 							break;
545 						case UPDATE:
546 						case UPDATE_NONFASTFORWARD:
547 							RefUpdate ruu = newUpdate(cmd);
548 							cmd.setResult(ruu.update(walk));
549 							break;
550 						case CREATE:
551 							for (String prefix : getPrefixes(cmd.getRefName())) {
552 								if (takenNames.contains(prefix)) {
553 									cmd.setResult(Result.LOCK_FAILURE);
554 									break SWITCH;
555 								}
556 							}
557 							if (takenPrefixes.contains(cmd.getRefName())) {
558 								cmd.setResult(Result.LOCK_FAILURE);
559 								break SWITCH;
560 							}
561 							ru.setCheckConflicting(false);
562 							takenPrefixes.addAll(getPrefixes(cmd.getRefName()));
563 							takenNames.add(cmd.getRefName());
564 							cmd.setResult(ru.update(walk));
565 						}
566 					}
567 				} catch (IOException err) {
568 					cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
569 							JGitText.get().lockError, err.getMessage()));
570 				} finally {
571 					monitor.update(1);
572 				}
573 			}
574 		}
575 		monitor.endTask();
576 	}
577 
578 	private static boolean isMissing(RevWalk walk, ObjectId id)
579 			throws IOException {
580 		if (id.equals(ObjectId.zeroId())) {
581 			return false; // Explicit add or delete is not missing.
582 		}
583 		try {
584 			walk.parseAny(id);
585 			return false;
586 		} catch (MissingObjectException e) {
587 			return true;
588 		}
589 	}
590 
591 	/**
592 	 * Wait for timestamps to be in the past, aborting commands on timeout.
593 	 *
594 	 * @param maxWait
595 	 *            maximum amount of time to wait for timestamps to resolve.
596 	 * @return true if timestamps were successfully waited for; false if
597 	 *         commands were aborted.
598 	 * @since 4.6
599 	 */
600 	protected boolean blockUntilTimestamps(Duration maxWait) {
601 		if (timestamps == null) {
602 			return true;
603 		}
604 		try {
605 			ProposedTimestamp.blockUntil(timestamps, maxWait);
606 			return true;
607 		} catch (TimeoutException | InterruptedException e) {
608 			String msg = JGitText.get().timeIsUncertain;
609 			for (ReceiveCommand c : commands) {
610 				if (c.getResult() == NOT_ATTEMPTED) {
611 					c.setResult(REJECTED_OTHER_REASON, msg);
612 				}
613 			}
614 			return false;
615 		}
616 	}
617 
618 	/**
619 	 * Execute this batch update without option strings.
620 	 *
621 	 * @param walk
622 	 *            a RevWalk to parse tags in case the storage system wants to
623 	 *            store them pre-peeled, a common performance optimization.
624 	 * @param monitor
625 	 *            progress monitor to receive update status on.
626 	 * @throws java.io.IOException
627 	 *             the database is unable to accept the update. Individual
628 	 *             command status must be tested to determine if there is a
629 	 *             partial failure, or a total failure.
630 	 */
631 	public void execute(RevWalk walk, ProgressMonitor monitor)
632 			throws IOException {
633 		execute(walk, monitor, null);
634 	}
635 
636 	private static Collection<String> getTakenPrefixes(Collection<String> names) {
637 		Collection<String> ref = new HashSet<>();
638 		for (String name : names) {
639 			addPrefixesTo(name, ref);
640 		}
641 		return ref;
642 	}
643 
644 	/**
645 	 * Get all path prefixes of a ref name.
646 	 *
647 	 * @param name
648 	 *            ref name.
649 	 * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
650 	 *         {@code refs} and {@code refs/heads}.
651 	 * @since 4.9
652 	 */
653 	protected static Collection<String> getPrefixes(String name) {
654 		Collection<String> ret = new HashSet<>();
655 		addPrefixesTo(name, ret);
656 		return ret;
657 	}
658 
659 	/**
660 	 * Add prefixes of a ref name to an existing collection.
661 	 *
662 	 * @param name
663 	 *            ref name.
664 	 * @param out
665 	 *            path prefixes of the ref name. For {@code refs/heads/foo},
666 	 *            returns {@code refs} and {@code refs/heads}.
667 	 * @since 4.9
668 	 */
669 	protected static void addPrefixesTo(String name, Collection<String> out) {
670 		int p1 = name.indexOf('/');
671 		while (p1 > 0) {
672 			out.add(name.substring(0, p1));
673 			p1 = name.indexOf('/', p1 + 1);
674 		}
675 	}
676 
677 	/**
678 	 * Create a new RefUpdate copying the batch settings.
679 	 *
680 	 * @param cmd
681 	 *            specific command the update should be created to copy.
682 	 * @return a single reference update command.
683 	 * @throws java.io.IOException
684 	 *             the reference database cannot make a new update object for
685 	 *             the given reference.
686 	 */
687 	protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
688 		RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
689 		if (isRefLogDisabled(cmd)) {
690 			ru.disableRefLog();
691 		} else {
692 			ru.setRefLogIdent(refLogIdent);
693 			ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
694 			ru.setForceRefLog(isForceRefLog(cmd));
695 		}
696 		ru.setPushCertificate(pushCert);
697 		switch (cmd.getType()) {
698 		case DELETE:
699 			if (!ObjectId.zeroId().equals(cmd.getOldId()))
700 				ru.setExpectedOldObjectId(cmd.getOldId());
701 			ru.setForceUpdate(true);
702 			return ru;
703 
704 		case CREATE:
705 		case UPDATE:
706 		case UPDATE_NONFASTFORWARD:
707 		default:
708 			ru.setForceUpdate(isAllowNonFastForwards());
709 			ru.setExpectedOldObjectId(cmd.getOldId());
710 			ru.setNewObjectId(cmd.getNewId());
711 			return ru;
712 		}
713 	}
714 
715 	/**
716 	 * Check whether reflog is disabled for a command.
717 	 *
718 	 * @param cmd
719 	 *            specific command.
720 	 * @return whether the reflog is disabled, taking into account the state from
721 	 *         this instance as well as overrides in the given command.
722 	 * @since 4.9
723 	 */
724 	protected boolean isRefLogDisabled(ReceiveCommand cmd) {
725 		return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
726 	}
727 
728 	/**
729 	 * Get reflog message for a command.
730 	 *
731 	 * @param cmd
732 	 *            specific command.
733 	 * @return reflog message, taking into account the state from this instance as
734 	 *         well as overrides in the given command.
735 	 * @since 4.9
736 	 */
737 	protected String getRefLogMessage(ReceiveCommand cmd) {
738 		return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
739 	}
740 
741 	/**
742 	 * Check whether the reflog message for a command should include the result.
743 	 *
744 	 * @param cmd
745 	 *            specific command.
746 	 * @return whether the reflog message should show the result, taking into
747 	 *         account the state from this instance as well as overrides in the
748 	 *         given command.
749 	 * @since 4.9
750 	 */
751 	protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
752 		return cmd.hasCustomRefLog()
753 				? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
754 	}
755 
756 	/**
757 	 * Check whether the reflog for a command should be written regardless of repo
758 	 * defaults.
759 	 *
760 	 * @param cmd
761 	 *            specific command.
762 	 * @return whether force writing is enabled.
763 	 * @since 4.9
764 	 */
765 	protected boolean isForceRefLog(ReceiveCommand cmd) {
766 		Boolean isForceRefLog = cmd.isForceRefLog();
767 		return isForceRefLog != null ? isForceRefLog.booleanValue()
768 				: isForceRefLog();
769 	}
770 
771 	/** {@inheritDoc} */
772 	@Override
773 	public String toString() {
774 		StringBuilder r = new StringBuilder();
775 		r.append(getClass().getSimpleName()).append('[');
776 		if (commands.isEmpty())
777 			return r.append(']').toString();
778 
779 		r.append('\n');
780 		for (ReceiveCommand cmd : commands) {
781 			r.append("  "); //$NON-NLS-1$
782 			r.append(cmd);
783 			r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
784 			if (cmd.getMessage() != null) {
785 				r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
786 			}
787 			r.append(")\n"); //$NON-NLS-1$
788 		}
789 		return r.append(']').toString();
790 	}
791 }