org.eclipse.jgit.diff.DiffEntry.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.diff.DiffEntry.java

Source

/*
 * 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
 */
public class DiffEntry {
    /** 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. */
    public static final String DEV_NULL = "/dev/null"; //$NON-NLS-1$

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

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

        /** Delete an existing file from the project */
        DELETE,

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

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

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

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

    /**
     * Create an empty DiffEntry
     */
    protected DiffEntry() {
        // reduce the visibility of the default constructor
    }

    /**
     * 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
     * {@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
     * {@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.
     *
     * @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). */
    protected String oldPath;

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

    /**
     * diff filter attribute
     *
     * @since 4.11
     */
    protected Attribute diffAttribute;

    /** 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. */
    protected FileMode newMode;

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

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

    /** 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) */
    protected AbbreviatedObjectId newId;

    /**
     * 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.
     * <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.
     * <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.
     *
     * @param side
     *            which path to obtain.
     * @return name for this file.
     */
    public String getPath(Side side) {
        return side == Side.OLD ? getOldPath() : getNewPath();
    }

    /**
     * @return the {@link Attribute} determining filters to be applied.
     * @since 4.11
     */
    public Attribute getDiffAttribute() {
        return diffAttribute;
    }

    /**
     * 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
     *
     * @return the new file mode, if described in the patch
     */
    public FileMode getNewMode() {
        return newMode;
    }

    /**
     * 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
     *
     * @return the type of change this patch makes on {@link #getNewPath()}
     */
    public ChangeType getChangeType() {
        return changeType;
    }

    /**
     * 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 <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 <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 {@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
     * {@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.
     *
     * @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} */
    @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();
    }
}