org.eclipse.egit.ui.internal.history.FileDiff.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.ui.internal.history.FileDiff.java

Source

/*******************************************************************************
 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * Copyright (c) 2010, Stefan Lay <stefan.lay@sap.com>
 * Copyright (C) 2012, Robin Stocker <robin@nibor.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.egit.ui.internal.history;

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

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.DecorationOverlayDescriptor;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.MyersDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilterMarker;
import org.eclipse.ui.model.WorkbenchAdapter;

/**
 * A class with information about the changes to a file introduced in a
 * commit.
 */
public class FileDiff extends WorkbenchAdapter {

    private final RevCommit commit;

    private DiffEntry diffEntry;

    private static ObjectId[] trees(final RevCommit commit) {
        final ObjectId[] r = new ObjectId[commit.getParentCount() + 1];
        for (int i = 0; i < r.length - 1; i++)
            r[i] = commit.getParent(i).getTree().getId();
        r[r.length - 1] = commit.getTree().getId();
        return r;
    }

    /**
     * Computer file diffs for specified tree walk and commit
     *
     * @param walk
     * @param commit
     * @param markTreeFilters optional filters for marking entries, see {@link #isMarked(int)}
     * @return non-null but possibly empty array of file diffs
     * @throws MissingObjectException
     * @throws IncorrectObjectTypeException
     * @throws CorruptObjectException
     * @throws IOException
     */
    public static FileDiff[] compute(final TreeWalk walk, final RevCommit commit,
            final TreeFilter... markTreeFilters)
            throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
        final ArrayList<FileDiff> r = new ArrayList<FileDiff>();

        if (commit.getParentCount() > 0)
            walk.reset(trees(commit));
        else {
            walk.reset();
            walk.addTree(new EmptyTreeIterator());
            walk.addTree(commit.getTree());
        }

        if (walk.getTreeCount() <= 2) {
            List<DiffEntry> entries = DiffEntry.scan(walk, false, markTreeFilters);
            for (DiffEntry entry : entries) {
                final FileDiff d = new FileDiff(commit, entry);
                r.add(d);
            }
        } else { // DiffEntry does not support walks with more than two trees
            final int nTree = walk.getTreeCount();
            final int myTree = nTree - 1;

            TreeFilterMarker treeFilterMarker = new TreeFilterMarker(markTreeFilters);

            while (walk.next()) {
                if (matchAnyParent(walk, myTree))
                    continue;

                int treeFilterMarks = treeFilterMarker.getMarks(walk);

                final FileDiffForMerges d = new FileDiffForMerges(commit, treeFilterMarks);
                d.path = walk.getPathString();
                int m0 = 0;
                for (int i = 0; i < myTree; i++)
                    m0 |= walk.getRawMode(i);
                final int m1 = walk.getRawMode(myTree);
                d.change = ChangeType.MODIFY;
                if (m0 == 0 && m1 != 0)
                    d.change = ChangeType.ADD;
                else if (m0 != 0 && m1 == 0)
                    d.change = ChangeType.DELETE;
                else if (m0 != m1 && walk.idEqual(0, myTree))
                    d.change = ChangeType.MODIFY; // there is no ChangeType.TypeChanged
                d.blobs = new ObjectId[nTree];
                d.modes = new FileMode[nTree];
                for (int i = 0; i < nTree; i++) {
                    d.blobs[i] = walk.getObjectId(i);
                    d.modes[i] = walk.getFileMode(i);
                }

                r.add(d);
            }

        }

