Package org.eclipse.egit.ui.internal.merge

Source Code of org.eclipse.egit.ui.internal.merge.GitMergeEditorInput

/*******************************************************************************
* Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com> and others.
*
* 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.merge;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.internal.CompareCoreUtils;
import org.eclipse.egit.core.internal.storage.GitFileRevision;
import org.eclipse.egit.core.internal.storage.WorkingTreeFileRevision;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.CompareUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.revision.EditableRevision;
import org.eclipse.egit.ui.internal.revision.FileRevisionTypedElement;
import org.eclipse.egit.ui.internal.revision.GitCompareFileRevisionEditorInput.EmptyTypedElement;
import org.eclipse.egit.ui.internal.revision.LocalFileRevision;
import org.eclipse.egit.ui.internal.revision.LocationEditableRevision;
import org.eclipse.egit.ui.internal.revision.ResourceEditableRevision;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jgit.api.RebaseCommand;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE.SharedImages;

/**
* A Git-specific {@link CompareEditorInput}
*/
public class GitMergeEditorInput extends CompareEditorInput {
  private static final String LABELPATTERN = "{0} - {1}"; //$NON-NLS-1$

  private static final Image FOLDER_IMAGE = PlatformUI.getWorkbench()
      .getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);

  private static final Image PROJECT_IMAGE = PlatformUI.getWorkbench()
      .getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT);

  private final boolean useWorkspace;

  private final IPath[] locations;

  /**
   * @param useWorkspace
   *            if <code>true</code>, use the workspace content (i.e. the
   *            Git-merged version) as "left" content, otherwise use HEAD
   *            (i.e. the previous, non-merged version)
   * @param locations
   *            as selected by the user
   */
  public GitMergeEditorInput(boolean useWorkspace, IPath... locations) {
    super(new CompareConfiguration());
    this.useWorkspace = useWorkspace;
    this.locations = locations;
    CompareConfiguration config = getCompareConfiguration();
    config.setLeftEditable(true);
  }

  @Override
  public Object getAdapter(Class adapter) {
    if (adapter == IFile.class || adapter == IResource.class) {
      Object selectedEdition = getSelectedEdition();
      if (selectedEdition instanceof DiffNode) {
        DiffNode diffNode = (DiffNode) selectedEdition;
        ITypedElement element = diffNode.getLeft();
        if (element instanceof ResourceEditableRevision) {
          ResourceEditableRevision resourceRevision = (ResourceEditableRevision) element;
          return resourceRevision.getFile();
        }
      }
    }
    return super.getAdapter(adapter);
  }

  @Override
  protected Object prepareInput(IProgressMonitor monitor)
      throws InvocationTargetException, InterruptedException {
    // make sure all resources belong to the same repository
    RevWalk rw = null;
    try {
      monitor.beginTask(
          UIText.GitMergeEditorInput_CheckingResourcesTaskName,
          IProgressMonitor.UNKNOWN);

      Map<Repository, Collection<String>> pathsByRepository = ResourceUtil
          .splitPathsByRepository(Arrays.asList(locations));
      if (pathsByRepository.size() != 1) {
        throw new InvocationTargetException(
            new IllegalStateException(
                UIText.RepositoryAction_multiRepoSelection));
      }
      Repository repo = pathsByRepository.keySet().iterator().next();
      List<String> filterPaths = new ArrayList<String>(
          pathsByRepository.get(repo));

      if (monitor.isCanceled())
        throw new InterruptedException();

      rw = new RevWalk(repo);

      // get the "right" side (MERGE_HEAD for merge, ORIG_HEAD for rebase)
      final RevCommit rightCommit;
      try {
        String target;
        if (repo.getRepositoryState().equals(RepositoryState.MERGING))
          target = Constants.MERGE_HEAD;
        else if (repo.getRepositoryState().equals(RepositoryState.CHERRY_PICKING))
          target = Constants.CHERRY_PICK_HEAD;
        else if (repo.getRepositoryState().equals(
            RepositoryState.REBASING_INTERACTIVE))
          target = readFile(repo.getDirectory(),
              RebaseCommand.REBASE_MERGE + File.separatorChar
                  + RebaseCommand.STOPPED_SHA);
        else
          target = Constants.ORIG_HEAD;
        ObjectId mergeHead = repo.resolve(target);
        if (mergeHead == null)
          throw new IOException(NLS.bind(
              UIText.ValidationUtils_CanNotResolveRefMessage,
              target));
        rightCommit = rw.parseCommit(mergeHead);
      } catch (IOException e) {
        throw new InvocationTargetException(e);
      }

      // we need the HEAD, also to determine the common
      // ancestor
      final RevCommit headCommit;
      try {
        ObjectId head = repo.resolve(Constants.HEAD);
        if (head == null)
          throw new IOException(NLS.bind(
              UIText.ValidationUtils_CanNotResolveRefMessage,
              Constants.HEAD));
        headCommit = rw.parseCommit(head);
      } catch (IOException e) {
        throw new InvocationTargetException(e);
      }

      final String fullBranch;
      try {
        fullBranch = repo.getFullBranch();
      } catch (IOException e) {
        throw new InvocationTargetException(e);
      }

      // try to obtain the common ancestor
      List<RevCommit> startPoints = new ArrayList<RevCommit>();
      rw.setRevFilter(RevFilter.MERGE_BASE);
      startPoints.add(rightCommit);
      startPoints.add(headCommit);
      RevCommit ancestorCommit;
      try {
        rw.markStart(startPoints);
        ancestorCommit = rw.next();
      } catch (Exception e) {
        ancestorCommit = null;
      }

      if (monitor.isCanceled())
        throw new InterruptedException();

      // set the labels
      CompareConfiguration config = getCompareConfiguration();
      config.setRightLabel(NLS.bind(LABELPATTERN, rightCommit
          .getShortMessage(), CompareUtils.truncatedRevision(rightCommit.name())));

      if (!useWorkspace)
        config.setLeftLabel(NLS.bind(LABELPATTERN, headCommit
            .getShortMessage(), CompareUtils.truncatedRevision(headCommit.name())));
      else
        config.setLeftLabel(UIText.GitMergeEditorInput_WorkspaceHeader);

      if (ancestorCommit != null)
        config.setAncestorLabel(NLS.bind(LABELPATTERN, ancestorCommit
            .getShortMessage(), CompareUtils.truncatedRevision(ancestorCommit.name())));

      // set title and icon
      setTitle(NLS.bind(UIText.GitMergeEditorInput_MergeEditorTitle,
          new Object[] {
              Activator.getDefault().getRepositoryUtil()
                  .getRepositoryName(repo),
              rightCommit.getShortMessage(), fullBranch }));

      // build the nodes
      try {
        return buildDiffContainer(repo, headCommit,
            ancestorCommit, filterPaths, rw, monitor);
      } catch (IOException e) {
        throw new InvocationTargetException(e);
      }
    } finally {
      if (rw != null)
        rw.dispose();
      monitor.done();
    }
  }

  @Override
  protected void contentsCreated() {
    super.contentsCreated();
    // select the first conflict
    getNavigator().selectChange(true);
  }

  @Override
  protected void handleDispose() {
    super.handleDispose();
    // we do NOT dispose the images, as these are shared
  }

  private IDiffContainer buildDiffContainer(Repository repository,
      RevCommit headCommit,
      RevCommit ancestorCommit, List<String> filterPaths, RevWalk rw,
      IProgressMonitor monitor) throws IOException, InterruptedException {

    monitor.setTaskName(UIText.GitMergeEditorInput_CalculatingDiffTaskName);
    IDiffContainer result = new DiffNode(Differencer.CONFLICTING);

    TreeWalk tw = new TreeWalk(repository);
    try {
      int dirCacheIndex = tw.addTree(new DirCacheIterator(repository
          .readDirCache()));
      int fileTreeIndex = tw.addTree(new FileTreeIterator(repository));
      int repositoryTreeIndex = tw.addTree(rw.parseTree(repository
          .resolve(Constants.HEAD)));

      // skip ignored resources
      NotIgnoredFilter notIgnoredFilter = new NotIgnoredFilter(
          fileTreeIndex);
      // filter by selected resources
      if (filterPaths.size() > 1) {
        List<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
        for (String filterPath : filterPaths)
          suffixFilters.add(PathFilter.create(filterPath));
        TreeFilter otf = OrTreeFilter.create(suffixFilters);
        tw.setFilter(AndTreeFilter.create(otf, notIgnoredFilter));
      } else if (filterPaths.size() > 0) {
        String path = filterPaths.get(0);
        if (path.length() == 0)
          tw.setFilter(notIgnoredFilter);
        else
          tw.setFilter(AndTreeFilter.create(PathFilter.create(path),
              notIgnoredFilter));
      } else
        tw.setFilter(notIgnoredFilter);

      tw.setRecursive(true);

      while (tw.next()) {
        if (monitor.isCanceled())
          throw new InterruptedException();
        String gitPath = tw.getPathString();
        monitor.setTaskName(gitPath);

        FileTreeIterator fit = tw.getTree(fileTreeIndex,
            FileTreeIterator.class);
        if (fit == null)
          continue;

        DirCacheIterator dit = tw.getTree(dirCacheIndex,
            DirCacheIterator.class);

        final DirCacheEntry dirCacheEntry = dit == null ? null : dit
            .getDirCacheEntry();

        boolean conflicting = dirCacheEntry != null
            && dirCacheEntry.getStage() > 0;

        AbstractTreeIterator rt = tw.getTree(repositoryTreeIndex,
            AbstractTreeIterator.class);

        // compare local file against HEAD to see if it was modified
        boolean modified = rt != null
            && !fit.getEntryObjectId()
                .equals(rt.getEntryObjectId());

        // if this is neither conflicting nor changed, we skip it
        if (!conflicting && !modified)
          continue;

        ITypedElement right;
        if (conflicting) {
          GitFileRevision revision = GitFileRevision.inIndex(
              repository, gitPath, DirCacheEntry.STAGE_3);
          String encoding = CompareCoreUtils.getResourceEncoding(
              repository, gitPath);
          right = new FileRevisionTypedElement(revision, encoding);
        } else
          right = CompareUtils.getFileRevisionTypedElement(gitPath,
              headCommit, repository);

        // can this really happen?
        if (right instanceof EmptyTypedElement)
          continue;

        IFileRevision rev;
        // if the file is not conflicting (as it was auto-merged)
        // we will show the auto-merged (local) version

        Path repositoryPath = new Path(repository.getWorkTree()
            .getAbsolutePath());
        IPath location = repositoryPath
            .append(fit.getEntryPathString());
        IFile file = ResourceUtil.getFileForLocation(location);
        if (!conflicting || useWorkspace) {
          if (file != null)
            rev = new LocalFileRevision(file);
          else
            rev = new WorkingTreeFileRevision(location.toFile());
        } else {
          rev = GitFileRevision.inIndex(repository, gitPath,
              DirCacheEntry.STAGE_2);
        }

        IRunnableContext runnableContext = getContainer();
        if (runnableContext == null)
          runnableContext = PlatformUI.getWorkbench().getProgressService();

        EditableRevision leftEditable;
        if (file != null)
          leftEditable = new ResourceEditableRevision(rev, file,
              runnableContext);
        else
          leftEditable = new LocationEditableRevision(rev, location,
              runnableContext);
        // make sure we don't need a round trip later
        try {
          leftEditable.cacheContents(monitor);
        } catch (CoreException e) {
          throw new IOException(e.getMessage());
        }

        int kind = Differencer.NO_CHANGE;
        if (conflicting)
          kind = Differencer.CONFLICTING;
        else if (modified)
          kind = Differencer.PSEUDO_CONFLICT;

        IDiffContainer fileParent = getFileParent(result,
            repositoryPath, file, location);

        ITypedElement anc;
        if (ancestorCommit != null)
          anc = CompareUtils.getFileRevisionTypedElement(gitPath,
              ancestorCommit, repository);
        else
          anc = null;
        // we get an ugly black icon if we have an EmptyTypedElement
        // instead of null
        if (anc instanceof EmptyTypedElement)
          anc = null;
        // create the node as child
        new DiffNode(fileParent, kind, anc, leftEditable, right);
      }
      return result;
    } finally {
      tw.release();
    }
  }

  private IDiffContainer getFileParent(IDiffContainer root,
      IPath repositoryPath, IFile file, IPath location) {
    int projectSegment = -1;
    String projectName = null;
    if (file != null) {
      IProject project = file.getProject();
      IPath projectLocation = project.getLocation();
      if (projectLocation != null) {
        IPath projectPath = project.getLocation().makeRelativeTo(
            repositoryPath);
        projectSegment = projectPath.segmentCount() - 1;
        projectName = project.getName();
      }
    }

    IPath path = location.makeRelativeTo(repositoryPath);
    IDiffContainer child = root;
    for (int i = 0; i < path.segmentCount() - 1; i++) {
      if (i == projectSegment)
        child = getOrCreateChild(child, projectName, true);
      else
        child = getOrCreateChild(child, path.segment(i), false);
    }
    return child;
  }

  private DiffNode getOrCreateChild(IDiffContainer parent, final String name,
      final boolean projectMode) {
    for (IDiffElement child : parent.getChildren()) {
      if (child.getName().equals(name)) {
        return ((DiffNode) child);
      }
    }
    DiffNode child = new DiffNode(parent, Differencer.NO_CHANGE) {

      @Override
      public String getName() {
        return name;
      }

      @Override
      public Image getImage() {
        if (projectMode)
          return PROJECT_IMAGE;
        else
          return FOLDER_IMAGE;
      }
    };
    return child;
  }

  private String readFile(File directory, String fileName) throws IOException {
    byte[] content = IO.readFully(new File(directory, fileName));
    // strip off the last LF
    int end = content.length;
    while (0 < end && content[end - 1] == '\n')
      end--;
    return RawParseUtils.decode(content, 0, end);
  }
}
TOP

Related Classes of org.eclipse.egit.ui.internal.merge.GitMergeEditorInput

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.