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 }