/*
 * Copyright (C) 2008-2013, Google Inc.
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.diff;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilterMarker;

A value class representing a change to a file
/** * A value class representing a change to a file */
public class DiffEntry {
Magical SHA1 used for file adds or deletes
/** Magical SHA1 used for file adds or deletes */
static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId .fromObjectId(ObjectId.zeroId());
Magical file name used for file adds or deletes.
/** Magical file name used for file adds or deletes. */
public static final String DEV_NULL = "/dev/null"; //$NON-NLS-1$
General type of change a single file-level patch describes.
/** General type of change a single file-level patch describes. */
public static enum ChangeType {
Add a new file to the project
/** Add a new file to the project */
ADD,
Modify an existing file in the project (content and/or mode)
/** Modify an existing file in the project (content and/or mode) */
MODIFY,
Delete an existing file from the project
/** Delete an existing file from the project */
DELETE,
Rename an existing file to a new location
/** Rename an existing file to a new location */
RENAME,
Copy an existing file to a new location, keeping the original
/** Copy an existing file to a new location, keeping the original */
COPY; }
Specify the old or new side for more generalized access.
/** Specify the old or new side for more generalized access. */
public static enum Side {
The old side of a DiffEntry.
/** The old side of a DiffEntry. */
OLD,
The new side of a DiffEntry.
/** The new side of a DiffEntry. */
NEW; }
Create an empty DiffEntry
/** * Create an empty DiffEntry */
protected DiffEntry(){ // reduce the visibility of the default constructor }
Convert the TreeWalk into DiffEntry headers.
Params:
  • walk – the TreeWalk to walk through. Must have exactly two trees.
Throws:
Returns:headers describing the changed files.
/** * Convert the TreeWalk into DiffEntry headers. * * @param walk * the TreeWalk to walk through. Must have exactly two trees. * @return headers describing the changed files. * @throws java.io.IOException * the repository cannot be accessed. * @throws java.lang.IllegalArgumentException * When given TreeWalk doesn't have exactly two trees. */
public static List<DiffEntry> scan(TreeWalk walk) throws IOException { return scan(walk, false); }
Convert the TreeWalk into DiffEntry headers, depending on includeTrees it will add tree objects into result or not.
Params:
  • walk – the TreeWalk to walk through. Must have exactly two trees and when includeTrees parameter is true it can't be recursive.
  • includeTrees – include tree objects.
Throws:
  • IOException – the repository cannot be accessed.
  • IllegalArgumentException – when includeTrees is true and given TreeWalk is recursive. Or when given TreeWalk doesn't have exactly two trees
Returns:headers describing the changed files.
/** * Convert the TreeWalk into DiffEntry headers, depending on * {@code includeTrees} it will add tree objects into result or not. * * @param walk * the TreeWalk to walk through. Must have exactly two trees and * when {@code includeTrees} parameter is {@code true} it can't * be recursive. * @param includeTrees * include tree objects. * @return headers describing the changed files. * @throws java.io.IOException * the repository cannot be accessed. * @throws java.lang.IllegalArgumentException * when {@code includeTrees} is true and given TreeWalk is * recursive. Or when given TreeWalk doesn't have exactly two * trees */
public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees) throws IOException { return scan(walk, includeTrees, null); }
Convert the TreeWalk into DiffEntry headers, depending on includeTrees it will add tree objects into result or not.
Params:
  • walk – the TreeWalk to walk through. Must have exactly two trees and when includeTrees parameter is true it can't be recursive.
  • includeTrees – include tree objects.
  • markTreeFilters – array of tree filters which will be tested for each entry. If an entry matches, the entry will later return true when queried through {isMarked(int) (with the index from this passed array).
Throws:
  • IOException – the repository cannot be accessed.
  • IllegalArgumentException – when includeTrees is true and given TreeWalk is recursive. Or when given TreeWalk doesn't have exactly two trees
Returns:headers describing the changed files.
Since:2.3
/** * Convert the TreeWalk into DiffEntry headers, depending on * {@code includeTrees} it will add tree objects into result or not. * * @param walk * the TreeWalk to walk through. Must have exactly two trees and * when {@code includeTrees} parameter is {@code true} it can't * be recursive. * @param includeTrees * include tree objects. * @param markTreeFilters * array of tree filters which will be tested for each entry. If * an entry matches, the entry will later return true when * queried through {{@link #isMarked(int)} (with the index from * this passed array). * @return headers describing the changed files. * @throws java.io.IOException * the repository cannot be accessed. * @throws java.lang.IllegalArgumentException * when {@code includeTrees} is true and given TreeWalk is * recursive. Or when given TreeWalk doesn't have exactly two * trees * @since 2.3 */
public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees, TreeFilter[] markTreeFilters) throws IOException { if (walk.getTreeCount() != 2) throw new IllegalArgumentException( JGitText.get().treeWalkMustHaveExactlyTwoTrees); if (includeTrees && walk.isRecursive()) throw new IllegalArgumentException( JGitText.get().cannotBeRecursiveWhenTreesAreIncluded); TreeFilterMarker treeFilterMarker; if (markTreeFilters != null && markTreeFilters.length > 0) treeFilterMarker = new TreeFilterMarker(markTreeFilters); else treeFilterMarker = null; List<DiffEntry> r = new ArrayList<>(); MutableObjectId idBuf = new MutableObjectId(); while (walk.next()) { DiffEntry entry = new DiffEntry(); walk.getObjectId(idBuf, 0); entry.oldId = AbbreviatedObjectId.fromObjectId(idBuf); walk.getObjectId(idBuf, 1); entry.newId = AbbreviatedObjectId.fromObjectId(idBuf); entry.oldMode = walk.getFileMode(0); entry.newMode = walk.getFileMode(1); entry.newPath = entry.oldPath = walk.getPathString(); if (walk.getAttributesNodeProvider() != null) { entry.diffAttribute = walk.getAttributes() .get(Constants.ATTR_DIFF); } if (treeFilterMarker != null) entry.treeFilterMarks = treeFilterMarker.getMarks(walk); if (entry.oldMode == FileMode.MISSING) { entry.oldPath = DiffEntry.DEV_NULL; entry.changeType = ChangeType.ADD; r.add(entry); } else if (entry.newMode == FileMode.MISSING) { entry.newPath = DiffEntry.DEV_NULL; entry.changeType = ChangeType.DELETE; r.add(entry); } else if (!entry.oldId.equals(entry.newId)) { entry.changeType = ChangeType.MODIFY; if (RenameDetector.sameType(entry.oldMode, entry.newMode)) r.add(entry); else r.addAll(breakModify(entry)); } else if (entry.oldMode != entry.newMode) { entry.changeType = ChangeType.MODIFY; r.add(entry); } if (includeTrees && walk.isSubtree()) walk.enterSubtree(); } return r; } static DiffEntry add(String path, AnyObjectId id) { DiffEntry e = new DiffEntry(); e.oldId = A_ZERO; e.oldMode = FileMode.MISSING; e.oldPath = DEV_NULL; e.newId = AbbreviatedObjectId.fromObjectId(id); e.newMode = FileMode.REGULAR_FILE; e.newPath = path; e.changeType = ChangeType.ADD; return e; } static DiffEntry delete(String path, AnyObjectId id) { DiffEntry e = new DiffEntry(); e.oldId = AbbreviatedObjectId.fromObjectId(id); e.oldMode = FileMode.REGULAR_FILE; e.oldPath = path; e.newId = A_ZERO; e.newMode = FileMode.MISSING; e.newPath = DEV_NULL; e.changeType = ChangeType.DELETE; return e; } static DiffEntry modify(String path) { DiffEntry e = new DiffEntry(); e.oldMode = FileMode.REGULAR_FILE; e.oldPath = path; e.newMode = FileMode.REGULAR_FILE; e.newPath = path; e.changeType = ChangeType.MODIFY; return e; }
Breaks apart a DiffEntry into two entries, one DELETE and one ADD.
Params:
  • entry – the DiffEntry to break apart.
Returns:a list containing two entries. Calling getChangeType() on the first entry will return ChangeType.DELETE. Calling it on the second entry will return ChangeType.ADD.
/** * Breaks apart a DiffEntry into two entries, one DELETE and one ADD. * * @param entry * the DiffEntry to break apart. * @return a list containing two entries. Calling {@link #getChangeType()} * on the first entry will return ChangeType.DELETE. Calling it on * the second entry will return ChangeType.ADD. */
static List<DiffEntry> breakModify(DiffEntry entry) { DiffEntry del = new DiffEntry(); del.oldId = entry.getOldId(); del.oldMode = entry.getOldMode(); del.oldPath = entry.getOldPath(); del.newId = A_ZERO; del.newMode = FileMode.MISSING; del.newPath = DiffEntry.DEV_NULL; del.changeType = ChangeType.DELETE; del.diffAttribute = entry.diffAttribute; DiffEntry add = new DiffEntry(); add.oldId = A_ZERO; add.oldMode = FileMode.MISSING; add.oldPath = DiffEntry.DEV_NULL; add.newId = entry.getNewId(); add.newMode = entry.getNewMode(); add.newPath = entry.getNewPath(); add.changeType = ChangeType.ADD; add.diffAttribute = entry.diffAttribute; return Arrays.asList(del, add); } static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst, int score) { DiffEntry r = new DiffEntry(); r.oldId = src.oldId; r.oldMode = src.oldMode; r.oldPath = src.oldPath; r.newId = dst.newId; r.newMode = dst.newMode; r.newPath = dst.newPath; r.diffAttribute = dst.diffAttribute; r.changeType = changeType; r.score = score; r.treeFilterMarks = src.treeFilterMarks | dst.treeFilterMarks; return r; }
File name of the old (pre-image).
/** File name of the old (pre-image). */
protected String oldPath;
File name of the new (post-image).
/** File name of the new (post-image). */
protected String newPath;
diff filter attribute
Since:4.11
/** * diff filter attribute * * @since 4.11 */
protected Attribute diffAttribute;
Old mode of the file, if described by the patch, else null.
/** Old mode of the file, if described by the patch, else null. */
protected FileMode oldMode;
New mode of the file, if described by the patch, else null.
/** New mode of the file, if described by the patch, else null. */
protected FileMode newMode;
General type of change indicated by the patch.
/** General type of change indicated by the patch. */
protected ChangeType changeType;
Similarity score if changeType is a copy or rename.
/** Similarity score if {@link #changeType} is a copy or rename. */
protected int score;
ObjectId listed on the index line for the old (pre-image)
/** ObjectId listed on the index line for the old (pre-image) */
protected AbbreviatedObjectId oldId;
ObjectId listed on the index line for the new (post-image)
/** ObjectId listed on the index line for the new (post-image) */
protected AbbreviatedObjectId newId;
Bitset for marked flags of tree filters passed to scan(TreeWalk, boolean, TreeFilter...)
/** * Bitset for marked flags of tree filters passed to * {@link #scan(TreeWalk, boolean, TreeFilter...)} */
private int treeFilterMarks = 0;
Get the old name associated with this file.

