Package org.eclipse.egit.core.internal.indexdiff

Source Code of org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry

/*******************************************************************************
* Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com>
* Copyright (C) 2012, Markus Duft <markus.duft@salomon.at>
* Copyright (C) 2012, 2013 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.core.internal.indexdiff;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.EclipseGitProgressTransformer;
import org.eclipse.egit.core.IteratorService;
import org.eclipse.egit.core.JobFamilies;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.internal.trace.GitTraceLocation;
import org.eclipse.egit.core.internal.util.ProjectUtil;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.events.IndexChangedListener;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.IndexDiff;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.InterIndexDiffFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.osgi.util.NLS;

/**
* This class caches the {@link IndexDiff} for a given repository. The cache
* listens for changes in the related repository and notifies listeners about
* changes.
*
*/
public class IndexDiffCacheEntry {

  private static final int RESOURCE_LIST_UPDATE_LIMIT = 1000;

  private Repository repository;

  private volatile IndexDiffData indexDiffData;

  private Job reloadJob;

  private volatile boolean reloadJobIsInitializing;

  private IndexDiffUpdateJob updateJob;

  private DirCache lastIndex;

  // used to serialize index diff update jobs
  private ReentrantLock lock = new ReentrantLock(true);

  private Set<IndexDiffChangedListener> listeners = new HashSet<IndexDiffChangedListener>();

  private final ListenerHandle indexChangedListenerHandle;
  private final ListenerHandle refsChangedListenerHandle;
  private IResourceChangeListener resourceChangeListener;

  private static Semaphore parallelism = new Semaphore(2);

  /**
   * @param repository
   */
  public IndexDiffCacheEntry(Repository repository) {
    this.repository = repository;
    indexChangedListenerHandle = repository.getListenerList().addIndexChangedListener(
        new IndexChangedListener() {
          public void onIndexChanged(IndexChangedEvent event) {
            refreshIndexDelta();
          }
        });
    refsChangedListenerHandle = repository.getListenerList().addRefsChangedListener(
        new RefsChangedListener() {
          public void onRefsChanged(RefsChangedEvent event) {
            scheduleReloadJob("RefsChanged"); //$NON-NLS-1$
          }
        });
    scheduleReloadJob("IndexDiffCacheEntry construction"); //$NON-NLS-1$
    createResourceChangeListener();
    if (!repository.isBare()) {
      try {
        lastIndex = repository.readDirCache();
      } catch (IOException ex) {
        Activator
            .error(MessageFormat
                .format(CoreText.IndexDiffCacheEntry_errorCalculatingIndexDelta,
                    repository), ex);
      }
    }
  }

  /**
   * Use this method to register an {@link IndexDiffChangedListener}. The
   * listener is notified when a new index diff is available.
   *
   * @param listener
   */
  public void addIndexDiffChangedListener(IndexDiffChangedListener listener) {
    synchronized (listeners) {
      listeners.add(listener);
    }
  }

  /**
   * @param listener
   */
  public void removeIndexDiffChangedListener(IndexDiffChangedListener listener) {
    synchronized (listeners) {
      listeners.remove(listener);
    }
  }

  /**
   * This method starts a Job that refreshes all open projects related to the
   * repository and afterwards triggers the (asynchronous) recalculation of
   * the IndexDiff. This ensures that the IndexDiff calculation is not working
   * on out-dated resources.
   *
   */
  public void refreshResourcesAndIndexDiff() {
    String repositoryName = Activator.getDefault().getRepositoryUtil()
        .getRepositoryName(repository);
    String jobName = MessageFormat
        .format(CoreText.IndexDiffCacheEntry_refreshingProjects,
            repositoryName);
    Job job = new WorkspaceJob(jobName) {

      @Override
      public IStatus runInWorkspace(IProgressMonitor monitor) {
        try {
          IProject[] validOpenProjects = ProjectUtil
              .getValidOpenProjects(repository);
          ProjectUtil.refreshResources(validOpenProjects, monitor);
        } catch (CoreException e) {
          return Activator.error(e.getMessage(), e);
        }
        refresh();
        return Status.OK_STATUS;
      }

    };
    job.setRule(ResourcesPlugin.getWorkspace().getRoot());
    job.schedule();
  }

