View Javadoc
1   /*
2    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
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 java.io.IOException;
47  import java.text.MessageFormat;
48  
49  import org.eclipse.jgit.internal.JGitText;
50  import org.eclipse.jgit.lib.ObjectId;
51  import org.eclipse.jgit.lib.Ref;
52  import org.eclipse.jgit.lib.RefUpdate;
53  import org.eclipse.jgit.lib.Repository;
54  import org.eclipse.jgit.revwalk.RevWalk;
55  
56  /**
57   * Represent request and status of a remote ref update. Specification is
58   * provided by client, while status is handled by
59   * {@link org.eclipse.jgit.transport.PushProcess} class, being read-only for
60   * client.
61   * <p>
62   * Client can create instances of this class directly, basing on user
63   * specification and advertised refs
64   * ({@link org.eclipse.jgit.transport.Connection} or through
65   * {@link org.eclipse.jgit.transport.Transport} helper methods. Apply this
66   * specification on remote repository using
67   * {@link org.eclipse.jgit.transport.Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)}
68   * method.
69   * </p>
70   */
71  public class RemoteRefUpdate {
72  	/**
73  	 * Represent current status of a remote ref update.
74  	 */
75  	public static enum Status {
76  		/**
77  		 * Push process hasn't yet attempted to update this ref. This is the
78  		 * default status, prior to push process execution.
79  		 */
80  		NOT_ATTEMPTED,
81  
82  		/**
83  		 * Remote ref was up to date, there was no need to update anything.
84  		 */
85  		UP_TO_DATE,
86  
87  		/**
88  		 * Remote ref update was rejected, as it would cause non fast-forward
89  		 * update.
90  		 */
91  		REJECTED_NONFASTFORWARD,
92  
93  		/**
94  		 * Remote ref update was rejected, because remote side doesn't
95  		 * support/allow deleting refs.
96  		 */
97  		REJECTED_NODELETE,
98  
99  		/**
100 		 * Remote ref update was rejected, because old object id on remote
101 		 * repository wasn't the same as defined expected old object.
102 		 */
103 		REJECTED_REMOTE_CHANGED,
104 
105 		/**
106 		 * Remote ref update was rejected for other reason, possibly described
107 		 * in {@link RemoteRefUpdate#getMessage()}.
108 		 */
109 		REJECTED_OTHER_REASON,
110 
111 		/**
112 		 * Remote ref didn't exist. Can occur on delete request of a non
113 		 * existing ref.
114 		 */
115 		NON_EXISTING,
116 
117 		/**
118 		 * Push process is awaiting update report from remote repository. This
119 		 * is a temporary state or state after critical error in push process.
120 		 */
121 		AWAITING_REPORT,
122 
123 		/**
124 		 * Remote ref was successfully updated.
125 		 */
126 		OK;
127 	}
128 
129 	private ObjectId expectedOldObjectId;
130 
131 	private final ObjectId newObjectId;
132 
133 	private final String remoteName;
134 
135 	private final TrackingRefUpdate trackingRefUpdate;
136 
137 	private final String srcRef;
138 
139 	private final boolean forceUpdate;
140 
141 	private Status status;
142 
143 	private boolean fastForward;
144 
145 	private String message;
146 
147 	private final Repository localDb;
148 
149 	private RefUpdate localUpdate;
150 
151 	/**
152 	 * Construct remote ref update request by providing an update specification.
153 	 * Object is created with default
154 	 * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
155 	 * status and no message.
156 	 *
157 	 * @param localDb
158 	 *            local repository to push from.
159 	 * @param srcRef
160 	 *            source revision - any string resolvable by
161 	 *            {@link org.eclipse.jgit.lib.Repository#resolve(String)}. This
162 	 *            resolves to the new object that the caller want remote ref to
163 	 *            be after update. Use null or
164 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} string for
165 	 *            delete request.
166 	 * @param remoteName
167 	 *            full name of a remote ref to update, e.g. "refs/heads/master"
168 	 *            (no wildcard, no short name).
169 	 * @param forceUpdate
170 	 *            true when caller want remote ref to be updated regardless
171 	 *            whether it is fast-forward update (old object is ancestor of
172 	 *            new object).
173 	 * @param localName
174 	 *            optional full name of a local stored tracking branch, to
175 	 *            update after push, e.g. "refs/remotes/zawir/dirty" (no
176 	 *            wildcard, no short name); null if no local tracking branch
177 	 *            should be updated.
178 	 * @param expectedOldObjectId
179 	 *            optional object id that caller is expecting, requiring to be
180 	 *            advertised by remote side before update; update will take
181 	 *            place ONLY if remote side advertise exactly this expected id;
182 	 *            null if caller doesn't care what object id remote side
183 	 *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
184 	 *            when expecting no remote ref with this name.
185 	 * @throws java.io.IOException
186 	 *             when I/O error occurred during creating
187 	 *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
188 	 *             local tracking branch or srcRef can't be resolved to any
189 	 *             object.
190 	 * @throws java.lang.IllegalArgumentException
191 	 *             if some required parameter was null
192 	 */
193 	public RemoteRefUpdate(final Repository localDb, final String srcRef,
194 			final String remoteName, final boolean forceUpdate,
195 			final String localName, final ObjectId expectedOldObjectId)
196 			throws IOException {
197 		this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
198 				: ObjectId.zeroId(), remoteName, forceUpdate, localName,
199 				expectedOldObjectId);
200 	}
201 
202 	/**
203 	 * Construct remote ref update request by providing an update specification.
204 	 * Object is created with default
205 	 * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
206 	 * status and no message.
207 	 *
208 	 * @param localDb
209 	 *            local repository to push from.
210 	 * @param srcRef
211 	 *            source revision. Use null to delete.
212 	 * @param remoteName
213 	 *            full name of a remote ref to update, e.g. "refs/heads/master"
214 	 *            (no wildcard, no short name).
215 	 * @param forceUpdate
216 	 *            true when caller want remote ref to be updated regardless
217 	 *            whether it is fast-forward update (old object is ancestor of
218 	 *            new object).
219 	 * @param localName
220 	 *            optional full name of a local stored tracking branch, to
221 	 *            update after push, e.g. "refs/remotes/zawir/dirty" (no
222 	 *            wildcard, no short name); null if no local tracking branch
223 	 *            should be updated.
224 	 * @param expectedOldObjectId
225 	 *            optional object id that caller is expecting, requiring to be
226 	 *            advertised by remote side before update; update will take
227 	 *            place ONLY if remote side advertise exactly this expected id;
228 	 *            null if caller doesn't care what object id remote side
229 	 *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
230 	 *            when expecting no remote ref with this name.
231 	 * @throws java.io.IOException
232 	 *             when I/O error occurred during creating
233 	 *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
234 	 *             local tracking branch or srcRef can't be resolved to any
235 	 *             object.
236 	 * @throws java.lang.IllegalArgumentException
237 	 *             if some required parameter was null
238 	 */
239 	public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
240 			final String remoteName, final boolean forceUpdate,
241 			final String localName, final ObjectId expectedOldObjectId)
242 			throws IOException {
243 		this(localDb, srcRef != null ? srcRef.getName() : null,
244 				srcRef != null ? srcRef.getObjectId() : null, remoteName,
245 				forceUpdate, localName, expectedOldObjectId);
246 	}
247 
248 	/**
249 	 * Construct remote ref update request by providing an update specification.
250 	 * Object is created with default
251 	 * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
252 	 * status and no message.
253 	 *
254 	 * @param localDb
255 	 *            local repository to push from.
256 	 * @param srcRef
257 	 *            source revision to label srcId with. If null srcId.name() will
258 	 *            be used instead.
259 	 * @param srcId
260 	 *            The new object that the caller wants remote ref to be after
261 	 *            update. Use null or
262 	 *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} for delete
263 	 *            request.
264 	 * @param remoteName
265 	 *            full name of a remote ref to update, e.g. "refs/heads/master"
266 	 *            (no wildcard, no short name).
267 	 * @param forceUpdate
268 	 *            true when caller want remote ref to be updated regardless
269 	 *            whether it is fast-forward update (old object is ancestor of
270 	 *            new object).
271 	 * @param localName
272 	 *            optional full name of a local stored tracking branch, to
273 	 *            update after push, e.g. "refs/remotes/zawir/dirty" (no
274 	 *            wildcard, no short name); null if no local tracking branch
275 	 *            should be updated.
276 	 * @param expectedOldObjectId
277 	 *            optional object id that caller is expecting, requiring to be
278 	 *            advertised by remote side before update; update will take
279 	 *            place ONLY if remote side advertise exactly this expected id;
280 	 *            null if caller doesn't care what object id remote side
281 	 *            advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
282 	 *            when expecting no remote ref with this name.
283 	 * @throws java.io.IOException
284 	 *             when I/O error occurred during creating
285 	 *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
286 	 *             local tracking branch or srcRef can't be resolved to any
287 	 *             object.
288 	 * @throws java.lang.IllegalArgumentException
289 	 *             if some required parameter was null
290 	 */
291 	public RemoteRefUpdate(final Repository localDb, final String srcRef,
292 			final ObjectId srcId, final String remoteName,
293 			final boolean forceUpdate, final String localName,
294 			final ObjectId expectedOldObjectId) throws IOException {
295 		if (remoteName == null)
296 			throw new IllegalArgumentException(JGitText.get().remoteNameCantBeNull);
297 		if (srcId == null && srcRef != null)
298 			throw new IOException(MessageFormat.format(
299 					JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
300 
301 		if (srcRef != null)
302 			this.srcRef = srcRef;
303 		else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
304 			this.srcRef = srcId.name();
305 		else
306 			this.srcRef = null;
307 
308 		if (srcId != null)
309 			this.newObjectId = srcId;
310 		else
311 			this.newObjectId = ObjectId.zeroId();
312 
313 		this.remoteName = remoteName;
314 		this.forceUpdate = forceUpdate;
315 		if (localName != null && localDb != null) {
316 			localUpdate = localDb.updateRef(localName);
317 			localUpdate.setForceUpdate(true);
318 			localUpdate.setRefLogMessage("push", true); //$NON-NLS-1$
319 			localUpdate.setNewObjectId(newObjectId);
320 			trackingRefUpdate = new TrackingRefUpdate(
321 					true,
322 					remoteName,
323 					localName,
324 					localUpdate.getOldObjectId() != null
325 						? localUpdate.getOldObjectId()
326 						: ObjectId.zeroId(),
327 					newObjectId);
328 		} else
329 			trackingRefUpdate = null;
330 		this.localDb = localDb;
331 		this.expectedOldObjectId = expectedOldObjectId;
332 		this.status = Status.NOT_ATTEMPTED;
333 	}
334 
335 	/**
336 	 * Create a new instance of this object basing on existing instance for
337 	 * configuration. State (like {@link #getMessage()}, {@link #getStatus()})
338 	 * of base object is not shared. Expected old object id is set up from
339 	 * scratch, as this constructor may be used for 2-stage push: first one
340 	 * being dry run, second one being actual push.
341 	 *
342 	 * @param base
343 	 *            configuration base.
344 	 * @param newExpectedOldObjectId
345 	 *            new expected object id value.
346 	 * @throws java.io.IOException
347 	 *             when I/O error occurred during creating
348 	 *             {@link org.eclipse.jgit.transport.TrackingRefUpdate} for
349 	 *             local tracking branch or srcRef of base object no longer can
350 	 *             be resolved to any object.
351 	 */
352 	public RemoteRefUpdate(final RemoteRefUpdate base,
353 			final ObjectId newExpectedOldObjectId) throws IOException {
354 		this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
355 				(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
356 						.getLocalName()), newExpectedOldObjectId);
357 	}
358 
359 	/**
360 	 * Get expected old object id
361 	 *
362 	 * @return expectedOldObjectId required to be advertised by remote side, as
363 	 *         set in constructor; may be null.
364 	 */
365 	public ObjectId getExpectedOldObjectId() {
366 		return expectedOldObjectId;
367 	}
368 
369 	/**
370 	 * Whether some object is required to be advertised by remote side, as set
371 	 * in constructor
372 	 *
373 	 * @return true if some object is required to be advertised by remote side,
374 	 *         as set in constructor; false otherwise.
375 	 */
376 	public boolean isExpectingOldObjectId() {
377 		return expectedOldObjectId != null;
378 	}
379 
380 	/**
381 	 * Get new object id
382 	 *
383 	 * @return newObjectId for remote ref, as set in constructor.
384 	 */
385 	public ObjectId getNewObjectId() {
386 		return newObjectId;
387 	}
388 
389 	/**
390 	 * Whether this update is a deleting update
391 	 *
392 	 * @return true if this update is deleting update; false otherwise.
393 	 */
394 	public boolean isDelete() {
395 		return ObjectId.zeroId().equals(newObjectId);
396 	}
397 
398 	/**
399 	 * Get name of remote ref to update
400 	 *
401 	 * @return name of remote ref to update, as set in constructor.
402 	 */
403 	public String getRemoteName() {
404 		return remoteName;
405 	}
406 
407 	/**
408 	 * Get tracking branch update if localName was set in constructor.
409 	 *
410 	 * @return local tracking branch update if localName was set in constructor.
411 	 */
412 	public TrackingRefUpdate getTrackingRefUpdate() {
413 		return trackingRefUpdate;
414 	}
415 
416 	/**
417 	 * Get source revision as specified by user (in constructor)
418 	 *
419 	 * @return source revision as specified by user (in constructor), could be
420 	 *         any string parseable by
421 	 *         {@link org.eclipse.jgit.lib.Repository#resolve(String)}; can be
422 	 *         null if specified that way in constructor - this stands for
423 	 *         delete request.
424 	 */
425 	public String getSrcRef() {
426 		return srcRef;
427 	}
428 
429 	/**
430 	 * Whether user specified a local tracking branch for remote update
431 	 *
432 	 * @return true if user specified a local tracking branch for remote update;
433 	 *         false otherwise.
434 	 */
435 	public boolean hasTrackingRefUpdate() {
436 		return trackingRefUpdate != null;
437 	}
438 
439 	/**
440 	 * Whether this update is forced regardless of old remote ref object
441 	 *
442 	 * @return true if this update is forced regardless of old remote ref
443 	 *         object; false otherwise.
444 	 */
445 	public boolean isForceUpdate() {
446 		return forceUpdate;
447 	}
448 
449 	/**
450 	 * Get status of remote ref update operation.
451 	 *
452 	 * @return status of remote ref update operation.
453 	 */
454 	public Status getStatus() {
455 		return status;
456 	}
457 
458 	/**
459 	 * Check whether update was fast-forward. Note that this result is
460 	 * meaningful only after successful update (when status is
461 	 * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#OK}).
462 	 *
463 	 * @return true if update was fast-forward; false otherwise.
464 	 */
465 	public boolean isFastForward() {
466 		return fastForward;
467 	}
468 
469 	/**
470 	 * Get message describing reasons of status when needed/possible; may be
471 	 * null.
472 	 *
473 	 * @return message describing reasons of status when needed/possible; may be
474 	 *         null.
475 	 */
476 	public String getMessage() {
477 		return message;
478 	}
479 
480 	void setExpectedOldObjectId(ObjectId id) {
481 		expectedOldObjectId = id;
482 	}
483 
484 	void setStatus(Status status) {
485 		this.status = status;
486 	}
487 
488 	void setFastForward(boolean fastForward) {
489 		this.fastForward = fastForward;
490 	}
491 
492 	void setMessage(String message) {
493 		this.message = message;
494 	}
495 
496 	/**
497 	 * Update locally stored tracking branch with the new object.
498 	 *
499 	 * @param walk
500 	 *            walker used for checking update properties.
501 	 * @throws java.io.IOException
502 	 *             when I/O error occurred during update
503 	 */
504 	protected void updateTrackingRef(RevWalk walk) throws IOException {
505 		if (isDelete())
506 			trackingRefUpdate.setResult(localUpdate.delete(walk));
507 		else
508 			trackingRefUpdate.setResult(localUpdate.update(walk));
509 	}
510 
511 	/** {@inheritDoc} */
512 	@SuppressWarnings("nls")
513 	@Override
514 	public String toString() {
515 		return "RemoteRefUpdate[remoteName="
516 				+ remoteName
517 				+ ", "
518 				+ status
519 				+ ", "
520 				+ (expectedOldObjectId != null ? expectedOldObjectId.name()
521 						: "(null)") + "..."
522 				+ (newObjectId != null ? newObjectId.name() : "(null)")
523 				+ (fastForward ? ", fastForward" : "")
524  + ", srcRef=" + srcRef
525 				+ (forceUpdate ? ", forceUpdate" : "") + ", message="
526 				+ (message != null ? "\"" + message + "\"" : "null") + "]";
527 	}
528 }