blob: c9b519618d9dd1ffdf8eb03d4b25399000be3a9f [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008, 2015 Shawn O. Pearce <spearce@spearce.org> and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.egit.core;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Authenticator;
import java.net.ProxySelector;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.net.proxy.IProxyService;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IRegistryEventListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.internal.EGitSshdSessionFactory;
import org.eclipse.egit.core.internal.ReportingTypedConfigGetter;
import org.eclipse.egit.core.internal.ResourceRefreshHandler;
import org.eclipse.egit.core.internal.SshPreferencesMirror;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCache;
import org.eclipse.egit.core.internal.job.JobUtil;
import org.eclipse.egit.core.internal.trace.GitTraceLocation;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.core.op.IgnoreOperation;
import org.eclipse.egit.core.project.GitProjectData;
import org.eclipse.egit.core.project.RepositoryFinder;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.securestorage.EGitSecureStore;
import org.eclipse.equinox.security.storage.SecurePreferencesFactory;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jsch.core.IJSchService;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
/**
* The plugin class for the org.eclipse.egit.core plugin. This
* is a singleton class.
*/
public class Activator extends Plugin implements DebugOptionsListener {
private enum SshClientType {
JSCH, APACHE
}
private enum HttpClientType {
JDK, APACHE
}
private static Activator plugin;
private static String pluginId;
private RepositoryCache repositoryCache;
private IndexDiffCache indexDiffCache;
private RepositoryUtil repositoryUtil;
private EGitSecureStore secureStore;
private AutoShareProjects shareGitProjectsJob;
private IResourceChangeListener preDeleteProjectListener;
private IgnoreDerivedResources ignoreDerivedResourcesListener;
private MergeStrategyRegistryListener mergeStrategyRegistryListener;
private IPreferenceChangeListener preferenceChangeListener;
private ServiceTracker<IProxyService, IProxyService> proxyServiceTracker;
private ListenerHandle refreshHandle;
/**
* @return the singleton {@link Activator}
*/
public static Activator getDefault() {
return plugin;
}
/**
* @return the name of this plugin
*/
public static String getPluginId() {
return pluginId;
}
/**
* Utility to create an error status for this plug-in.
*
* @param message User comprehensible message
* @param thr cause
* @return an initialized error status
*/
public static IStatus error(final String message, final Throwable thr) {
return new Status(IStatus.ERROR, getPluginId(), 0, message, thr);
}
/**
* Utility to create a cancel status for this plug-in.
*
* @param message
* User comprehensible message
* @param thr
* cause
* @return an initialized cancel status
*/
public static IStatus cancel(final String message, final Throwable thr) {
return new Status(IStatus.CANCEL, getPluginId(), 0, message, thr);
}
/**
* Utility method to log errors in the Egit plugin.
*
* @param message
* User comprehensible message
* @param thr
* The exception through which we noticed the error
*/
public static void logError(final String message, final Throwable thr) {
getDefault().getLog().log(error(message, thr));
}
/**
* Log an info message for this plug-in
*
* @param message
*/
public static void logInfo(final String message) {
getDefault().getLog().log(
new Status(IStatus.INFO, getPluginId(), 0, message, null));
}
/**
* Utility to create a warning status for this plug-in.
*
* @param message
* User comprehensible message
* @param thr
* cause
* @return an initialized warning status
*/
public static IStatus warning(final String message, final Throwable thr) {
return new Status(IStatus.WARNING, getPluginId(), 0, message, thr);
}
/**
* Utility method to log warnings for this plug-in.
*
* @param message
* User comprehensible message
* @param thr
* The exception through which we noticed the warning
*/
public static void logWarning(final String message, final Throwable thr) {
getDefault().getLog().log(warning(message, thr));
}
/**
* Construct the {@link Activator} singleton instance
*/
public Activator() {
Activator.setActivator(this);
}
private static void setActivator(Activator a) {
plugin = a;
}
@Override
public void start(final BundleContext context) throws Exception {
super.start(context);
pluginId = context.getBundle().getSymbolicName();
FS.FileStoreAttributes.setBackground(true);
SystemReader.setInstance(
new EclipseSystemReader(SystemReader.getInstance()));
Config.setTypedConfigGetter(new ReportingTypedConfigGetter());
// we want to be notified about debug options changes
Dictionary<String, String> props = new Hashtable<>(4);
props.put(DebugOptions.LISTENER_SYMBOLICNAME, pluginId);
context.registerService(DebugOptionsListener.class.getName(), this,
props);
setupHttp();
SshPreferencesMirror.INSTANCE.start();
proxyServiceTracker = new ServiceTracker<>(context,
IProxyService.class.getName(), null);
proxyServiceTracker.open();
setupSSH(context);
preferenceChangeListener = event -> {
if (GitCorePreferences.core_sshClient.equals(event.getKey())) {
setupSSH(getBundle().getBundleContext());
} else if (GitCorePreferences.core_httpClient
.equals(event.getKey())) {
setupHttp();
}
};
InstanceScope.INSTANCE.getNode(pluginId)
.addPreferenceChangeListener(preferenceChangeListener);
setupProxy();
repositoryCache = new RepositoryCache();
indexDiffCache = new IndexDiffCache();
try {
GitProjectData.reconfigureWindowCache();
} catch (RuntimeException e) {
logError(CoreText.Activator_ReconfigureWindowCacheError, e);
}
GitProjectData.attachToWorkspace();
setupResourceRefresh();
repositoryUtil = new RepositoryUtil();
secureStore = new EGitSecureStore(SecurePreferencesFactory.getDefault());
registerAutoShareProjects();
registerAutoIgnoreDerivedResources();
registerPreDeleteResourceChangeListener();
registerMergeStrategyRegistryListener();
registerBuiltinLFS();
}
@SuppressWarnings("unchecked")
private void setupSSH(final BundleContext context) {
String sshClient = Platform.getPreferencesService().getString(pluginId,
GitCorePreferences.core_sshClient, "apache", null); //$NON-NLS-1$
SshSessionFactory previous = SshSessionFactory.getInstance();
if (SshClientType.JSCH.name().equalsIgnoreCase(sshClient)) {
if (previous instanceof EclipseSshSessionFactory) {
return;
}
ServiceReference ssh = context
.getServiceReference(IJSchService.class.getName());
if (ssh != null) {
SshSessionFactory.setInstance(new EclipseSshSessionFactory(
(IJSchService) context.getService(ssh)));
} else {
// Should never happen
logWarning(CoreText.Activator_SshClientNoJsch, null);
if (previous instanceof EGitSshdSessionFactory) {
return;
}
SshSessionFactory.setInstance(new EGitSshdSessionFactory());
}
} else {
if (!SshClientType.APACHE.name().equalsIgnoreCase(sshClient)) {
logWarning(
MessageFormat.format(
CoreText.Activator_SshClientUnknown, sshClient),
null);
}
if (previous instanceof EGitSshdSessionFactory) {
return;
}
SshSessionFactory.setInstance(new EGitSshdSessionFactory());
}
if (previous instanceof SshdSessionFactory) {
((SshdSessionFactory) previous).close();
}
}
private void setupHttp() {
String sshClient = Platform.getPreferencesService().getString(pluginId,
GitCorePreferences.core_httpClient, "jdk", null); //$NON-NLS-1$
if (HttpClientType.APACHE.name().equalsIgnoreCase(sshClient)) {
HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
} else {
if (!HttpClientType.JDK.name().equalsIgnoreCase(sshClient)) {
logWarning(
MessageFormat.format(
CoreText.Activator_HttpClientUnknown, sshClient),
null);
}
HttpTransport.setConnectionFactory(new JDKHttpConnectionFactory());
}
}
private void setupProxy() {
IProxyService proxy = getProxyService();
if (proxy != null) {
ProxySelector.setDefault(new EclipseProxySelector(proxy));
Authenticator.setDefault(new EclipseAuthenticator(proxy));
}
}
private void setupResourceRefresh() {
refreshHandle = repositoryCache.getGlobalListenerList()
.addWorkingTreeModifiedListener(new ResourceRefreshHandler());
}
private void registerPreDeleteResourceChangeListener() {
if (preDeleteProjectListener == null) {
preDeleteProjectListener = new IResourceChangeListener() {
@Override
public void resourceChanged(IResourceChangeEvent event) {
IResource resource = event.getResource();
if (resource instanceof IProject) {
IProject project = (IProject) resource;
if (project.isAccessible()) {
if (ResourceUtil.isSharedWithGit(project)) {
IResource dotGit = project
.findMember(Constants.DOT_GIT);
if (dotGit != null && dotGit
.getType() == IResource.FOLDER) {
GitProjectData.reconfigureWindowCache();
}
}
} else {
// bug 419706: project is closed - use java.io API
IPath locationPath = project.getLocation();
if (locationPath != null) {
File locationDir = locationPath.toFile();
File dotGit = new File(locationDir,
Constants.DOT_GIT);
if (dotGit.exists() && dotGit.isDirectory()) {
GitProjectData.reconfigureWindowCache();
}
}
}
}
}
};
ResourcesPlugin.getWorkspace().addResourceChangeListener(preDeleteProjectListener, IResourceChangeEvent.PRE_DELETE);
}
}
private void registerBuiltinLFS() {
if (Platform.getBundle("org.eclipse.jgit.lfs") != null) { //$NON-NLS-1$
Class<?> lfs;
try {
lfs = Class.forName("org.eclipse.jgit.lfs.BuiltinLFS"); //$NON-NLS-1$
if (lfs != null) {
lfs.getMethod("register").invoke(null); //$NON-NLS-1$
}
} catch (ClassNotFoundException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e1) {
logWarning(CoreText.Activator_noBuiltinLfsSupportDetected, e1);
}
}
}
@Override
public void optionsChanged(DebugOptions options) {
// initialize the trace stuff
GitTraceLocation.initializeFromOptions(options, isDebugging());
}
/**
* Provides the 3-way merge strategy to use according to the user's
* preferences. The preferred merge strategy is JGit's default merge
* strategy unless the user has explicitly chosen a different strategy among
* the registered strategies.
*
* @return The MergeStrategy to use, can be {@code null}, in which case the
* default merge strategy should be used as defined by JGit.
* @since 4.1
*/
public MergeStrategy getPreferredMergeStrategy() {
// Get preferences set by user in the UI
final IEclipsePreferences prefs = InstanceScope.INSTANCE
.getNode(Activator.getPluginId());
String preferredMergeStrategyKey = prefs.get(
GitCorePreferences.core_preferredMergeStrategy, null);
// Get default preferences, wherever they are defined
if (preferredMergeStrategyKey == null
|| preferredMergeStrategyKey.isEmpty()) {
final IEclipsePreferences defaultPrefs = DefaultScope.INSTANCE
.getNode(Activator.getPluginId());
preferredMergeStrategyKey = defaultPrefs.get(
GitCorePreferences.core_preferredMergeStrategy, null);
}
if (preferredMergeStrategyKey != null
&& !preferredMergeStrategyKey.isEmpty()
&& !GitCorePreferences.core_preferredMergeStrategy_Default
.equals(preferredMergeStrategyKey)) {
MergeStrategy result = MergeStrategy.get(preferredMergeStrategyKey);
if (result != null) {
return result;
}
logError(NLS.bind(CoreText.Activator_invalidPreferredMergeStrategy,
preferredMergeStrategyKey), null);
}
return null;
}
/**
* @return Provides a read-only view of the registered MergeStrategies
* available.
* @since 4.1
*/
public Collection<MergeStrategyDescriptor> getRegisteredMergeStrategies() {
if (mergeStrategyRegistryListener == null) {
return Collections.emptyList();
}
return mergeStrategyRegistryListener.getStrategies();
}
private void registerMergeStrategyRegistryListener() {
mergeStrategyRegistryListener = new MergeStrategyRegistryListener(
Platform.getExtensionRegistry());
Platform.getExtensionRegistry().addListener(
mergeStrategyRegistryListener,
"org.eclipse.egit.core.mergeStrategy"); //$NON-NLS-1$
}
/**
* @return cache for Repository objects
*/
public RepositoryCache getRepositoryCache() {
return repositoryCache;
}
/**
* @return cache for index diffs
*/
public IndexDiffCache getIndexDiffCache() {
return indexDiffCache;
}
/**
* @return the {@link RepositoryUtil} instance
*/
public RepositoryUtil getRepositoryUtil() {
return repositoryUtil;
}
/**
* @return the secure store
*/
public EGitSecureStore getSecureStore() {
return secureStore;
}
/**
* Obtains the {@link IProxyService}.
*
* @return the {@link IProxyService} or {@code null} if none is available.
*/
public IProxyService getProxyService() {
return proxyServiceTracker.getService();
}
@Override
public void stop(final BundleContext context) throws Exception {
SshPreferencesMirror.INSTANCE.stop();
if (preferenceChangeListener != null) {
InstanceScope.INSTANCE.getNode(pluginId)
.removePreferenceChangeListener(preferenceChangeListener);
preferenceChangeListener = null;
}
SshSessionFactory current = SshSessionFactory.getInstance();
if (current instanceof SshdSessionFactory) {
((SshdSessionFactory) current).close();
}
if (proxyServiceTracker != null) {
proxyServiceTracker.close();
proxyServiceTracker = null;
}
if (mergeStrategyRegistryListener != null) {
Platform.getExtensionRegistry()
.removeListener(mergeStrategyRegistryListener);
mergeStrategyRegistryListener = null;
}
if (preDeleteProjectListener != null) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(preDeleteProjectListener);
preDeleteProjectListener = null;
}
if (ignoreDerivedResourcesListener != null) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(
ignoreDerivedResourcesListener);
ignoreDerivedResourcesListener.stop();
ignoreDerivedResourcesListener = null;
}
if (refreshHandle != null) {
refreshHandle.remove();
refreshHandle = null;
}
if (shareGitProjectsJob != null) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(
shareGitProjectsJob);
shareGitProjectsJob.stop();
shareGitProjectsJob = null;
}
GitProjectData.detachFromWorkspace();
indexDiffCache.dispose();
indexDiffCache = null;
repositoryCache.clear();
repositoryCache = null;
repositoryUtil.dispose();
repositoryUtil = null;
secureStore = null;
Config.setTypedConfigGetter(null);
super.stop(context);
plugin = null;
}
private void registerAutoShareProjects() {
shareGitProjectsJob = new AutoShareProjects();
ResourcesPlugin.getWorkspace().addResourceChangeListener(
shareGitProjectsJob, IResourceChangeEvent.POST_CHANGE);
}
private static class AutoShareProjects implements IResourceChangeListener {
private static int INTERESTING_CHANGES = IResourceDelta.ADDED
| IResourceDelta.OPEN;
private final CheckProjectsToShare checkProjectsJob;
public AutoShareProjects() {
checkProjectsJob = new CheckProjectsToShare();
}
private boolean doAutoShare() {
IEclipsePreferences d = DefaultScope.INSTANCE.getNode(Activator
.getPluginId());
IEclipsePreferences p = InstanceScope.INSTANCE.getNode(Activator
.getPluginId());
return p.getBoolean(GitCorePreferences.core_autoShareProjects, d
.getBoolean(GitCorePreferences.core_autoShareProjects,
true));
}
public void stop() {
boolean isRunning = !checkProjectsJob.cancel();
Job.getJobManager().cancel(JobFamilies.AUTO_SHARE);
try {
if (isRunning) {
checkProjectsJob.join();
}
Job.getJobManager().join(JobFamilies.AUTO_SHARE,
new NullProgressMonitor());
} catch (OperationCanceledException e) {
// Ignore
} catch (InterruptedException e) {
logError(e.getLocalizedMessage(), e);
}
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (!doAutoShare()) {
return;
}
try {
final Set<IProject> projectCandidates = new LinkedHashSet<>();
event.getDelta().accept(new IResourceDeltaVisitor() {
@Override
public boolean visit(IResourceDelta delta)
throws CoreException {
return collectOpenedProjects(delta,
projectCandidates);
}
});
if(!projectCandidates.isEmpty()){
checkProjectsJob.addProjectsToCheck(projectCandidates);
}
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
return;
}
}
/*
* This method should not use RepositoryMapping.getMapping(project) or
* RepositoryProvider.getProvider(project) which can trigger
* RepositoryProvider.map(project) and deadlock current thread. See
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=468270
*/
private boolean collectOpenedProjects(IResourceDelta delta,
Set<IProject> projects) {
if (delta.getKind() == IResourceDelta.CHANGED
&& (delta.getFlags() & INTERESTING_CHANGES) == 0) {
return true;
}
final IResource resource = delta.getResource();
if (resource.getType() == IResource.ROOT) {
return true;
}
if (resource.getType() != IResource.PROJECT) {
return false;
}
if (!resource.isAccessible() || resource.getLocation() == null) {
return false;
}
projects.add((IProject) resource);
return false;
}
}
private static class CheckProjectsToShare extends Job {
private Object lock = new Object();
private Set<IProject> projectCandidates;
public CheckProjectsToShare() {
super(CoreText.Activator_AutoShareJobName);
this.projectCandidates = new LinkedHashSet<>();
setUser(false);
setSystem(true);
}
public void addProjectsToCheck(Set<IProject> projects) {
synchronized (lock) {
this.projectCandidates.addAll(projects);
if (!projectCandidates.isEmpty()) {
schedule(100);
}
}
}
@Override
protected IStatus run(IProgressMonitor monitor) {
Set<IProject> projectsToCheck;
synchronized (lock) {
projectsToCheck = projectCandidates;
projectCandidates = new LinkedHashSet<>();
}
if (projectsToCheck.isEmpty()) {
return Status.OK_STATUS;
}
final Map<IProject, File> projects = new HashMap<>();
for (IProject project : projectsToCheck) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
if (project.isAccessible()) {
try {
visitConnect(project, projects);
} catch (CoreException e) {
logError(e.getMessage(), e);
}
}
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
if (projects.size() > 0) {
ConnectProviderOperation op = new ConnectProviderOperation(
projects);
op.setRefreshResources(false);
JobUtil.scheduleUserJob(op,
CoreText.Activator_AutoShareJobName,
JobFamilies.AUTO_SHARE);
}
return Status.OK_STATUS;
}
private void visitConnect(IProject project,
final Map<IProject, File> projects) throws CoreException {
if (RepositoryMapping.getMapping(project) != null) {
return;
}
RepositoryProvider provider = RepositoryProvider
.getProvider(project);
// respect if project is already shared with another
// team provider
if (provider != null) {
return;
}
RepositoryFinder f = new RepositoryFinder(project);
f.setFindInChildren(false);
List<RepositoryMapping> mappings = f
.find(new NullProgressMonitor());
if (mappings.isEmpty()) {
return;
}
RepositoryMapping m = mappings.get(0);
IPath gitDirPath = m.getGitDirAbsolutePath();
if (gitDirPath == null || !isValidRepositoryPath(gitDirPath)) {
return;
}
// connect
File repositoryDir = gitDirPath.toFile();
projects.put(project, repositoryDir);
Set<String> configured = Activator.getDefault().getRepositoryUtil()
.getRepositories();
if (configured.contains(gitDirPath.toString())) {
return;
}
int nofMappings = mappings.size();
if (nofMappings > 1) {
// We don't want to add submodules, that would only lead to
// problems when a configured repository is deleted. Walk up the
// hierarchy of nested repositories found. If we hit an already
// configured repository, we're done anyway. Otherwise add the
// topmost not yet configured repository that has a valid path.
IPath lastPath = gitDirPath;
for (int i = 1; i < nofMappings; i++) {
IPath nextPath = mappings.get(i).getGitDirAbsolutePath();
if (nextPath == null) {
continue;
}
if (configured.contains(nextPath.toString())) {
return;
} else if (!isValidRepositoryPath(nextPath)) {
break;
}
lastPath = nextPath;
}
repositoryDir = lastPath.toFile();
}
try {
Activator.getDefault().getRepositoryUtil()
.addConfiguredRepository(repositoryDir);
} catch (IllegalArgumentException e) {
logError(CoreText.Activator_AutoSharingFailed, e);
}
}
}
private static boolean isValidRepositoryPath(@NonNull IPath gitDirPath) {
if (gitDirPath.segmentCount() == 0) {
return false;
}
IPath workingDir = gitDirPath.removeLastSegments(1);
// Don't connect "/" or "C:\"
if (workingDir.isRoot()) {
return false;
}
File userHome = FS.DETECTED.userHome();
if (userHome != null) {
Path userHomePath = new Path(userHome.getAbsolutePath());
// Don't connect "/home" or "/home/username"
if (workingDir.isPrefixOf(userHomePath)) {
return false;
}
}
return true;
}
private void registerAutoIgnoreDerivedResources() {
ignoreDerivedResourcesListener = new IgnoreDerivedResources();
ResourcesPlugin.getWorkspace().addResourceChangeListener(
ignoreDerivedResourcesListener,
IResourceChangeEvent.POST_CHANGE);
}
/**
* @return true if the derived resources should be automatically added to
* the .gitignore files
*/
public static boolean autoIgnoreDerived() {
IEclipsePreferences d = DefaultScope.INSTANCE
.getNode(Activator.getPluginId());
IEclipsePreferences p = InstanceScope.INSTANCE
.getNode(Activator.getPluginId());
return p.getBoolean(GitCorePreferences.core_autoIgnoreDerivedResources,
d.getBoolean(GitCorePreferences.core_autoIgnoreDerivedResources,
true));
}
/**
* @return {@code true} if files that get deleted should be automatically
* staged
* @since 4.6
*/
public static boolean autoStageDeletion() {
IEclipsePreferences d = DefaultScope.INSTANCE
.getNode(Activator.getPluginId());
IEclipsePreferences p = InstanceScope.INSTANCE
.getNode(Activator.getPluginId());
boolean autoStageDeletion = p.getBoolean(
GitCorePreferences.core_autoStageDeletion,
d.getBoolean(GitCorePreferences.core_autoStageDeletion, false));
return autoStageDeletion;
}
/**
* @return {@code true} if files that are moved should be automatically
* staged
* @since 4.6
*/
public static boolean autoStageMoves() {
IEclipsePreferences d = DefaultScope.INSTANCE
.getNode(Activator.getPluginId());
IEclipsePreferences p = InstanceScope.INSTANCE
.getNode(Activator.getPluginId());
boolean autoStageMoves = p.getBoolean(
GitCorePreferences.core_autoStageMoves,
d.getBoolean(GitCorePreferences.core_autoStageMoves, false));
return autoStageMoves;
}
private static class IgnoreDerivedResources implements
IResourceChangeListener {
public void stop() {
Job.getJobManager().cancel(JobFamilies.AUTO_IGNORE);
try {
Job.getJobManager().join(JobFamilies.AUTO_IGNORE,
new NullProgressMonitor());
} catch (OperationCanceledException e) {
// Ignore
} catch (InterruptedException e) {
logError(e.getLocalizedMessage(), e);
}
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
try {
IResourceDelta d = event.getDelta();
if (d == null || !autoIgnoreDerived()) {
return;
}
final Set<IPath> toBeIgnored = new LinkedHashSet<>();
d.accept(new IResourceDeltaVisitor() {
@Override
public boolean visit(IResourceDelta delta)
throws CoreException {
if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) == 0)
return false;
int flags = delta.getFlags();
if ((flags != 0)
&& ((flags & IResourceDelta.DERIVED_CHANGED) == 0))
return false;
final IResource r = delta.getResource();
// don't consider resources contained in a project not
// shared with Git team provider
if ((r.getProject() != null)
&& (RepositoryMapping.getMapping(r) == null))
return false;
if (r.isTeamPrivateMember())
return false;
if (r.isDerived()) {
try {
IPath location = r.getLocation();
if (RepositoryUtil.canBeAutoIgnored(location)) {
toBeIgnored.add(location);
}
} catch (IOException e) {
logError(
MessageFormat.format(
CoreText.Activator_ignoreResourceFailed,
r.getFullPath()), e);
}
return false;
}
return true;
}
});
if (toBeIgnored.size() > 0)
JobUtil.scheduleUserJob(new IgnoreOperation(toBeIgnored),
CoreText.Activator_autoIgnoreDerivedResources,
JobFamilies.AUTO_IGNORE);
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
return;
}
}
}
/**
* Describes a MergeStrategy which can be registered with the mergeStrategy
* extension point.
*
* @since 4.1
*/
public static class MergeStrategyDescriptor {
private final String name;
private final String label;
private final Class<?> implementedBy;
/**
* @param name
* The referred strategy's name, to use for retrieving the
* strategy from MergeRegistry via
* {@link MergeStrategy#get(String)}
* @param label
* The label to display to users so they can select the
* strategy they need
* @param implementedBy
* The class of the MergeStrategy registered through the
* mergeStrategy extension point
*/
public MergeStrategyDescriptor(String name, String label,
Class<?> implementedBy) {
this.name = name;
this.label = label;
this.implementedBy = implementedBy;
}
/**
* @return The actual strategy's name, which can be used to retrieve
* that actual strategy via {@link MergeStrategy#get(String)}.
*/
public String getName() {
return name;
}
/**
* @return The strategy label, for display purposes.
*/
public String getLabel() {
return label;
}
/**
* @return The class of the MergeStrategy registered through the
* mergeStrategy extension point.
*/
public Class<?> getImplementedBy() {
return implementedBy;
}
}
private static class MergeStrategyRegistryListener implements
IRegistryEventListener {
private Map<String, MergeStrategyDescriptor> strategies;
private MergeStrategyRegistryListener(IExtensionRegistry registry) {
strategies = new LinkedHashMap<>();
IConfigurationElement[] elements = registry
.getConfigurationElementsFor("org.eclipse.egit.core.mergeStrategy"); //$NON-NLS-1$
loadMergeStrategies(elements);
}
private Collection<MergeStrategyDescriptor> getStrategies() {
return Collections.unmodifiableCollection(strategies.values());
}
@Override
public void added(IExtension[] extensions) {
for (IExtension extension : extensions) {
loadMergeStrategies(extension.getConfigurationElements());
}
}
@Override
public void added(IExtensionPoint[] extensionPoints) {
// Nothing to do here
}
@Override
public void removed(IExtension[] extensions) {
for (IExtension extension : extensions) {
for (IConfigurationElement element : extension
.getConfigurationElements()) {
try {
Object ext = element.createExecutableExtension("class"); //$NON-NLS-1$
if (ext instanceof MergeStrategy) {
MergeStrategy strategy = (MergeStrategy) ext;
strategies.remove(strategy.getName());
}
} catch (CoreException e) {
Activator.logError(CoreText.MergeStrategy_UnloadError,
e);
}
}
}
}
@Override
public void removed(IExtensionPoint[] extensionPoints) {
// Nothing to do here
}
private void loadMergeStrategies(IConfigurationElement[] elements) {
for (IConfigurationElement element : elements) {
try {
Object ext = element.createExecutableExtension("class"); //$NON-NLS-1$
if (ext instanceof MergeStrategy) {
MergeStrategy strategy = (MergeStrategy) ext;
String name = element.getAttribute("name"); //$NON-NLS-1$
if (name == null || name.isEmpty()) {
name = strategy.getName();
}
if (canRegister(name, strategy)) {
if (MergeStrategy.get(name) == null) {
MergeStrategy.register(name, strategy);
}
strategies
.put(name,
new MergeStrategyDescriptor(
name,
element.getAttribute("label"), //$NON-NLS-1$
strategy.getClass()));
}
}
} catch (CoreException e) {
Activator.logError(CoreText.MergeStrategy_LoadError, e);
}
}
}
/**
* Checks whether it's possible to register the provided strategy with
* the given name
*
* @param name
* Name to use to register the strategy
* @param strategy
* Strategy to register
* @return <code>true</code> if the name is neither null nor empty, no
* other strategy is already register for the same name, and the
* name is not one of the core JGit strategies. If the given
* name is that of a core JGit strategy, the method will return
* <code>true</code> only if the strategy is the matching JGit
* strategy for that name.
*/
private boolean canRegister(String name, MergeStrategy strategy) {
boolean result = true;
if (name == null || name.isEmpty()) {
// name is mandatory
Activator.logError(
NLS.bind(CoreText.MergeStrategy_MissingName,
strategy.getClass()), null);
result = false;
} else if (strategies.containsKey(name)) {
// Other strategy already registered for this name
Activator.logError(NLS.bind(
CoreText.MergeStrategy_DuplicateName, new Object[] {
name, strategies.get(name).getImplementedBy(),
strategy.getClass() }), null);
result = false;
} else if (MergeStrategy.get(name) != null
&& MergeStrategy.get(name) != strategy) {
// The name is reserved by a core JGit strategy, and the
// provided instance is not that of JGit
Activator.logError(NLS.bind(
CoreText.MergeStrategy_ReservedName, new Object[] {
name, MergeStrategy.get(name).getClass(),
strategy.getClass() }), null);
result = false;
}
return result;
}
}
/**
* A system reader that hides certain global git environment variables from
* JGit.
*/
private static class EclipseSystemReader extends SystemReader {
/**
* Hide these variables lest JGit tries to use them for different
* repositories.
*/
private static final String[] HIDDEN_VARIABLES = {
Constants.GIT_DIR_KEY, Constants.GIT_WORK_TREE_KEY,
Constants.GIT_OBJECT_DIRECTORY_KEY,
Constants.GIT_INDEX_FILE_KEY,
Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY };
private final SystemReader delegate;
public EclipseSystemReader(SystemReader delegate) {
this.delegate = delegate;
}
@Override
public String getenv(String variable) {
String result = delegate.getenv(variable);
if (result == null) {
return result;
}
boolean isWin = isWindows();
for (String gitvar : HIDDEN_VARIABLES) {
if (isWin && gitvar.equalsIgnoreCase(variable)
|| !isWin && gitvar.equals(variable)) {
return null;
}
}
return result;
}
@Override
public String getHostname() {
return delegate.getHostname();
}
@Override
public String getProperty(String key) {
return delegate.getProperty(key);
}
@Override
public FileBasedConfig openUserConfig(Config parent, FS fs) {
return delegate.openUserConfig(parent, fs);
}
@Override
public FileBasedConfig openJGitConfig(Config parent, FS fs) {
return delegate.openJGitConfig(parent, fs);
}
@Override
public FileBasedConfig openSystemConfig(Config parent, FS fs) {
return delegate.openSystemConfig(parent, fs);
}
@Override
public long getCurrentTime() {
return delegate.getCurrentTime();
}
@Override
public int getTimezone(long when) {
return delegate.getTimezone(when);
}
@Override
public StoredConfig getUserConfig()
throws IOException, ConfigInvalidException {
return delegate.getUserConfig();
}
@Override
public StoredConfig getJGitConfig()
throws IOException, ConfigInvalidException {
return delegate.getJGitConfig();
}
@Override
public StoredConfig getSystemConfig()
throws IOException, ConfigInvalidException {
return delegate.getSystemConfig();
}
}
}