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.Nullable;
56  import org.eclipse.jgit.internal.JGitText;
57  import org.eclipse.jgit.lib.AnyObjectId;
58  import org.eclipse.jgit.lib.ObjectId;
59  import org.eclipse.jgit.lib.Ref;
60  import org.eclipse.jgit.lib.RefUpdate;
61  import org.eclipse.jgit.revwalk.RevCommit;
62  import org.eclipse.jgit.revwalk.RevObject;
63  import org.eclipse.jgit.revwalk.RevWalk;
64  
65  /**
66   * A command being processed by {@link BaseReceivePack}.
67   * <p>
68   * This command instance roughly translates to the server side representation of
69   * the {@link RemoteRefUpdate} created by the client.
70   */
71  public class ReceiveCommand {
72  	/** Type of operation requested. */
73  	public static enum Type {
74  		/** Create a new ref; the ref must not already exist. */
75  		CREATE,
76  
77  		/**
78  		 * Update an existing ref with a fast-forward update.
79  		 * <p>
80  		 * During a fast-forward update no changes will be lost; only new
81  		 * commits are inserted into the ref.
82  		 */
83  		UPDATE,
84  
85  		/**
86  		 * Update an existing ref by potentially discarding objects.
87  		 * <p>
88  		 * The current value of the ref is not fully reachable from the new
89  		 * value of the ref, so a successful command may result in one or more
90  		 * objects becoming unreachable.
91  		 */
92  		UPDATE_NONFASTFORWARD,
93  
94  		/** Delete an existing ref; the ref should already exist. */
95  		DELETE;
96  	}
97  
98  	/** Result of the update command. */
99  	public static enum Result {
100 		/** The command has not yet been attempted by the server. */
101 		NOT_ATTEMPTED,
102 
103 		/** The server is configured to deny creation of this ref. */
104 		REJECTED_NOCREATE,
105 
106 		/** The server is configured to deny deletion of this ref. */
107 		REJECTED_NODELETE,
108 
109 		/** The update is a non-fast-forward update and isn't permitted. */
110 		REJECTED_NONFASTFORWARD,
111 
112 		/** The update affects <code>HEAD</code> and cannot be permitted. */
113 		REJECTED_CURRENT_BRANCH,
114 
115 		/**
116 		 * One or more objects aren't in the repository.
117 		 * <p>
118 		 * This is severe indication of either repository corruption on the
119 		 * server side, or a bug in the client wherein the client did not supply
120 		 * all required objects during the pack transfer.
121 		 */
122 		REJECTED_MISSING_OBJECT,
123 
124 		/** Other failure; see {@link ReceiveCommand#getMessage()}. */
125 		REJECTED_OTHER_REASON,
126 
127 		/** The ref could not be locked and updated atomically; try again. */
128 		LOCK_FAILURE,
129 
130 		/** The change was completed successfully. */
131 		OK;
132 	}
133 
134 	/**
135 	 * Filter a collection of commands according to result.
136 	 *
137 	 * @param in
138 	 *            commands to filter.
139 	 * @param want
140 	 *            desired status to filter by.
141 	 * @return a copy of the command list containing only those commands with
142 	 *         the desired status.
143 	 * @since 4.2
144 	 */
145 	public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in,
146 			Result want) {
147 		List<ReceiveCommand> r;
148 		if (in instanceof Collection)
149 			r = new ArrayList<>(((Collection<?>) in).size());
150 		else
151 			r = new ArrayList<>();
152 		for (ReceiveCommand cmd : in) {
153 			if (cmd.getResult() == want)
154 				r.add(cmd);
155 		}
156 		return r;
157 	}
158 
159 	/**
160 	 * Filter a list of commands according to result.
161 	 *
162 	 * @param commands
163 	 *            commands to filter.
164 	 * @param want
165 	 *            desired status to filter by.
166 	 * @return a copy of the command list containing only those commands with
167 	 *         the desired status.
168 	 * @since 2.0
169 	 */
170 	public static List<ReceiveCommand> filter(List<ReceiveCommand> commands,
171 			Result want) {
172 		return filter((Iterable<ReceiveCommand>) commands, want);
173 	}
174 
175 	/**
176 	 * Set unprocessed commands as failed due to transaction aborted.
177 	 * <p>
178 	 * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
179 	 * {@link Result#REJECTED_OTHER_REASON}.
180 	 *
181 	 * @param commands
182 	 *            commands to mark as failed.
183 	 * @since 4.2
184 	 */
185 	public static void abort(Iterable<ReceiveCommand> commands) {
186 		for (ReceiveCommand c : commands) {
187 			if (c.getResult() == NOT_ATTEMPTED) {
188 				c.setResult(REJECTED_OTHER_REASON,
189 						JGitText.get().transactionAborted);
190 			}
191 		}
192 	}
193 
194 	/**
195 	 * Check whether a command failed due to transaction aborted.
196 	 *
197 	 * @param cmd
198 	 *            command.
199 	 * @return whether the command failed due to transaction aborted, as in {@link
200 	 *         #abort(Iterable)}.
201 	 * @since 4.9
202 	 */
203 	public static boolean isTransactionAborted(ReceiveCommand cmd) {
204 		return cmd.getResult() == REJECTED_OTHER_REASON
205 				&& cmd.getMessage().equals(JGitText.get().transactionAborted);
206 	}
207 
208 	private final ObjectId oldId;
209 
210 	private final ObjectId newId;
211 
212 	private final String name;
213 
214 	private Type type;
215 
216 	private Ref ref;
217 
218 	private Result status = Result.NOT_ATTEMPTED;
219 
220 	private String message;
221 
222 	private boolean customRefLog;
223 
224 	private String refLogMessage;
225 
226 	private boolean refLogIncludeResult;
227 
228 	private Boolean forceRefLog;
229 
230 	private boolean typeIsCorrect;
231 
232 	/**
233 	 * Create a new command for {@link BaseReceivePack}.
234 	 *
235 	 * @param oldId
236 	 *            the expected old object id; must not be null. Use
237 	 *            {@link ObjectId#zeroId()} to indicate a ref creation.
238 	 * @param newId
239 	 *            the new object id; must not be null. Use
240 	 *            {@link ObjectId#zeroId()} to indicate a ref deletion.
241 	 * @param name
242 	 *            name of the ref being affected.
243 	 */
244 	public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
245 			final String name) {
246 		if (oldId == null) {
247 			throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
248 		}
249 		if (newId == null) {
250 			throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
251 		}
252 		this.oldId = oldId;
253 		this.newId = newId;
254 		this.name = name;
255 
256 		type = Type.UPDATE;
257 		if (ObjectId.zeroId().equals(oldId)) {
258 			type = Type.CREATE;
259 		}
260 		if (ObjectId.zeroId().equals(newId)) {
261 			type = Type.DELETE;
262 		}
263 	}
264 
265 	/**
266 	 * Create a new command for {@link BaseReceivePack}.
267 	 *
268 	 * @param oldId
269 	 *            the old object id; must not be null. Use
270 	 *            {@link ObjectId#zeroId()} to indicate a ref creation.
271 	 * @param newId
272 	 *            the new object id; must not be null. Use
273 	 *            {@link ObjectId#zeroId()} to indicate a ref deletion.
274 	 * @param name
275 	 *            name of the ref being affected.
276 	 * @param type
277 	 *            type of the command. Must be {@link Type#CREATE} if {@code
278 	 *            oldId} is zero, or {@link Type#DELETE} if {@code newId} is zero.
279 	 * @since 2.0
280 	 */
281 	public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
282 			final String name, final Type type) {
283 		if (oldId == null) {
284 			throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
285 		}
286 		if (newId == null) {
287 			throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
288 		}
289 		this.oldId = oldId;
290 		this.newId = newId;
291 		this.name = name;
292 		switch (type) {
293 		case CREATE:
294 			if (!ObjectId.zeroId().equals(oldId)) {
295 				throw new IllegalArgumentException(
296 						JGitText.get().createRequiresZeroOldId);
297 			}
298 			break;
299 		case DELETE:
300 			if (!ObjectId.zeroId().equals(newId)) {
301 				throw new IllegalArgumentException(
302 						JGitText.get().deleteRequiresZeroNewId);
303 			}
304 			break;
305 		case UPDATE:
306 		case UPDATE_NONFASTFORWARD:
307 			if (ObjectId.zeroId().equals(newId)
308 					|| ObjectId.zeroId().equals(oldId)) {
309 				throw new IllegalArgumentException(
310 						JGitText.get().updateRequiresOldIdAndNewId);
311 			}
312 			break;
313 		default:
314 			throw new IllegalStateException(JGitText.get().enumValueNotSupported0);
315 		}
316 		this.type = type;
317 	}
318 
319 	/** @return the old value the client thinks the ref has. */
320 	public ObjectId getOldId() {
321 		return oldId;
322 	}
323 
324 	/** @return the requested new value for this ref. */
325 	public ObjectId getNewId() {
326 		return newId;
327 	}
328 
329 	/** @return the name of the ref being updated. */
330 	public String getRefName() {
331 		return name;
332 	}
333 
334 	/** @return the type of this command; see {@link Type}. */
335 	public Type getType() {
336 		return type;
337 	}
338 
339 	/** @return the ref, if this was advertised by the connection. */
340 	public Ref getRef() {
341 		return ref;
342 	}
343 
344 	/** @return the current status code of this command. */
345 	public Result getResult() {
346 		return status;
347 	}
348 
349 	/** @return the message associated with a failure status. */
350 	public String getMessage() {
351 		return message;
352 	}
353 
354 	/**
355 	 * Set the message to include in the reflog.
356 	 * <p>
357 	 * Overrides the default set by {@code setRefLogMessage} on any containing
358 	 * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
359 	 *
360 	 * @param msg
361 	 *            the message to describe this change. If null and appendStatus is
362 	 *            false, the reflog will not be updated.
363 	 * @param appendStatus
364 	 *            true if the status of the ref change (fast-forward or
365 	 *            forced-update) should be appended to the user supplied message.
366 	 * @since 4.9
367 	 */
368 	public void setRefLogMessage(String msg, boolean appendStatus) {
369 		customRefLog = true;
370 		if (msg == null && !appendStatus) {
371 			disableRefLog();
372 		} else if (msg == null && appendStatus) {
373 			refLogMessage = ""; //$NON-NLS-1$
374 			refLogIncludeResult = true;
375 		} else {
376 			refLogMessage = msg;
377 			refLogIncludeResult = appendStatus;
378 		}
379 	}
380 
381 	/**
382 	 * Don't record this update in the ref's associated reflog.
383 	 * <p>
384 	 * Equivalent to {@code setRefLogMessage(null, false)}.
385 	 *
386 	 * @since 4.9
387 	 */
388 	public void disableRefLog() {
389 		customRefLog = true;
390 		refLogMessage = null;
391 		refLogIncludeResult = false;
392 	}
393 
394 	/**
395 	 * Force writing a reflog for the updated ref.
396 	 *
397 	 * @param force whether to force.
398 	 * @since 4.9
399 	 */
400 	public void setForceRefLog(boolean force) {
401 		forceRefLog = Boolean.valueOf(force);
402 	}
403 
404 	/**
405 	 * Check whether this command has a custom reflog message setting that should
406 	 * override defaults in any containing
407 	 * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
408 	 * <p>
409 	 * Does not take into account whether {@code #setForceRefLog(boolean)} has
410 	 * been called.
411 	 *
412 	 * @return whether a custom reflog is set.
413 	 * @since 4.9
414 	 */
415 	public boolean hasCustomRefLog() {
416 		return customRefLog;
417 	}
418 
419 	/**
420 	 * Check whether log has been disabled by {@link #disableRefLog()}.
421 	 *
422 	 * @return true if disabled.
423 	 * @since 4.9
424 	 */
425 	public boolean isRefLogDisabled() {
426 		return refLogMessage == null;
427 	}
428 
429 	/**
430 	 * Get the message to include in the reflog.
431 	 *
432 	 * @return message the caller wants to include in the reflog; null if the
433 	 *         update should not be logged.
434 	 * @since 4.9
435 	 */
436 	@Nullable
437 	public String getRefLogMessage() {
438 		return refLogMessage;
439 	}
440 
441 	/**
442 	 * Check whether the reflog message should include the result of the update,
443 	 * such as fast-forward or force-update.
444 	 *
445 	 * @return true if the message should include the result.
446 	 * @since 4.9
447 	 */
448 	public boolean isRefLogIncludingResult() {
449 		return refLogIncludeResult;
450 	}
451 
452 	/**
453 	 * Check whether the reflog should be written regardless of repo defaults.
454 	 *
455 	 * @return whether force writing is enabled; null if {@code
456 	 * #setForceRefLog(boolean)} was never called.
457 	 * @since 4.9
458 	 */
459 	@Nullable
460 	public Boolean isForceRefLog() {
461 		return forceRefLog;
462 	}
463 
464 	/**
465 	 * Set the status of this command.
466 	 *
467 	 * @param s
468 	 *            the new status code for this command.
469 	 */
470 	public void setResult(final Result s) {
471 		setResult(s, null);
472 	}
473 
474 	/**
475 	 * Set the status of this command.
476 	 *
477 	 * @param s
478 	 *            new status code for this command.
479 	 * @param m
480 	 *            optional message explaining the new status.
481 	 */
482 	public void setResult(final Result s, final String m) {
483 		status = s;
484 		message = m;
485 	}
486 
487 	/**
488 	 * Update the type of this command by checking for fast-forward.
489 	 * <p>
490 	 * If the command's current type is UPDATE, a merge test will be performed
491 	 * using the supplied RevWalk to determine if {@link #getOldId()} is fully
492 	 * merged into {@link #getNewId()}. If some commits are not merged the
493 	 * update type is changed to {@link Type#UPDATE_NONFASTFORWARD}.
494 	 *
495 	 * @param walk
496 	 *            an instance to perform the merge test with. The caller must
497 	 *            allocate and release this object.
498 	 * @throws IOException
499 	 *             either oldId or newId is not accessible in the repository
500 	 *             used by the RevWalk. This usually indicates data corruption,
501 	 *             and the command cannot be processed.
502 	 */
503 	public void updateType(RevWalk walk) throws IOException {
504 		if (typeIsCorrect)
505 			return;
506 		if (type == Type.UPDATE && !AnyObjectId.equals(oldId, newId)) {
507 			RevObject o = walk.parseAny(oldId);
508 			RevObject n = walk.parseAny(newId);
509 			if (!(o instanceof RevCommit)
510 					|| !(n instanceof RevCommit)
511 					|| !walk.isMergedInto((RevCommit) o, (RevCommit) n))
512 				setType(Type.UPDATE_NONFASTFORWARD);
513 		}
514 		typeIsCorrect = true;
515 	}
516 
517 	/**
518 	 * Execute this command during a receive-pack session.
519 	 * <p>
520 	 * Sets the status of the command as a side effect.
521 	 *
522 	 * @param rp
523 	 *            receive-pack session.
524 	 * @since 2.0
525 	 */
526 	public void execute(final BaseReceivePack rp) {
527 		try {
528 			final RefUpdate ru = rp.getRepository().updateRef(getRefName());
529 			ru.setRefLogIdent(rp.getRefLogIdent());
530 			ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
531 			switch (getType()) {
532 			case DELETE:
533 				if (!ObjectId.zeroId().equals(getOldId())) {
534 					// We can only do a CAS style delete if the client
535 					// didn't bork its delete request by sending the
536 					// wrong zero id rather than the advertised one.
537 					//
538 					ru.setExpectedOldObjectId(getOldId());
539 				}
540 				ru.setForceUpdate(true);
541 				setResult(ru.delete(rp.getRevWalk()));
542 				break;
543 
544 			case CREATE:
545 			case UPDATE:
546 			case UPDATE_NONFASTFORWARD:
547 				ru.setForceUpdate(rp.isAllowNonFastForwards());
548 				ru.setExpectedOldObjectId(getOldId());
549 				ru.setNewObjectId(getNewId());
550 				ru.setRefLogMessage("push", true); //$NON-NLS-1$
551 				setResult(ru.update(rp.getRevWalk()));
552 				break;
553 			}
554 		} catch (IOException err) {
555 			reject(err);
556 		}
557 	}
558 
559 	void setRef(final Ref r) {
560 		ref = r;
561 	}
562 
563 	void setType(final Type t) {
564 		type = t;
565 	}
566 
567 	void setTypeFastForwardUpdate() {
568 		type = Type.UPDATE;
569 		typeIsCorrect = true;
570 	}
571 
572 	/**
573 	 * Set the result of this command.
574 	 *
575 	 * @param r
576 	 *            the new result code for this command.
577 	 */
578 	public void setResult(RefUpdate.Result r) {
579 		switch (r) {
580 		case NOT_ATTEMPTED:
581 			setResult(Result.NOT_ATTEMPTED);
582 			break;
583 
584 		case LOCK_FAILURE:
585 		case IO_FAILURE:
586 			setResult(Result.LOCK_FAILURE);
587 			break;
588 
589 		case NO_CHANGE:
590 		case NEW:
591 		case FORCED:
592 		case FAST_FORWARD:
593 			setResult(Result.OK);
594 			break;
595 
596 		case REJECTED:
597 			setResult(Result.REJECTED_NONFASTFORWARD);
598 			break;
599 
600 		case REJECTED_CURRENT_BRANCH:
601 			setResult(Result.REJECTED_CURRENT_BRANCH);
602 			break;
603 
604 		case REJECTED_MISSING_OBJECT:
605 			setResult(Result.REJECTED_MISSING_OBJECT);
606 			break;
607 
608 		case REJECTED_OTHER_REASON:
609 			setResult(Result.REJECTED_OTHER_REASON);
610 			break;
611 
612 		default:
613 			setResult(Result.REJECTED_OTHER_REASON, r.name());
614 			break;
615 		}
616 	}
617 
618 	void reject(IOException err) {
619 		setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
620 				JGitText.get().lockError, err.getMessage()));
621 	}
622 
623 	@SuppressWarnings("nls")
624 	@Override
625 	public String toString() {
626 		return getType().name() + ": " + getOldId().name() + " "
627 				+ getNewId().name() + " " + getRefName();
628 	}
629 }