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