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 }