1 /*******************************************************************************
2 * Copyright (C) 2007,2010 Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
5 * Copyright (C) 2012, Matthias Sohn <matthias.sohn@sap.com>
6 * Copyright (C) 2015, Philipp Bumann <bumannp@gmail.com>
7 * Copyright (C) 2016, Dani Megert <daniel_megert@ch.ibm.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * which accompanies this distribution, and is available at
12 * http://www.eclipse.org/legal/epl-v10.html
13 *******************************************************************************/
14 package org
.eclipse
.egit
.ui
;
16 import java
.io
.IOException
;
17 import java
.lang
.reflect
.InvocationTargetException
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Dictionary
;
20 import java
.util
.Hashtable
;
21 import java
.util
.Iterator
;
22 import java
.util
.LinkedHashSet
;
23 import java
.util
.List
;
26 import org
.eclipse
.core
.resources
.IProject
;
27 import org
.eclipse
.core
.resources
.IResource
;
28 import org
.eclipse
.core
.resources
.IWorkspace
;
29 import org
.eclipse
.core
.resources
.IWorkspaceRunnable
;
30 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
31 import org
.eclipse
.core
.runtime
.CoreException
;
32 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
33 import org
.eclipse
.core
.runtime
.IStatus
;
34 import org
.eclipse
.core
.runtime
.Status
;
35 import org
.eclipse
.core
.runtime
.SubMonitor
;
36 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
37 import org
.eclipse
.core
.runtime
.jobs
.Job
;
38 import org
.eclipse
.egit
.core
.RepositoryCache
;
39 import org
.eclipse
.egit
.core
.RepositoryUtil
;
40 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
41 import org
.eclipse
.egit
.ui
.internal
.ConfigurationChecker
;
42 import org
.eclipse
.egit
.ui
.internal
.KnownHosts
;
43 import org
.eclipse
.egit
.ui
.internal
.RepositoryCacheRule
;
44 import org
.eclipse
.egit
.ui
.internal
.UIText
;
45 import org
.eclipse
.egit
.ui
.internal
.credentials
.EGitCredentialsProvider
;
46 import org
.eclipse
.egit
.ui
.internal
.trace
.GitTraceLocation
;
47 import org
.eclipse
.egit
.ui
.internal
.variables
.GitTemplateVariableResolver
;
48 import org
.eclipse
.jdt
.internal
.ui
.JavaPlugin
;
49 import org
.eclipse
.jface
.resource
.JFaceResources
;
50 import org
.eclipse
.jface
.resource
.LocalResourceManager
;
51 import org
.eclipse
.jface
.resource
.ResourceManager
;
52 import org
.eclipse
.jface
.text
.templates
.ContextTypeRegistry
;
53 import org
.eclipse
.jface
.text
.templates
.TemplateContextType
;
54 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
55 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
56 import org
.eclipse
.jgit
.events
.IndexChangedEvent
;
57 import org
.eclipse
.jgit
.events
.IndexChangedListener
;
58 import org
.eclipse
.jgit
.events
.ListenerHandle
;
59 import org
.eclipse
.jgit
.events
.RepositoryEvent
;
60 import org
.eclipse
.jgit
.lib
.Repository
;
61 import org
.eclipse
.jgit
.transport
.CredentialsProvider
;
62 import org
.eclipse
.osgi
.service
.debug
.DebugOptions
;
63 import org
.eclipse
.osgi
.service
.debug
.DebugOptionsListener
;
64 import org
.eclipse
.swt
.graphics
.Font
;
65 import org
.eclipse
.swt
.widgets
.Display
;
66 import org
.eclipse
.ui
.IWindowListener
;
67 import org
.eclipse
.ui
.IWorkbenchWindow
;
68 import org
.eclipse
.ui
.PlatformUI
;
69 import org
.eclipse
.ui
.plugin
.AbstractUIPlugin
;
70 import org
.eclipse
.ui
.statushandlers
.StatusManager
;
71 import org
.eclipse
.ui
.themes
.ITheme
;
72 import org
.osgi
.framework
.BundleContext
;
75 * This is a plugin singleton mostly controlling logging.
77 public class Activator
extends AbstractUIPlugin
implements DebugOptionsListener
{
80 * The one and only instance
82 private static Activator plugin
;
85 * Property listeners for plugin specific events
87 private static List
<IPropertyChangeListener
> propertyChangeListeners
=
91 * Property constant indicating the decorator configuration has changed.
93 public static final String DECORATORS_CHANGED
= "org.eclipse.egit.ui.DECORATORS_CHANGED"; //$NON-NLS-1$
96 * @return the {@link Activator} singleton.
98 public static Activator
getDefault() {
103 * @return the id of the egit ui plugin
105 public static String
getPluginId() {
106 return getDefault().getBundle().getSymbolicName();
110 * Creates an {@link IStatus} from the parameters. If the throwable is an
111 * {@link InvocationTargetException}, the status is created from the first
112 * exception that is either not an InvocationTargetException or that has a
113 * message. If the message passed is empty, tries to supply a message from
117 * of the {@link IStatus}
121 * that caused the status, may be {@code null}
124 private static IStatus
toStatus(int severity
, String message
,
125 Throwable throwable
) {
126 Throwable exc
= throwable
;
127 while (exc
instanceof InvocationTargetException
) {
128 String msg
= exc
.getLocalizedMessage();
129 if (msg
!= null && !msg
.isEmpty()) {
132 Throwable cause
= exc
.getCause();
138 if (exc
!= null && (message
== null || message
.isEmpty())) {
139 message
= exc
.getLocalizedMessage();
141 return new Status(severity
, getPluginId(), message
, exc
);
145 * Handle an error. The error is logged. If <code>show</code> is
146 * <code>true</code> the error is shown to the user.
149 * a localized message
153 public static void handleError(String message
, Throwable throwable
,
155 handleIssue(IStatus
.ERROR
, message
, throwable
, show
);
159 * Handle an issue. The issue is logged. If <code>show</code> is
160 * <code>true</code> the issue is shown to the user.
163 * status severity, use constants defined in {@link IStatus}
165 * a localized message
170 public static void handleIssue(int severity
, String message
, Throwable throwable
,
172 IStatus status
= toStatus(severity
, message
, throwable
);
173 int style
= StatusManager
.LOG
;
175 style
|= StatusManager
.SHOW
;
176 StatusManager
.getManager().handle(status
, style
);
180 * Shows an error. The error is NOT logged.
183 * a localized message
186 public static void showError(String message
, Throwable throwable
) {
187 IStatus status
= toStatus(IStatus
.ERROR
, message
, throwable
);
188 StatusManager
.getManager().handle(status
, StatusManager
.SHOW
);
192 * Shows an error. The error is NOT logged.
195 * a localized message
198 public static void showErrorStatus(String message
, IStatus status
) {
199 StatusManager
.getManager().handle(status
, StatusManager
.SHOW
);
206 public static void logError(String message
, Throwable e
) {
207 handleError(message
, e
, false);
211 * Utility method to log warnings for this plug-in.
214 * User comprehensible message
216 * The exception through which we noticed the warning
218 public static void logWarning(final String message
, final Throwable thr
) {
219 handleIssue(IStatus
.WARNING
, message
, thr
, false);
226 public static void error(String message
, Throwable e
) {
227 handleError(message
, e
, false);
231 * Creates an error status
234 * a localized message
236 * @return a new Status object
238 public static IStatus
createErrorStatus(String message
,
239 Throwable throwable
) {
240 return toStatus(IStatus
.ERROR
, message
, throwable
);
244 * Creates an error status
247 * a localized message
248 * @return a new Status object
250 public static IStatus
createErrorStatus(String message
) {
251 return toStatus(IStatus
.ERROR
, message
, null);
255 * Get the theme used by this plugin.
259 public static ITheme
getTheme() {
260 return plugin
.getWorkbench().getThemeManager().getCurrentTheme();
264 * Get a font known to this plugin.
267 * one of our THEME_* font preference ids (see
268 * {@link UIPreferences});
269 * @return the configured font, borrowed from the registry.
271 public static Font
getFont(final String id
) {
272 return getTheme().getFontRegistry().get(id
);
276 * Get a font known to this plugin, but with bold style applied over top.
279 * one of our THEME_* font preference ids (see
280 * {@link UIPreferences});
281 * @return the configured font, borrowed from the registry.
283 public static Font
getBoldFont(final String id
) {
284 return getTheme().getFontRegistry().getBold(id
);
287 private ResourceManager resourceManager
;
288 private RepositoryChangeScanner rcs
;
289 private ResourceRefreshJob refreshJob
;
290 private ListenerHandle refreshHandle
;
291 private DebugOptions debugOptions
;
293 private volatile boolean uiIsActive
;
294 private IWindowListener focusListener
;
297 * Construct the {@link Activator} egit ui plugin singleton instance
300 Activator
.setActivator(this);
303 private static void setActivator(Activator a
) {
309 public void start(final BundleContext context
) throws Exception
{
310 super.start(context
);
311 // we want to be notified about debug options changes
312 Dictionary
<String
, String
> props
= new Hashtable
<>(4);
313 props
.put(DebugOptions
.LISTENER_SYMBOLICNAME
, context
.getBundle()
315 context
.registerService(DebugOptionsListener
.class.getName(), this,
318 setupRepoChangeScanner();
319 setupRepoIndexRefresh();
320 setupFocusHandling();
321 setupCredentialsProvider();
322 ConfigurationChecker
.checkConfiguration();
324 registerTemplateVariableResolvers();
327 private void setupCredentialsProvider() {
328 CredentialsProvider
.setDefault(new EGitCredentialsProvider());
331 private void registerTemplateVariableResolvers() {
332 if (hasJavaPlugin()) {
333 final ContextTypeRegistry codeTemplateContextRegistry
= JavaPlugin
334 .getDefault().getCodeTemplateContextRegistry();
335 final Iterator
<?
> ctIter
= codeTemplateContextRegistry
338 while (ctIter
.hasNext()) {
339 final TemplateContextType contextType
= (TemplateContextType
) ctIter
342 .addResolver(new GitTemplateVariableResolver(
343 "git_config", //$NON-NLS-1$
344 UIText
.GitTemplateVariableResolver_GitConfigDescription
));
350 * @return true if at least one Eclipse window is active
352 static boolean isActive() {
353 return getDefault().uiIsActive
;
357 private void setupFocusHandling() {
358 focusListener
= new IWindowListener() {
360 private void updateUiState() {
361 Display
.getCurrent().asyncExec(new Runnable() {
364 boolean wasActive
= uiIsActive
;
365 uiIsActive
= Display
.getCurrent().getActiveShell() != null;
366 if (uiIsActive
!= wasActive
367 && GitTraceLocation
.REPOSITORYCHANGESCANNER
372 private void traceUiIsActive() {
373 StringBuilder message
= new StringBuilder(
374 "workbench is "); //$NON-NLS-1$
375 message
.append(uiIsActive ?
"active" : "inactive"); //$NON-NLS-1$//$NON-NLS-2$
376 GitTraceLocation
.getTrace().trace(
377 GitTraceLocation
.REPOSITORYCHANGESCANNER
378 .getLocation(), message
.toString());
384 public void windowOpened(IWorkbenchWindow window
) {
389 public void windowDeactivated(IWorkbenchWindow window
) {
394 public void windowClosed(IWorkbenchWindow window
) {
399 public void windowActivated(IWorkbenchWindow window
) {
401 // 500: give the UI task a chance to update the active state
403 refreshJob
.triggerRefresh();
406 Job job
= new Job(UIText
.Activator_setupFocusListener
) {
408 protected IStatus
run(IProgressMonitor monitor
) {
409 if (PlatformUI
.isWorkbenchRunning())
410 PlatformUI
.getWorkbench().addWindowListener(focusListener
);
413 return Status
.OK_STATUS
;
420 public void optionsChanged(DebugOptions options
) {
421 // initialize the trace stuff
422 debugOptions
= options
;
423 GitTraceLocation
.initializeFromOptions(options
, isDebugging());
427 * @return the {@link DebugOptions}
429 public DebugOptions
getDebugOptions() {
433 private void setupRepoIndexRefresh() {
434 refreshJob
= new ResourceRefreshJob();
435 refreshHandle
= Repository
.getGlobalListenerList()
436 .addIndexChangedListener(refreshJob
);
440 * Register for changes made to Team properties.
443 * The listener to register
445 public static synchronized void addPropertyChangeListener(
446 IPropertyChangeListener listener
) {
447 propertyChangeListeners
.add(listener
);
451 * Remove a Team property changes.
454 * The listener to remove
456 public static synchronized void removePropertyChangeListener(
457 IPropertyChangeListener listener
) {
458 propertyChangeListeners
.remove(listener
);
462 * Broadcast a Team property change.
465 * The event to broadcast
467 public static synchronized void broadcastPropertyChange(PropertyChangeEvent event
) {
468 for (IPropertyChangeListener listener
: propertyChangeListeners
)
469 listener
.propertyChange(event
);
473 * Refresh projects in repositories that we suspect may have resource
476 static class ResourceRefreshJob
extends Job
implements
477 IndexChangedListener
{
479 ResourceRefreshJob() {
480 super(UIText
.Activator_refreshJobName
);
485 private Set
<Repository
> repositoriesChanged
= new LinkedHashSet
<>();
488 public IStatus
run(IProgressMonitor monitor
) {
489 Set
<Repository
> repos
;
490 synchronized (repositoriesChanged
) {
491 if (repositoriesChanged
.isEmpty()) {
492 return Status
.OK_STATUS
;
494 repos
= new LinkedHashSet
<>(repositoriesChanged
);
495 repositoriesChanged
.clear();
497 IWorkspace workspace
= ResourcesPlugin
.getWorkspace();
498 IProject
[] projects
= workspace
.getRoot().getProjects();
499 final Set
<IProject
> toRefresh
= new LinkedHashSet
<>();
500 for (IProject p
: projects
) {
501 if (!p
.isAccessible()) {
504 RepositoryMapping mapping
= RepositoryMapping
.getMapping(p
);
506 && repos
.contains(mapping
.getRepository())) {
511 if (toRefresh
.isEmpty()) {
512 return Status
.OK_STATUS
;
516 workspace
.run(new IWorkspaceRunnable() {
518 public void run(IProgressMonitor m
) throws CoreException
{
519 SubMonitor subMonitor
= SubMonitor
.convert(m
,
520 UIText
.Activator_refreshingProjects
,
522 for (IProject p
: toRefresh
) {
523 if (subMonitor
.isCanceled()) {
526 ISchedulingRule rule
= p
.getWorkspace().getRuleFactory().refreshRule(p
);
528 getJobManager().beginRule(rule
, subMonitor
);
529 // handle missing projects after branch switch
530 if (p
.isAccessible()) {
531 p
.refreshLocal(IResource
.DEPTH_INFINITE
,
532 subMonitor
.newChild(1));
534 } catch (CoreException e
) {
535 handleError(UIText
.Activator_refreshFailed
, e
, false);
537 getJobManager().endRule(rule
);
541 }, workspace
.getRuleFactory().refreshRule(workspace
.getRoot()),
542 IWorkspace
.AVOID_UPDATE
, monitor
);
543 } catch (CoreException e
) {
544 handleError(UIText
.Activator_refreshFailed
, e
, false);
545 return new Status(IStatus
.ERROR
, getPluginId(), e
.getMessage());
548 if (!monitor
.isCanceled()) {
549 // re-schedule if we got some changes in the meantime
550 synchronized (repositoriesChanged
) {
551 if (!repositoriesChanged
.isEmpty()) {
557 return Status
.OK_STATUS
;
561 public void onIndexChanged(IndexChangedEvent e
) {
562 if (Activator
.getDefault().getPreferenceStore()
563 .getBoolean(UIPreferences
.REFESH_ON_INDEX_CHANGE
)) {
564 mayTriggerRefresh(e
);
569 * Record which projects have changes. Initiate a resource refresh job
570 * if the user settings allow it.
573 * The {@link RepositoryEvent} that triggered this refresh
575 private void mayTriggerRefresh(RepositoryEvent e
) {
576 synchronized (repositoriesChanged
) {
577 repositoriesChanged
.add(e
.getRepository());
579 if (!Activator
.getDefault().getPreferenceStore()
580 .getBoolean(UIPreferences
.REFESH_ONLY_WHEN_ACTIVE
)
587 * Figure which projects belong to a repository, add them to a set of
588 * project to refresh and schedule the refresh as a job.
590 void triggerRefresh() {
591 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
592 GitTraceLocation
.getTrace().trace(
593 GitTraceLocation
.REPOSITORYCHANGESCANNER
.getLocation(),
594 "Triggered refresh"); //$NON-NLS-1$
601 * A Job that looks at the repository meta data and triggers a refresh of
602 * the resources in the affected projects.
604 private static class RepositoryChangeScanner
extends Job
605 implements IPropertyChangeListener
{
607 // volatile in order to ensure thread synchronization
608 private volatile boolean doReschedule
;
610 private int interval
;
612 private final RepositoryCache repositoryCache
;
614 RepositoryChangeScanner() {
615 super(UIText
.Activator_repoScanJobName
);
616 setRule(new RepositoryCacheRule());
619 repositoryCache
= org
.eclipse
.egit
.core
.Activator
.getDefault()
620 .getRepositoryCache();
621 updateRefreshInterval();
625 public boolean shouldSchedule() {
630 public boolean shouldRun() {
634 void setReschedule(boolean reschedule
){
635 doReschedule
= reschedule
;
639 protected IStatus
run(IProgressMonitor monitor
) {
640 // When people use Git from the command line a lot of changes
641 // may happen. Don't scan when inactive depending on the user's
643 if (getDefault().getPreferenceStore()
644 .getBoolean(UIPreferences
.REFESH_ONLY_WHEN_ACTIVE
)) {
647 return Status
.OK_STATUS
;
651 Repository
[] repos
= repositoryCache
.getAllRepositories();
652 if (repos
.length
== 0) {
653 return Status
.OK_STATUS
;
656 monitor
.beginTask(UIText
.Activator_scanningRepositories
,
659 for (Repository repo
: repos
) {
660 if (monitor
.isCanceled()) {
663 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
664 GitTraceLocation
.getTrace().trace(
665 GitTraceLocation
.REPOSITORYCHANGESCANNER
667 "Scanning " + repo
+ " for changes"); //$NON-NLS-1$ //$NON-NLS-2$
670 repo
.scanForRepoChanges();
673 } catch (IOException e
) {
674 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
675 GitTraceLocation
.getTrace().trace(
676 GitTraceLocation
.REPOSITORYCHANGESCANNER
678 "Stopped rescheduling " + getName() + "job"); //$NON-NLS-1$ //$NON-NLS-2$
680 return createErrorStatus(UIText
.Activator_scanError
, e
);
684 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
685 GitTraceLocation
.getTrace().trace(
686 GitTraceLocation
.REPOSITORYCHANGESCANNER
.getLocation(),
687 "Rescheduling " + getName() + " job"); //$NON-NLS-1$ //$NON-NLS-2$
690 return Status
.OK_STATUS
;
694 public void propertyChange(PropertyChangeEvent event
) {
695 if (!UIPreferences
.REFESH_INDEX_INTERVAL
696 .equals(event
.getProperty())) {
699 updateRefreshInterval();
702 private void updateRefreshInterval() {
703 interval
= getRefreshIndexInterval();
704 setReschedule(interval
> 0);
710 * @return interval in milliseconds for automatic index check, 0 is if
711 * check should be disabled
713 private static int getRefreshIndexInterval() {
714 return 1000 * getDefault().getPreferenceStore()
715 .getInt(UIPreferences
.REFESH_INDEX_INTERVAL
);
719 private void setupRepoChangeScanner() {
720 rcs
= new RepositoryChangeScanner();
721 getPreferenceStore().addPropertyChangeListener(rcs
);
725 public void stop(final BundleContext context
) throws Exception
{
726 if (refreshHandle
!= null) {
727 refreshHandle
.remove();
728 refreshHandle
= null;
731 if (focusListener
!= null) {
732 if (PlatformUI
.isWorkbenchRunning()) {
733 PlatformUI
.getWorkbench().removeWindowListener(focusListener
);
735 focusListener
= null;
738 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
739 GitTraceLocation
.getTrace().trace(
740 GitTraceLocation
.REPOSITORYCHANGESCANNER
.getLocation(),
741 "Trying to cancel " + rcs
.getName() + " job"); //$NON-NLS-1$ //$NON-NLS-2$
744 getPreferenceStore().removePropertyChangeListener(rcs
);
745 rcs
.setReschedule(false);
747 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
748 GitTraceLocation
.getTrace().trace(
749 GitTraceLocation
.REPOSITORYCHANGESCANNER
.getLocation(),
750 "Trying to cancel " + refreshJob
.getName() + " job"); //$NON-NLS-1$ //$NON-NLS-2$
757 if (GitTraceLocation
.REPOSITORYCHANGESCANNER
.isActive()) {
758 GitTraceLocation
.getTrace().trace(
759 GitTraceLocation
.REPOSITORYCHANGESCANNER
.getLocation(),
760 "Jobs terminated"); //$NON-NLS-1$
762 if (resourceManager
!= null) {
763 resourceManager
.dispose();
764 resourceManager
= null;
771 protected void saveDialogSettings() {
773 super.saveDialogSettings();
776 * @return the {@link RepositoryUtil} instance
778 public RepositoryUtil
getRepositoryUtil() {
779 return org
.eclipse
.egit
.core
.Activator
.getDefault().getRepositoryUtil();
783 * Gets this plugin's {@link ResourceManager}.
785 * @return the {@link ResourceManager} of this plugin
787 public synchronized ResourceManager
getResourceManager() {
788 if (resourceManager
== null) {
789 Display display
= PlatformUI
.getWorkbench().getDisplay();
790 if (display
== null) {
791 // Workbench already closed?
792 throw new IllegalStateException();
794 resourceManager
= new LocalResourceManager(JFaceResources
795 .getResources(display
));
797 return resourceManager
;
801 * @return true if the Java Plugin is loaded
803 public static final boolean hasJavaPlugin() {
805 return Class
.forName("org.eclipse.jdt.internal.ui.JavaPlugin") != null; //$NON-NLS-1$
806 } catch (ClassNotFoundException e
) {