1 /* 2 * Copyright (C) 2008-2013, Google Inc. 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.diff; 45 46 import java.io.IOException; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 51 import org.eclipse.jgit.internal.JGitText; 52 import org.eclipse.jgit.lib.AbbreviatedObjectId; 53 import org.eclipse.jgit.lib.AnyObjectId; 54 import org.eclipse.jgit.lib.FileMode; 55 import org.eclipse.jgit.lib.MutableObjectId; 56 import org.eclipse.jgit.lib.ObjectId; 57 import org.eclipse.jgit.treewalk.TreeWalk; 58 import org.eclipse.jgit.treewalk.filter.TreeFilter; 59 import org.eclipse.jgit.treewalk.filter.TreeFilterMarker; 60 61 /** A value class representing a change to a file */ 62 public class DiffEntry { 63 /** Magical SHA1 used for file adds or deletes */ 64 static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId 65 .fromObjectId(ObjectId.zeroId()); 66 67 /** Magical file name used for file adds or deletes. */ 68 public static final String DEV_NULL = "/dev/null"; //$NON-NLS-1$ 69 70 /** General type of change a single file-level patch describes. */ 71 public static enum ChangeType { 72 /** Add a new file to the project */ 73 ADD, 74 75 /** Modify an existing file in the project (content and/or mode) */ 76 MODIFY, 77 78 /** Delete an existing file from the project */ 79 DELETE, 80 81 /** Rename an existing file to a new location */ 82 RENAME, 83 84 /** Copy an existing file to a new location, keeping the original */ 85 COPY; 86 } 87 88 /** Specify the old or new side for more generalized access. */ 89 public static enum Side { 90 /** The old side of a DiffEntry. */ 91 OLD, 92 93 /** The new side of a DiffEntry. */ 94 NEW; 95 } 96 97 /** 98 * Create an empty DiffEntry 99 */ 100 protected DiffEntry(){ 101 // reduce the visibility of the default constructor 102 } 103 104 /** 105 * Convert the TreeWalk into DiffEntry headers. 106 * 107 * @param walk 108 * the TreeWalk to walk through. Must have exactly two trees. 109 * @return headers describing the changed files. 110 * @throws IOException 111 * the repository cannot be accessed. 112 * @throws IllegalArgumentException 113 * When given TreeWalk doesn't have exactly two trees. 114 */ 115 public static List<DiffEntry> scan(TreeWalk walk) throws IOException { 116 return scan(walk, false); 117 } 118 119 /** 120 * Convert the TreeWalk into DiffEntry headers, depending on 121 * {@code includeTrees} it will add tree objects into result or not. 122 * 123 * @param walk 124 * the TreeWalk to walk through. Must have exactly two trees and 125 * when {@code includeTrees} parameter is {@code true} it can't 126 * be recursive. 127 * @param includeTrees 128 * include tree objects. 129 * @return headers describing the changed files. 130 * @throws IOException 131 * the repository cannot be accessed. 132 * @throws IllegalArgumentException 133 * when {@code includeTrees} is true and given TreeWalk is 134 * recursive. Or when given TreeWalk doesn't have exactly two 135 * trees 136 */ 137 public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees) 138 throws IOException { 139 return scan(walk, includeTrees, null); 140 } 141 142 /** 143 * Convert the TreeWalk into DiffEntry headers, depending on 144 * {@code includeTrees} it will add tree objects into result or not. 145 * 146 * @param walk 147 * the TreeWalk to walk through. Must have exactly two trees and 148 * when {@code includeTrees} parameter is {@code true} it can't 149 * be recursive. 150 * @param includeTrees 151 * include tree objects. 152 * @param markTreeFilters 153 * array of tree filters which will be tested for each entry. If 154 * an entry matches, the entry will later return true when 155 * queried through {{@link #isMarked(int)} (with the index from 156 * this passed array). 157 * @return headers describing the changed files. 158 * @throws IOException 159 * the repository cannot be accessed. 160 * @throws IllegalArgumentException 161 * when {@code includeTrees} is true and given TreeWalk is 162 * recursive. Or when given TreeWalk doesn't have exactly two 163 * trees 164 * @since 2.3 165 */ 166 public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees, 167 TreeFilter[] markTreeFilters) 168 throws IOException { 169 if (walk.getTreeCount() != 2) 170 throw new IllegalArgumentException( 171 JGitText.get().treeWalkMustHaveExactlyTwoTrees); 172 if (includeTrees && walk.isRecursive()) 173 throw new IllegalArgumentException( 174 JGitText.get().cannotBeRecursiveWhenTreesAreIncluded); 175 176 TreeFilterMarker treeFilterMarker; 177 if (markTreeFilters != null && markTreeFilters.length > 0) 178 treeFilterMarker = new TreeFilterMarker(markTreeFilters); 179 else 180 treeFilterMarker = null; 181 182 List<DiffEntry> r = new ArrayList<>(); 183 MutableObjectId idBuf = new MutableObjectId(); 184 while (walk.next()) { 185 DiffEntry entry = new DiffEntry(); 186 187 walk.getObjectId(idBuf, 0); 188 entry.oldId = AbbreviatedObjectId.fromObjectId(idBuf); 189 190 walk.getObjectId(idBuf, 1); 191 entry.newId = AbbreviatedObjectId.fromObjectId(idBuf); 192 193 entry.oldMode = walk.getFileMode(0); 194 entry.newMode = walk.getFileMode(1); 195 entry.newPath = entry.oldPath = walk.getPathString(); 196 197 if (treeFilterMarker != null) 198 entry.treeFilterMarks = treeFilterMarker.getMarks(walk); 199 200 if (entry.oldMode == FileMode.MISSING) { 201 entry.oldPath = DiffEntry.DEV_NULL; 202 entry.changeType = ChangeType.ADD; 203 r.add(entry); 204 205 } else if (entry.newMode == FileMode.MISSING) { 206 entry.newPath = DiffEntry.DEV_NULL; 207 entry.changeType = ChangeType.DELETE; 208 r.add(entry); 209 210 } else if (!entry.oldId.equals(entry.newId)) { 211 entry.changeType = ChangeType.MODIFY; 212 if (RenameDetector.sameType(entry.oldMode, entry.newMode)) 213 r.add(entry); 214 else 215 r.addAll(breakModify(entry)); 216 } else if (entry.oldMode != entry.newMode) { 217 entry.changeType = ChangeType.MODIFY; 218 r.add(entry); 219 } 220 221 if (includeTrees && walk.isSubtree()) 222 walk.enterSubtree(); 223 } 224 return r; 225 } 226 227 static DiffEntry add(String path, AnyObjectId id) { 228 DiffEntry e = new DiffEntry(); 229 e.oldId = A_ZERO; 230 e.oldMode = FileMode.MISSING; 231 e.oldPath = DEV_NULL; 232 233 e.newId = AbbreviatedObjectId.fromObjectId(id); 234 e.newMode = FileMode.REGULAR_FILE; 235 e.newPath = path; 236 e.changeType = ChangeType.ADD; 237 return e; 238 } 239 240 static DiffEntry delete(String path, AnyObjectId id) { 241 DiffEntry e = new DiffEntry(); 242 e.oldId = AbbreviatedObjectId.fromObjectId(id); 243 e.oldMode = FileMode.REGULAR_FILE; 244 e.oldPath = path; 245 246 e.newId = A_ZERO; 247 e.newMode = FileMode.MISSING; 248 e.newPath = DEV_NULL; 249 e.changeType = ChangeType.DELETE; 250 return e; 251 } 252 253 static DiffEntry modify(String path) { 254 DiffEntry e = new DiffEntry(); 255 e.oldMode = FileMode.REGULAR_FILE; 256 e.oldPath = path; 257 258 e.newMode = FileMode.REGULAR_FILE; 259 e.newPath = path; 260 e.changeType = ChangeType.MODIFY; 261 return e; 262 } 263 264 /** 265 * Breaks apart a DiffEntry into two entries, one DELETE and one ADD. 266 * 267 * @param entry 268 * the DiffEntry to break apart. 269 * @return a list containing two entries. Calling {@link #getChangeType()} 270 * on the first entry will return ChangeType.DELETE. Calling it on 271 * the second entry will return ChangeType.ADD. 272 */ 273 static List<DiffEntry> breakModify(DiffEntry entry) { 274 DiffEntry del = new DiffEntry(); 275 del.oldId = entry.getOldId(); 276 del.oldMode = entry.getOldMode(); 277 del.oldPath = entry.getOldPath(); 278 279 del.newId = A_ZERO; 280 del.newMode = FileMode.MISSING; 281 del.newPath = DiffEntry.DEV_NULL; 282 del.changeType = ChangeType.DELETE; 283 284 DiffEntry add = new DiffEntry(); 285 add.oldId = A_ZERO; 286 add.oldMode = FileMode.MISSING; 287 add.oldPath = DiffEntry.DEV_NULL; 288 289 add.newId = entry.getNewId(); 290 add.newMode = entry.getNewMode(); 291 add.newPath = entry.getNewPath(); 292 add.changeType = ChangeType.ADD; 293 return Arrays.asList(del, add); 294 } 295 296 static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst, 297 int score) { 298 DiffEntry r = new DiffEntry(); 299 300 r.oldId = src.oldId; 301 r.oldMode = src.oldMode; 302 r.oldPath = src.oldPath; 303 304 r.newId = dst.newId; 305 r.newMode = dst.newMode; 306 r.newPath = dst.newPath; 307 308 r.changeType = changeType; 309 r.score = score; 310 311 r.treeFilterMarks = src.treeFilterMarks | dst.treeFilterMarks; 312 313 return r; 314 } 315 316 /** File name of the old (pre-image). */ 317 protected String oldPath; 318 319 /** File name of the new (post-image). */ 320 protected String newPath; 321 322 /** Old mode of the file, if described by the patch, else null. */ 323 protected FileMode oldMode; 324 325 /** New mode of the file, if described by the patch, else null. */ 326 protected FileMode newMode; 327 328 /** General type of change indicated by the patch. */ 329 protected ChangeType changeType; 330 331 /** Similarity score if {@link #changeType} is a copy or rename. */ 332 protected int score; 333 334 /** ObjectId listed on the index line for the old (pre-image) */ 335 protected AbbreviatedObjectId oldId; 336 337 /** ObjectId listed on the index line for the new (post-image) */ 338 protected AbbreviatedObjectId newId; 339 340 /** 341 * Bitset for marked flags of tree filters passed to 342 * {@link #scan(TreeWalk, boolean, TreeFilter...)} 343 */ 344 private int treeFilterMarks = 0; 345 346 /** 347 * Get the old name associated with this file. 348 * <p> 349 * The meaning of the old name can differ depending on the semantic meaning 350 * of this patch: 351 * <ul> 352 * <li><i>file add</i>: always <code>/dev/null</code></li> 353 * <li><i>file modify</i>: always {@link #getNewPath()}</li> 354 * <li><i>file delete</i>: always the file being deleted</li> 355 * <li><i>file copy</i>: source file the copy originates from</li> 356 * <li><i>file rename</i>: source file the rename originates from</li> 357 * </ul> 358 * 359 * @return old name for this file. 360 */ 361 public String getOldPath() { 362 return oldPath; 363 } 364 365 /** 366 * Get the new name associated with this file. 367 * <p> 368 * The meaning of the new name can differ depending on the semantic meaning 369 * of this patch: 370 * <ul> 371 * <li><i>file add</i>: always the file being created</li> 372 * <li><i>file modify</i>: always {@link #getOldPath()}</li> 373 * <li><i>file delete</i>: always <code>/dev/null</code></li> 374 * <li><i>file copy</i>: destination file the copy ends up at</li> 375 * <li><i>file rename</i>: destination file the rename ends up at</li> 376 * </ul> 377 * 378 * @return new name for this file. 379 */ 380 public String getNewPath() { 381 return newPath; 382 } 383 384 /** 385 * Get the path associated with this file. 386 * 387 * @param side 388 * which path to obtain. 389 * @return name for this file. 390 */ 391 public String getPath(Side side) { 392 return side == Side.OLD ? getOldPath() : getNewPath(); 393 } 394 395 /** @return the old file mode, if described in the patch */ 396 public FileMode getOldMode() { 397 return oldMode; 398 } 399 400 /** @return the new file mode, if described in the patch */ 401 public FileMode getNewMode() { 402 return newMode; 403 } 404 405 /** 406 * Get the mode associated with this file. 407 * 408 * @param side 409 * which mode to obtain. 410 * @return the mode. 411 */ 412 public FileMode getMode(Side side) { 413 return side == Side.OLD ? getOldMode() : getNewMode(); 414 } 415 416 /** @return the type of change this patch makes on {@link #getNewPath()} */ 417 public ChangeType getChangeType() { 418 return changeType; 419 } 420 421 /** 422 * @return similarity score between {@link #getOldPath()} and 423 * {@link #getNewPath()} if {@link #getChangeType()} is 424 * {@link ChangeType#COPY} or {@link ChangeType#RENAME}. 425 */ 426 public int getScore() { 427 return score; 428 } 429 430 /** 431 * Get the old object id from the <code>index</code>. 432 * 433 * @return the object id; null if there is no index line 434 */ 435 public AbbreviatedObjectId getOldId() { 436 return oldId; 437 } 438 439 /** 440 * Get the new object id from the <code>index</code>. 441 * 442 * @return the object id; null if there is no index line 443 */ 444 public AbbreviatedObjectId getNewId() { 445 return newId; 446 } 447 448 /** 449 * Whether the mark tree filter with the specified index matched during scan 450 * or not, see {@link #scan(TreeWalk, boolean, TreeFilter...)}. Example: 451 * <p> 452 * 453 * <pre> 454 * TreeFilter filterA = ...; 455 * TreeFilter filterB = ...; 456 * List<DiffEntry> entries = DiffEntry.scan(walk, false, filterA, filterB); 457 * DiffEntry entry = entries.get(0); 458 * boolean filterAMatched = entry.isMarked(0); 459 * boolean filterBMatched = entry.isMarked(1); 460 * </pre> 461 * <p> 462 * Note that 0 corresponds to filterA because it was the first filter that 463 * was passed to scan. 464 * <p> 465 * To query more than one flag at once, see {@link #getTreeFilterMarks()}. 466 * 467 * @param index 468 * the index of the tree filter to check for (must be between 0 469 * and {@link Integer#SIZE}). 470 * 471 * @return true, if the tree filter matched; false if not 472 * @since 2.3 473 */ 474 public boolean isMarked(int index) { 475 return (treeFilterMarks & (1L << index)) != 0; 476 } 477 478 /** 479 * Get the raw tree filter marks, as set during 480 * {@link #scan(TreeWalk, boolean, TreeFilter...)}. See 481 * {@link #isMarked(int)} to query each mark individually. 482 * 483 * @return the bitset of tree filter marks 484 * @since 2.3 485 */ 486 public int getTreeFilterMarks() { 487 return treeFilterMarks; 488 } 489 490 /** 491 * Get the object id. 492 * 493 * @param side 494 * the side of the id to get. 495 * @return the object id; null if there is no index line 496 */ 497 public AbbreviatedObjectId getId(Side side) { 498 return side == Side.OLD ? getOldId() : getNewId(); 499 } 500 501 @SuppressWarnings("nls") 502 @Override 503 public String toString() { 504 StringBuilder buf = new StringBuilder(); 505 buf.append("DiffEntry["); 506 buf.append(changeType); 507 buf.append(" "); 508 switch (changeType) { 509 case ADD: 510 buf.append(newPath); 511 break; 512 case COPY: 513 buf.append(oldPath + "->" + newPath); 514 break; 515 case DELETE: 516 buf.append(oldPath); 517 break; 518 case MODIFY: 519 buf.append(oldPath); 520 break; 521 case RENAME: 522 buf.append(oldPath + "->" + newPath); 523 break; 524 } 525 buf.append("]"); 526 return buf.toString(); 527 } 528 }