  /**
   * Trigger a new index diff calculation manually
   */
  public void refresh() {
    scheduleReloadJob("Refresh called"); //$NON-NLS-1$
  }

  /**
   * Trigger a new index diff calculation manually for the passed files.
   *
   * @param filesToRefresh (repository-relative paths)
   */
  public void refreshFiles(final Collection<String> filesToRefresh) {
    List<IResource> resources = Collections.emptyList();
    scheduleUpdateJob(filesToRefresh, resources);
  }

  /**
   * Refreshes all resources that changed in the index since the last call to
   * this method. This is suitable for incremental updates on index changed
   * events
   *
   * For bare repositories this does nothing.
   */
  private void refreshIndexDelta() {
    if (repository.isBare())
      return;

    try {
      DirCache currentIndex = repository.readDirCache();
      DirCache oldIndex = lastIndex;

      lastIndex = currentIndex;

      if (oldIndex == null) {
        refresh(); // full refresh in case we have no data to compare.
        return;
      }

      Set<String> paths = new TreeSet<String>();
      TreeWalk walk = new TreeWalk(repository);

      try {
        walk.addTree(new DirCacheIterator(oldIndex));
        walk.addTree(new DirCacheIterator(currentIndex));
        walk.setFilter(new InterIndexDiffFilter());

        while (walk.next()) {
          if (walk.isSubtree())
            walk.enterSubtree();
          else
            paths.add(walk.getPathString());
        }
      } finally {
        walk.release();
      }

      if (!paths.isEmpty())
        refreshFiles(paths);

    } catch (IOException ex) {
      Activator.error(MessageFormat.format(
          CoreText.IndexDiffCacheEntry_errorCalculatingIndexDelta,
          repository), ex);
      scheduleReloadJob("Exception while calculating index delta, doing full reload instead"); //$NON-NLS-1$
    }
  }

  /**
   * The method returns the current index diff or null. Null is returned if
   * the first index diff calculation has not completed yet.
   *
   * @return index diff
   */
  public IndexDiffData getIndexDiff() {
    return indexDiffData;
  }

  private void scheduleReloadJob(final String trigger) {
    if (reloadJob != null) {
      if (reloadJobIsInitializing)
        return;
      reloadJob.cancel();
    }
    if (updateJob != null)
      updateJob.cancel();

    if (!checkRepository())
      return;
    reloadJob = new Job(getReloadJobName()) {
      @Override
      protected IStatus run(IProgressMonitor monitor) {
        try {
          reloadJobIsInitializing = true;
          waitForWorkspaceLock(monitor);
        } finally {
          reloadJobIsInitializing = false;
        }
        lock.lock();
        try {
          if (monitor.isCanceled())
            return Status.CANCEL_STATUS;
          parallelism.acquire();
          long startTime = System.currentTimeMillis();
          IndexDiffData result = calcIndexDiffDataFull(monitor, getName());
          if (monitor.isCanceled() || (result == null))
            return Status.CANCEL_STATUS;
          indexDiffData = result;
          if (GitTraceLocation.INDEXDIFFCACHE.isActive()) {
            long time = System.currentTimeMillis() - startTime;
            StringBuilder message = new StringBuilder(
                getTraceMessage(time));
            GitTraceLocation.getTrace().trace(
                GitTraceLocation.INDEXDIFFCACHE.getLocation(),
                message.append(indexDiffData.toString())
                    .toString());
          }
          notifyListeners();
          return Status.OK_STATUS;
        } catch (IOException e) {
          if (GitTraceLocation.INDEXDIFFCACHE.isActive())
            GitTraceLocation.getTrace().trace(
                GitTraceLocation.INDEXDIFFCACHE.getLocation(),
                "Calculating IndexDiff failed", e); //$NON-NLS-1$
          return Status.OK_STATUS;
        } catch (InterruptedException e) {
          return Status.CANCEL_STATUS;
        } finally {
          lock.unlock();
          parallelism.release();
        }
      }

      private String getTraceMessage(long time) {
        return NLS
            .bind("\nUpdated IndexDiffData in {0} ms\nReason: {1}\nRepository: {2}\n", //$NON-NLS-1$
            new Object[] { Long.valueOf(time), trigger,
                repository.getWorkTree().getName() });
      }

      @Override
      public boolean belongsTo(Object family) {
        if (JobFamilies.INDEX_DIFF_CACHE_UPDATE.equals(family))
          return true;
        return super.belongsTo(family);
      }

    };
    reloadJob.schedule();
  }

