1 /*******************************************************************************
2 * Copyright (c) 2010, 2017 SAP AG and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * Mathias Kinzler (SAP AG) - initial implementation
10 * Dariusz Luksza <dariusz@luksza.org> - add synchronization feature
11 * Daniel Megert <daniel_megert@ch.ibm.com> - Only check out on double-click
12 * Daniel Megert <daniel_megert@ch.ibm.com> - Don't reveal selection on refresh
13 * Robin Stocker <robin@nibor.org> - Show In support
14 * Daniel Megert <daniel_megert@ch.ibm.com> - Show Git Staging view in Show In menu
15 *******************************************************************************/
16 package org
.eclipse
.egit
.ui
.internal
.repository
;
19 import java
.io
.IOException
;
20 import java
.text
.MessageFormat
;
21 import java
.util
.ArrayList
;
22 import java
.util
.Arrays
;
23 import java
.util
.Collection
;
24 import java
.util
.HashSet
;
25 import java
.util
.Iterator
;
26 import java
.util
.LinkedList
;
27 import java
.util
.List
;
31 import org
.eclipse
.core
.commands
.Command
;
32 import org
.eclipse
.core
.resources
.IResource
;
33 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
34 import org
.eclipse
.core
.runtime
.IPath
;
35 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
36 import org
.eclipse
.core
.runtime
.IStatus
;
37 import org
.eclipse
.core
.runtime
.Path
;
38 import org
.eclipse
.core
.runtime
.Status
;
39 import org
.eclipse
.core
.runtime
.jobs
.Job
;
40 import org
.eclipse
.core
.runtime
.preferences
.IEclipsePreferences
.IPreferenceChangeListener
;
41 import org
.eclipse
.core
.runtime
.preferences
.IEclipsePreferences
.PreferenceChangeEvent
;
42 import org
.eclipse
.egit
.core
.AdapterUtils
;
43 import org
.eclipse
.egit
.core
.RepositoryCache
;
44 import org
.eclipse
.egit
.core
.RepositoryUtil
;
45 import org
.eclipse
.egit
.core
.internal
.util
.ResourceUtil
;
46 import org
.eclipse
.egit
.ui
.Activator
;
47 import org
.eclipse
.egit
.ui
.JobFamilies
;
48 import org
.eclipse
.egit
.ui
.UIPreferences
;
49 import org
.eclipse
.egit
.ui
.UIUtils
;
50 import org
.eclipse
.egit
.ui
.internal
.CommonUtils
;
51 import org
.eclipse
.egit
.ui
.internal
.UIIcons
;
52 import org
.eclipse
.egit
.ui
.internal
.UIText
;
53 import org
.eclipse
.egit
.ui
.internal
.branch
.BranchOperationUI
;
54 import org
.eclipse
.egit
.ui
.internal
.history
.HistoryPageInput
;
55 import org
.eclipse
.egit
.ui
.internal
.reflog
.ReflogView
;
56 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.FetchNode
;
57 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.FileNode
;
58 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.FolderNode
;
59 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.PushNode
;
60 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.RefNode
;
61 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.RemoteNode
;
62 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.RepositoryNode
;
63 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.RepositoryTreeNode
;
64 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.RepositoryTreeNodeType
;
65 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.StashedCommitNode
;
66 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.TagNode
;
67 import org
.eclipse
.egit
.ui
.internal
.repository
.tree
.WorkingDirNode
;
68 import org
.eclipse
.egit
.ui
.internal
.selection
.SelectionUtils
;
69 import org
.eclipse
.egit
.ui
.internal
.staging
.StagingView
;
70 import org
.eclipse
.egit
.ui
.internal
.trace
.GitTraceLocation
;
71 import org
.eclipse
.jface
.action
.IMenuListener
;
72 import org
.eclipse
.jface
.action
.IMenuManager
;
73 import org
.eclipse
.jface
.action
.MenuManager
;
74 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
75 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
76 import org
.eclipse
.jface
.dialogs
.MessageDialogWithToggle
;
77 import org
.eclipse
.jface
.layout
.GridDataFactory
;
78 import org
.eclipse
.jface
.layout
.GridLayoutFactory
;
79 import org
.eclipse
.jface
.preference
.IPreferenceStore
;
80 import org
.eclipse
.jface
.resource
.JFaceColors
;
81 import org
.eclipse
.jface
.viewers
.DoubleClickEvent
;
82 import org
.eclipse
.jface
.viewers
.IDoubleClickListener
;
83 import org
.eclipse
.jface
.viewers
.IOpenListener
;
84 import org
.eclipse
.jface
.viewers
.ISelection
;
85 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
86 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
87 import org
.eclipse
.jface
.viewers
.OpenEvent
;
88 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
89 import org
.eclipse
.jface
.viewers
.TreeSelection
;
90 import org
.eclipse
.jface
.window
.Window
;
91 import org
.eclipse
.jgit
.events
.ConfigChangedEvent
;
92 import org
.eclipse
.jgit
.events
.ConfigChangedListener
;
93 import org
.eclipse
.jgit
.events
.IndexChangedEvent
;
94 import org
.eclipse
.jgit
.events
.IndexChangedListener
;
95 import org
.eclipse
.jgit
.events
.ListenerHandle
;
96 import org
.eclipse
.jgit
.events
.RefsChangedEvent
;
97 import org
.eclipse
.jgit
.events
.RefsChangedListener
;
98 import org
.eclipse
.jgit
.lib
.Repository
;
99 import org
.eclipse
.osgi
.util
.NLS
;
100 import org
.eclipse
.swt
.SWT
;
101 import org
.eclipse
.swt
.custom
.StackLayout
;
102 import org
.eclipse
.swt
.events
.DisposeEvent
;
103 import org
.eclipse
.swt
.events
.DisposeListener
;
104 import org
.eclipse
.swt
.graphics
.Color
;
105 import org
.eclipse
.swt
.graphics
.Image
;
106 import org
.eclipse
.swt
.widgets
.Composite
;
107 import org
.eclipse
.swt
.widgets
.Label
;
108 import org
.eclipse
.swt
.widgets
.Menu
;
109 import org
.eclipse
.team
.ui
.history
.IHistoryView
;
110 import org
.eclipse
.ui
.IEditorInput
;
111 import org
.eclipse
.ui
.IEditorPart
;
112 import org
.eclipse
.ui
.IFileEditorInput
;
113 import org
.eclipse
.ui
.IPageLayout
;
114 import org
.eclipse
.ui
.ISelectionListener
;
115 import org
.eclipse
.ui
.ISelectionService
;
116 import org
.eclipse
.ui
.IURIEditorInput
;
117 import org
.eclipse
.ui
.IViewPart
;
118 import org
.eclipse
.ui
.IWorkbenchPart
;
119 import org
.eclipse
.ui
.IWorkbenchWindow
;
120 import org
.eclipse
.ui
.PlatformUI
;
121 import org
.eclipse
.ui
.commands
.ICommandService
;
122 import org
.eclipse
.ui
.contexts
.IContextService
;
123 import org
.eclipse
.ui
.forms
.events
.HyperlinkAdapter
;
124 import org
.eclipse
.ui
.forms
.events
.HyperlinkEvent
;
125 import org
.eclipse
.ui
.forms
.widgets
.FormToolkit
;
126 import org
.eclipse
.ui
.forms
.widgets
.Hyperlink
;
127 import org
.eclipse
.ui
.handlers
.IHandlerService
;
128 import org
.eclipse
.ui
.handlers
.RegistryToggleState
;
129 import org
.eclipse
.ui
.navigator
.CommonNavigator
;
130 import org
.eclipse
.ui
.navigator
.CommonViewer
;
131 import org
.eclipse
.ui
.part
.IPage
;
132 import org
.eclipse
.ui
.part
.IShowInSource
;
133 import org
.eclipse
.ui
.part
.IShowInTargetList
;
134 import org
.eclipse
.ui
.part
.ShowInContext
;
135 import org
.eclipse
.ui
.progress
.IWorkbenchSiteProgressService
;
136 import org
.eclipse
.ui
.progress
.WorkbenchJob
;
137 import org
.eclipse
.ui
.views
.properties
.IPropertySheetPage
;
138 import org
.eclipse
.ui
.views
.properties
.PropertySheet
;
139 import org
.eclipse
.ui
.views
.properties
.PropertySheetPage
;
142 * The "Git Repositories View"
144 public class RepositoriesView
extends CommonNavigator
implements IShowInSource
, IShowInTargetList
{
147 public static final String REMOTE
= "remote"; //$NON-NLS-1$
150 public static final String URL
= "url"; //$NON-NLS-1$
153 public static final String PUSHURL
= "pushurl"; //$NON-NLS-1$
156 public static final String PUSH
= "push"; //$NON-NLS-1$
159 public static final String FETCH
= "fetch"; //$NON-NLS-1$
162 public static final String VIEW_ID
= "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
164 private static final long DEFAULT_REFRESH_DELAY
= 1000;
166 private final Set
<Repository
> repositories
= new HashSet
<>();
168 private final RefsChangedListener myRefsChangedListener
;
170 private final IndexChangedListener myIndexChangedListener
;
172 private final ConfigChangedListener myConfigChangeListener
;
174 private final List
<ListenerHandle
> myListeners
= new LinkedList
<>();
176 private Job scheduledJob
;
178 private RefreshUiJob refreshUiJob
;
180 private final RepositoryUtil repositoryUtil
;
182 private final RepositoryCache repositoryCache
;
184 private Composite emptyArea
;
186 private StackLayout layout
;
188 private volatile long lastInputChange
= 0L;
190 private volatile long lastInputUpdate
= -1L;
192 private boolean reactOnSelection
;
194 private final IPreferenceChangeListener configurationListener
;
196 private ISelectionListener selectionChangedListener
;
199 * The default constructor
201 public RepositoriesView() {
202 refreshUiJob
= new RefreshUiJob();
203 repositoryUtil
= Activator
.getDefault().getRepositoryUtil();
204 repositoryCache
= org
.eclipse
.egit
.core
.Activator
.getDefault()
205 .getRepositoryCache();
207 configurationListener
= new IPreferenceChangeListener() {
209 public void preferenceChange(PreferenceChangeEvent event
) {
210 if (RepositoryUtil
.PREFS_DIRECTORIES_REL
211 .equals(event
.getKey())) {
212 lastInputChange
= System
.currentTimeMillis();
213 scheduleRefresh(DEFAULT_REFRESH_DELAY
, null);
218 myRefsChangedListener
= new RefsChangedListener() {
220 public void onRefsChanged(RefsChangedEvent e
) {
221 scheduleRefresh(DEFAULT_REFRESH_DELAY
, null);
225 myIndexChangedListener
= new IndexChangedListener() {
227 public void onIndexChanged(IndexChangedEvent event
) {
228 scheduleRefresh(DEFAULT_REFRESH_DELAY
, null);
233 myConfigChangeListener
= new ConfigChangedListener() {
235 public void onConfigChanged(ConfigChangedEvent event
) {
236 scheduleRefresh(DEFAULT_REFRESH_DELAY
, null);
240 selectionChangedListener
= new ISelectionListener() {
242 public void selectionChanged(IWorkbenchPart part
,
243 ISelection selection
) {
244 if (!reactOnSelection
|| part
== RepositoriesView
.this) {
248 // this may happen if we switch between editors
249 if (part
instanceof IEditorPart
) {
250 IEditorInput input
= ((IEditorPart
) part
).getEditorInput();
251 if (input
instanceof IFileEditorInput
) {
252 reactOnSelection(new StructuredSelection(
253 ((IFileEditorInput
) input
).getFile()));
254 } else if (input
instanceof IURIEditorInput
) {
255 reactOnSelection(new StructuredSelection(input
));
259 reactOnSelection(selection
);
266 * Create area shown when no repositories are present
270 protected void createEmptyArea(Composite parent
) {
271 emptyArea
= new Composite(parent
, SWT
.NONE
);
272 emptyArea
.setBackgroundMode(SWT
.INHERIT_FORCE
);
273 MenuManager manager
= new MenuManager();
274 manager
.addMenuListener(new IMenuListener() {
276 public void menuAboutToShow(IMenuManager m
) {
277 getNavigatorActionService().fillContextMenu(m
);
280 getSite().registerContextMenu(manager
, getCommonViewer());
281 Menu menu
= manager
.createContextMenu(emptyArea
);
282 emptyArea
.setMenu(menu
);
283 GridLayoutFactory
.fillDefaults().applyTo(emptyArea
);
284 Composite infoArea
= new Composite(emptyArea
, SWT
.NONE
);
285 infoArea
.setMenu(menu
);
286 GridDataFactory
.swtDefaults().align(SWT
.CENTER
, SWT
.CENTER
)
287 .grab(true, true).applyTo(infoArea
);
288 GridLayoutFactory
.swtDefaults().applyTo(infoArea
);
289 Label messageLabel
= new Label(infoArea
, SWT
.WRAP
);
290 messageLabel
.setText(UIText
.RepositoriesView_messsageEmpty
);
291 messageLabel
.setMenu(menu
);
292 GridDataFactory
.swtDefaults().align(SWT
.FILL
, SWT
.FILL
)
293 .grab(true, false).applyTo(messageLabel
);
294 Composite optionsArea
= new Composite(infoArea
, SWT
.NONE
);
295 optionsArea
.setMenu(menu
);
296 GridLayoutFactory
.swtDefaults().numColumns(2).applyTo(optionsArea
);
297 GridDataFactory
.swtDefaults().align(SWT
.CENTER
, SWT
.CENTER
)
298 .grab(true, true).applyTo(optionsArea
);
300 final FormToolkit toolkit
= new FormToolkit(emptyArea
.getDisplay());
301 emptyArea
.addDisposeListener(new DisposeListener() {
304 public void widgetDisposed(DisposeEvent e
) {
308 final Color linkColor
= JFaceColors
.getHyperlinkText(emptyArea
311 Label addLabel
= new Label(optionsArea
, SWT
.NONE
);
312 Image addImage
= UIIcons
.NEW_REPOSITORY
.createImage();
313 UIUtils
.hookDisposal(addLabel
, addImage
);
314 addLabel
.setImage(addImage
);
315 Hyperlink addLink
= toolkit
.createHyperlink(optionsArea
,
316 UIText
.RepositoriesView_linkAdd
, SWT
.WRAP
);
317 addLink
.setForeground(linkColor
);
318 addLink
.addHyperlinkListener(new HyperlinkAdapter() {
320 public void linkActivated(HyperlinkEvent e
) {
321 IHandlerService service
= CommonUtils
.getService(getViewSite(), IHandlerService
.class);
322 UIUtils
.executeCommand(service
,
323 "org.eclipse.egit.ui.RepositoriesViewAddRepository"); //$NON-NLS-1$
326 GridDataFactory
.swtDefaults().align(SWT
.FILL
, SWT
.FILL
)
327 .grab(true, false).applyTo(addLink
);
329 Label cloneLabel
= new Label(optionsArea
, SWT
.NONE
);
330 Image cloneImage
= UIIcons
.CLONEGIT
.createImage();
331 UIUtils
.hookDisposal(cloneLabel
, cloneImage
);
332 cloneLabel
.setImage(cloneImage
);
333 Hyperlink cloneLink
= toolkit
.createHyperlink(optionsArea
,
334 UIText
.RepositoriesView_linkClone
, SWT
.WRAP
);
335 cloneLink
.setForeground(linkColor
);
336 cloneLink
.addHyperlinkListener(new HyperlinkAdapter() {
338 public void linkActivated(HyperlinkEvent e
) {
339 IHandlerService service
= CommonUtils
.getService(getViewSite(), IHandlerService
.class);
340 UIUtils
.executeCommand(service
,
341 "org.eclipse.egit.ui.RepositoriesViewClone"); //$NON-NLS-1$
344 GridDataFactory
.swtDefaults().align(SWT
.FILL
, SWT
.FILL
)
345 .grab(true, false).applyTo(cloneLink
);
347 Label createLabel
= new Label(optionsArea
, SWT
.NONE
);
348 Image createImage
= UIIcons
.CREATE_REPOSITORY
.createImage();
349 UIUtils
.hookDisposal(createLabel
, createImage
);
350 createLabel
.setImage(createImage
);
351 Hyperlink createLink
= toolkit
.createHyperlink(optionsArea
,
352 UIText
.RepositoriesView_linkCreate
, SWT
.WRAP
);
353 createLink
.setForeground(linkColor
);
354 createLink
.setText(UIText
.RepositoriesView_linkCreate
);
355 createLink
.addHyperlinkListener(new HyperlinkAdapter() {
357 public void linkActivated(HyperlinkEvent e
) {
358 IHandlerService service
= CommonUtils
.getService(getViewSite(), IHandlerService
.class);
359 UIUtils
.executeCommand(service
,
360 "org.eclipse.egit.ui.RepositoriesViewCreateRepository"); //$NON-NLS-1$
363 GridDataFactory
.swtDefaults().align(SWT
.FILL
, SWT
.FILL
)
364 .grab(true, false).applyTo(createLink
);
367 @SuppressWarnings("boxing")
369 public void createPartControl(Composite aParent
) {
370 Composite displayArea
= new Composite(aParent
, SWT
.NONE
);
371 layout
= new StackLayout();
372 displayArea
.setLayout(layout
);
373 createEmptyArea(displayArea
);
375 super.createPartControl(displayArea
);
377 IWorkbenchWindow w
= PlatformUI
.getWorkbench()
378 .getActiveWorkbenchWindow();
379 ICommandService csrv
= CommonUtils
.getService(w
, ICommandService
.class);
380 Command command
= csrv
381 .getCommand("org.eclipse.egit.ui.RepositoriesLinkWithSelection"); //$NON-NLS-1$
382 reactOnSelection
= (Boolean
) command
.getState(
383 RegistryToggleState
.STATE_ID
).getValue();
385 IWorkbenchSiteProgressService service
= CommonUtils
.getService(getSite(), IWorkbenchSiteProgressService
.class);
386 if (service
!= null) {
387 service
.showBusyForFamily(JobFamilies
.REPO_VIEW_REFRESH
);
388 service
.showBusyForFamily(JobFamilies
.CLONE
);
393 public Object
getAdapter(Class adapter
) {
394 // integrate with Properties view
395 if (adapter
== IPropertySheetPage
.class) {
396 PropertySheetPage page
= new PropertySheetPage();
398 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
402 return super.getAdapter(adapter
);
406 * Used by the "link with selection" action
408 * @param reactOnSelection
410 public void setReactOnSelection(boolean reactOnSelection
) {
411 this.reactOnSelection
= reactOnSelection
;
415 protected CommonViewer
createCommonViewer(Composite aParent
) {
416 CommonViewer viewer
= super.createCommonViewer(aParent
);
417 // handle the double-click event for tags and branches
418 viewer
.addDoubleClickListener(new IDoubleClickListener() {
420 public void doubleClick(DoubleClickEvent event
) {
421 TreeSelection sel
= (TreeSelection
) event
.getSelection();
422 RepositoryTreeNode element
= (RepositoryTreeNode
) sel
424 // Disable checkout for bare repositories
425 if (element
.getRepository().isBare())
427 if (element
instanceof RefNode
)
428 executeOpenCommandWithConfirmation(((RefNode
) element
)
429 .getObject().getName());
430 if (element
instanceof TagNode
)
431 executeOpenCommandWithConfirmation(((TagNode
) element
)
432 .getObject().getName());
435 // handle open event for the working directory
436 viewer
.addOpenListener(new IOpenListener() {
438 public void open(OpenEvent event
) {
439 TreeSelection sel
= (TreeSelection
) event
.getSelection();
440 RepositoryTreeNode element
= (RepositoryTreeNode
) sel
442 if (element
instanceof FileNode
443 || element
instanceof StashedCommitNode
)
444 executeOpenCommand();
447 // react on selection changes
448 ISelectionService srv
= CommonUtils
.getService(getSite(), ISelectionService
.class);
449 srv
.addPostSelectionListener(selectionChangedListener
);
450 // react on changes in the configured repositories
451 repositoryUtil
.getPreferences().addPreferenceChangeListener(
452 configurationListener
);
453 initRepositoriesAndListeners();
454 activateContextService();
456 emptyArea
.setBackground(viewer
.getControl().getBackground());
457 if (!repositories
.isEmpty())
458 layout
.topControl
= viewer
.getControl();
460 layout
.topControl
= emptyArea
;
465 private void executeOpenCommandWithConfirmation(String refName
) {
466 if (!BranchOperationUI
.checkoutWillShowQuestionDialog(refName
)) {
467 IPreferenceStore store
= Activator
.getDefault()
468 .getPreferenceStore();
470 if (store
.getBoolean(UIPreferences
.SHOW_CHECKOUT_CONFIRMATION
)) {
471 MessageDialogWithToggle dialog
= new MessageDialogWithToggle(
472 getViewSite().getShell(),
473 UIText
.RepositoriesView_CheckoutConfirmationTitle
, null,
474 MessageFormat
.format(
475 UIText
.RepositoriesView_CheckoutConfirmationMessage
,
476 Repository
.shortenRefName(refName
)),
477 MessageDialog
.QUESTION
,
479 UIText
.RepositoriesView_CheckoutConfirmationDefaultButtonLabel
,
480 IDialogConstants
.CANCEL_LABEL
},
482 UIText
.RepositoriesView_CheckoutConfirmationToggleMessage
,
484 // Since we use a custom button here, we may get back the first
485 // internal ID instead of Window.OK.
486 int result
= dialog
.open();
487 if (result
!= Window
.OK
488 && result
!= IDialogConstants
.INTERNAL_ID
) {
491 // And with custom buttons and internal IDs, the framework
492 // doesn't save the preference (even if we set the preference
494 if (dialog
.getToggleState()) {
495 store
.setValue(UIPreferences
.SHOW_CHECKOUT_CONFIRMATION
,
500 executeOpenCommand();
503 private void executeOpenCommand() {
504 IHandlerService srv
= CommonUtils
.getService(getViewSite(), IHandlerService
.class);
507 srv
.executeCommand("org.eclipse.egit.ui.RepositoriesViewOpen", null); //$NON-NLS-1$
508 } catch (Exception e
) {
509 Activator
.handleError(e
.getMessage(), e
, false);
513 private void activateContextService() {
514 IContextService contextService
= CommonUtils
.getService(getSite(), IContextService
.class);
515 if (contextService
!= null)
516 contextService
.activateContext(VIEW_ID
);
520 private void initRepositoriesAndListeners() {
521 synchronized (repositories
) {
522 repositories
.clear();
523 unregisterRepositoryListener();
524 // listen for repository changes
525 for (String dir
: repositoryUtil
.getConfiguredRepositories()) {
526 File repoDir
= new File(dir
);
528 Repository repo
= repositoryCache
.lookupRepository(repoDir
);
529 myListeners
.add(repo
.getListenerList()
530 .addIndexChangedListener(myIndexChangedListener
));
531 myListeners
.add(repo
.getListenerList()
532 .addRefsChangedListener(myRefsChangedListener
));
533 myListeners
.add(repo
.getListenerList()
534 .addConfigChangedListener(myConfigChangeListener
));
535 repositories
.add(repo
);
536 } catch (IOException e
) {
538 .bind(UIText
.RepositoriesView_ExceptionLookingUpRepoMessage
,
540 Activator
.handleError(message
, e
, false);
541 repositoryUtil
.removeDir(repoDir
);
548 public void dispose() {
549 // make sure to cancel the refresh job
550 if (this.scheduledJob
!= null) {
551 this.scheduledJob
.cancel();
552 this.scheduledJob
= null;
554 refreshUiJob
.cancel();
556 repositoryUtil
.getPreferences().removePreferenceChangeListener(
557 configurationListener
);
559 ISelectionService srv
= CommonUtils
.getService(getSite(), ISelectionService
.class);
560 srv
.removePostSelectionListener(selectionChangedListener
);
562 // remove RepositoryChangedListener
563 unregisterRepositoryListener();
564 repositories
.clear();
570 * @see #showPaths(List)
573 private void showResource(final IResource resource
) {
574 IPath location
= resource
.getLocation();
575 if (location
!= null)
576 showPaths(Arrays
.asList(location
));
580 * Opens the tree and marks the working directory files or folders that
581 * represent the passed paths if possible.
586 private void showPaths(final List
<IPath
> paths
) {
587 Map
<Repository
, Collection
<String
>> pathsByRepo
= ResourceUtil
588 .splitPathsByRepository(paths
);
589 boolean added
= checkNotConfiguredRepositories(pathsByRepo
);
591 scheduleRefresh(0, () -> {
592 if (UIUtils
.isUsable(getCommonViewer())) {
593 selectAndReveal(pathsByRepo
);
597 selectAndReveal(pathsByRepo
);
601 private boolean checkNotConfiguredRepositories(
602 Map
<Repository
, Collection
<String
>> pathsByRepo
) {
603 boolean added
= false;
604 for (Map
.Entry
<Repository
, Collection
<String
>> entry
: pathsByRepo
.entrySet()) {
605 Repository repository
= entry
.getKey();
607 boolean newOne
= repositoryUtil
608 .addConfiguredRepository(repository
.getDirectory());
612 } catch (IllegalArgumentException iae
) {
613 Activator
.handleError(iae
.getMessage(), iae
, false);
620 private void selectAndReveal(
621 Map
<Repository
, Collection
<String
>> pathsByRepo
) {
622 final List
<RepositoryTreeNode
> nodesToShow
= new ArrayList
<>();
623 for (Map
.Entry
<Repository
, Collection
<String
>> entry
: pathsByRepo
625 Repository repository
= entry
.getKey();
626 for (String repoPath
: entry
.getValue()) {
627 final RepositoryTreeNode node
= getNodeForPath(repository
,
630 nodesToShow
.add(node
);
634 selectReveal(new StructuredSelection(nodesToShow
));
638 * Reveals and shows the given repository in the view.
640 * @param repositoryToShow
642 public void showRepository(Repository repositoryToShow
) {
643 ITreeContentProvider cp
= (ITreeContentProvider
) getCommonViewer()
644 .getContentProvider();
645 for (Object repo
: cp
.getElements(getCommonViewer().getInput())) {
646 RepositoryTreeNode node
= (RepositoryTreeNode
) repo
;
647 if (repositoryToShow
.getDirectory().equals(node
.getRepository().getDirectory()))
648 selectReveal(new StructuredSelection(node
));
652 * Refresh Repositories View
654 public void refresh() {
655 lastInputUpdate
= -1L;
656 scheduleRefresh(0, null);
659 private void trace(String message
) {
660 GitTraceLocation
.getTrace().trace(
661 GitTraceLocation
.REPOSITORIESVIEW
.getLocation(), message
);
664 private synchronized void scheduleRefresh(long delay
, Runnable uiTask
) {
665 if (GitTraceLocation
.REPOSITORIESVIEW
.isActive()) {
666 trace("Entering scheduleRefresh()"); //$NON-NLS-1$
669 refreshUiJob
.cancel();
670 refreshUiJob
.uiTask
= uiTask
;
672 if (scheduledJob
!= null) {
673 schedule(scheduledJob
, delay
);
677 Job job
= new Job("Refreshing Git Repositories data") { //$NON-NLS-1$
680 protected IStatus
run(IProgressMonitor monitor
) {
681 final CommonViewer tv
= getCommonViewer();
682 if (!UIUtils
.isUsable(tv
)) {
683 return Status
.CANCEL_STATUS
;
685 final boolean trace
= GitTraceLocation
.REPOSITORIESVIEW
687 final boolean needsNewInput
= lastInputChange
> lastInputUpdate
;
689 trace("Running the update, new input required: " //$NON-NLS-1$
690 + (lastInputChange
> lastInputUpdate
));
692 lastInputUpdate
= System
.currentTimeMillis();
694 initRepositoriesAndListeners();
697 refreshUiJob
.needsNewInput
= needsNewInput
;
698 refreshUiJob
.schedule();
699 if (monitor
.isCanceled()) {
700 return Status
.CANCEL_STATUS
;
702 return Status
.OK_STATUS
;
706 public boolean belongsTo(Object family
) {
707 return JobFamilies
.REPO_VIEW_REFRESH
.equals(family
);
713 schedule(job
, delay
);
717 class RefreshUiJob
extends WorkbenchJob
{
718 volatile boolean needsNewInput
;
719 volatile Runnable uiTask
;
722 super(PlatformUI
.getWorkbench().getDisplay(),
723 "Refreshing Git Repositories View"); //$NON-NLS-1$
729 public boolean belongsTo(Object family
) {
730 return JobFamilies
.REPO_VIEW_REFRESH
.equals(family
);
734 public IStatus
runInUIThread(IProgressMonitor monitor
) {
735 final boolean trace
= GitTraceLocation
.REPOSITORIESVIEW
.isActive();
738 start
= System
.currentTimeMillis();
739 trace("Starting async update job"); //$NON-NLS-1$
741 CommonViewer tv
= getCommonViewer();
742 if (monitor
.isCanceled() || !UIUtils
.isUsable(tv
)) {
743 return Status
.CANCEL_STATUS
;
747 // keep expansion state and selection so that we can
748 // restore the tree after update
749 Object
[] expanded
= tv
.getExpandedElements();
750 tv
.setInput(ResourcesPlugin
.getWorkspace().getRoot());
751 tv
.setExpandedElements(expanded
);
755 if (monitor
.isCanceled()) {
756 return Status
.CANCEL_STATUS
;
759 IWorkbenchWindow ww
= PlatformUI
.getWorkbench()
760 .getActiveWorkbenchWindow();
761 IViewPart part
= ww
== null ?
null
762 : ww
.getActivePage().findView(IPageLayout
.ID_PROP_SHEET
);
763 if (part
instanceof PropertySheet
) {
764 PropertySheet sheet
= (PropertySheet
) part
;
765 IPage page
= sheet
.getCurrentPage();
766 if (page
instanceof PropertySheetPage
) {
767 ((PropertySheetPage
) page
).refresh();
770 if (monitor
.isCanceled()) {
771 return Status
.CANCEL_STATUS
;
774 if (!repositories
.isEmpty()) {
775 layout
.topControl
= getCommonViewer().getControl();
777 layout
.topControl
= emptyArea
;
779 emptyArea
.getParent().layout(true, true);
780 if (monitor
.isCanceled()) {
781 return Status
.CANCEL_STATUS
;
784 Runnable task
= uiTask
;
789 trace("Ending async update job after " //$NON-NLS-1$
790 + (System
.currentTimeMillis() - start
) + " ms"); //$NON-NLS-1$
792 return monitor
.isCanceled() ? Status
.CANCEL_STATUS
797 private void schedule(Job job
, long delay
) {
798 IWorkbenchSiteProgressService service
= CommonUtils
.getService(getSite(), IWorkbenchSiteProgressService
.class);
800 if (GitTraceLocation
.REPOSITORIESVIEW
.isActive()) {
801 GitTraceLocation
.getTrace().trace(
802 GitTraceLocation
.REPOSITORIESVIEW
.getLocation(),
803 "Scheduling refresh job"); //$NON-NLS-1$
805 service
.schedule(job
, delay
);
808 private void unregisterRepositoryListener() {
809 for (ListenerHandle lh
: myListeners
)
815 public boolean show(ShowInContext context
) {
816 ISelection selection
= context
.getSelection();
817 if ((selection
instanceof IStructuredSelection
)
818 && !selection
.isEmpty()) {
819 IStructuredSelection ss
= (IStructuredSelection
) selection
;
820 List
<IPath
> paths
= new ArrayList
<>();
821 for (Iterator it
= ss
.iterator(); it
.hasNext();) {
822 Object element
= it
.next();
823 IResource resource
= AdapterUtils
.adaptToAnyResource(element
);
824 if (resource
!= null) {
825 IPath location
= resource
.getLocation();
826 if (location
!= null)
828 } else if (element
instanceof IPath
)
829 paths
.add((IPath
) element
);
831 if (!paths
.isEmpty()) {
836 Repository repository
= SelectionUtils
.getRepository(ss
);
837 if (repository
!= null) {
838 showRepository(repository
);
842 if(context
.getInput() instanceof IFileEditorInput
) {
843 IFileEditorInput input
= (IFileEditorInput
) context
.getInput();
844 showResource(input
.getFile());
847 Repository repository
= AdapterUtils
.adapt(context
.getInput(),
849 if (repository
!= null) {
850 showRepository(repository
);
857 public ShowInContext
getShowInContext() {
858 IStructuredSelection selection
= (IStructuredSelection
) getCommonViewer()
860 List
<Object
> elements
= getShowInElements(selection
);
861 // GenericHistoryView only shows a selection of a single resource (see
862 // bug 392949), so prepare our own history page input which can contain
863 // multiple files to support showing more than one file in history.
864 // It's also necessary for a single file that is outside of the
865 // workspace (and as such is not an IResource).
866 HistoryPageInput historyPageInput
= getHistoryPageInput(selection
);
867 return new ShowInContext(historyPageInput
, new StructuredSelection(elements
));
871 public String
[] getShowInTargetIds() {
872 IStructuredSelection selection
= (IStructuredSelection
) getCommonViewer()
874 for (Object element
: selection
.toList())
875 if (element
instanceof RepositoryNode
)
876 return new String
[] { IHistoryView
.VIEW_ID
, ReflogView
.VIEW_ID
,
877 StagingView
.VIEW_ID
};
879 // Make sure History view is always listed, regardless of perspective
880 return new String
[] { IHistoryView
.VIEW_ID
};
883 private static List
<Object
> getShowInElements(IStructuredSelection selection
) {
884 List
<Object
> elements
= new ArrayList
<>();
885 for (Object element
: selection
.toList()) {
886 if (element
instanceof FileNode
|| element
instanceof FolderNode
887 || element
instanceof WorkingDirNode
) {
888 RepositoryTreeNode treeNode
= (RepositoryTreeNode
) element
;
889 IPath path
= treeNode
.getPath();
890 IResource resource
= ResourceUtil
.getResourceForLocation(path
,
892 if (resource
!= null)
893 elements
.add(resource
);
894 } else if (element
instanceof RepositoryNode
) {
895 // Can be shown in History, Reflog and Properties views
896 elements
.add(element
);
897 } else if (element
instanceof RepositoryNode
898 || element
instanceof RemoteNode
899 || element
instanceof FetchNode
900 || element
instanceof PushNode
901 || element
instanceof TagNode
902 || element
instanceof RefNode
) {
903 // These can be shown in Properties view directly
904 elements
.add(element
);
912 * @return the HistoryPageInput corresponding to the selection, or null
914 private static HistoryPageInput
getHistoryPageInput(IStructuredSelection selection
) {
915 List
<File
> files
= new ArrayList
<>();
916 Repository repo
= null;
917 for (Object element
: selection
.toList()) {
918 Repository nodeRepository
;
919 if (element
instanceof FileNode
) {
920 FileNode fileNode
= (FileNode
) element
;
921 files
.add(fileNode
.getObject());
922 nodeRepository
= fileNode
.getRepository();
923 } else if (element
instanceof FolderNode
) {
924 FolderNode folderNode
= (FolderNode
) element
;
925 files
.add(folderNode
.getObject());
926 nodeRepository
= folderNode
.getRepository();
928 // Don't return input if selection is not file/folder
932 repo
= nodeRepository
;
933 // Don't return input if nodes from different repositories are selected
934 if (repo
!= nodeRepository
)
938 return new HistoryPageInput(repo
, files
.toArray(new File
[files
.size()]));
943 private void reactOnSelection(ISelection selection
) {
944 if (selection
instanceof StructuredSelection
) {
945 StructuredSelection ssel
= (StructuredSelection
) selection
;
946 if (ssel
.size() != 1) {
949 IResource adapted
= AdapterUtils
950 .adaptToAnyResource(ssel
.getFirstElement());
951 if (adapted
!= null) {
952 showResource(adapted
);
955 File file
= AdapterUtils
.adapt(ssel
.getFirstElement(), File
.class);
957 IPath path
= new Path(file
.getAbsolutePath());
958 showPaths(Arrays
.asList(path
));
961 Repository repository
= AdapterUtils
.adapt(ssel
.getFirstElement(),
963 if (repository
!= null) {
964 showRepository(repository
);
970 private RepositoryTreeNode
getNodeForPath(Repository repository
, String repoRelativePath
) {
971 RepositoryTreeNode currentNode
= null;
972 ITreeContentProvider cp
= (ITreeContentProvider
) getCommonViewer()
973 .getContentProvider();
974 for (Object repo
: cp
.getElements(getCommonViewer().getInput())) {
975 RepositoryTreeNode node
= (RepositoryTreeNode
) repo
;
976 // TODO equals implementation of Repository?
977 if (repository
.getDirectory().equals(
978 ((Repository
) node
.getObject()).getDirectory())) {
979 for (Object child
: cp
.getChildren(node
)) {
980 RepositoryTreeNode childNode
= (RepositoryTreeNode
) child
;
981 if (childNode
.getType() == RepositoryTreeNodeType
.WORKINGDIR
) {
982 currentNode
= childNode
;
990 IPath relPath
= new Path(repoRelativePath
);
992 for (String segment
: relPath
.segments())
993 for (Object child
: cp
.getChildren(currentNode
)) {
994 @SuppressWarnings("unchecked")
995 RepositoryTreeNode
<File
> childNode
= (RepositoryTreeNode
<File
>) child
;
996 if (childNode
.getObject().getName().equals(segment
)) {
997 currentNode
= childNode
;