Toolbar button to switch repositories in git views
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blobab88dd3356702b480e5eb887707b10692e655223
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
8 * Contributors:
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;
18 import java.io.File;
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;
28 import java.util.Map;
29 import java.util.Set;
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 {
146 /** "remote" */
147 public static final String REMOTE = "remote"; //$NON-NLS-1$
149 /** "url" */
150 public static final String URL = "url"; //$NON-NLS-1$
152 /** "pushurl" */
153 public static final String PUSHURL = "pushurl"; //$NON-NLS-1$
155 /** "push" */
156 public static final String PUSH = "push"; //$NON-NLS-1$
158 /** "fetch" */
159 public static final String FETCH = "fetch"; //$NON-NLS-1$
161 /** view id */
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() {
208 @Override
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() {
219 @Override
220 public void onRefsChanged(RefsChangedEvent e) {
221 scheduleRefresh(DEFAULT_REFRESH_DELAY, null);
225 myIndexChangedListener = new IndexChangedListener() {
226 @Override
227 public void onIndexChanged(IndexChangedEvent event) {
228 scheduleRefresh(DEFAULT_REFRESH_DELAY, null);
233 myConfigChangeListener = new ConfigChangedListener() {
234 @Override
235 public void onConfigChanged(ConfigChangedEvent event) {
236 scheduleRefresh(DEFAULT_REFRESH_DELAY, null);
240 selectionChangedListener = new ISelectionListener() {
241 @Override
242 public void selectionChanged(IWorkbenchPart part,
243 ISelection selection) {
244 if (!reactOnSelection || part == RepositoriesView.this) {
245 return;
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));
258 } else {
259 reactOnSelection(selection);
266 * Create area shown when no repositories are present
268 * @param parent
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() {
275 @Override
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() {
303 @Override
304 public void widgetDisposed(DisposeEvent e) {
305 toolkit.dispose();
308 final Color linkColor = JFaceColors.getHyperlinkText(emptyArea
309 .getDisplay());
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() {
319 @Override
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() {
337 @Override
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() {
356 @Override
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")
368 @Override
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);
392 @Override
393 public Object getAdapter(Class adapter) {
394 // integrate with Properties view
395 if (adapter == IPropertySheetPage.class) {
396 PropertySheetPage page = new PropertySheetPage();
397 page
398 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
399 page));
400 return page;
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;
414 @Override
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() {
419 @Override
420 public void doubleClick(DoubleClickEvent event) {
421 TreeSelection sel = (TreeSelection) event.getSelection();
422 RepositoryTreeNode element = (RepositoryTreeNode) sel
423 .getFirstElement();
424 // Disable checkout for bare repositories
425 if (element.getRepository().isBare())
426 return;
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() {
437 @Override
438 public void open(OpenEvent event) {
439 TreeSelection sel = (TreeSelection) event.getSelection();
440 RepositoryTreeNode element = (RepositoryTreeNode) sel
441 .getFirstElement();
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();
459 else
460 layout.topControl = emptyArea;
462 return viewer;
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,
478 new String[] {
479 UIText.RepositoriesView_CheckoutConfirmationDefaultButtonLabel,
480 IDialogConstants.CANCEL_LABEL },
482 UIText.RepositoriesView_CheckoutConfirmationToggleMessage,
483 false);
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) {
489 return;
491 // And with custom buttons and internal IDs, the framework
492 // doesn't save the preference (even if we set the preference
493 // store and key).
494 if (dialog.getToggleState()) {
495 store.setValue(UIPreferences.SHOW_CHECKOUT_CONFIRMATION,
496 false);
500 executeOpenCommand();
503 private void executeOpenCommand() {
504 IHandlerService srv = CommonUtils.getService(getViewSite(), IHandlerService.class);
506 try {
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);
527 try {
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) {
537 String message = NLS
538 .bind(UIText.RepositoriesView_ExceptionLookingUpRepoMessage,
539 repoDir.getPath());
540 Activator.handleError(message, e, false);
541 repositoryUtil.removeDir(repoDir);
547 @Override
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();
566 super.dispose();
570 * @see #showPaths(List)
571 * @param resource
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.
583 * @param paths
584 * the paths to show
586 private void showPaths(final List<IPath> paths) {
587 Map<Repository, Collection<String>> pathsByRepo = ResourceUtil
588 .splitPathsByRepository(paths);
589 boolean added = checkNotConfiguredRepositories(pathsByRepo);
590 if (added) {
591 scheduleRefresh(0, () -> {
592 if (UIUtils.isUsable(getCommonViewer())) {
593 selectAndReveal(pathsByRepo);
596 } else {
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();
606 try {
607 boolean newOne = repositoryUtil
608 .addConfiguredRepository(repository.getDirectory());
609 if (newOne) {
610 added = true;
612 } catch (IllegalArgumentException iae) {
613 Activator.handleError(iae.getMessage(), iae, false);
614 continue;
617 return added;
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
624 .entrySet()) {
625 Repository repository = entry.getKey();
626 for (String repoPath : entry.getValue()) {
627 final RepositoryTreeNode node = getNodeForPath(repository,
628 repoPath);
629 if (node != null) {
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);
674 return;
677 Job job = new Job("Refreshing Git Repositories data") { //$NON-NLS-1$
679 @Override
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
686 .isActive();
687 final boolean needsNewInput = lastInputChange > lastInputUpdate;
688 if (trace) {
689 trace("Running the update, new input required: " //$NON-NLS-1$
690 + (lastInputChange > lastInputUpdate));
692 lastInputUpdate = System.currentTimeMillis();
693 if (needsNewInput) {
694 initRepositoriesAndListeners();
697 refreshUiJob.needsNewInput = needsNewInput;
698 refreshUiJob.schedule();
699 if (monitor.isCanceled()) {
700 return Status.CANCEL_STATUS;
702 return Status.OK_STATUS;
705 @Override
706 public boolean belongsTo(Object family) {
707 return JobFamilies.REPO_VIEW_REFRESH.equals(family);
711 job.setSystem(true);
712 job.setUser(false);
713 schedule(job, delay);
714 scheduledJob = job;
717 class RefreshUiJob extends WorkbenchJob {
718 volatile boolean needsNewInput;
719 volatile Runnable uiTask;
721 RefreshUiJob() {
722 super(PlatformUI.getWorkbench().getDisplay(),
723 "Refreshing Git Repositories View"); //$NON-NLS-1$
724 setSystem(true);
725 setUser(false);
728 @Override
729 public boolean belongsTo(Object family) {
730 return JobFamilies.REPO_VIEW_REFRESH.equals(family);
733 @Override
734 public IStatus runInUIThread(IProgressMonitor monitor) {
735 final boolean trace = GitTraceLocation.REPOSITORIESVIEW.isActive();
736 long start = 0;
737 if (trace) {
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;
746 if (needsNewInput) {
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);
752 } else {
753 tv.refresh(true);
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();
776 } else {
777 layout.topControl = emptyArea;
779 emptyArea.getParent().layout(true, true);
780 if (monitor.isCanceled()) {
781 return Status.CANCEL_STATUS;
784 Runnable task = uiTask;
785 if (task != null) {
786 task.run();
788 if (trace) {
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
793 : Status.OK_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)
810 lh.remove();
811 myListeners.clear();
814 @Override
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)
827 paths.add(location);
828 } else if (element instanceof IPath)
829 paths.add((IPath) element);
831 if (!paths.isEmpty()) {
832 showPaths(paths);
833 return true;
836 Repository repository = SelectionUtils.getRepository(ss);
837 if (repository != null) {
838 showRepository(repository);
839 return true;
842 if(context.getInput() instanceof IFileEditorInput) {
843 IFileEditorInput input = (IFileEditorInput) context.getInput();
844 showResource(input.getFile());
845 return true;
847 Repository repository = AdapterUtils.adapt(context.getInput(),
848 Repository.class);
849 if (repository != null) {
850 showRepository(repository);
851 return true;
853 return false;
856 @Override
857 public ShowInContext getShowInContext() {
858 IStructuredSelection selection = (IStructuredSelection) getCommonViewer()
859 .getSelection();
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));
870 @Override
871 public String[] getShowInTargetIds() {
872 IStructuredSelection selection = (IStructuredSelection) getCommonViewer()
873 .getSelection();
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,
891 false);
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);
907 return elements;
911 * @param selection
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();
927 } else {
928 // Don't return input if selection is not file/folder
929 return null;
931 if (repo == null)
932 repo = nodeRepository;
933 // Don't return input if nodes from different repositories are selected
934 if (repo != nodeRepository)
935 return null;
937 if (repo != null)
938 return new HistoryPageInput(repo, files.toArray(new File[files.size()]));
939 else
940 return null;
943 private void reactOnSelection(ISelection selection) {
944 if (selection instanceof StructuredSelection) {
945 StructuredSelection ssel = (StructuredSelection) selection;
946 if (ssel.size() != 1) {
947 return;
949 IResource adapted = AdapterUtils
950 .adaptToAnyResource(ssel.getFirstElement());
951 if (adapted != null) {
952 showResource(adapted);
953 return;
955 File file = AdapterUtils.adapt(ssel.getFirstElement(), File.class);
956 if (file != null) {
957 IPath path = new Path(file.getAbsolutePath());
958 showPaths(Arrays.asList(path));
959 return;
961 Repository repository = AdapterUtils.adapt(ssel.getFirstElement(),
962 Repository.class);
963 if (repository != null) {
964 showRepository(repository);
965 return;
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;
983 break;
986 break;
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;
998 break;
1002 return currentNode;