The meaning of the old name can differ depending on the semantic meaning of this patch:

  • file add: always /dev/null
  • file modify: always getNewPath()
  • file delete: always the file being deleted
  • file copy: source file the copy originates from
  • file rename: source file the rename originates from
Returns:old name for this file.
/** * Get the old name associated with this file. * <p> * The meaning of the old name can differ depending on the semantic meaning * of this patch: * <ul> * <li><i>file add</i>: always <code>/dev/null</code></li> * <li><i>file modify</i>: always {@link #getNewPath()}</li> * <li><i>file delete</i>: always the file being deleted</li> * <li><i>file copy</i>: source file the copy originates from</li> * <li><i>file rename</i>: source file the rename originates from</li> * </ul> * * @return old name for this file. */
public String getOldPath() { return oldPath; }
Get the new name associated with this file.

The meaning of the new name can differ depending on the semantic meaning of this patch:

  • file add: always the file being created
  • file modify: always getOldPath()
  • file delete: always /dev/null
  • file copy: destination file the copy ends up at
  • file rename: destination file the rename ends up at
Returns:new name for this file.
/** * Get the new name associated with this file. * <p> * The meaning of the new name can differ depending on the semantic meaning * of this patch: * <ul> * <li><i>file add</i>: always the file being created</li> * <li><i>file modify</i>: always {@link #getOldPath()}</li> * <li><i>file delete</i>: always <code>/dev/null</code></li> * <li><i>file copy</i>: destination file the copy ends up at</li> * <li><i>file rename</i>: destination file the rename ends up at</li> * </ul> * * @return new name for this file. */
public String getNewPath() { return newPath; }
Get the path associated with this file.
Params:
  • side – which path to obtain.
