| /******************************************************************************* |
| * Copyright (c) 2010, 2015 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Mathias Kinzler (SAP AG) - initial implementation |
| * Dariusz Luksza <dariusz@luksza.org> - add synchronization feature |
| * Daniel Megert <daniel_megert@ch.ibm.com> - Only check out on double-click |
| * Daniel Megert <daniel_megert@ch.ibm.com> - Don't reveal selection on refresh |
| * Robin Stocker <robin@nibor.org> - Show In support |
| *******************************************************************************/ |
| package org.eclipse.egit.ui.internal.repository; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.commands.Command; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; |
| import org.eclipse.egit.core.AdapterUtils; |
| import org.eclipse.egit.core.RepositoryCache; |
| import org.eclipse.egit.core.RepositoryUtil; |
| import org.eclipse.egit.core.internal.util.ResourceUtil; |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.JobFamilies; |
| import org.eclipse.egit.ui.UIPreferences; |
| import org.eclipse.egit.ui.UIUtils; |
| import org.eclipse.egit.ui.internal.CommonUtils; |
| import org.eclipse.egit.ui.internal.UIIcons; |
| import org.eclipse.egit.ui.internal.UIText; |
| import org.eclipse.egit.ui.internal.branch.BranchOperationUI; |
| import org.eclipse.egit.ui.internal.history.HistoryPageInput; |
| import org.eclipse.egit.ui.internal.reflog.ReflogView; |
| import org.eclipse.egit.ui.internal.repository.tree.FetchNode; |
| import org.eclipse.egit.ui.internal.repository.tree.FileNode; |
| import org.eclipse.egit.ui.internal.repository.tree.FolderNode; |
| import org.eclipse.egit.ui.internal.repository.tree.PushNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RefNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RemoteNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RepositoryNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType; |
| import org.eclipse.egit.ui.internal.repository.tree.StashedCommitNode; |
| import org.eclipse.egit.ui.internal.repository.tree.TagNode; |
| import org.eclipse.egit.ui.internal.repository.tree.WorkingDirNode; |
| import org.eclipse.egit.ui.internal.selection.SelectionUtils; |
| import org.eclipse.egit.ui.internal.trace.GitTraceLocation; |
| import org.eclipse.jface.action.IMenuListener; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.dialogs.MessageDialogWithToggle; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.JFaceColors; |
| import org.eclipse.jface.viewers.DoubleClickEvent; |
| import org.eclipse.jface.viewers.IDoubleClickListener; |
| import org.eclipse.jface.viewers.IOpenListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.OpenEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TreeSelection; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.jgit.events.ConfigChangedEvent; |
| import org.eclipse.jgit.events.ConfigChangedListener; |
| import org.eclipse.jgit.events.IndexChangedEvent; |
| import org.eclipse.jgit.events.IndexChangedListener; |
| import org.eclipse.jgit.events.ListenerHandle; |
| import org.eclipse.jgit.events.RefsChangedEvent; |
| import org.eclipse.jgit.events.RefsChangedListener; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StackLayout; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.team.ui.history.IHistoryView; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IFileEditorInput; |
| import org.eclipse.ui.IPageLayout; |
| import org.eclipse.ui.ISelectionListener; |
| import org.eclipse.ui.ISelectionService; |
| import org.eclipse.ui.IViewPart; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.commands.ICommandService; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.forms.events.HyperlinkAdapter; |
| import org.eclipse.ui.forms.events.HyperlinkEvent; |
| import org.eclipse.ui.forms.widgets.FormToolkit; |
| import org.eclipse.ui.forms.widgets.Hyperlink; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.handlers.RegistryToggleState; |
| import org.eclipse.ui.navigator.CommonNavigator; |
| import org.eclipse.ui.navigator.CommonViewer; |
| import org.eclipse.ui.part.IPage; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.progress.IWorkbenchSiteProgressService; |
| import org.eclipse.ui.views.properties.IPropertySheetPage; |
| import org.eclipse.ui.views.properties.PropertySheet; |
| import org.eclipse.ui.views.properties.PropertySheetPage; |
| |
| /** |
| * The "Git Repositories View" |
| */ |
| public class RepositoriesView extends CommonNavigator implements IShowInSource, IShowInTargetList { |
| |
| /** "remote" */ |
| public static final String REMOTE = "remote"; //$NON-NLS-1$ |
| |
| /** "url" */ |
| public static final String URL = "url"; //$NON-NLS-1$ |
| |
| /** "pushurl" */ |
| public static final String PUSHURL = "pushurl"; //$NON-NLS-1$ |
| |
| /** "push" */ |
| public static final String PUSH = "push"; //$NON-NLS-1$ |
| |
| /** "fetch" */ |
| public static final String FETCH = "fetch"; //$NON-NLS-1$ |
| |
| /** view id */ |
| public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$ |
| |
| private static final long DEFAULT_REFRESH_DELAY = 1000; |
| |
| private final Set<Repository> repositories = new HashSet<Repository>(); |
| |
| private final RefsChangedListener myRefsChangedListener; |
| |
| private final IndexChangedListener myIndexChangedListener; |
| |
| private final ConfigChangedListener myConfigChangeListener; |
| |
| private final List<ListenerHandle> myListeners = new LinkedList<ListenerHandle>(); |
| |
| private Job scheduledJob; |
| |
| private final RepositoryUtil repositoryUtil; |
| |
| private final RepositoryCache repositoryCache; |
| |
| private Composite emptyArea; |
| |
| private StackLayout layout; |
| |
| private volatile long lastInputChange = 0L; |
| |
| private volatile long lastInputUpdate = -1L; |
| |
| private boolean reactOnSelection; |
| |
| private final IPreferenceChangeListener configurationListener; |
| |
| private ISelectionListener selectionChangedListener; |
| |
| /** |
| * The default constructor |
| */ |
| public RepositoriesView() { |
| repositoryUtil = Activator.getDefault().getRepositoryUtil(); |
| repositoryCache = org.eclipse.egit.core.Activator.getDefault() |
| .getRepositoryCache(); |
| |
| configurationListener = new IPreferenceChangeListener() { |
| @Override |
| public void preferenceChange(PreferenceChangeEvent event) { |
| lastInputChange = System.currentTimeMillis(); |
| scheduleRefresh(DEFAULT_REFRESH_DELAY); |
| } |
| }; |
| |
| myRefsChangedListener = new RefsChangedListener() { |
| @Override |
| public void onRefsChanged(RefsChangedEvent e) { |
| scheduleRefresh(DEFAULT_REFRESH_DELAY); |
| } |
| }; |
| |
| myIndexChangedListener = new IndexChangedListener() { |
| @Override |
| public void onIndexChanged(IndexChangedEvent event) { |
| scheduleRefresh(DEFAULT_REFRESH_DELAY); |
| |
| } |
| }; |
| |
| myConfigChangeListener = new ConfigChangedListener() { |
| @Override |
| public void onConfigChanged(ConfigChangedEvent event) { |
| scheduleRefresh(DEFAULT_REFRESH_DELAY); |
| } |
| }; |
| |
| selectionChangedListener = new ISelectionListener() { |
| @Override |
| public void selectionChanged(IWorkbenchPart part, |
| ISelection selection) { |
| if (!reactOnSelection) |
| return; |
| |
| // this may happen if we switch between editors |
| if (part instanceof IEditorPart) { |
| IEditorInput input = ((IEditorPart) part).getEditorInput(); |
| if (input instanceof IFileEditorInput) |
| reactOnSelection(new StructuredSelection( |
| ((IFileEditorInput) input).getFile())); |
| |
| } else |
| reactOnSelection(selection); |
| } |
| }; |
| } |
| |
| /** |
| * Create area shown when no repositories are present |
| * |
| * @param parent |
| */ |
| protected void createEmptyArea(Composite parent) { |
| emptyArea = new Composite(parent, SWT.NONE); |
| emptyArea.setBackgroundMode(SWT.INHERIT_FORCE); |
| MenuManager manager = new MenuManager(); |
| manager.addMenuListener(new IMenuListener() { |
| @Override |
| public void menuAboutToShow(IMenuManager m) { |
| getNavigatorActionService().fillContextMenu(m); |
| } |
| }); |
| getSite().registerContextMenu(manager, getCommonViewer()); |
| Menu menu = manager.createContextMenu(emptyArea); |
| emptyArea.setMenu(menu); |
| GridLayoutFactory.fillDefaults().applyTo(emptyArea); |
| Composite infoArea = new Composite(emptyArea, SWT.NONE); |
| infoArea.setMenu(menu); |
| GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER) |
| .grab(true, true).applyTo(infoArea); |
| GridLayoutFactory.swtDefaults().applyTo(infoArea); |
| Label messageLabel = new Label(infoArea, SWT.WRAP); |
| messageLabel.setText(UIText.RepositoriesView_messsageEmpty); |
| messageLabel.setMenu(menu); |
| GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL) |
| .grab(true, false).applyTo(messageLabel); |
| Composite optionsArea = new Composite(infoArea, SWT.NONE); |
| optionsArea.setMenu(menu); |
| GridLayoutFactory.swtDefaults().numColumns(2).applyTo(optionsArea); |
| GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER) |
| .grab(true, true).applyTo(optionsArea); |
| |
| final FormToolkit toolkit = new FormToolkit(emptyArea.getDisplay()); |
| emptyArea.addDisposeListener(new DisposeListener() { |
| |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| toolkit.dispose(); |
| } |
| }); |
| final Color linkColor = JFaceColors.getHyperlinkText(emptyArea |
| .getDisplay()); |
| |
| Label addLabel = new Label(optionsArea, SWT.NONE); |
| Image addImage = UIIcons.NEW_REPOSITORY.createImage(); |
| UIUtils.hookDisposal(addLabel, addImage); |
| addLabel.setImage(addImage); |
| Hyperlink addLink = toolkit.createHyperlink(optionsArea, |
| UIText.RepositoriesView_linkAdd, SWT.WRAP); |
| addLink.setForeground(linkColor); |
| addLink.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| IHandlerService service = CommonUtils.getService(getViewSite(), IHandlerService.class); |
| UIUtils.executeCommand(service, |
| "org.eclipse.egit.ui.RepositoriesViewAddRepository"); //$NON-NLS-1$ |
| } |
| }); |
| GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL) |
| .grab(true, false).applyTo(addLink); |
| |
| Label cloneLabel = new Label(optionsArea, SWT.NONE); |
| Image cloneImage = UIIcons.CLONEGIT.createImage(); |
| UIUtils.hookDisposal(cloneLabel, cloneImage); |
| cloneLabel.setImage(cloneImage); |
| Hyperlink cloneLink = toolkit.createHyperlink(optionsArea, |
| UIText.RepositoriesView_linkClone, SWT.WRAP); |
| cloneLink.setForeground(linkColor); |
| cloneLink.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| IHandlerService service = CommonUtils.getService(getViewSite(), IHandlerService.class); |
| UIUtils.executeCommand(service, |
| "org.eclipse.egit.ui.RepositoriesViewClone"); //$NON-NLS-1$ |
| } |
| }); |
| GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL) |
| .grab(true, false).applyTo(cloneLink); |
| |
| Label createLabel = new Label(optionsArea, SWT.NONE); |
| Image createImage = UIIcons.CREATE_REPOSITORY.createImage(); |
| UIUtils.hookDisposal(createLabel, createImage); |
| createLabel.setImage(createImage); |
| Hyperlink createLink = toolkit.createHyperlink(optionsArea, |
| UIText.RepositoriesView_linkCreate, SWT.WRAP); |
| createLink.setForeground(linkColor); |
| createLink.setText(UIText.RepositoriesView_linkCreate); |
| createLink.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| IHandlerService service = CommonUtils.getService(getViewSite(), IHandlerService.class); |
| UIUtils.executeCommand(service, |
| "org.eclipse.egit.ui.RepositoriesViewCreateRepository"); //$NON-NLS-1$ |
| } |
| }); |
| GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL) |
| .grab(true, false).applyTo(createLink); |
| } |
| |
| @SuppressWarnings("boxing") |
| @Override |
| public void createPartControl(Composite aParent) { |
| Composite displayArea = new Composite(aParent, SWT.NONE); |
| layout = new StackLayout(); |
| displayArea.setLayout(layout); |
| createEmptyArea(displayArea); |
| |
| super.createPartControl(displayArea); |
| |
| IWorkbenchWindow w = PlatformUI.getWorkbench() |
| .getActiveWorkbenchWindow(); |
| ICommandService csrv = CommonUtils.getService(w, ICommandService.class); |
| Command command = csrv |
| .getCommand("org.eclipse.egit.ui.RepositoriesLinkWithSelection"); //$NON-NLS-1$ |
| reactOnSelection = (Boolean) command.getState( |
| RegistryToggleState.STATE_ID).getValue(); |
| |
| IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class); |
| if (service != null) { |
| service.showBusyForFamily(JobFamilies.REPO_VIEW_REFRESH); |
| service.showBusyForFamily(JobFamilies.CLONE); |
| } |
| } |
| |
| @Override |
| public Object getAdapter(Class adapter) { |
| // integrate with Properties view |
| if (adapter == IPropertySheetPage.class) { |
| PropertySheetPage page = new PropertySheetPage(); |
| page |
| .setPropertySourceProvider(new RepositoryPropertySourceProvider( |
| page)); |
| return page; |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| /** |
| * Used by the "link with selection" action |
| * |
| * @param reactOnSelection |
| */ |
| public void setReactOnSelection(boolean reactOnSelection) { |
| this.reactOnSelection = reactOnSelection; |
| } |
| |
| @Override |
| protected CommonViewer createCommonViewer(Composite aParent) { |
| CommonViewer viewer = super.createCommonViewer(aParent); |
| // handle the double-click event for tags and branches |
| viewer.addDoubleClickListener(new IDoubleClickListener() { |
| @Override |
| public void doubleClick(DoubleClickEvent event) { |
| TreeSelection sel = (TreeSelection) event.getSelection(); |
| RepositoryTreeNode element = (RepositoryTreeNode) sel |
| .getFirstElement(); |
| // Disable checkout for bare repositories |
| if (element.getRepository().isBare()) |
| return; |
| if (element instanceof RefNode) |
| executeOpenCommandWithConfirmation(((RefNode) element) |
| .getObject().getName()); |
| if (element instanceof TagNode) |
| executeOpenCommandWithConfirmation(((TagNode) element) |
| .getObject().getName()); |
| } |
| }); |
| // handle open event for the working directory |
| viewer.addOpenListener(new IOpenListener() { |
| @Override |
| public void open(OpenEvent event) { |
| TreeSelection sel = (TreeSelection) event.getSelection(); |
| RepositoryTreeNode element = (RepositoryTreeNode) sel |
| .getFirstElement(); |
| if (element instanceof FileNode |
| || element instanceof StashedCommitNode) |
| executeOpenCommand(); |
| } |
| }); |
| // react on selection changes |
| ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class); |
| srv.addPostSelectionListener(selectionChangedListener); |
| // react on changes in the configured repositories |
| repositoryUtil.getPreferences().addPreferenceChangeListener( |
| configurationListener); |
| initRepositoriesAndListeners(); |
| activateContextService(); |
| |
| emptyArea.setBackground(viewer.getControl().getBackground()); |
| if (!repositories.isEmpty()) |
| layout.topControl = viewer.getControl(); |
| else |
| layout.topControl = emptyArea; |
| |
| return viewer; |
| } |
| |
| private void executeOpenCommandWithConfirmation(String refName) { |
| if (!BranchOperationUI.checkoutWillShowQuestionDialog(refName)) { |
| String shortName = Repository.shortenRefName(refName); |
| |
| IPreferenceStore store = Activator.getDefault() |
| .getPreferenceStore(); |
| |
| if (store.getBoolean(UIPreferences.SHOW_CHECKOUT_CONFIRMATION)) { |
| String toggleMessage = UIText.RepositoriesView_CheckoutConfirmationToggleMessage; |
| MessageDialogWithToggle dlg = MessageDialogWithToggle |
| .openOkCancelConfirm( |
| getViewSite().getShell(), |
| UIText.RepositoriesView_CheckoutConfirmationTitle, |
| MessageFormat.format(UIText.RepositoriesView_CheckoutConfirmationMessage, |
| shortName), |
| toggleMessage, false, store, |
| UIPreferences.SHOW_CHECKOUT_CONFIRMATION); |
| if (dlg.getReturnCode() != Window.OK) |
| return; |
| } |
| } |
| executeOpenCommand(); |
| } |
| |
| private void executeOpenCommand() { |
| IHandlerService srv = CommonUtils.getService(getViewSite(), IHandlerService.class); |
| |
| try { |
| srv.executeCommand("org.eclipse.egit.ui.RepositoriesViewOpen", null); //$NON-NLS-1$ |
| } catch (Exception e) { |
| Activator.handleError(e.getMessage(), e, false); |
| } |
| } |
| |
| private void activateContextService() { |
| IContextService contextService = CommonUtils.getService(getSite(), IContextService.class); |
| if (contextService != null) |
| contextService.activateContext(VIEW_ID); |
| |
| } |
| |
| private void initRepositoriesAndListeners() { |
| synchronized (repositories) { |
| repositories.clear(); |
| unregisterRepositoryListener(); |
| // listen for repository changes |
| for (String dir : repositoryUtil.getConfiguredRepositories()) { |
| File repoDir = new File(dir); |
| try { |
| Repository repo = repositoryCache.lookupRepository(repoDir); |
| myListeners.add(repo.getListenerList() |
| .addIndexChangedListener(myIndexChangedListener)); |
| myListeners.add(repo.getListenerList() |
| .addRefsChangedListener(myRefsChangedListener)); |
| myListeners.add(repo.getListenerList() |
| .addConfigChangedListener(myConfigChangeListener)); |
| repositories.add(repo); |
| } catch (IOException e) { |
| String message = NLS |
| .bind(UIText.RepositoriesView_ExceptionLookingUpRepoMessage, |
| repoDir.getPath()); |
| Activator.handleError(message, e, false); |
| repositoryUtil.removeDir(repoDir); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| // make sure to cancel the refresh job |
| if (this.scheduledJob != null) { |
| this.scheduledJob.cancel(); |
| this.scheduledJob = null; |
| } |
| |
| repositoryUtil.getPreferences().removePreferenceChangeListener( |
| configurationListener); |
| |
| ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class); |
| srv.removePostSelectionListener(selectionChangedListener); |
| |
| // remove RepositoryChangedListener |
| unregisterRepositoryListener(); |
| repositories.clear(); |
| |
| super.dispose(); |
| } |
| |
| /** |
| * @see #showPaths(List) |
| * @param resource |
| */ |
| private void showResource(final IResource resource) { |
| IPath location = resource.getLocation(); |
| if (location != null) |
| showPaths(Arrays.asList(location)); |
| } |
| |
| /** |
| * Opens the tree and marks the working directory files or folders that |
| * represent the passed paths if possible. |
| * |
| * @param paths |
| * the paths to show |
| */ |
| private void showPaths(final List<IPath> paths) { |
| final List<RepositoryTreeNode> nodesToShow = new ArrayList<RepositoryTreeNode>(); |
| |
| Map<Repository, Collection<String>> pathsByRepo = ResourceUtil.splitPathsByRepository(paths); |
| for (Map.Entry<Repository, Collection<String>> entry : pathsByRepo.entrySet()) { |
| Repository repository = entry.getKey(); |
| try { |
| boolean added = repositoryUtil.addConfiguredRepository(repository.getDirectory()); |
| if (added) |
| scheduleRefresh(0); |
| } catch (IllegalArgumentException iae) { |
| Activator.handleError(iae.getMessage(), iae, false); |
| continue; |
| } |
| |
| if (this.scheduledJob != null) |
| try { |
| this.scheduledJob.join(); |
| } catch (InterruptedException e) { |
| Activator.handleError(e.getMessage(), e, false); |
| } |
| |
| for (String repoPath : entry.getValue()) { |
| final RepositoryTreeNode node = getNodeForPath(repository, repoPath); |
| if (node != null) |
| nodesToShow.add(node); |
| } |
| } |
| |
| Display.getDefault().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| selectReveal(new StructuredSelection(nodesToShow)); |
| } |
| }); |
| } |
| |
| /** |
| * Reveals and shows the given repository in the view. |
| * |
| * @param repositoryToShow |
| */ |
| public void showRepository(Repository repositoryToShow) { |
| ITreeContentProvider cp = (ITreeContentProvider) getCommonViewer() |
| .getContentProvider(); |
| for (Object repo : cp.getElements(getCommonViewer().getInput())) { |
| RepositoryTreeNode node = (RepositoryTreeNode) repo; |
| if (repositoryToShow.getDirectory().equals(node.getRepository().getDirectory())) |
| selectReveal(new StructuredSelection(node)); |
| } |
| } |
| /** |
| * Refresh Repositories View |
| */ |
| public void refresh() { |
| lastInputUpdate = -1L; |
| scheduleRefresh(0); |
| } |
| |
| private void trace(String message) { |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.REPOSITORIESVIEW.getLocation(), message); |
| } |
| |
| private Job scheduleRefresh(long delay) { |
| if (GitTraceLocation.REPOSITORIESVIEW.isActive()) { |
| trace("Entering scheduleRefresh()"); //$NON-NLS-1$ |
| } |
| |
| if (scheduledJob != null) { |
| schedule(scheduledJob, delay); |
| return scheduledJob; |
| } |
| |
| Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$ |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| final CommonViewer tv = getCommonViewer(); |
| if (!UIUtils.isUsable(tv)) { |
| return Status.CANCEL_STATUS; |
| } |
| final boolean trace = GitTraceLocation.REPOSITORIESVIEW |
| .isActive(); |
| final boolean needsNewInput = lastInputChange > lastInputUpdate; |
| if (trace) { |
| trace("Running the update, new input required: " //$NON-NLS-1$ |
| + (lastInputChange > lastInputUpdate)); |
| } |
| lastInputUpdate = System.currentTimeMillis(); |
| if (needsNewInput) { |
| initRepositoriesAndListeners(); |
| } |
| |
| PlatformUI.getWorkbench().getDisplay() |
| .syncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (!UIUtils.isUsable(tv)) { |
| return; |
| } |
| long start = 0; |
| if (trace) { |
| start = System.currentTimeMillis(); |
| trace("Starting async update job"); //$NON-NLS-1$ |
| } |
| |
| if (needsNewInput) { |
| // keep expansion state and selection so that we can |
| // restore the tree |
| // after update |
| Object[] expanded = tv.getExpandedElements(); |
| tv.setInput(ResourcesPlugin.getWorkspace() |
| .getRoot()); |
| tv.setExpandedElements(expanded); |
| } else { |
| tv.refresh(true); |
| } |
| |
| IViewPart part = PlatformUI.getWorkbench() |
| .getActiveWorkbenchWindow().getActivePage() |
| .findView(IPageLayout.ID_PROP_SHEET); |
| if (part instanceof PropertySheet) { |
| PropertySheet sheet = (PropertySheet) part; |
| IPage page = sheet.getCurrentPage(); |
| if (page instanceof PropertySheetPage) { |
| ((PropertySheetPage) page).refresh(); |
| } |
| } |
| if (trace) { |
| trace("Ending async update job after " //$NON-NLS-1$ |
| + (System.currentTimeMillis() - start) |
| + " ms"); //$NON-NLS-1$ |
| } |
| if (!repositories.isEmpty()) { |
| layout.topControl = getCommonViewer().getControl(); |
| } else { |
| layout.topControl = emptyArea; |
| } |
| emptyArea.getParent().layout(true, true); |
| } |
| }); |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return JobFamilies.REPO_VIEW_REFRESH.equals(family); |
| } |
| |
| }; |
| job.setSystem(true); |
| |
| schedule(job, delay); |
| |
| scheduledJob = job; |
| return scheduledJob; |
| } |
| |
| private void schedule(Job job, long delay) { |
| IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class); |
| |
| if (GitTraceLocation.REPOSITORIESVIEW.isActive()) { |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.REPOSITORIESVIEW.getLocation(), |
| "Scheduling refresh job"); //$NON-NLS-1$ |
| } |
| service.schedule(job, delay); |
| } |
| |
| private void unregisterRepositoryListener() { |
| for (ListenerHandle lh : myListeners) |
| lh.remove(); |
| myListeners.clear(); |
| } |
| |
| @Override |
| public boolean show(ShowInContext context) { |
| ISelection selection = context.getSelection(); |
| if (selection instanceof IStructuredSelection) { |
| IStructuredSelection ss = (IStructuredSelection) selection; |
| List<IPath> paths = new ArrayList<IPath>(); |
| for (Iterator it = ss.iterator(); it.hasNext();) { |
| Object element = it.next(); |
| IResource resource = AdapterUtils.adapt(element, IResource.class); |
| if (resource != null) { |
| IPath location = resource.getLocation(); |
| if (location != null) |
| paths.add(location); |
| } else if (element instanceof IPath) |
| paths.add((IPath) element); |
| } |
| if (!paths.isEmpty()) { |
| showPaths(paths); |
| return true; |
| } |
| |
| Repository repository = SelectionUtils.getRepository(ss); |
| if (repository != null) { |
| showRepository(repository); |
| return true; |
| } |
| } |
| if(context.getInput() instanceof IFileEditorInput) { |
| IFileEditorInput input = (IFileEditorInput) context.getInput(); |
| showResource(input.getFile()); |
| } |
| return false; |
| } |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| IStructuredSelection selection = (IStructuredSelection) getCommonViewer() |
| .getSelection(); |
| List<Object> elements = getShowInElements(selection); |
| // GenericHistoryView only shows a selection of a single resource (see |
| // bug 392949), so prepare our own history page input which can contain |
| // multiple files to support showing more than one file in history. |
| // It's also necessary for a single file that is outside of the |
| // workspace (and as such is not an IResource). |
| HistoryPageInput historyPageInput = getHistoryPageInput(selection); |
| return new ShowInContext(historyPageInput, new StructuredSelection(elements)); |
| } |
| |
| @Override |
| public String[] getShowInTargetIds() { |
| IStructuredSelection selection = (IStructuredSelection) getCommonViewer() |
| .getSelection(); |
| for (Object element : selection.toList()) |
| if (element instanceof RepositoryNode) |
| return new String[] { IHistoryView.VIEW_ID, ReflogView.VIEW_ID }; |
| |
| // Make sure History view is always listed, regardless of perspective |
| return new String[] { IHistoryView.VIEW_ID }; |
| } |
| |
| private static List<Object> getShowInElements(IStructuredSelection selection) { |
| List<Object> elements = new ArrayList<Object>(); |
| for (Object element : selection.toList()) { |
| if (element instanceof FileNode || element instanceof FolderNode |
| || element instanceof WorkingDirNode) { |
| RepositoryTreeNode treeNode = (RepositoryTreeNode) element; |
| IPath path = treeNode.getPath(); |
| IResource resource = ResourceUtil.getResourceForLocation(path); |
| if (resource != null) |
| elements.add(resource); |
| } else if (element instanceof RepositoryNode) { |
| // Can be shown in History, Reflog and Properties views |
| elements.add(element); |
| } else if (element instanceof RepositoryNode |
| || element instanceof RemoteNode |
| || element instanceof FetchNode |
| || element instanceof PushNode |
| || element instanceof TagNode |
| || element instanceof RefNode) { |
| // These can be shown in Properties view directly |
| elements.add(element); |
| } |
| } |
| return elements; |
| } |
| |
| /** |
| * @param selection |
| * @return the HistoryPageInput corresponding to the selection, or null |
| */ |
| private static HistoryPageInput getHistoryPageInput(IStructuredSelection selection) { |
| List<File> files = new ArrayList<File>(); |
| Repository repo = null; |
| for (Object element : selection.toList()) { |
| Repository nodeRepository; |
| if (element instanceof FileNode) { |
| FileNode fileNode = (FileNode) element; |
| files.add(fileNode.getObject()); |
| nodeRepository = fileNode.getRepository(); |
| } else if (element instanceof FolderNode) { |
| FolderNode folderNode = (FolderNode) element; |
| files.add(folderNode.getObject()); |
| nodeRepository = folderNode.getRepository(); |
| } else { |
| // Don't return input if selection is not file/folder |
| return null; |
| } |
| if (repo == null) |
| repo = nodeRepository; |
| // Don't return input if nodes from different repositories are selected |
| if (repo != nodeRepository) |
| return null; |
| } |
| if (repo != null) |
| return new HistoryPageInput(repo, files.toArray(new File[files.size()])); |
| else |
| return null; |
| } |
| |
| private void reactOnSelection(ISelection selection) { |
| if (selection instanceof StructuredSelection) { |
| StructuredSelection ssel = (StructuredSelection) selection; |
| if (ssel.size() != 1) |
| return; |
| if (ssel.getFirstElement() instanceof IResource) |
| showResource((IResource) ssel.getFirstElement()); |
| if (ssel.getFirstElement() instanceof IAdaptable) { |
| IResource adapted = CommonUtils.getAdapter(((IAdaptable) ssel |
| .getFirstElement()), IResource.class); |
| if (adapted != null) |
| showResource(adapted); |
| } |
| } |
| } |
| |
| private RepositoryTreeNode getNodeForPath(Repository repository, String repoRelativePath) { |
| RepositoryTreeNode currentNode = null; |
| ITreeContentProvider cp = (ITreeContentProvider) getCommonViewer() |
| .getContentProvider(); |
| for (Object repo : cp.getElements(getCommonViewer().getInput())) { |
| RepositoryTreeNode node = (RepositoryTreeNode) repo; |
| // TODO equals implementation of Repository? |
| if (repository.getDirectory().equals( |
| ((Repository) node.getObject()).getDirectory())) { |
| for (Object child : cp.getChildren(node)) { |
| RepositoryTreeNode childNode = (RepositoryTreeNode) child; |
| if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) { |
| currentNode = childNode; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| IPath relPath = new Path(repoRelativePath); |
| |
| for (String segment : relPath.segments()) |
| for (Object child : cp.getChildren(currentNode)) { |
| @SuppressWarnings("unchecked") |
| RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child; |
| if (childNode.getObject().getName().equals(segment)) { |
| currentNode = childNode; |
| break; |
| } |
| } |
| |
| return currentNode; |
| } |
| } |