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