DiffEntry.java

  1. /*
  2.  * Copyright (C) 2008-2013, Google Inc. 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. package org.eclipse.jgit.diff;

  11. import java.io.IOException;
  12. import java.util.ArrayList;
  13. import java.util.Arrays;
  14. import java.util.List;

  15. import org.eclipse.jgit.attributes.Attribute;
  16. import org.eclipse.jgit.internal.JGitText;
  17. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  18. import org.eclipse.jgit.lib.AnyObjectId;
  19. import org.eclipse.jgit.lib.Constants;
  20. import org.eclipse.jgit.lib.FileMode;
  21. import org.eclipse.jgit.lib.MutableObjectId;
  22. import org.eclipse.jgit.lib.ObjectId;
  23. import org.eclipse.jgit.treewalk.TreeWalk;
  24. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  25. import org.eclipse.jgit.treewalk.filter.TreeFilterMarker;

  26. /**
  27.  * A value class representing a change to a file
  28.  */
  29. public class DiffEntry {
  30.     /** Magical SHA1 used for file adds or deletes */
  31.     static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId
  32.             .fromObjectId(ObjectId.zeroId());

  33.     /** Magical file name used for file adds or deletes. */
  34.     public static final String DEV_NULL = "/dev/null"; //$NON-NLS-1$

  35.     /** General type of change a single file-level patch describes. */
  36.     public enum ChangeType {
  37.         /** Add a new file to the project */
  38.         ADD,

  39.         /** Modify an existing file in the project (content and/or mode) */
  40.         MODIFY,

  41.         /** Delete an existing file from the project */
  42.         DELETE,

  43.         /** Rename an existing file to a new location */
  44.         RENAME,

  45.         /** Copy an existing file to a new location, keeping the original */
  46.         COPY;
  47.     }

  48.     /** Specify the old or new side for more generalized access. */
  49.     public enum Side {
  50.         /** The old side of a DiffEntry. */
  51.         OLD,

  52.         /** The new side of a DiffEntry. */
  53.         NEW;
  54.     }

  55.     /**
  56.      * Create an empty DiffEntry
  57.      */
  58.     protected DiffEntry(){
  59.         // reduce the visibility of the default constructor
  60.     }

  61.     /**
  62.      * Convert the TreeWalk into DiffEntry headers.
  63.      *
  64.      * @param walk
  65.      *            the TreeWalk to walk through. Must have exactly two trees.
  66.      * @return headers describing the changed files.
  67.      * @throws java.io.IOException
  68.      *             the repository cannot be accessed.
  69.      * @throws java.lang.IllegalArgumentException
  70.      *             When given TreeWalk doesn't have exactly two trees.
  71.      */
  72.     public static List<DiffEntry> scan(TreeWalk walk) throws IOException {
  73.         return scan(walk, false);
  74.     }

  75.     /**
  76.      * Convert the TreeWalk into DiffEntry headers, depending on
  77.      * {@code includeTrees} it will add tree objects into result or not.
  78.      *
  79.      * @param walk
  80.      *            the TreeWalk to walk through. Must have exactly two trees and
  81.      *            when {@code includeTrees} parameter is {@code true} it can't
  82.      *            be recursive.
  83.      * @param includeTrees
  84.      *            include tree objects.
  85.      * @return headers describing the changed files.
  86.      * @throws java.io.IOException
  87.      *             the repository cannot be accessed.
  88.      * @throws java.lang.IllegalArgumentException
  89.      *             when {@code includeTrees} is true and given TreeWalk is
  90.      *             recursive. Or when given TreeWalk doesn't have exactly two
  91.      *             trees
  92.      */
  93.     public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees)
  94.             throws IOException {
  95.         return scan(walk, includeTrees, null);
  96.     }

  97.     /**
  98.      * Convert the TreeWalk into DiffEntry headers, depending on
  99.      * {@code includeTrees} it will add tree objects into result or not.
  100.      *
  101.      * @param walk
  102.      *            the TreeWalk to walk through. Must have exactly two trees and
  103.      *            when {@code includeTrees} parameter is {@code true} it can't
  104.      *            be recursive.
  105.      * @param includeTrees
  106.      *            include tree objects.
  107.      * @param markTreeFilters
  108.      *            array of tree filters which will be tested for each entry. If
  109.      *            an entry matches, the entry will later return true when
  110.      *            queried through {{@link #isMarked(int)} (with the index from
  111.      *            this passed array).
  112.      * @return headers describing the changed files.
  113.      * @throws java.io.IOException
  114.      *             the repository cannot be accessed.
  115.      * @throws java.lang.IllegalArgumentException
  116.      *             when {@code includeTrees} is true and given TreeWalk is
  117.      *             recursive. Or when given TreeWalk doesn't have exactly two
  118.      *             trees
  119.      * @since 2.3
  120.      */
  121.     public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees,
  122.             TreeFilter[] markTreeFilters)
  123.             throws IOException {
  124.         if (walk.getTreeCount() != 2)
  125.             throw new IllegalArgumentException(
  126.                     JGitText.get().treeWalkMustHaveExactlyTwoTrees);
  127.         if (includeTrees && walk.isRecursive())
  128.             throw new IllegalArgumentException(
  129.                     JGitText.get().cannotBeRecursiveWhenTreesAreIncluded);

  130.         TreeFilterMarker treeFilterMarker;
  131.         if (markTreeFilters != null && markTreeFilters.length > 0)
  132.             treeFilterMarker = new TreeFilterMarker(markTreeFilters);
  133.         else
  134.             treeFilterMarker = null;

  135.         List<DiffEntry> r = new ArrayList<>();
  136.         MutableObjectId idBuf = new MutableObjectId();
  137.         while (walk.next()) {
  138.             DiffEntry entry = new DiffEntry();

  139.             walk.getObjectId(idBuf, 0);
  140.             entry.oldId = AbbreviatedObjectId.fromObjectId(idBuf);

  141.             walk.getObjectId(idBuf, 1);
  142.             entry.newId = AbbreviatedObjectId.fromObjectId(idBuf);

  143.             entry.oldMode = walk.getFileMode(0);
  144.             entry.newMode = walk.getFileMode(1);
  145.             entry.newPath = entry.oldPath = walk.getPathString();

  146.             if (walk.getAttributesNodeProvider() != null) {
  147.                 entry.diffAttribute = walk.getAttributes()
  148.                         .get(Constants.ATTR_DIFF);
  149.             }

  150.             if (treeFilterMarker != null)
  151.                 entry.treeFilterMarks = treeFilterMarker.getMarks(walk);

  152.             if (entry.oldMode == FileMode.MISSING) {
  153.                 entry.oldPath = DiffEntry.DEV_NULL;
  154.                 entry.changeType = ChangeType.ADD;
  155.                 r.add(entry);

  156.             } else if (entry.newMode == FileMode.MISSING) {
  157.                 entry.newPath = DiffEntry.DEV_NULL;
  158.                 entry.changeType = ChangeType.DELETE;
  159.                 r.add(entry);

  160.             } else if (!entry.oldId.equals(entry.newId)) {
  161.                 entry.changeType = ChangeType.MODIFY;
  162.                 if (RenameDetector.sameType(entry.oldMode, entry.newMode))
  163.                     r.add(entry);
  164.                 else
  165.                     r.addAll(breakModify(entry));
  166.             } else if (entry.oldMode != entry.newMode) {
  167.                 entry.changeType = ChangeType.MODIFY;
  168.                 r.add(entry);
  169.             }

  170.             if (includeTrees && walk.isSubtree())
  171.                 walk.enterSubtree();
  172.         }
  173.         return r;
  174.     }

  175.     static DiffEntry add(String path, AnyObjectId id) {
  176.         DiffEntry e = new DiffEntry();
  177.         e.oldId = A_ZERO;
  178.         e.oldMode = FileMode.MISSING;
  179.         e.oldPath = DEV_NULL;

  180.         e.newId = AbbreviatedObjectId.fromObjectId(id);
  181.         e.newMode = FileMode.REGULAR_FILE;
  182.         e.newPath = path;
  183.         e.changeType = ChangeType.ADD;
  184.         return e;
  185.     }

  186.     static DiffEntry delete(String path, AnyObjectId id) {
  187.         DiffEntry e = new DiffEntry();
  188.         e.oldId = AbbreviatedObjectId.fromObjectId(id);
  189.         e.oldMode = FileMode.REGULAR_FILE;
  190.         e.oldPath = path;

  191.         e.newId = A_ZERO;
  192.         e.newMode = FileMode.MISSING;
  193.         e.newPath = DEV_NULL;
  194.         e.changeType = ChangeType.DELETE;
  195.         return e;
  196.     }

  197.     static DiffEntry modify(String path) {
  198.         DiffEntry e = new DiffEntry();
  199.         e.oldMode = FileMode.REGULAR_FILE;
  200.         e.oldPath = path;

  201.         e.newMode = FileMode.REGULAR_FILE;
  202.         e.newPath = path;
  203.         e.changeType = ChangeType.MODIFY;
  204.         return e;
  205.     }

  206.     /**
  207.      * Breaks apart a DiffEntry into two entries, one DELETE and one ADD.
  208.      *
  209.      * @param entry
  210.      *            the DiffEntry to break apart.
  211.      * @return a list containing two entries. Calling {@link #getChangeType()}
  212.      *         on the first entry will return ChangeType.DELETE. Calling it on
  213.      *         the second entry will return ChangeType.ADD.
  214.      */
  215.     static List<DiffEntry> breakModify(DiffEntry entry) {
  216.         DiffEntry del = new DiffEntry();
  217.         del.oldId = entry.getOldId();
  218.         del.oldMode = entry.getOldMode();
  219.         del.oldPath = entry.getOldPath();

  220.         del.newId = A_ZERO;
  221.         del.newMode = FileMode.MISSING;
  222.         del.newPath = DiffEntry.DEV_NULL;
  223.         del.changeType = ChangeType.DELETE;
  224.         del.diffAttribute = entry.diffAttribute;

  225.         DiffEntry add = new DiffEntry();
  226.         add.oldId = A_ZERO;
  227.         add.oldMode = FileMode.MISSING;
  228.         add.oldPath = DiffEntry.DEV_NULL;

  229.         add.newId = entry.getNewId();
  230.         add.newMode = entry.getNewMode();
  231.         add.newPath = entry.getNewPath();
  232.         add.changeType = ChangeType.ADD;
  233.         add.diffAttribute = entry.diffAttribute;
  234.         return Arrays.asList(del, add);
  235.     }

  236.     static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
  237.             int score) {
  238.         DiffEntry r = new DiffEntry();

  239.         r.oldId = src.oldId;
  240.         r.oldMode = src.oldMode;
  241.         r.oldPath = src.oldPath;

  242.         r.newId = dst.newId;
  243.         r.newMode = dst.newMode;
  244.         r.newPath = dst.newPath;
  245.         r.diffAttribute = dst.diffAttribute;

  246.         r.changeType = changeType;
  247.         r.score = score;

  248.         r.treeFilterMarks = src.treeFilterMarks | dst.treeFilterMarks;

  249.         return r;
  250.     }

  251.     /** File name of the old (pre-image). */
  252.     protected String oldPath;

  253.     /** File name of the new (post-image). */
  254.     protected String newPath;

  255.     /**
  256.      * diff filter attribute
  257.      *
  258.      * @since 4.11
  259.      */
  260.     protected Attribute diffAttribute;

  261.     /** Old mode of the file, if described by the patch, else null. */
  262.     protected FileMode oldMode;

  263.     /** New mode of the file, if described by the patch, else null. */
  264.     protected FileMode newMode;

  265.     /** General type of change indicated by the patch. */
  266.     protected ChangeType changeType;

  267.     /** Similarity score if {@link #changeType} is a copy or rename. */
  268.     protected int score;

  269.     /** ObjectId listed on the index line for the old (pre-image) */
  270.     protected AbbreviatedObjectId oldId;

  271.     /** ObjectId listed on the index line for the new (post-image) */
  272.     protected AbbreviatedObjectId newId;

  273.     /**
  274.      * Bitset for marked flags of tree filters passed to
  275.      * {@link #scan(TreeWalk, boolean, TreeFilter...)}
  276.      */
  277.     private int treeFilterMarks = 0;

  278.     /**
  279.      * Get the old name associated with this file.
  280.      * <p>
  281.      * The meaning of the old name can differ depending on the semantic meaning
  282.      * of this patch:
  283.      * <ul>
  284.      * <li><i>file add</i>: always <code>/dev/null</code></li>
  285.      * <li><i>file modify</i>: always {@link #getNewPath()}</li>
  286.      * <li><i>file delete</i>: always the file being deleted</li>
  287.      * <li><i>file copy</i>: source file the copy originates from</li>
  288.      * <li><i>file rename</i>: source file the rename originates from</li>
  289.      * </ul>
  290.      *
  291.      * @return old name for this file.
  292.      */
  293.     public String getOldPath() {
  294.         return oldPath;
  295.     }

  296.     /**
  297.      * Get the new name associated with this file.
  298.      * <p>
  299.      * The meaning of the new name can differ depending on the semantic meaning
  300.      * of this patch:
  301.      * <ul>
  302.      * <li><i>file add</i>: always the file being created</li>
  303.      * <li><i>file modify</i>: always {@link #getOldPath()}</li>
  304.      * <li><i>file delete</i>: always <code>/dev/null</code></li>
  305.      * <li><i>file copy</i>: destination file the copy ends up at</li>
  306.      * <li><i>file rename</i>: destination file the rename ends up at</li>
  307.      * </ul>
  308.      *
  309.      * @return new name for this file.
  310.      */
  311.     public String getNewPath() {
  312.         return newPath;
  313.     }

  314.     /**
  315.      * Get the path associated with this file.
  316.      *
  317.      * @param side
  318.      *            which path to obtain.
  319.      * @return name for this file.
  320.      */
  321.     public String getPath(Side side) {
  322.         return side == Side.OLD ? getOldPath() : getNewPath();
  323.     }

  324.     /**
  325.      * @return the {@link Attribute} determining filters to be applied.
  326.      * @since 4.11
  327.      */
  328.     public Attribute getDiffAttribute() {
  329.         return diffAttribute;
  330.     }

  331.     /**
  332.      * Get the old file mode
  333.      *
  334.      * @return the old file mode, if described in the patch
  335.      */
  336.     public FileMode getOldMode() {
  337.         return oldMode;
  338.     }

  339.     /**
  340.      * Get the new file mode
  341.      *
  342.      * @return the new file mode, if described in the patch
  343.      */
  344.     public FileMode getNewMode() {
  345.         return newMode;
  346.     }

  347.     /**
  348.      * Get the mode associated with this file.
  349.      *
  350.      * @param side
  351.      *            which mode to obtain.
  352.      * @return the mode.
  353.      */
  354.     public FileMode getMode(Side side) {
  355.         return side == Side.OLD ? getOldMode() : getNewMode();
  356.     }

  357.     /**
  358.      * Get the change type
  359.      *
  360.      * @return the type of change this patch makes on {@link #getNewPath()}
  361.      */
  362.     public ChangeType getChangeType() {
  363.         return changeType;
  364.     }

  365.     /**
  366.      * Get similarity score
  367.      *
  368.      * @return similarity score between {@link #getOldPath()} and
  369.      *         {@link #getNewPath()} if {@link #getChangeType()} is
  370.      *         {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#COPY} or
  371.      *         {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#RENAME}.
  372.      */
  373.     public int getScore() {
  374.         return score;
  375.     }

  376.     /**
  377.      * Get the old object id from the <code>index</code>.
  378.      *
  379.      * @return the object id; null if there is no index line
  380.      */
  381.     public AbbreviatedObjectId getOldId() {
  382.         return oldId;
  383.     }

  384.     /**
  385.      * Get the new object id from the <code>index</code>.
  386.      *
  387.      * @return the object id; null if there is no index line
  388.      */
  389.     public AbbreviatedObjectId getNewId() {
  390.         return newId;
  391.     }

  392.     /**
  393.      * Whether the mark tree filter with the specified index matched during scan
  394.      * or not, see {@link #scan(TreeWalk, boolean, TreeFilter...)}. Example:
  395.      * <p>
  396.      *
  397.      * <pre>
  398.      * TreeFilter filterA = ...;
  399.      * TreeFilter filterB = ...;
  400.      * List&lt;DiffEntry&gt; entries = DiffEntry.scan(walk, false, filterA, filterB);
  401.      * DiffEntry entry = entries.get(0);
  402.      * boolean filterAMatched = entry.isMarked(0);
  403.      * boolean filterBMatched = entry.isMarked(1);
  404.      * </pre>
  405.      * <p>
  406.      * Note that 0 corresponds to filterA because it was the first filter that
  407.      * was passed to scan.
  408.      * <p>
  409.      * To query more than one flag at once, see {@link #getTreeFilterMarks()}.
  410.      *
  411.      * @param index
  412.      *            the index of the tree filter to check for (must be between 0
  413.      *            and {@link java.lang.Integer#SIZE}).
  414.      * @since 2.3
  415.      * @return a boolean.
  416.      */
  417.     public boolean isMarked(int index) {
  418.         return (treeFilterMarks & (1L << index)) != 0;
  419.     }

  420.     /**
  421.      * Get the raw tree filter marks, as set during
  422.      * {@link #scan(TreeWalk, boolean, TreeFilter...)}. See
  423.      * {@link #isMarked(int)} to query each mark individually.
  424.      *
  425.      * @return the bitset of tree filter marks
  426.      * @since 2.3
  427.      */
  428.     public int getTreeFilterMarks() {
  429.         return treeFilterMarks;
  430.     }

  431.     /**
  432.      * Get the object id.
  433.      *
  434.      * @param side
  435.      *            the side of the id to get.
  436.      * @return the object id; null if there is no index line
  437.      */
  438.     public AbbreviatedObjectId getId(Side side) {
  439.         return side == Side.OLD ? getOldId() : getNewId();
  440.     }

  441.     /** {@inheritDoc} */
  442.     @SuppressWarnings("nls")
  443.     @Override
  444.     public String toString() {
  445.         StringBuilder buf = new StringBuilder();
  446.         buf.append("DiffEntry[");
  447.         buf.append(changeType);
  448.         buf.append(" ");
  449.         switch (changeType) {
  450.         case ADD:
  451.             buf.append(newPath);
  452.             break;
  453.         case COPY:
  454.             buf.append(oldPath + "->" + newPath);
  455.             break;
  456.         case DELETE:
  457.             buf.append(oldPath);
  458.             break;
  459.         case MODIFY:
  460.             buf.append(oldPath);
  461.             break;
  462.         case RENAME:
  463.             buf.append(oldPath + "->" + newPath);
  464.             break;
  465.         }
  466.         buf.append("]");
  467.         return buf.toString();
  468.     }
  469. }