Returns:name for this file.
/** * Get the path associated with this file. * * @param side * which path to obtain. * @return name for this file. */
public String getPath(Side side) { return side == Side.OLD ? getOldPath() : getNewPath(); }
Returns:the Attribute determining filters to be applied.
Since:4.11
/** * @return the {@link Attribute} determining filters to be applied. * @since 4.11 */
public Attribute getDiffAttribute() { return diffAttribute; }
Get the old file mode
Returns:the old file mode, if described in the patch
/** * Get the old file mode * * @return the old file mode, if described in the patch */
public FileMode getOldMode() { return oldMode; }
Get the new file mode
Returns:the new file mode, if described in the patch
/** * Get the new file mode * * @return the new file mode, if described in the patch */
public FileMode getNewMode() { return newMode; }
Get the mode associated with this file.
Params:
  • side – which mode to obtain.
Returns:the mode.
/** * Get the mode associated with this file. * * @param side * which mode to obtain. * @return the mode. */
public FileMode getMode(Side side) { return side == Side.OLD ? getOldMode() : getNewMode(); }
Get the change type
Returns:the type of change this patch makes on getNewPath()
/** * Get the change type * * @return the type of change this patch makes on {@link #getNewPath()} */
public ChangeType getChangeType() { return changeType; }
Get similarity score
Returns:similarity score between getOldPath() and getNewPath() if getChangeType() is ChangeType.COPY or ChangeType.RENAME.
/** * Get similarity score * * @return similarity score between {@link #getOldPath()} and * {@link #getNewPath()} if {@link #getChangeType()} is * {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#COPY} or * {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#RENAME}. */
public int getScore() { return score; }
Get the old object id from the index.
Returns:the object id; null if there is no index line
/** * Get the old object id from the <code>index</code>. * * @return the object id; null if there is no index line */
public AbbreviatedObjectId getOldId() { return oldId; }
Get the new object id from the index.
Returns:the object id; null if there is no index line
/** * Get the new object id from the <code>index</code>. * * @return the object id; null if there is no index line */
public AbbreviatedObjectId getNewId() { return newId; }
Whether the mark tree filter with the specified index matched during scan or not, see scan(TreeWalk, boolean, TreeFilter...). Example:

