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