Package org.eclipse.egit.ui.internal.history

Source Code of org.eclipse.egit.ui.internal.history.FileDiff$FileDiffForMerges

/*******************************************************************************
* 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.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.core.resources.IResource;
import org.eclipse.egit.core.internal.util.ResourceUtil;
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.diff.RenameDetector;
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 {

  /**
   * Comparator for sorting FileDiffs based on getPath().
   */
  public static Comparator<FileDiff> PATH_COMPARATOR = new Comparator<FileDiff>() {
    public int compare(FileDiff o1, FileDiff o2) {
      return o1.getPath().compareTo(o2.getPath());
    }
  };

  private final RevCommit commit;

  private DiffEntry diffEntry;

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

  /**
   * Computer file diffs for specified tree walk and commit
   *
   * @param repository
   * @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 Repository repository,
      final TreeWalk walk, final RevCommit commit,
      final TreeFilter... markTreeFilters) throws MissingObjectException,
      IncorrectObjectTypeException, CorruptObjectException, IOException {
    return compute(repository, walk, commit, commit.getParents(),
        markTreeFilters);
  }

  /**
   * Computer file diffs for specified tree walk and commit
   *
   * @param repository
   * @param walk
   * @param commit
   * @param parents
   * @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 Repository repository,
      final TreeWalk walk, final RevCommit commit,
      final RevCommit[] parents,
      final TreeFilter... markTreeFilters) throws MissingObjectException,
      IncorrectObjectTypeException, CorruptObjectException, IOException {
    final ArrayList<FileDiff> r = new ArrayList<FileDiff>();

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

    if (walk.getTreeCount() <= 2) {
      List<DiffEntry> entries = DiffEntry.scan(walk, false, markTreeFilters);
      List<DiffEntry> xentries = new LinkedList<DiffEntry>(entries);
      RenameDetector detector = new RenameDetector(repository);
      detector.addAll(entries);
      List<DiffEntry> renames = detector.compute(walk.getObjectReader(),
          org.eclipse.jgit.lib.NullProgressMonitor.INSTANCE);
      for (DiffEntry m : renames) {
        final FileDiff d = new FileDiff(commit, m);
        r.add(d);
        for (Iterator<DiffEntry> i = xentries.iterator(); i.hasNext();) {
          DiffEntry n = i.next();
          if (m.getOldPath().equals(n.getOldPath()))
            i.remove();
          else if (m.getNewPath().equals(n.getNewPath()))
            i.remove();
        }
      }
      for (DiffEntry m : xentries) {
        final FileDiff d = new FileDiff(commit, m);
        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 projectRelativeNewPath = getProjectRelativePath(db, getNewPath());
    String projectRelativeOldPath = getProjectRelativePath(db, getOldPath());
    d.append("diff --git ").append(projectRelativeOldPath).append(" ") //$NON-NLS-1$ //$NON-NLS-2$
        .append(projectRelativeNewPath).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, getOldPath()));
      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, getNewPath()));
      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) {
    IResource resource = ResourceUtil.getFileForLocation(db, repoPath);
    if (resource == null)
      return null;
    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;
  }

  /**
   * @return the old path in case of a delete, the new path otherwise, but
   *         never null or <code>/dev/null</code>
   * @see #getNewPath()
   * @see #getOldPath()
   */
  public String getPath() {
    if (ChangeType.DELETE.equals(diffEntry.getChangeType()))
      return diffEntry.getOldPath();
    return diffEntry.getNewPath();
  }

  /**
   * @return the old path or <code>/dev/null</code> for a completely new file
   * @see #getPath() for getting the new or old path depending on change type
   */
  public String getOldPath() {
    return diffEntry.getOldPath();
  }

  /**
   * @return the new path or <code>/dev/null</code> for a deleted file
   * @see #getPath() for getting the new or old path depending on change type
   */
  public String getNewPath() {
    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(Repository, TreeWalk, RevCommit, 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);
    case RENAME:
      return new DecorationOverlayDescriptor(base,
          UIIcons.OVR_STAGED_RENAME, 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 String getNewPath() {
      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;
    }
  }
}
TOP

Related Classes of org.eclipse.egit.ui.internal.history.FileDiff$FileDiffForMerges

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.