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 }