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