View Javadoc
1   /*
2    * Copyright (C) 2008, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
47  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
48  
49  import java.io.IOException;
50  import java.text.MessageFormat;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.List;
54  
55  import org.eclipse.jgit.annotations.NonNull;
56  import org.eclipse.jgit.annotations.Nullable;
57  import org.eclipse.jgit.internal.JGitText;
58  import org.eclipse.jgit.lib.AnyObjectId;
59  import org.eclipse.jgit.lib.ObjectId;
60  import org.eclipse.jgit.lib.Ref;
61  import org.eclipse.jgit.lib.RefUpdate;
62  import org.eclipse.jgit.revwalk.RevCommit;
63  import org.eclipse.jgit.revwalk.RevObject;
64  import org.eclipse.jgit.revwalk.RevWalk;
65  
66  /**
67   * A command being processed by
68   * {@link org.eclipse.jgit.transport.BaseReceivePack}.
69   * <p>
70   * This command instance roughly translates to the server side representation of
71   * the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client.
72   */
73  public class ReceiveCommand {
74  	/** Type of operation requested. */
75  	public static enum Type {
76  		/** Create a new ref; the ref must not already exist. */
77  		CREATE,
78  
79  		/**
80  		 * Update an existing ref with a fast-forward update.
81  		 * <p>
82  		 * During a fast-forward update no changes will be lost; only new
83  		 * commits are inserted into the ref.
84  		 */
85  		UPDATE,
86  
87  		/**
88  		 * Update an existing ref by potentially discarding objects.
89  		 * <p>
90  		 * The current value of the ref is not fully reachable from the new
91  		 * value of the ref, so a successful command may result in one or more
92  		 * objects becoming unreachable.
93  		 */
94  		UPDATE_NONFASTFORWARD,
95  
96  		/** Delete an existing ref; the ref should already exist. */
97  		DELETE;
98  	}
99  
100 	/** Result of the update command. */
101 	public static enum Result {
102 		/** The command has not yet been attempted by the server. */
103 		NOT_ATTEMPTED,
104 
105 		/** The server is configured to deny creation of this ref. */
106 		REJECTED_NOCREATE,
107 
108 		/** The server is configured to deny deletion of this ref. */
109 		REJECTED_NODELETE,
110 
111 		/** The update is a non-fast-forward update and isn't permitted. */
112 		REJECTED_NONFASTFORWARD,
113 
114 		/** The update affects <code>HEAD</code> and cannot be permitted. */
115 		REJECTED_CURRENT_BRANCH,
116 
117 		/**
118 		 * One or more objects aren't in the repository.
119 		 * <p>
120 		 * This is severe indication of either repository corruption on the
121 		 * server side, or a bug in the client wherein the client did not supply
122 		 * all required objects during the pack transfer.
123 		 */
124 		REJECTED_MISSING_OBJECT,
125 
126 		/** Other failure; see {@link ReceiveCommand#getMessage()}. */
127 		REJECTED_OTHER_REASON,
128 
129 		/** The ref could not be locked and updated atomically; try again. */
130 		LOCK_FAILURE,
131 
132 		/** The change was completed successfully. */
133 		OK;
134 	}
135 
136 	/**
137 	 * Filter a collection of commands according to result.
138 	 *
139 	 * @param in
140 	 *            commands to filter.
141 	 * @param want
142 	 *            desired status to filter by.
143 	 * @return a copy of the command list containing only those commands with
144 	 *         the desired status.
145 	 * @since 4.2
146 	 */
147 	public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in,
148 			Result want) {
149 		List<ReceiveCommand> r;
150 		if (in instanceof Collection)
151 			r = new ArrayList<>(((Collection<?>) in).size());
152 		else
153 			r = new ArrayList<>();
154 		for (ReceiveCommand cmd : in) {
155 			if (cmd.getResult() == want)
156 				r.add(cmd);
157 		}
158 		return r;
159 	}
160 
161 	/**
162 	 * Filter a list of commands according to result.
163 	 *
164 	 * @param commands
165 	 *            commands to filter.
166 	 * @param want
167 	 *            desired status to filter by.
168 	 * @return a copy of the command list containing only those commands with
169 	 *         the desired status.
170 	 * @since 2.0
171 	 */
172 	public static List<ReceiveCommand> filter(List<ReceiveCommand> commands,
173 			Result want) {
174 		return filter((Iterable<ReceiveCommand>) commands, want);
175 	}
176 
177 	/**
178 	 * Set unprocessed commands as failed due to transaction aborted.
179 	 * <p>
180 	 * If a command is still
181 	 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} it
182 	 * will be set to
183 	 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}.
184 	 *
185 	 * @param commands
186 	 *            commands to mark as failed.
187 	 * @since 4.2
188 	 */
189 	public static void abort(Iterable<ReceiveCommand> commands) {
190 		for (ReceiveCommand c : commands) {
191 			if (c.getResult() == NOT_ATTEMPTED) {
192 				c.setResult(REJECTED_OTHER_REASON,
193 						JGitText.get().transactionAborted);
194 			}
195 		}
196 	}
197 
198 	/**
199 	 * Check whether a command failed due to transaction aborted.
200 	 *
201 	 * @param cmd
202 	 *            command.
203 	 * @return whether the command failed due to transaction aborted, as in
204 	 *         {@link #abort(Iterable)}.
205 	 * @since 4.9
206 	 */
207 	public static boolean isTransactionAborted(ReceiveCommand cmd) {
208 		return cmd.getResult() == REJECTED_OTHER_REASON
209 				&& cmd.getMessage().equals(JGitText.get().transactionAborted);
210 	}
211 
212 	/**
213 	 * Create a command to switch a reference from object to symbolic.
214 	 *
215 	 * @param oldId
216 	 *            expected oldId. May be {@code zeroId} to create.
217 	 * @param newTarget
218 	 *            new target; must begin with {@code "refs/"}.
219 	 * @param name
220 	 *            name of the reference to make symbolic.
221 	 * @return command instance.
222 	 * @since 4.10
223 	 */
224 	public static ReceiveCommand link(@NonNull ObjectId oldId,
225 			@NonNull String newTarget, @NonNull String name) {
226 		return new ReceiveCommand(oldId, newTarget, name);
227 	}
228 
229 	/**
230 	 * Create a command to switch a symbolic reference's target.
231 	 *
232 	 * @param oldTarget
233 	 *            expected old target. May be null to create.
234 	 * @param newTarget
235 	 *            new target; must begin with {@code "refs/"}.
236 	 * @param name
237 	 *            name of the reference to make symbolic.
238 	 * @return command instance.
239 	 * @since 4.10
240 	 */
241 	public static ReceiveCommand link(@Nullable String oldTarget,
242 			@NonNull String newTarget, @NonNull String name) {
243 		return new ReceiveCommand(oldTarget, newTarget, name);
244 	}
245 
246 	/**
247 	 * Create a command to switch a reference from symbolic to object.
248 	 *
249 	 * @param oldTarget
250 	 *            expected old target.
251 	 * @param newId
252 	 *            new object identifier. May be {@code zeroId()} to delete.
253 	 * @param name
254 	 *            name of the reference to convert from symbolic.
255 	 * @return command instance.
256 	 * @since 4.10
257 	 */
258 	public static ReceiveCommand unlink(@NonNull String oldTarget,
259 			@NonNull ObjectId newId, @NonNull String name) {
260 		return new ReceiveCommand(oldTarget, newId, name);
261 	}
262 
263 	private final ObjectId oldId;
264 
265 	private final String oldSymref;
266 
267 	private final ObjectId newId;
268 
269 	private final String newSymref;
270 
271 	private final String name;
272 
273 	private Type type;
274 
275 	private boolean typeIsCorrect;
276 
277 	private Ref ref;
278 
279 	private Result status = Result.NOT_ATTEMPTED;
280 
281 	private String message;
282 
283 	private boolean customRefLog;
284 
285 	private String refLogMessage;
286 
287 	private boolean refLogIncludeResult;
288 
289 	private Boolean forceRefLog;
290 
291 	/**
292 	 * Create a new command for
293 	 * {@link org.eclipse.jgit.transport.BaseReceivePack}.
294 	 *
295 	 * @param oldId
296 	 *            the expected old object id; must not be null. Use
297 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a
298 	 *            ref creation.
299 	 * @param newId
300 	 *            the new object id; must not be null. Use
301 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a
302 	 *            ref deletion.
303 	 * @param name
304 	 *            name of the ref being affected.
305 	 */
306 	public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
307 			final String name) {
308 		if (oldId == null) {
309 			throw new IllegalArgumentException(
310 					JGitText.get().oldIdMustNotBeNull);
311 		}
312 		if (newId == null) {
313 			throw new IllegalArgumentException(
314 					JGitText.get().newIdMustNotBeNull);
315 		}
316 		if (name == null || name.isEmpty()) {
317 			throw new IllegalArgumentException(
318 					JGitText.get().nameMustNotBeNullOrEmpty);
319 		}
320 		this.oldId = oldId;
321 		this.oldSymref = null;
322 		this.newId = newId;
323 		this.newSymref = null;
324 		this.name = name;
325 
326 		type = Type.UPDATE;
327 		if (ObjectId.zeroId().equals(oldId)) {
328 			type = Type.CREATE;
329 		}
330 		if (ObjectId.zeroId().equals(newId)) {
331 			type = Type.DELETE;
332 		}
333 	}
334 
335 	/**
336 	 * Create a new command for
337 	 * {@link org.eclipse.jgit.transport.BaseReceivePack}.
338 	 *
339 	 * @param oldId
340 	 *            the old object id; must not be null. Use
341 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a
342 	 *            ref creation.
343 	 * @param newId
344 	 *            the new object id; must not be null. Use
345 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a
346 	 *            ref deletion.
347 	 * @param name
348 	 *            name of the ref being affected.
349 	 * @param type
350 	 *            type of the command. Must be
351 	 *            {@link org.eclipse.jgit.transport.ReceiveCommand.Type#CREATE}
352 	 *            if {@code
353 	 *            oldId} is zero, or
354 	 *            {@link org.eclipse.jgit.transport.ReceiveCommand.Type#DELETE}
355 	 *            if {@code newId} is zero.
356 	 * @since 2.0
357 	 */
358 	public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
359 			final String name, final Type type) {
360 		if (oldId == null) {
361 			throw new IllegalArgumentException(
362 					JGitText.get().oldIdMustNotBeNull);
363 		}
364 		if (newId == null) {
365 			throw new IllegalArgumentException(
366 					JGitText.get().newIdMustNotBeNull);
367 		}
368 		if (name == null || name.isEmpty()) {
369 			throw new IllegalArgumentException(
370 					JGitText.get().nameMustNotBeNullOrEmpty);
371 		}
372 		this.oldId = oldId;
373 		this.oldSymref = null;
374 		this.newId = newId;
375 		this.newSymref = null;
376 		this.name = name;
377 		switch (type) {
378 		case CREATE:
379 			if (!ObjectId.zeroId().equals(oldId)) {
380 				throw new IllegalArgumentException(
381 						JGitText.get().createRequiresZeroOldId);
382 			}
383 			break;
384 		case DELETE:
385 			if (!ObjectId.zeroId().equals(newId)) {
386 				throw new IllegalArgumentException(
387 						JGitText.get().deleteRequiresZeroNewId);
388 			}
389 			break;
390 		case UPDATE:
391 		case UPDATE_NONFASTFORWARD:
392 			if (ObjectId.zeroId().equals(newId)
393 					|| ObjectId.zeroId().equals(oldId)) {
394 				throw new IllegalArgumentException(
395 						JGitText.get().updateRequiresOldIdAndNewId);
396 			}
397 			break;
398 		default:
399 			throw new IllegalStateException(
400 					JGitText.get().enumValueNotSupported0);
401 		}
402 		this.type = type;
403 	}
404 
405 	/**
406 	 * Create a command to switch a reference from object to symbolic.
407 	 *
408 	 * @param oldId
409 	 *            the old object id; must not be null. Use
410 	 *            {@link ObjectId#zeroId()} to indicate a ref creation.
411 	 * @param newSymref
412 	 *            new target, must begin with {@code "refs/"}. Use {@code null}
413 	 *            to indicate a ref deletion.
414 	 * @param name
415 	 *            name of the reference to make symbolic.
416 	 * @since 4.10
417 	 */
418 	private ReceiveCommand(ObjectId oldId, String newSymref, String name) {
419 		if (oldId == null) {
420 			throw new IllegalArgumentException(
421 					JGitText.get().oldIdMustNotBeNull);
422 		}
423 		if (name == null || name.isEmpty()) {
424 			throw new IllegalArgumentException(
425 					JGitText.get().nameMustNotBeNullOrEmpty);
426 		}
427 		this.oldId = oldId;
428 		this.oldSymref = null;
429 		this.newId = ObjectId.zeroId();
430 		this.newSymref = newSymref;
431 		this.name = name;
432 		if (AnyObjectId.equals(ObjectId.zeroId(), oldId)) {
433 			type = Type.CREATE;
434 		} else if (newSymref != null) {
435 			type = Type.UPDATE;
436 		} else {
437 			type = Type.DELETE;
438 		}
439 		typeIsCorrect = true;
440 	}
441 
442 	/**
443 	 * Create a command to switch a reference from symbolic to object.
444 	 *
445 	 * @param oldSymref
446 	 *            expected old target. Use {@code null} to indicate a ref
447 	 *            creation.
448 	 * @param newId
449 	 *            the new object id; must not be null. Use
450 	 *            {@link ObjectId#zeroId()} to indicate a ref deletion.
451 	 * @param name
452 	 *            name of the reference to convert from symbolic.
453 	 * @since 4.10
454 	 */
455 	private ReceiveCommand(String oldSymref, ObjectId newId, String name) {
456 		if (newId == null) {
457 			throw new IllegalArgumentException(
458 					JGitText.get().newIdMustNotBeNull);
459 		}
460 		if (name == null || name.isEmpty()) {
461 			throw new IllegalArgumentException(
462 					JGitText.get().nameMustNotBeNullOrEmpty);
463 		}
464 		this.oldId = ObjectId.zeroId();
465 		this.oldSymref = oldSymref;
466 		this.newId = newId;
467 		this.newSymref = null;
468 		this.name = name;
469 		if (oldSymref == null) {
470 			type = Type.CREATE;
471 		} else if (!AnyObjectId.equals(ObjectId.zeroId(), newId)) {
472 			type = Type.UPDATE;
473 		} else {
474 			type = Type.DELETE;
475 		}
476 		typeIsCorrect = true;
477 	}
478 
479 	/**
480 	 * Create a command to switch a symbolic reference's target.
481 	 *
482 	 * @param oldTarget
483 	 *            expected old target. Use {@code null} to indicate a ref
484 	 *            creation.
485 	 * @param newTarget
486 	 *            new target. Use {@code null} to indicate a ref deletion.
487 	 * @param name
488 	 *            name of the reference to make symbolic.
489 	 * @since 4.10
490 	 */
491 	private ReceiveCommand(@Nullable String oldTarget, String newTarget, String name) {
492 		if (name == null || name.isEmpty()) {
493 			throw new IllegalArgumentException(
494 					JGitText.get().nameMustNotBeNullOrEmpty);
495 		}
496 		this.oldId = ObjectId.zeroId();
497 		this.oldSymref = oldTarget;
498 		this.newId = ObjectId.zeroId();
499 		this.newSymref = newTarget;
500 		this.name = name;
501 		if (oldTarget == null) {
502 			if (newTarget == null) {
503 				throw new IllegalArgumentException(
504 						JGitText.get().bothRefTargetsMustNotBeNull);
505 			}
506 			type = Type.CREATE;
507 		} else if (newTarget != null) {
508 			type = Type.UPDATE;
509 		} else {
510 			type = Type.DELETE;
511 		}
512 		typeIsCorrect = true;
513 	}
514 
515 	/**
516 	 * Get the old value the client thinks the ref has.
517 	 *
518 	 * @return the old value the client thinks the ref has.
519 	 */
520 	public ObjectId getOldId() {
521 		return oldId;
522 	}
523 
524 	/**
525 	 * Get expected old target for a symbolic reference.
526 	 *
527 	 * @return expected old target for a symbolic reference.
528 	 * @since 4.10
529 	 */
530 	@Nullable
531 	public String getOldSymref() {
532 		return oldSymref;
533 	}
534 
535 	/**
536 	 * Get the requested new value for this ref.
537 	 *
538 	 * @return the requested new value for this ref.
539 	 */
540 	public ObjectId getNewId() {
541 		return newId;
542 	}
543 
544 	/**
545 	 * Get requested new target for a symbolic reference.
546 	 *
547 	 * @return requested new target for a symbolic reference.
548 	 * @since 4.10
549 	 */
550 	@Nullable
551 	public String getNewSymref() {
552 		return newSymref;
553 	}
554 
555 	/**
556 	 * Get the name of the ref being updated.
557 	 *
558 	 * @return the name of the ref being updated.
559 	 */
560 	public String getRefName() {
561 		return name;
562 	}
563 
564 	/**
565 	 * Get the type of this command; see {@link Type}.
566 	 *
567 	 * @return the type of this command; see {@link Type}.
568 	 */
569 	public Type getType() {
570 		return type;
571 	}
572 
573 	/**
574 	 * Get the ref, if this was advertised by the connection.
575 	 *
576 	 * @return the ref, if this was advertised by the connection.
577 	 */
578 	public Ref getRef() {
579 		return ref;
580 	}
581 
582 	/**
583 	 * Get the current status code of this command.
584 	 *
585 	 * @return the current status code of this command.
586 	 */
587 	public Result getResult() {
588 		return status;
589 	}
590 
591 	/**
592 	 * Get the message associated with a failure status.
593 	 *
594 	 * @return the message associated with a failure status.
595 	 */
596 	public String getMessage() {
597 		return message;
598 	}
599 
600 	/**
601 	 * Set the message to include in the reflog.
602 	 * <p>
603 	 * Overrides the default set by {@code setRefLogMessage} on any containing
604 	 * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
605 	 *
606 	 * @param msg
607 	 *            the message to describe this change. If null and appendStatus is
608 	 *            false, the reflog will not be updated.
609 	 * @param appendStatus
610 	 *            true if the status of the ref change (fast-forward or
611 	 *            forced-update) should be appended to the user supplied message.
612 	 * @since 4.9
613 	 */
614 	public void setRefLogMessage(String msg, boolean appendStatus) {
615 		customRefLog = true;
616 		if (msg == null && !appendStatus) {
617 			disableRefLog();
618 		} else if (msg == null && appendStatus) {
619 			refLogMessage = ""; //$NON-NLS-1$
620 			refLogIncludeResult = true;
621 		} else {
622 			refLogMessage = msg;
623 			refLogIncludeResult = appendStatus;
624 		}
625 	}
626 
627 	/**
628 	 * Don't record this update in the ref's associated reflog.
629 	 * <p>
630 	 * Equivalent to {@code setRefLogMessage(null, false)}.
631 	 *
632 	 * @since 4.9
633 	 */
634 	public void disableRefLog() {
635 		customRefLog = true;
636 		refLogMessage = null;
637 		refLogIncludeResult = false;
638 	}
639 
640 	/**
641 	 * Force writing a reflog for the updated ref.
642 	 *
643 	 * @param force whether to force.
644 	 * @since 4.9
645 	 */
646 	public void setForceRefLog(boolean force) {
647 		forceRefLog = Boolean.valueOf(force);
648 	}
649 
650 	/**
651 	 * Check whether this command has a custom reflog message setting that should
652 	 * override defaults in any containing
653 	 * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
654 	 * <p>
655 	 * Does not take into account whether {@code #setForceRefLog(boolean)} has
656 	 * been called.
657 	 *
658 	 * @return whether a custom reflog is set.
659 	 * @since 4.9
660 	 */
661 	public boolean hasCustomRefLog() {
662 		return customRefLog;
663 	}
664 
665 	/**
666 	 * Check whether log has been disabled by {@link #disableRefLog()}.
667 	 *
668 	 * @return true if disabled.
669 	 * @since 4.9
670 	 */
671 	public boolean isRefLogDisabled() {
672 		return refLogMessage == null;
673 	}
674 
675 	/**
676 	 * Get the message to include in the reflog.
677 	 *
678 	 * @return message the caller wants to include in the reflog; null if the
679 	 *         update should not be logged.
680 	 * @since 4.9
681 	 */
682 	@Nullable
683 	public String getRefLogMessage() {
684 		return refLogMessage;
685 	}
686 
687 	/**
688 	 * Check whether the reflog message should include the result of the update,
689 	 * such as fast-forward or force-update.
690 	 *
691 	 * @return true if the message should include the result.
692 	 * @since 4.9
693 	 */
694 	public boolean isRefLogIncludingResult() {
695 		return refLogIncludeResult;
696 	}
697 
698 	/**
699 	 * Check whether the reflog should be written regardless of repo defaults.
700 	 *
701 	 * @return whether force writing is enabled; {@code null} if
702 	 *         {@code #setForceRefLog(boolean)} was never called.
703 	 * @since 4.9
704 	 */
705 	@Nullable
706 	public Boolean isForceRefLog() {
707 		return forceRefLog;
708 	}
709 
710 	/**
711 	 * Set the status of this command.
712 	 *
713 	 * @param s
714 	 *            the new status code for this command.
715 	 */
716 	public void setResult(Result s) {
717 		setResult(s, null);
718 	}
719 
720 	/**
721 	 * Set the status of this command.
722 	 *
723 	 * @param s
724 	 *            new status code for this command.
725 	 * @param m
726 	 *            optional message explaining the new status.
727 	 */
728 	public void setResult(Result s, String m) {
729 		status = s;
730 		message = m;
731 	}
732 
733 	/**
734 	 * Update the type of this command by checking for fast-forward.
735 	 * <p>
736 	 * If the command's current type is UPDATE, a merge test will be performed
737 	 * using the supplied RevWalk to determine if {@link #getOldId()} is fully
738 	 * merged into {@link #getNewId()}. If some commits are not merged the
739 	 * update type is changed to
740 	 * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD}.
741 	 *
742 	 * @param walk
743 	 *            an instance to perform the merge test with. The caller must
744 	 *            allocate and release this object.
745 	 * @throws java.io.IOException
746 	 *             either oldId or newId is not accessible in the repository
747 	 *             used by the RevWalk. This usually indicates data corruption,
748 	 *             and the command cannot be processed.
749 	 */
750 	public void updateType(RevWalk walk) throws IOException {
751 		if (typeIsCorrect)
752 			return;
753 		if (type == Type.UPDATE && !AnyObjectId.equals(oldId, newId)) {
754 			RevObject o = walk.parseAny(oldId);
755 			RevObject n = walk.parseAny(newId);
756 			if (!(o instanceof RevCommit)
757 					|| !(n instanceof RevCommit)
758 					|| !walk.isMergedInto((RevCommit) o, (RevCommit) n))
759 				setType(Type.UPDATE_NONFASTFORWARD);
760 		}
761 		typeIsCorrect = true;
762 	}
763 
764 	/**
765 	 * Execute this command during a receive-pack session.
766 	 * <p>
767 	 * Sets the status of the command as a side effect.
768 	 *
769 	 * @param rp
770 	 *            receive-pack session.
771 	 * @since 2.0
772 	 */
773 	public void execute(BaseReceivePack rp) {
774 		try {
775 			String expTarget = getOldSymref();
776 			boolean detach = getNewSymref() != null
777 					|| (type == Type.DELETE && expTarget != null);
778 			RefUpdate ru = rp.getRepository().updateRef(getRefName(), detach);
779 			if (expTarget != null) {
780 				if (!ru.getRef().isSymbolic() || !ru.getRef().getTarget()
781 						.getName().equals(expTarget)) {
782 					setResult(Result.LOCK_FAILURE);
783 					return;
784 				}
785 			}
786 
787 			ru.setRefLogIdent(rp.getRefLogIdent());
788 			ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
789 			switch (getType()) {
790 			case DELETE:
791 				if (!ObjectId.zeroId().equals(getOldId())) {
792 					// We can only do a CAS style delete if the client
793 					// didn't bork its delete request by sending the
794 					// wrong zero id rather than the advertised one.
795 					//
796 					ru.setExpectedOldObjectId(getOldId());
797 				}
798 				ru.setForceUpdate(true);
799 				setResult(ru.delete(rp.getRevWalk()));
800 				break;
801 
802 			case CREATE:
803 			case UPDATE:
804 			case UPDATE_NONFASTFORWARD:
805 				ru.setForceUpdate(rp.isAllowNonFastForwards());
806 				ru.setExpectedOldObjectId(getOldId());
807 				ru.setRefLogMessage("push", true); //$NON-NLS-1$
808 				if (getNewSymref() != null) {
809 					setResult(ru.link(getNewSymref()));
810 				} else {
811 					ru.setNewObjectId(getNewId());
812 					setResult(ru.update(rp.getRevWalk()));
813 				}
814 				break;
815 			}
816 		} catch (IOException err) {
817 			reject(err);
818 		}
819 	}
820 
821 	void setRef(Ref r) {
822 		ref = r;
823 	}
824 
825 	void setType(Type t) {
826 		type = t;
827 	}
828 
829 	void setTypeFastForwardUpdate() {
830 		type = Type.UPDATE;
831 		typeIsCorrect = true;
832 	}
833 
834 	/**
835 	 * Set the result of this command.
836 	 *
837 	 * @param r
838 	 *            the new result code for this command.
839 	 */
840 	public void setResult(RefUpdate.Result r) {
841 		switch (r) {
842 		case NOT_ATTEMPTED:
843 			setResult(Result.NOT_ATTEMPTED);
844 			break;
845 
846 		case LOCK_FAILURE:
847 		case IO_FAILURE:
848 			setResult(Result.LOCK_FAILURE);
849 			break;
850 
851 		case NO_CHANGE:
852 		case NEW:
853 		case FORCED:
854 		case FAST_FORWARD:
855 			setResult(Result.OK);
856 			break;
857 
858 		case REJECTED:
859 			setResult(Result.REJECTED_NONFASTFORWARD);
860 			break;
861 
862 		case REJECTED_CURRENT_BRANCH:
863 			setResult(Result.REJECTED_CURRENT_BRANCH);
864 			break;
865 
866 		case REJECTED_MISSING_OBJECT:
867 			setResult(Result.REJECTED_MISSING_OBJECT);
868 			break;
869 
870 		case REJECTED_OTHER_REASON:
871 			setResult(Result.REJECTED_OTHER_REASON);
872 			break;
873 
874 		default:
875 			setResult(Result.REJECTED_OTHER_REASON, r.name());
876 			break;
877 		}
878 	}
879 
880 	void reject(IOException err) {
881 		setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
882 				JGitText.get().lockError, err.getMessage()));
883 	}
884 
885 	/** {@inheritDoc} */
886 	@SuppressWarnings("nls")
887 	@Override
888 	public String toString() {
889 		return getType().name() + ": " + getOldId().name() + " "
890 				+ getNewId().name() + " " + getRefName();
891 	}
892 }