blob: de41007dda9e654bdcb41ac788346de5fc7f98e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2010, 2014 Dariusz Luksza <dariusz@luksza.org> 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:
* Dariusz Luksza <dariusz@luksza.org> - initial API and implementation
* Laurent Goubet <laurent.goubet@obeo.fr> - Logical Model enhancements
* Gunnar Wagenknecht <gunnar@wagenknecht.org> - Logical Model enhancements
*******************************************************************************/
package org.eclipse.egit.ui.internal.synchronize;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.compare.CompareNavigator;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IEncodedStorage;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ModelProvider;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.internal.storage.WorkspaceFileRevision;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.core.project.GitProjectData;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber;
import org.eclipse.egit.core.synchronize.GitSubscriberMergeContext;
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.UIPreferences;
import org.eclipse.egit.ui.internal.FileRevisionTypedElement;
import org.eclipse.egit.ui.internal.GitCompareFileRevisionEditorInput;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.synchronize.model.GitModelBlob;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.ISynchronizationContext;
import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
import org.eclipse.team.core.mapping.provider.MergeContext;
import org.eclipse.team.core.mapping.provider.SynchronizationScopeManager;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.subscribers.SubscriberMergeContext;
import org.eclipse.team.internal.ui.mapping.ResourceDiffCompareInput;
import org.eclipse.team.ui.TeamUI;
import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;
import org.eclipse.team.ui.synchronize.ModelSynchronizeParticipant;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
/**
* Git model synchronization participant
*/
public class GitModelSynchronizeParticipant extends ModelSynchronizeParticipant {
/**
* Key value for obtaining {@link GitSynchronizeDataSet} from {@link ISynchronizePageConfiguration}
*/
public static final String SYNCHRONIZATION_DATA = "GIT_SYNCHRONIZE_DATA_SET"; //$NON-NLS-1$
/**
* Id of model compare participant
*/
public static final String ID = "org.eclipse.egit.ui.modelCompareParticipant"; //$NON-NLS-1$
/**
* Id of model synchronization participant
*/
public static final String VIEWER_ID = "org.eclipse.egit.ui.compareSynchronization"; //$NON-NLS-1$
private static final String P_NAVIGATOR = "org.eclipse.team.ui.P_NAVIGATOR"; //$NON-NLS-1$
private static final String WORKSPACE_MODEL_PROVIDER_ID = "org.eclipse.core.resources.modelProvider"; //$NON-NLS-1$
private static final String DATA_NODE_KEY = "gitSynchronizeData"; //$NON-NLS-1$
private static final String INCLUDED_PATHS_NODE_KEY = "includedPaths"; //$NON-NLS-1$
private static final String INCLUDED_PATH_KEY = "path"; //$NON-NLS-1$
private static final String CONTAINER_PATH_KEY = "container"; //$NON-NLS-1$
private static final String SRC_REV_KEY = "srcRev"; //$NON-NLS-1$
private static final String DST_REV_KEY = "dstRev"; //$NON-NLS-1$
private static final String INCLUDE_LOCAL_KEY = "inludeLocal"; //$NON-NLS-1$
private static final String FORCE_FETCH_KEY = "forceFetch"; //$NON-NLS-1$
private GitSynchronizeDataSet gsds;
/**
* DO NOT USE. This constructor is preserved for dynamic initialization when
* synchronization context is restored
*/
public GitModelSynchronizeParticipant() {
}
/**
* Creates {@link GitModelSynchronizeParticipant} for given context
*
* @param context
*/
public GitModelSynchronizeParticipant(GitSubscriberMergeContext context) {
super(context);
gsds = context.getSyncData();
try {
setInitializationData(TeamUI.getSynchronizeManager()
.getParticipantDescriptor(ID));
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
}
setSecondaryId(Long.toString(System.currentTimeMillis()));
}
protected void initializeConfiguration(
final ISynchronizePageConfiguration configuration) {
configuration.setProperty(ISynchronizePageConfiguration.P_VIEWER_ID,
VIEWER_ID);
String modelProvider = WORKSPACE_MODEL_PROVIDER_ID;
final IPreferenceStore preferenceStore = Activator.getDefault()
.getPreferenceStore();
if (!gsds.containsFolderLevelSynchronizationRequest()) {
if (preferenceStore
.getBoolean(UIPreferences.SYNC_VIEW_ALWAYS_SHOW_CHANGESET_MODEL)) {
modelProvider = GitChangeSetModelProvider.ID;
} else {
String lastSelectedModel = preferenceStore.getString(UIPreferences.SYNC_VIEW_LAST_SELECTED_MODEL);
if (!"".equals(lastSelectedModel)) //$NON-NLS-1$
modelProvider = lastSelectedModel;
}
}
configuration.setProperty(
ModelSynchronizeParticipant.P_VISIBLE_MODEL_PROVIDER,
modelProvider);
configuration.setProperty(SYNCHRONIZATION_DATA, gsds);
super.initializeConfiguration(configuration);
configuration.addActionContribution(new GitActionContributor());
configuration.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (property.equals(
ModelSynchronizeParticipant.P_VISIBLE_MODEL_PROVIDER)) {
String newValue = (String) event.getNewValue();
preferenceStore.setValue(
UIPreferences.SYNC_VIEW_LAST_SELECTED_MODEL,
newValue);
} else if (property.equals(P_NAVIGATOR)) {
Object oldNavigator = configuration
.getProperty(P_NAVIGATOR);
if (!(oldNavigator instanceof GitTreeCompareNavigator))
configuration.setProperty(P_NAVIGATOR,
new GitTreeCompareNavigator(
(CompareNavigator) oldNavigator));
}
}
});
}
@Override
public ModelProvider[] getEnabledModelProviders() {
ModelProvider[] avaliableProviders = super.getEnabledModelProviders();
for (ModelProvider provider : avaliableProviders)
if (provider.getId().equals(GitChangeSetModelProvider.ID))
return avaliableProviders;
int capacity = avaliableProviders.length + 1;
ArrayList<ModelProvider> providers = new ArrayList<ModelProvider>(
capacity);
providers.add(GitChangeSetModelProvider.getProvider());
return providers.toArray(new ModelProvider[providers.size()]);
}
@Override
public boolean hasCompareInputFor(Object object) {
if (object instanceof GitModelBlob || object instanceof IFile)
return true;
// in Java Workspace model Java source files are passed as type
// CompilationUnit which can be adapted to IResource
IResource res = AdapterUtils.adapt(object, IResource.class);
if (res != null && res.getType() == IResource.FILE)
return true;
// fallback to super ISynchronizationCompareAdapter
return super.hasCompareInputFor(object);
}
@Override
public ICompareInput asCompareInput(Object object) {
final ICompareInput input = super.asCompareInput(object);
final ISynchronizationContext ctx = getContext();
if (input instanceof ResourceDiffCompareInput && ctx instanceof SubscriberMergeContext) {
// Team only considers local resources as "left"
// We'll use the cached data instead as left could be remote
final IResource resource = ((ResourceNode) input.getLeft())
.getResource();
final Subscriber subscriber = ((SubscriberMergeContext)ctx).getSubscriber();
if (resource instanceof IFile
&& subscriber instanceof GitResourceVariantTreeSubscriber) {
try {
final IFileRevision revision = ((GitResourceVariantTreeSubscriber) subscriber)
.getSourceFileRevision((IFile) resource);
if (revision == null) {
final ITypedElement newSource = new GitCompareFileRevisionEditorInput.EmptyTypedElement(
resource.getName());
((ResourceDiffCompareInput) input).setLeft(newSource);
} else if (!(revision instanceof WorkspaceFileRevision)) {
final ITypedElement newSource = new FileRevisionTypedElement(
revision, getLocalEncoding(resource));
((ResourceDiffCompareInput) input).setLeft(newSource);
}
} catch (TeamException e) {
// Keep the input from super as-is
String error = NLS
.bind(UIText.GitModelSynchronizeParticipant_noCachedSourceVariant,
resource.getName());
Activator.logError(error, e);
}
}
}
return input;
}
private static String getLocalEncoding(IResource resource) {
if (resource instanceof IEncodedStorage) {
IEncodedStorage es = (IEncodedStorage) resource;
try {
return es.getCharset();
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
}
}
return null;
}
@Override
public void run(final IWorkbenchPart part) {
boolean fetchPossible = false;
for (GitSynchronizeData data : gsds)
if (data.getDstRemoteName() != null)
fetchPossible = true;
boolean launchFetch = Activator.getDefault().getPreferenceStore()
.getBoolean(UIPreferences.SYNC_VIEW_FETCH_BEFORE_LAUNCH);
if (fetchPossible && (launchFetch || gsds.forceFetch())) {
Job fetchJob = new SynchronizeFetchJob(gsds);
fetchJob.setUser(true);
fetchJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
GitModelSynchronizeParticipant.super.run(part);
}
});
fetchJob.schedule();
} else
super.run(part);
}
@Override
public void saveState(IMemento memento) {
super.saveState(memento);
for (GitSynchronizeData gsd : gsds) {
Repository repo = gsd.getRepository();
RepositoryMapping mapping = RepositoryMapping.findRepositoryMapping(repo);
if (mapping != null) {
IMemento child = memento.createChild(DATA_NODE_KEY);
child.putString(CONTAINER_PATH_KEY,
getPathForResource(mapping.getContainer()));
child.putString(SRC_REV_KEY, gsd.getSrcRev());
child.putString(DST_REV_KEY, gsd.getDstRev());
child.putBoolean(INCLUDE_LOCAL_KEY, gsd.shouldIncludeLocal());
Set<IResource> includedResources = gsd.getIncludedResources();
if (includedResources != null && !includedResources.isEmpty()) {
IMemento paths = child.createChild(INCLUDED_PATHS_NODE_KEY);
for (IResource resource : includedResources) {
String path = getPathForResource(resource);
paths.createChild(INCLUDED_PATH_KEY).putString(
INCLUDED_PATH_KEY, path);
}
}
}
}
memento.putBoolean(FORCE_FETCH_KEY, gsds.forceFetch());
}
@Override
public void init(String secondaryId, IMemento memento)
throws PartInitException {
try {
boolean forceFetchPref = Activator.getDefault().getPreferenceStore()
.getBoolean(UIPreferences.SYNC_VIEW_FETCH_BEFORE_LAUNCH);
boolean forceFetch = getBoolean(memento.getBoolean(FORCE_FETCH_KEY), forceFetchPref);
gsds = new GitSynchronizeDataSet(forceFetch);
IMemento[] children = memento.getChildren(DATA_NODE_KEY);
if (children != null)
restoreSynchronizationData(children);
} finally {
super.init(secondaryId, memento);
}
}
@Override
protected MergeContext restoreContext(ISynchronizationScopeManager manager)
throws CoreException {
GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
gsds);
subscriber.init(new NullProgressMonitor());
return new GitSubscriberMergeContext(subscriber, manager, gsds);
}
@Override
protected ISynchronizationScopeManager createScopeManager(
ResourceMapping[] mappings) {
GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
gsds);
subscriber.init(new NullProgressMonitor());
GitSubscriberResourceMappingContext context = new GitSubscriberResourceMappingContext(
subscriber, gsds);
return new SynchronizationScopeManager(
UIText.GitModelSynchronizeParticipant_initialScopeName,
mappings, context, true);
}
private void restoreSynchronizationData(IMemento[] children) {
for (IMemento child : children) {
String containerPath = child.getString(CONTAINER_PATH_KEY);
Repository repo = getRepositoryForPath(containerPath);
if (repo == null)
continue;
String srcRev = child.getString(SRC_REV_KEY);
String dstRev = child.getString(DST_REV_KEY);
boolean includeLocal = getBoolean(
child.getBoolean(INCLUDE_LOCAL_KEY), true);
Set<IResource> includedResources = getIncludedResources(child);
try {
GitSynchronizeData data = new GitSynchronizeData(repo, srcRev,
dstRev, includeLocal);
if (includedResources != null)
data.setIncludedResources(includedResources);
gsds.add(data);
} catch (IOException e) {
Activator.logError(e.getMessage(), e);
continue;
}
}
}
private Repository getRepositoryForPath(String containerPath) {
IPath path = Path.fromPortableString(containerPath);
IContainer mappedContainer = ResourcesPlugin.getWorkspace().getRoot()
.getContainerForLocation(path);
GitProjectData projectData = GitProjectData.get((IProject) mappedContainer);
if (projectData == null)
return null;
RepositoryMapping mapping = projectData.getRepositoryMapping(mappedContainer);
if (mapping != null)
return mapping.getRepository();
return null;
}
private boolean getBoolean(Boolean value, boolean defaultValue) {
return value != null ? value.booleanValue() : defaultValue;
}
private String getPathForResource(IResource resource) {
return resource.getLocation().toPortableString();
}
private Set<IResource> getIncludedResources(IMemento memento) {
IMemento child = memento.getChild(INCLUDED_PATHS_NODE_KEY);
Set<IResource> result = new HashSet<IResource>();
if (child != null) {
IMemento[] pathNode = child.getChildren(INCLUDED_PATH_KEY);
if (pathNode != null) {
for (IMemento path : pathNode) {
String includedPath = path.getString(INCLUDED_PATH_KEY);
IResource resource = ResourceUtil
.getResourceForLocation(new Path(includedPath));
if (resource != null)
result.add(resource);
}
return result;
}
}
return null;
}
}