| /******************************************************************************* |
| * 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; |
| } |
| |
| } |