        final FileDiff[] tmp = new FileDiff[r.size()];
        r.toArray(tmp);
        return tmp;
    }

    private static boolean matchAnyParent(final TreeWalk walk, final int myTree) {
        final int m = walk.getRawMode(myTree);
        for (int i = 0; i < myTree; i++)
            if (walk.getRawMode(i) == m && walk.idEqual(i, myTree))
                return true;
        return false;
    }

    /**
     * Creates a textual diff together with meta information.
     * TODO So far this works only in case of one parent commit.
     *
     * @param d
     *            the StringBuilder where the textual diff is added to
     * @param db
     *            the Repo
     * @param diffFmt
     *            the DiffFormatter used to create the textual diff
     * @param gitFormat
     *            if false, do not show any source or destination prefix,
     *            and the paths are calculated relative to the eclipse
     *            project, otherwise relative to the git repository
     * @throws IOException
     */
    public void outputDiff(final StringBuilder d, final Repository db, final DiffFormatter diffFmt,
            boolean gitFormat) throws IOException {
        if (gitFormat) {
            diffFmt.setRepository(db);
            diffFmt.format(diffEntry);
            return;
        }

        ObjectReader reader = db.newObjectReader();
        try {
            outputEclipseDiff(d, db, reader, diffFmt);
        } finally {
            reader.release();
        }
    }

    private void outputEclipseDiff(final StringBuilder d, final Repository db, final ObjectReader reader,
            final DiffFormatter diffFmt) throws IOException {
        if (!(getBlobs().length == 2))
            throw new UnsupportedOperationException(
                    "Not supported yet if the number of parents is different from one"); //$NON-NLS-1$

        String projectRelativePath = getProjectRelativePath(db, getPath());
        d.append("diff --git ").append(projectRelativePath).append(" ") //$NON-NLS-1$ //$NON-NLS-2$
                .append(projectRelativePath).append("\n"); //$NON-NLS-1$
        final ObjectId id1 = getBlobs()[0];
        final ObjectId id2 = getBlobs()[1];
        final FileMode mode1 = getModes()[0];
        final FileMode mode2 = getModes()[1];

        if (id1.equals(ObjectId.zeroId())) {
            d.append("new file mode " + mode2).append("\n"); //$NON-NLS-1$//$NON-NLS-2$
        } else if (id2.equals(ObjectId.zeroId())) {
            d.append("deleted file mode " + mode1).append("\n"); //$NON-NLS-1$//$NON-NLS-2$
        } else if (!mode1.equals(mode2)) {
            d.append("old mode " + mode1); //$NON-NLS-1$
            d.append("new mode " + mode2).append("\n"); //$NON-NLS-1$//$NON-NLS-2$
        }
        d.append("index ").append(reader.abbreviate(id1).name()). //$NON-NLS-1$
                append("..").append(reader.abbreviate(id2).name()). //$NON-NLS-1$
                append(mode1.equals(mode2) ? " " + mode1 : "").append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        if (id1.equals(ObjectId.zeroId()))
            d.append("--- /dev/null\n"); //$NON-NLS-1$
        else {
            d.append("--- "); //$NON-NLS-1$
            d.append(getProjectRelativePath(db, getPath()));
            d.append("\n"); //$NON-NLS-1$
        }

        if (id2.equals(ObjectId.zeroId()))
            d.append("+++ /dev/null\n"); //$NON-NLS-1$
        else {
            d.append("+++ "); //$NON-NLS-1$
            d.append(getProjectRelativePath(db, getPath()));
            d.append("\n"); //$NON-NLS-1$
        }

        final RawText a = getRawText(id1, reader);
        final RawText b = getRawText(id2, reader);
        EditList editList = MyersDiff.INSTANCE.diff(RawTextComparator.DEFAULT, a, b);
        diffFmt.format(editList, a, b);
    }

    private String getProjectRelativePath(Repository db, String repoPath) {
        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IWorkspaceRoot root = workspace.getRoot();
        IPath absolutePath = new Path(db.getWorkTree().getAbsolutePath()).append(repoPath);
        IResource resource = root.getFileForLocation(absolutePath);
        return resource.getProjectRelativePath().toString();
    }

    private RawText getRawText(ObjectId id, ObjectReader reader) throws IOException {
        if (id.equals(ObjectId.zeroId()))
            return new RawText(new byte[] {});
        ObjectLoader ldr = reader.open(id, Constants.OBJ_BLOB);
        return new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
    }

    /**
     * Get commit
     *
     * @return commit
     */
    public RevCommit getCommit() {
        return commit;
    }

    /**
     * Get path
     *
     * @return path
     */
    public String getPath() {
        if (ChangeType.DELETE.equals(diffEntry.getChangeType()))
            return diffEntry.getOldPath();
        return diffEntry.getNewPath();
    }

    /**
     * Get change type
     *
     * @return type
     */
    public ChangeType getChange() {
        return diffEntry.getChangeType();
    }

    /**
     * Get blob object ids
     *
     * @return non-null but possibly empty array of object ids
     */
    public ObjectId[] getBlobs() {
        List<ObjectId> objectIds = new ArrayList<ObjectId>();
        if (diffEntry.getOldId() != null)
            objectIds.add(diffEntry.getOldId().toObjectId());
        if (diffEntry.getNewId() != null)
            objectIds.add(diffEntry.getNewId().toObjectId());
        return objectIds.toArray(new ObjectId[] {});
    }

    /**
     * Get file modes
     *
     * @return non-null but possibly empty array of file modes
     */
    public FileMode[] getModes() {
        List<FileMode> modes = new ArrayList<FileMode>();
        if (diffEntry.getOldMode() != null)
            modes.add(diffEntry.getOldMode());
        if (diffEntry.getOldMode() != null)
            modes.add(diffEntry.getOldMode());
        return modes.toArray(new FileMode[] {});
    }

    /**
     * Whether the mark tree filter with the specified index matched during scan
     * or not, see {@link #compute(TreeWalk, RevCommit, TreeFilter...)}.
     *
     * @param index the tree filter index to check
     * @return true if it was marked, false otherwise
     */
    public boolean isMarked(int index) {
        return diffEntry != null && diffEntry.isMarked(index);
    }

    /**
     * Create a file diff for a specified {@link RevCommit} and
     * {@link DiffEntry}
     *
     * @param c
     * @param entry
     */
    public FileDiff(final RevCommit c, final DiffEntry entry) {
        diffEntry = entry;
        commit = c;
    }

    /**
     * Is this diff a submodule?
     *
     * @return true if submodule, false otherwise
     */
    public boolean isSubmodule() {
        if (diffEntry == null)
            return false;
        return diffEntry.getOldMode() == FileMode.GITLINK || diffEntry.getNewMode() == FileMode.GITLINK;
    }

    public ImageDescriptor getImageDescriptor(Object object) {
        final ImageDescriptor base;
        if (!isSubmodule())
            base = UIUtils.getEditorImage(getPath());
        else
            base = UIIcons.REPOSITORY;
        switch (getChange()) {
        case ADD:
            return new DecorationOverlayDescriptor(base, UIIcons.OVR_STAGED_ADD, IDecoration.BOTTOM_RIGHT);
        case DELETE:
            return new DecorationOverlayDescriptor(base, UIIcons.OVR_STAGED_REMOVE, IDecoration.BOTTOM_RIGHT);
        default:
            return base;
        }
    }

    public String getLabel(Object object) {
        return getPath();
    }

    private static class FileDiffForMerges extends FileDiff {
        private String path;

        private ChangeType change;

        private ObjectId[] blobs;

        private FileMode[] modes;

        private final int treeFilterMarks;

        private FileDiffForMerges(final RevCommit c, int treeFilterMarks) {
            super(c, null);
            this.treeFilterMarks = treeFilterMarks;
        }

        @Override
        public String getPath() {
            return path;
        }

        @Override
        public ChangeType getChange() {
            return change;
        }

        @Override
        public ObjectId[] getBlobs() {
            return blobs;
        }

        @Override
        public FileMode[] getModes() {
            return modes;
        }

        @Override
        public boolean isMarked(int index) {
            return (treeFilterMarks & (1L << index)) != 0;
        }
    }
}