  private boolean checkRepository() {
    if (Activator.getDefault() == null)
      return false;
    if (!repository.getDirectory().exists())
      return false;
    return true;
  }

  private void waitForWorkspaceLock(IProgressMonitor monitor) {
    // Wait for the workspace lock to avoid starting the calculation
    // of an IndexDiff while the workspace changes (e.g. due to a
    // branch switch).
    // The index diff calculation jobs do not lock the workspace
    // during execution to avoid blocking the workspace.
    IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    try {
      Job.getJobManager().beginRule(root, monitor);
    } catch (OperationCanceledException e) {
      return;
    } finally {
      Job.getJobManager().endRule(root);
    }
  }

  private void scheduleUpdateJob(final Collection<String> filesToUpdate,
      final Collection<IResource> resourcesToUpdate) {
    if (!checkRepository())
      return;
    if (reloadJob != null && reloadJobIsInitializing)
      return;
    if (updateJob != null) {
      updateJob.addChanges(filesToUpdate, resourcesToUpdate);
      return;
    }
    updateJob = new IndexDiffUpdateJob(getUpdateJobName(), 400) {
      @Override
      protected IStatus updateIndexDiff(Collection<String> files,
          Collection<IResource> resources,
          IProgressMonitor monitor) {
        waitForWorkspaceLock(monitor);
        if (monitor.isCanceled())
          return Status.CANCEL_STATUS;
        lock.lock();
        try {
          long startTime = System.currentTimeMillis();
          IndexDiffData result = calcIndexDiffDataIncremental(monitor,
              getName(), files, resources);
          if (monitor.isCanceled() || (result == null))
            return Status.CANCEL_STATUS;
          indexDiffData = result;
          if (GitTraceLocation.INDEXDIFFCACHE.isActive()) {
            long time = System.currentTimeMillis() - startTime;
            StringBuilder message = new StringBuilder(
                NLS.bind(
                    "Updated IndexDiffData based on resource list (length = {0}) in {1} ms\n", //$NON-NLS-1$
                    Integer.valueOf(resources
                        .size()), Long.valueOf(time)));
            GitTraceLocation.getTrace().trace(
                GitTraceLocation.INDEXDIFFCACHE.getLocation(),
                message.append(indexDiffData.toString())
                .toString());
          }
          notifyListeners();
          return Status.OK_STATUS;
        } catch (IOException e) {
          if (GitTraceLocation.INDEXDIFFCACHE.isActive())
            GitTraceLocation.getTrace().trace(
                GitTraceLocation.INDEXDIFFCACHE.getLocation(),
                "Calculating IndexDiff failed", e); //$NON-NLS-1$
          return Status.OK_STATUS;
        } finally {
          lock.unlock();
        }
      }
      @Override
      public boolean belongsTo(Object family) {
        if (JobFamilies.INDEX_DIFF_CACHE_UPDATE.equals(family))
          return true;
        return super.belongsTo(family);
      }

    };

    updateJob.addChanges(filesToUpdate, resourcesToUpdate);
  }

  private IndexDiffData calcIndexDiffDataIncremental(IProgressMonitor monitor,
      String jobName, Collection<String> filesToUpdate,
      Collection<IResource> resourcesToUpdate) throws IOException {
    if (indexDiffData == null)
      // Incremental update not possible without prior indexDiffData
      // -> do full refresh instead
      return calcIndexDiffDataFull(monitor, jobName);

    EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer(
        monitor);

    List<String> treeFilterPaths = calcTreeFilterPaths(filesToUpdate);

    WorkingTreeIterator iterator = IteratorService.createInitialIterator(repository);
    if (iterator == null)
      return null; // workspace is closed
    IndexDiff diffForChangedResources = new IndexDiff(repository,
        Constants.HEAD, iterator);
    diffForChangedResources.setFilter(PathFilterGroup
        .createFromStrings(treeFilterPaths));
    diffForChangedResources.diff(jgitMonitor, 0, 0, jobName);
    return new IndexDiffData(indexDiffData, filesToUpdate,
        resourcesToUpdate, diffForChangedResources);
  }

