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 }