Package org.eclipse.egit.core.synchronize

Source Code of org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber$GitResourceVariantFileRevision

/*******************************************************************************
* Copyright (c) 2010, 2013 IBM Corporation 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
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     Dariusz Luksza <dariusz@luksza.org>
*     François Rey - gracefully ignore linked resources
*     Laurent Goubet <laurent.goubet@obeo.fr> - 403363
*******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.eclipse.jgit.lib.Repository.stripWorkDir;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
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.IProgressMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.internal.storage.WorkspaceFileRevision;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.ITwoWayDiff;
import org.eclipse.team.core.diff.provider.ThreeWayDiff;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.provider.ResourceDiff;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.core.variants.IResourceVariantComparator;
import org.eclipse.team.core.variants.IResourceVariantTree;
import org.eclipse.team.core.variants.ResourceVariantTreeSubscriber;
import org.eclipse.team.internal.core.mapping.ResourceVariantFileRevision;
import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter;

/**
*
*/
@SuppressWarnings("restriction")
public class GitResourceVariantTreeSubscriber extends
    ResourceVariantTreeSubscriber {

  /** A resource variant tree of the source branch. */
  private GitSourceResourceVariantTree sourceTree;

  /**
   * A resource variant tree of the remote branch(es).
   */
  private GitRemoteResourceVariantTree remoteTree;

  /**
   * A resource variant tree against HEAD.
   */
  private GitBaseResourceVariantTree baseTree;

  private GitSynchronizeDataSet gsds;

  private IResource[] roots;

  private GitSyncCache cache;

  private GitSyncInfoToDiffConverter syncInfoConverter = new GitSyncInfoToDiffConverter();

  /**
   * @param data
   */
  public GitResourceVariantTreeSubscriber(GitSynchronizeDataSet data) {
    this.gsds = data;
  }

  /**
   * Initialize git subscriber. This method will pre-fetch data from git
   * repository. This approach will reduce number of {@link TreeWalk}'s
   * created during synchronization
   *
   * @param monitor
   */
  public void init(IProgressMonitor monitor) {
    monitor.beginTask(
        CoreText.GitResourceVariantTreeSubscriber_fetchTaskName,
        gsds.size());
    try {
      cache = GitSyncCache.getAllData(gsds, monitor);
    } finally {
      monitor.done();
    }
  }

  @Override
  public boolean isSupervised(IResource res) throws TeamException {
    return IResource.FILE == res.getType()
        && gsds.contains(res.getProject())
        && gsds.shouldBeIncluded(res);
  }

  /**
   * Returns all members of git repository (including those that are not
   * imported into workspace)
   *
   * @param res
   */
  @Override
  public IResource[] members(IResource res) throws TeamException {
    if (res.getType() == IResource.FILE || !gsds.shouldBeIncluded(res))
      return new IResource[0];

    GitSynchronizeData gsd = gsds.getData(res.getProject());
    Repository repo = gsd.getRepository();
    GitSyncObjectCache repoCache = cache.get(repo);

    Set<IResource> gitMembers = new HashSet<IResource>();
    Map<String, IResource> allMembers = new HashMap<String, IResource>();

    Set<GitSyncObjectCache> gitCachedMembers = new HashSet<GitSyncObjectCache>();
    String path = stripWorkDir(repo.getWorkTree(), res.getLocation().toFile());
    GitSyncObjectCache cachedMembers = repoCache.get(path);
    if (cachedMembers != null) {
      Collection<GitSyncObjectCache> members = cachedMembers.members();
      if (members != null)
        gitCachedMembers.addAll(members);
    }
    try {
      for (IResource member : ((IContainer) res).members())
        allMembers.put(member.getName(), member);

      for (GitSyncObjectCache gitMember : gitCachedMembers) {
        IResource member = allMembers.get(gitMember.getName());
        if (member != null)
          gitMembers.add(member);
      }
    } catch (CoreException e) {
      throw TeamException.asTeamException(e);
    }

    return gitMembers.toArray(new IResource[gitMembers.size()]);
  }

  @Override
  public void refresh(IResource[] resources, int depth,
      IProgressMonitor monitor) throws TeamException {
    for (IResource resource : resources) {
      // check to see if there is a full refresh
      if (resource.getType() == IResource.ROOT) {
        // refresh entire cache
        cache = GitSyncCache.getAllData(gsds, monitor);
        super.refresh(resources, depth, monitor);
        return;
      }
    }

    // not refreshing the workspace, locate and collect target resources
    Map<GitSynchronizeData, Collection<String>> updateRequests = new HashMap<GitSynchronizeData, Collection<String>>();
    for (IResource resource : resources) {
      IProject project = resource.getProject();
      GitSynchronizeData data = gsds.getData(project.getName());
      if (data != null) {
        RepositoryMapping mapping = RepositoryMapping
            .getMapping(project);
        // mapping may be null if the project has been closed
        if (mapping != null) {
          Collection<String> paths = updateRequests.get(data);
          if (paths == null) {
            paths = new ArrayList<String>();
            updateRequests.put(data, paths);
          }

          String path = mapping.getRepoRelativePath(resource);
          // null path may be returned, check for this
          if (path == null)
            // unknown, force a refresh of the whole repository
            path = ""; //$NON-NLS-1$
          paths.add(path);
        }
      }
    }

    // scan only the repositories that were affected
    if (!updateRequests.isEmpty()) {
      // refresh cache
      GitSyncCache.mergeAllDataIntoCache(updateRequests, monitor, cache);
    }

    super.refresh(resources, depth, monitor);
  }

  @Override
  public IResource[] roots() {
    if (roots == null)
      roots = gsds.getAllProjects();
    IResource[] result = new IResource[roots.length];
    System.arraycopy(roots, 0, result, 0, roots.length);
    return result;
  }

  /**
   * @param data
   */
  public void reset(GitSynchronizeDataSet data) {
    gsds = data;

    roots = null;
    sourceTree = null;
    baseTree = null;
    remoteTree = null;
  }

  /**
   * Disposes nested resources
   */
  public void dispose() {
    if (sourceTree != null)
      sourceTree.dispose();
    if (baseTree != null)
      baseTree.dispose();
    if (remoteTree != null)
      remoteTree.dispose();
    gsds.dispose();
  }

  @Override
  public IDiff getDiff(IResource resource) throws CoreException {
    final GitSynchronizeData syncData = gsds.getData(resource.getProject());
    if (syncData == null || syncData.shouldIncludeLocal())
      return super.getDiff(resource);

    SyncInfo info = getSyncInfo(resource);
    if (info == null || info.getKind() == SyncInfo.IN_SYNC)
      return null;
    return syncInfoConverter.getDeltaFor(info);
  }

  /**
   * The default implementation of SyncInfoToDiffConverter uses inaccurate
   * information with regards to some of EGit features.
   * <p>
   * SyncInfoToDiffConverter#asFileRevision(IResourceVariant) is called when a
   * user double-clicks a revision from the synchronize view (among others).
   * However, the default implementation returns an IFileRevision with no
   * comment, author or timestamp information (this can be observed by
   * commenting this implementation out and launching
   * HistoryTest#queryHistoryThroughTeam()).
   * </p>
   * <p>
   * SyncInfoToDiffConverter#getDeltaFor(SyncInfo) had been originally thought
   * by Team to be used for synchronizations that considered local changes.
   * This is not always the case with EGit. For example, a user might try and
   * compare two refs together from the Git repository explorer (right click >
   * synchronize with each other). In such a case, the local files must not be
   * taken into account (i.e. we must respect the value of our
   * GitSynchronizeData#shouldIncludeLocal(). Most of the private methods here
   * were copy/pasted from the super implementation.
   * </p>
   */
  private class GitSyncInfoToDiffConverter extends SyncInfoToDiffConverter {
    @Override
    public IDiff getDeltaFor(SyncInfo info) {
      if (info.getComparator().isThreeWay()) {
        ITwoWayDiff local = getLocalDelta(info);
        ITwoWayDiff remote = getRemoteDelta(info);
        return new ThreeWayDiff(local, remote);
      } else {
        if (info.getKind() != SyncInfo.IN_SYNC) {
          IResourceVariant remote = info.getRemote();
          IResource local = info.getLocal();

          int kind;
          if (remote == null)
            kind = IDiff.REMOVE;
          else if (!local.exists())
            kind = IDiff.ADD;
          else
            kind = IDiff.CHANGE;

          if (local.getType() == IResource.FILE) {
            IFileRevision after = asFileState(remote);
            IFileRevision before = getLocalFileRevision((IFile) local);
            return new ResourceDiff(info.getLocal(), kind, 0,
                before, after);
          }
          // For folders, we don't need file states
          return new ResourceDiff(info.getLocal(), kind);
        }
        return null;
      }
    }

    private ITwoWayDiff getLocalDelta(SyncInfo info) {
      int direction = SyncInfo.getDirection(info.getKind());
      if (direction == SyncInfo.OUTGOING
          || direction == SyncInfo.CONFLICTING) {
        IResourceVariant ancestor = info.getBase();
        IResource local = info.getLocal();

        int kind;
        if (ancestor == null)
          kind = IDiff.ADD;
        else if (!local.exists())
          kind = IDiff.REMOVE;
        else
          kind = IDiff.CHANGE;

        if (local.getType() == IResource.FILE) {
          IFileRevision before = asFileState(ancestor);
          IFileRevision after = getLocalFileRevision((IFile) local);
          return new ResourceDiff(info.getLocal(), kind, 0, before,
              after);
        }
        // For folders, we don't need file states
        return new ResourceDiff(info.getLocal(), kind);

      }
      return null;
    }

    /**
     * Depending on the Synchronize data, this will return either the local
     * file or the "source" revision.
     *
     * @param local
     *            The local file.
     * @return The file revision that should be considered for the local
     *         (left) side a delta
     */
    protected IFileRevision getLocalFileRevision(IFile local) {
      final GitSynchronizeData data = gsds.getData(local.getProject());
      if (data.shouldIncludeLocal())
        return new WorkspaceFileRevision(local);

      try {
        return asFileState(getSourceTree().getResourceVariant(local));
      } catch (TeamException e) {
        String error = NLS
            .bind(CoreText.GitResourceVariantTreeSubscriber_CouldNotFindSourceVariant,
                local.getName());
        Activator.logError(error, e);
        // fall back to the working tree version
        return new WorkspaceFileRevision(local);
      }
    }

    /*
     * copy-pasted from the private implementation in
     * SyncInfoToDiffConverter
     */
    private ITwoWayDiff getRemoteDelta(SyncInfo info) {
      int direction = SyncInfo.getDirection(info.getKind());
      if (direction == SyncInfo.INCOMING
          || direction == SyncInfo.CONFLICTING) {
        IResourceVariant ancestor = info.getBase();
        IResourceVariant remote = info.getRemote();

        int kind;
        if (ancestor == null)
          kind = IDiff.ADD;
        else if (remote == null)
          kind = IDiff.REMOVE;
        else
          kind = IDiff.CHANGE;

        // For folders, we don't need file states
        if (info.getLocal().getType() == IResource.FILE) {
          IFileRevision before = asFileState(ancestor);
          IFileRevision after = asFileState(remote);
          return new ResourceDiff(info.getLocal(), kind, 0, before,
              after);
        }

        return new ResourceDiff(info.getLocal(), kind);
      }
      return null;
    }

    /*
     * copy-pasted from the private implementation in
     * SyncInfoToDiffConverter
     */
    private IFileRevision asFileState(final IResourceVariant variant) {
      if (variant == null)
        return null;
      return asFileRevision(variant);
    }

    @Override
    protected ResourceVariantFileRevision asFileRevision(
        IResourceVariant variant) {
      return new GitResourceVariantFileRevision(variant);
    }
  }

  /**
   * The default implementation of ResourceVariantFileRevision has no author,
   * comment, timestamp... or any information that could be provided by the
   * Git resource variant. This implementation uses the variant's information.
   */
  private static class GitResourceVariantFileRevision extends
      ResourceVariantFileRevision {
    private final IResourceVariant variant;

    public GitResourceVariantFileRevision(IResourceVariant variant) {
      super(variant);
      this.variant = variant;
    }

    @Override
    public String getContentIdentifier() {
      // Use the same ID as would CommitFileRevision
      if (variant instanceof GitRemoteResource)
        return ((GitRemoteResource) variant).getCommitId().getId()
            .getName();

      return super.getContentIdentifier();
    }

    @Override
    public long getTimestamp() {
      if (variant instanceof GitRemoteResource) {
        final PersonIdent author = ((GitRemoteResource) variant)
            .getCommitId().getAuthorIdent();
        if (author != null)
          return author.getWhen().getTime();
      }

      return super.getTimestamp();
    }

    @Override
    public String getAuthor() {
      if (variant instanceof GitRemoteResource) {
        final PersonIdent author = ((GitRemoteResource) variant)
            .getCommitId().getAuthorIdent();
        if (author != null)
          return author.getName();
      }

      return super.getAuthor();
    }

    @Override
    public String getComment() {
      if (variant instanceof GitRemoteResource)
        return ((GitRemoteResource) variant).getCommitId()
            .getFullMessage();

      return super.getComment();
    }
  }

  @Override
  public String getName() {
    return CoreText.GitBranchResourceVariantTreeSubscriber_gitRepository;
  }

  @Override
  public IResourceVariantComparator getResourceComparator() {
    return new GitResourceVariantComparator(gsds);
  }

  /**
   * As opposed to the other repository providers, EGit allows for
   * synchronization between three remote branches. This will return the
   * "source" tree for such synchronization use cases.
   *
   * @return The source tree of this subscriber.
   * @since 3.0
   */
  protected IResourceVariantTree getSourceTree() {
    if (sourceTree == null)
      sourceTree = new GitSourceResourceVariantTree(cache, gsds);

    return sourceTree;
  }

  /**
   * This can be used to retrieve the version of the given resource
   * corresponding to the source tree of this subscriber.
   *
   * @param resource
   *            The resource for which we need a variant.
   * @return The revision of the given resource cached in the source tree of
   *         this subscriber.
   * @throws TeamException
   * @since 3.0
   */
  public IFileRevision getSourceFileRevision(IFile resource)
      throws TeamException {
    return syncInfoConverter.getLocalFileRevision(resource);
  }

  @Override
  protected IResourceVariantTree getBaseTree() {
    if (baseTree == null)
      baseTree = new GitBaseResourceVariantTree(cache, gsds);

    return baseTree;
  }

  @Override
  protected IResourceVariantTree getRemoteTree() {
    if (remoteTree == null)
      remoteTree = new GitRemoteResourceVariantTree(cache, gsds);

    return remoteTree;
  }

  @Override
  protected SyncInfo getSyncInfo(IResource local, IResourceVariant base,
      IResourceVariant remote) throws TeamException {

    Repository repo = gsds.getData(local.getProject()).getRepository();
    SyncInfo info = new GitSyncInfo(local, base, remote,
        getResourceComparator(), cache.get(repo), repo);

    info.init();
    return info;
  }

}
TOP

Related Classes of org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber$GitResourceVariantFileRevision

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.