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