TreeFilter filterA = ...;
TreeFilter filterB = ...;
List<DiffEntry> entries = DiffEntry.scan(walk, false, filterA, filterB);
DiffEntry entry = entries.get(0);
boolean filterAMatched = entry.isMarked(0);
boolean filterBMatched = entry.isMarked(1);

Note that 0 corresponds to filterA because it was the first filter that was passed to scan.

To query more than one flag at once, see getTreeFilterMarks().

Params:
  • index – the index of the tree filter to check for (must be between 0 and Integer.SIZE).
Since:2.3
Returns:a boolean.
/** * Whether the mark tree filter with the specified index matched during scan * or not, see {@link #scan(TreeWalk, boolean, TreeFilter...)}. Example: * <p> * * <pre> * TreeFilter filterA = ...; * TreeFilter filterB = ...; * List&lt;DiffEntry&gt; entries = DiffEntry.scan(walk, false, filterA, filterB); * DiffEntry entry = entries.get(0); * boolean filterAMatched = entry.isMarked(0); * boolean filterBMatched = entry.isMarked(1); * </pre> * <p> * Note that 0 corresponds to filterA because it was the first filter that * was passed to scan. * <p> * To query more than one flag at once, see {@link #getTreeFilterMarks()}. * * @param index * the index of the tree filter to check for (must be between 0 * and {@link java.lang.Integer#SIZE}). * @since 2.3 * @return a boolean. */
public boolean isMarked(int index) { return (treeFilterMarks & (1L << index)) != 0; }
Get the raw tree filter marks, as set during scan(TreeWalk, boolean, TreeFilter...). See isMarked(int) to query each mark individually.
Returns:the bitset of tree filter marks
Since:2.3
/** * Get the raw tree filter marks, as set during * {@link #scan(TreeWalk, boolean, TreeFilter...)}. See * {@link #isMarked(int)} to query each mark individually. * * @return the bitset of tree filter marks * @since 2.3 */
public int getTreeFilterMarks() { return treeFilterMarks; }
Get the object id.
Params:
  • side – the side of the id to get.
Returns:the object id; null if there is no index line
/** * Get the object id. * * @param side * the side of the id to get. * @return the object id; null if there is no index line */
public AbbreviatedObjectId getId(Side side) { return side == Side.OLD ? getOldId() : getNewId(); }
{@inheritDoc}
/** {@inheritDoc} */
@SuppressWarnings("nls") @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("DiffEntry["); buf.append(changeType); buf.append(" "); switch (changeType) { case ADD: buf.append(newPath); break; case COPY: buf.append(oldPath + "->" + newPath); break; case DELETE: buf.append(oldPath); break; case MODIFY: buf.append(oldPath); break; case RENAME: buf.append(oldPath + "->" + newPath); break; } buf.append("]"); return buf.toString(); } }