/******************************************************************************* * Copyright (C) 2016 Obeo. * * 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.synchronize; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.core.resources.mapping.ResourceMappingContext; import org.eclipse.core.resources.mapping.ResourceTraversal; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.egit.core.internal.storage.GitFileRevision; import org.eclipse.egit.core.internal.util.ResourceUtil; import org.eclipse.egit.core.synchronize.GitLazyResourceVariantTreeSubscriber; import org.eclipse.egit.core.synchronize.GitSubscriberResourceMappingContext; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.dialogs.CompareTreeView; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jgit.lib.Repository; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; /** * This synchronizer takes into account the logical models that contain the * resources being synchronized. With this synchronizer, even if only one file * is selected, the synchronization may involve several resources, depending on * the logical model providers. */ public class ModelAwareGitSynchronizer extends DefaultGitSynchronizer { /** The cached {@link ResourceMappingContext} used by this synchronizer. */ private ResourceMappingContext context; @Override public void compare(IResource[] resources, Repository repository, String leftRev, String rightRev, boolean includeLocal, IWorkbenchPage page) throws IOException { try { PlatformUI.getWorkbench().getProgressService() .busyCursorWhile(new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { Set<IResource> resourceSet = new LinkedHashSet<>( Arrays.asList(resources)); context = createResourceMappingContext( resourceSet, repository, leftRev, rightRev, includeLocal, monitor); ModelAwareGitSynchronizer.super.compare( resources, repository, leftRev, rightRev, includeLocal, page); } catch (IOException e) { throw new InvocationTargetException(e); } } }); } catch (InvocationTargetException e) { Activator.error(e.getTargetException().getMessage(), e.getTargetException()); } catch (InterruptedException e) { // Nothing to do, operation has been cancelled } } @Override public void compare(IFile file, Repository repository, String leftPath, String rightPath, String leftRev, String rightRev, boolean includeLocal, IWorkbenchPage page) throws IOException { try { PlatformUI.getWorkbench().getProgressService() .busyCursorWhile(new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { context = createResourceMappingContext( Collections.singleton(file), repository, leftRev, rightRev, includeLocal, monitor); ModelAwareGitSynchronizer.super.compare(file, repository, leftPath, rightPath, leftRev, rightRev, includeLocal, page); } catch (IOException e) { throw new InvocationTargetException(e); } } }); } catch (InvocationTargetException e) { Activator.error(e.getTargetException().getMessage(), e.getTargetException()); } catch (InterruptedException e) { // Nothing to do, operation has been cancelled } } @Override protected boolean canCompareDirectly(IFile file) { // Using a local context for the ResourceMapping computation would make // for a faster test... but we need the model providers to be able to // load remote information. The local file may very well be a single // file, but it is possible that the remote side has multiple files to // take into account for that model. (if part of the logical model has // been locally deleted, or if some new files have been created on the // remote side(s).) final ResourceMapping[] mappings = ResourceUtil .getResourceMappings(file, context); for (ResourceMapping mapping : mappings) { try { final ResourceTraversal[] traversals = mapping .getTraversals(context, null); for (ResourceTraversal traversal : traversals) { final IResource[] traversalResources = traversal .getResources(); if (traversalResources.length > 1 && Arrays .asList(traversalResources).contains(file)) { return false; } } } catch (CoreException e) { Activator.logError(e.getMessage(), e); } } return true; } @Override protected void synchronize(IResource[] resources, Repository repository, String leftRev, String rightRev, boolean includeLocal) throws IOException { if (rightRev.equals(GitFileRevision.INDEX)) { openGitTreeCompare(resources, leftRev, CompareTreeView.INDEX_VERSION, includeLocal); } else { final Set<IResource> includedResources = new HashSet<>( Arrays.asList(resources)); final Set<ResourceMapping> allMappings = new HashSet<>(); Set<IResource> newResources = new HashSet<>(includedResources); do { final Set<IResource> copy = newResources; newResources = new HashSet<>(); for (IResource resource : copy) { Assert.isNotNull(resource); ResourceMapping[] mappings = ResourceUtil .getResourceMappings(resource, context); allMappings.addAll(Arrays.asList(mappings)); newResources.addAll(collectResources(mappings)); } } while (includedResources.addAll(newResources)); if (GitFileRevision.INDEX.equals(leftRev)) { // Even git tree compare cannot handle index as // source... // Synchronize using the local data for now. final ResourceMapping[] mappings = allMappings .toArray(new ResourceMapping[allMappings.size()]); final GitSynchronizeData data = new GitSynchronizeData( repository, leftRev, rightRev, true, includedResources); GitModelSynchronize.launch(new GitSynchronizeDataSet(data), mappings); } else { final ResourceMapping[] mappings = allMappings .toArray(new ResourceMapping[allMappings.size()]); final GitSynchronizeData data = new GitSynchronizeData( repository, leftRev, rightRev, includeLocal, includedResources); GitModelSynchronize.launch(new GitSynchronizeDataSet(data), mappings); } } } /** * @param resources * @param repository * @param leftRev * @param rightRev * @param includeLocal * @param monitor * @return A resource mapping context to access the versions of the content * of the resources involved in the synchronization. */ protected ResourceMappingContext createResourceMappingContext( Set<IResource> resources, Repository repository, String leftRev, String rightRev, boolean includeLocal, IProgressMonitor monitor) { try { GitSynchronizeData gsd = new GitSynchronizeData(repository, leftRev, rightRev, includeLocal, resources); GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(gsd); GitLazyResourceVariantTreeSubscriber subscriber = new GitLazyResourceVariantTreeSubscriber( gsds); subscriber.init(monitor); return new GitSubscriberResourceMappingContext(subscriber, gsds); } catch (IOException e) { Activator.logError(e.getMessage(), e); } return ResourceMappingContext.LOCAL_CONTEXT; } /** * Provide all the resources to take into account for the given mappings. * * @param mappings * The mappings for which we seek the resources involved * @return The set of all the resources involved in the given mappings */ private Set<IResource> collectResources(ResourceMapping[] mappings) { final Set<IResource> result = new HashSet<>(); for (ResourceMapping mapping : mappings) { try { ResourceTraversal[] traversals = mapping.getTraversals(context, new NullProgressMonitor()); for (ResourceTraversal traversal : traversals) result.addAll(Arrays.asList(traversal.getResources())); } catch (CoreException e) { Activator.logError(e.getMessage(), e); } } return result; } }