  /*
   * In the case when a file to update was in a folder that was untracked
   * before, we need to visit more that just the file. E.g. when the file is
   * now tracked, the folder is no longer untracked but maybe some sub folders
   * have become newly untracked.
   */
  private List<String> calcTreeFilterPaths(Collection<String> filesToUpdate) {
    List<String> paths = new ArrayList<String>();
    for (String fileToUpdate : filesToUpdate) {
      for (String untrackedFolder : indexDiffData.getUntrackedFolders())
        if (fileToUpdate.startsWith(untrackedFolder))
          paths.add(untrackedFolder);
      paths.add(fileToUpdate);
    }
    return paths;
  }

  private void notifyListeners() {
    IndexDiffChangedListener[] tmpListeners;
    synchronized (listeners) {
      tmpListeners = listeners
          .toArray(new IndexDiffChangedListener[listeners.size()]);
    }
    for (int i = 0; i < tmpListeners.length; i++)
      try {
        tmpListeners[i].indexDiffChanged(repository, indexDiffData);
      } catch (RuntimeException e) {
        Activator.logError(
            "Exception occured in an IndexDiffChangedListener", e); //$NON-NLS-1$
      }
  }

  private IndexDiffData calcIndexDiffDataFull(IProgressMonitor monitor, String jobName)
      throws IOException {
    EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer(
        monitor);

    IndexDiff newIndexDiff;
    WorkingTreeIterator iterator = IteratorService
        .createInitialIterator(repository);
    if (iterator == null)
      return null; // workspace is closed
    newIndexDiff = new IndexDiff(repository, Constants.HEAD, iterator);
    newIndexDiff.diff(jgitMonitor, 0, 0, jobName);
    return new IndexDiffData(newIndexDiff);
  }

  private String getReloadJobName() {
    String repoName = Activator.getDefault().getRepositoryUtil()
        .getRepositoryName(repository);
    return MessageFormat.format(CoreText.IndexDiffCacheEntry_reindexing, repoName);
  }

  private String getUpdateJobName() {
    String repoName = Activator.getDefault().getRepositoryUtil()
        .getRepositoryName(repository);
    return MessageFormat.format(
        CoreText.IndexDiffCacheEntry_reindexingIncrementally, repoName);
  }

  private void createResourceChangeListener() {
    resourceChangeListener = new IResourceChangeListener() {
      public void resourceChanged(IResourceChangeEvent event) {
        GitResourceDeltaVisitor visitor = new GitResourceDeltaVisitor(repository);
        try {
          event.getDelta().accept(visitor);
        } catch (CoreException e) {
          Activator.logError(e.getMessage(), e);
          return;
        }
        Collection<String> filesToUpdate = visitor.getFilesToUpdate();
        if (visitor.getGitIgnoreChanged())
          scheduleReloadJob("A .gitignore changed"); //$NON-NLS-1$
        else if (indexDiffData == null)
          scheduleReloadJob("Resource changed, no diff available"); //$NON-NLS-1$
        else if (!filesToUpdate.isEmpty())
          if (filesToUpdate.size() < RESOURCE_LIST_UPDATE_LIMIT)
            scheduleUpdateJob(filesToUpdate, visitor.getResourcesToUpdate());
          else
            // Calculate new IndexDiff if too many resources changed
            // This happens e.g. when a project is opened
            scheduleReloadJob("Too many resources changed"); //$NON-NLS-1$
      }

    };
    ResourcesPlugin.getWorkspace().addResourceChangeListener(
        resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
  }

  /**
   * Dispose cache entry by removing listeners.
   */
  public void dispose() {
    indexChangedListenerHandle.remove();
    refsChangedListenerHandle.remove();
    if (resourceChangeListener != null)
      ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener);
  }

}
TOP

Related Classes of org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry

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.