Exception handling: make sure to retain stack trace
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob6feee36f784e28870985e769d223ec84bbb70fff
1 /*******************************************************************************
2 * Copyright (c) 2010 SAP AG.
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 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.repository;
13 import java.io.File;
14 import java.io.IOException;
15 import java.net.URISyntaxException;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.StringTokenizer;
23 import java.util.TreeSet;
25 import org.eclipse.core.filesystem.EFS;
26 import org.eclipse.core.filesystem.IFileStore;
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.resources.IProjectDescription;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.resources.IWorkspace;
31 import org.eclipse.core.resources.IWorkspaceRunnable;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IAdaptable;
35 import org.eclipse.core.runtime.IPath;
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.core.runtime.IStatus;
38 import org.eclipse.core.runtime.NullProgressMonitor;
39 import org.eclipse.core.runtime.Path;
40 import org.eclipse.core.runtime.Status;
41 import org.eclipse.core.runtime.SubProgressMonitor;
42 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
43 import org.eclipse.core.runtime.jobs.Job;
44 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
45 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
46 import org.eclipse.core.runtime.preferences.InstanceScope;
47 import org.eclipse.egit.core.op.BranchOperation;
48 import org.eclipse.egit.core.op.ConnectProviderOperation;
49 import org.eclipse.egit.core.project.RepositoryMapping;
50 import org.eclipse.egit.ui.Activator;
51 import org.eclipse.egit.ui.UIIcons;
52 import org.eclipse.egit.ui.UIText;
53 import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
54 import org.eclipse.egit.ui.internal.clone.GitImportProjectsWizard;
55 import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode.RepositoryTreeNodeType;
56 import org.eclipse.jface.action.Action;
57 import org.eclipse.jface.action.IAction;
58 import org.eclipse.jface.dialogs.MessageDialog;
59 import org.eclipse.jface.viewers.IOpenListener;
60 import org.eclipse.jface.viewers.ISelection;
61 import org.eclipse.jface.viewers.ISelectionChangedListener;
62 import org.eclipse.jface.viewers.ISelectionProvider;
63 import org.eclipse.jface.viewers.IStructuredSelection;
64 import org.eclipse.jface.viewers.ITreeContentProvider;
65 import org.eclipse.jface.viewers.OpenEvent;
66 import org.eclipse.jface.viewers.SelectionChangedEvent;
67 import org.eclipse.jface.viewers.StructuredSelection;
68 import org.eclipse.jface.viewers.TreeViewer;
69 import org.eclipse.jface.window.Window;
70 import org.eclipse.jface.wizard.Wizard;
71 import org.eclipse.jface.wizard.WizardDialog;
72 import org.eclipse.jgit.lib.Ref;
73 import org.eclipse.jgit.lib.Repository;
74 import org.eclipse.jgit.lib.RepositoryConfig;
75 import org.eclipse.jgit.transport.RemoteConfig;
76 import org.eclipse.osgi.util.NLS;
77 import org.eclipse.swt.SWT;
78 import org.eclipse.swt.events.MenuDetectEvent;
79 import org.eclipse.swt.events.MenuDetectListener;
80 import org.eclipse.swt.events.SelectionAdapter;
81 import org.eclipse.swt.events.SelectionEvent;
82 import org.eclipse.swt.graphics.Point;
83 import org.eclipse.swt.widgets.Composite;
84 import org.eclipse.swt.widgets.Display;
85 import org.eclipse.swt.widgets.Menu;
86 import org.eclipse.swt.widgets.MenuItem;
87 import org.eclipse.swt.widgets.TreeItem;
88 import org.eclipse.ui.IEditorInput;
89 import org.eclipse.ui.IEditorPart;
90 import org.eclipse.ui.IFileEditorInput;
91 import org.eclipse.ui.IPageLayout;
92 import org.eclipse.ui.ISelectionListener;
93 import org.eclipse.ui.ISelectionService;
94 import org.eclipse.ui.IViewPart;
95 import org.eclipse.ui.IWorkbenchPart;
96 import org.eclipse.ui.PartInitException;
97 import org.eclipse.ui.PlatformUI;
98 import org.eclipse.ui.editors.text.EditorsUI;
99 import org.eclipse.ui.ide.FileStoreEditorInput;
100 import org.eclipse.ui.ide.IDE;
101 import org.eclipse.ui.part.ViewPart;
102 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
103 import org.eclipse.ui.views.properties.IPropertySheetPage;
104 import org.eclipse.ui.views.properties.PropertySheet;
105 import org.eclipse.ui.views.properties.PropertySheetPage;
106 import org.osgi.service.prefs.BackingStoreException;
110 * The Git Repositories view.
111 * <p>
112 * This keeps track of a bunch of local directory names each of which represent
113 * a Git Repository. This list is stored in some Preferences object and used to
114 * build the tree in the view.
115 * <p>
116 * Implements {@link ISelectionProvider} in order to integrate with the
117 * Properties view.
118 * <p>
119 * TODO
120 * <li>Clarification whether to show projects, perhaps configurable switch</li>
123 public class RepositoriesView extends ViewPart implements ISelectionProvider {
125 /** The view ID */
126 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
128 // TODO central constants? RemoteConfig ones are private
129 static final String REMOTE = "remote"; //$NON-NLS-1$
131 static final String URL = "url"; //$NON-NLS-1$
133 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
135 static final String FETCH = "fetch"; //$NON-NLS-1$
137 static final String PUSH = "push"; //$NON-NLS-1$
139 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
141 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
143 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
145 private ISelection currentSelection = new StructuredSelection();
147 private Job scheduledJob;
149 private TreeViewer tv;
151 private IAction importAction;
153 private IAction addAction;
155 private IAction refreshAction;
157 private IAction linkWithSelectionAction;
159 private static List<String> getDirs() {
160 List<String> resultStrings = new ArrayList<String>();
161 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
162 if (dirs != null && dirs.length() > 0) {
163 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
164 while (tok.hasMoreTokens()) {
165 String dirName = tok.nextToken();
166 File testFile = new File(dirName);
167 if (testFile.exists()) {
168 resultStrings.add(dirName);
172 Collections.sort(resultStrings);
173 return resultStrings;
176 private static void removeDir(File file) {
178 String dir;
179 try {
180 dir = file.getCanonicalPath();
181 } catch (IOException e1) {
182 dir = file.getAbsolutePath();
185 IEclipsePreferences prefs = getPrefs();
187 TreeSet<String> resultStrings = new TreeSet<String>();
188 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
189 if (dirs != null && dirs.length() > 0) {
190 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
191 while (tok.hasMoreTokens()) {
192 String dirName = tok.nextToken();
193 File testFile = new File(dirName);
194 if (testFile.exists()) {
195 try {
196 resultStrings.add(testFile.getCanonicalPath());
197 } catch (IOException e) {
198 resultStrings.add(testFile.getAbsolutePath());
204 if (resultStrings.remove(dir)) {
205 StringBuilder sb = new StringBuilder();
206 for (String gitDirString : resultStrings) {
207 sb.append(gitDirString);
208 sb.append(File.pathSeparatorChar);
211 prefs.put(PREFS_DIRECTORIES, sb.toString());
212 try {
213 prefs.flush();
214 } catch (BackingStoreException e) {
215 IStatus error = new Status(IStatus.ERROR, Activator
216 .getPluginId(), e.getMessage(), e);
217 Activator.getDefault().getLog().log(error);
223 @Override
224 public Object getAdapter(Class adapter) {
225 // integrate with Properties view
226 if (adapter == IPropertySheetPage.class) {
227 PropertySheetPage page = new PropertySheetPage();
228 page
229 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
230 page));
231 return page;
234 return super.getAdapter(adapter);
237 @Override
238 public void createPartControl(Composite parent) {
239 tv = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
240 tv.setContentProvider(new RepositoriesViewContentProvider());
241 // the label provider registers itself
242 new RepositoriesViewLabelProvider(tv);
244 getSite().setSelectionProvider(this);
246 tv.addSelectionChangedListener(new ISelectionChangedListener() {
248 public void selectionChanged(SelectionChangedEvent event) {
250 IStructuredSelection ssel = (IStructuredSelection) event
251 .getSelection();
252 if (ssel.size() == 1) {
253 setSelection(new StructuredSelection(ssel.getFirstElement()));
254 } else {
255 setSelection(new StructuredSelection());
260 tv.addOpenListener(new IOpenListener() {
261 public void open(OpenEvent event) {
262 IStructuredSelection selection = (IStructuredSelection) event
263 .getSelection();
264 if (selection.isEmpty()) {
265 // nothing selected, ignore
266 return;
269 Object element = selection.getFirstElement();
270 ITreeContentProvider contentProvider = (ITreeContentProvider) tv
271 .getContentProvider();
272 if (contentProvider.hasChildren(element)) {
273 // this element has children, expand/collapse it
274 tv.setExpandedState(element, !tv.getExpandedState(element));
275 } else {
276 Object[] selectionArray = selection.toArray();
277 for (Object selectedElement : selectionArray) {
278 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
279 // if any of the selected elements are not files, ignore
280 // the open request
281 if (node.getType() != RepositoryTreeNodeType.FILE) {
282 return;
286 // open the files the user has selected
287 for (Object selectedElement : selectionArray) {
288 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
289 openFile((File) node.getObject());
295 addContextMenu();
297 addActionsToToolbar();
299 scheduleRefresh();
301 ISelectionService srv = (ISelectionService) getSite().getService(
302 ISelectionService.class);
303 srv.addPostSelectionListener(new ISelectionListener() {
305 public void selectionChanged(IWorkbenchPart part,
306 ISelection selection) {
308 // if the "link with selection" toggle is off, we're done
309 if (linkWithSelectionAction == null
310 || !linkWithSelectionAction.isChecked())
311 return;
313 // this may happen if we switch between editors
314 if (part instanceof IEditorPart) {
315 IEditorInput input = ((IEditorPart) part).getEditorInput();
316 if (input instanceof IFileEditorInput)
317 reactOnSelection(new StructuredSelection(
318 ((IFileEditorInput) input).getFile()));
320 } else {
321 reactOnSelection(selection);
328 private void reactOnSelection(ISelection selection) {
329 if (selection instanceof StructuredSelection) {
330 StructuredSelection ssel = (StructuredSelection) selection;
331 if (ssel.size() != 1)
332 return;
333 if (ssel.getFirstElement() instanceof IResource) {
334 showResource((IResource) ssel.getFirstElement());
336 if (ssel.getFirstElement() instanceof IAdaptable) {
337 IResource adapted = (IResource) ((IAdaptable) ssel
338 .getFirstElement()).getAdapter(IResource.class);
339 if (adapted != null)
340 showResource(adapted);
345 private void addContextMenu() {
346 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
348 public void menuDetected(MenuDetectEvent e) {
350 tv.getTree().setMenu(null);
351 Menu men = new Menu(tv.getTree());
353 TreeItem testItem = tv.getTree().getItem(
354 tv.getTree().toControl(new Point(e.x, e.y)));
355 if (testItem == null) {
356 addMenuItemsForPanel(men);
357 } else {
358 addMenuItemsForTreeSelection(men);
361 tv.getTree().setMenu(men);
366 private void addMenuItemsForPanel(Menu men) {
368 MenuItem importItem = new MenuItem(men, SWT.PUSH);
369 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
370 importItem.addSelectionListener(new SelectionAdapter() {
372 @Override
373 public void widgetSelected(SelectionEvent e) {
374 importAction.run();
379 MenuItem addItem = new MenuItem(men, SWT.PUSH);
380 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
381 addItem.addSelectionListener(new SelectionAdapter() {
383 @Override
384 public void widgetSelected(SelectionEvent e) {
385 addAction.run();
390 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
391 refreshItem.setText(refreshAction.getText());
392 refreshItem.addSelectionListener(new SelectionAdapter() {
394 @Override
395 public void widgetSelected(SelectionEvent e) {
396 refreshAction.run();
403 @SuppressWarnings("unchecked")
404 private void addMenuItemsForTreeSelection(Menu men) {
406 final IStructuredSelection sel = (IStructuredSelection) tv
407 .getSelection();
409 boolean importableProjectsOnly = true;
411 for (Object node : sel.toArray()) {
412 RepositoryTreeNode tnode = (RepositoryTreeNode) node;
413 importableProjectsOnly = tnode.getType() == RepositoryTreeNodeType.PROJ;
414 if (!importableProjectsOnly)
415 break;
418 if (importableProjectsOnly) {
419 MenuItem sync = new MenuItem(men, SWT.PUSH);
420 sync.setText(UIText.RepositoriesView_ImportProject_MenuItem);
422 sync.addSelectionListener(new SelectionAdapter() {
424 @Override
425 public void widgetSelected(SelectionEvent e) {
427 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
429 public void run(IProgressMonitor monitor)
430 throws CoreException {
432 for (Object selected : sel.toArray()) {
433 RepositoryTreeNode<File> projectNode = (RepositoryTreeNode<File>) selected;
434 File file = projectNode.getObject();
436 IProjectDescription pd = ResourcesPlugin
437 .getWorkspace().newProjectDescription(
438 file.getName());
439 IPath locationPath = new Path(file
440 .getAbsolutePath());
442 pd.setLocation(locationPath);
444 ResourcesPlugin.getWorkspace().getRoot()
445 .getProject(pd.getName()).create(pd,
446 monitor);
447 IProject project = ResourcesPlugin
448 .getWorkspace().getRoot().getProject(
449 pd.getName());
450 project.open(monitor);
452 File gitDir = projectNode.getRepository()
453 .getDirectory();
455 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
456 project, gitDir);
457 connectProviderOperation
458 .run(new SubProgressMonitor(monitor, 20));
465 try {
467 ResourcesPlugin.getWorkspace().run(wsr,
468 ResourcesPlugin.getWorkspace().getRoot(),
469 IWorkspace.AVOID_UPDATE,
470 new NullProgressMonitor());
472 scheduleRefresh();
473 } catch (CoreException e1) {
474 Activator.logError(e1.getMessage(), e1);
482 // from here on, we only deal with single selection
483 if (sel.size() > 1)
484 return;
486 final RepositoryTreeNode node = (RepositoryTreeNode) sel
487 .getFirstElement();
489 // for Refs (branches): checkout
490 if (node.getType() == RepositoryTreeNodeType.REF) {
492 final Ref ref = (Ref) node.getObject();
494 MenuItem checkout = new MenuItem(men, SWT.PUSH);
495 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
496 checkout.addSelectionListener(new SelectionAdapter() {
498 @Override
499 public void widgetSelected(SelectionEvent e) {
501 final String refName = ref.getLeaf().getName();
503 Job job = new Job(NLS.bind(UIText.RepositoriesView_CheckingOutMessage, refName)) {
505 @Override
506 protected IStatus run(IProgressMonitor monitor) {
508 Repository repo = node.getRepository();
510 final BranchOperation op = new BranchOperation(
511 repo, refName);
512 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
514 public void run(IProgressMonitor myMonitor)
515 throws CoreException {
516 op.run(myMonitor);
520 try {
521 ResourcesPlugin.getWorkspace().run(
522 wsr,
523 ResourcesPlugin.getWorkspace()
524 .getRoot(),
525 IWorkspace.AVOID_UPDATE,
526 monitor);
527 scheduleRefresh();
528 } catch (CoreException e1) {
529 return new Status(IStatus.ERROR, Activator
530 .getPluginId(), e1.getMessage(), e1);
532 return Status.OK_STATUS;
536 job.setUser(true);
537 job.schedule();
544 // for Repository: import existing projects, remove, (delete), open
545 // properties
546 if (node.getType() == RepositoryTreeNodeType.REPO) {
548 final Repository repo = (Repository) node.getObject();
550 // TODO "import existing plug-in" menu item
552 MenuItem remove = new MenuItem(men, SWT.PUSH);
553 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
554 remove.addSelectionListener(new SelectionAdapter() {
556 @Override
557 public void widgetSelected(SelectionEvent e) {
559 List<IProject> projectsToDelete = new ArrayList<IProject>();
560 File workDir = repo.getWorkDir();
561 final IPath wdPath = new Path(workDir.getAbsolutePath());
562 for (IProject prj : ResourcesPlugin.getWorkspace()
563 .getRoot().getProjects()) {
564 if (wdPath.isPrefixOf(prj.getLocation())) {
565 projectsToDelete.add(prj);
569 if (!projectsToDelete.isEmpty()) {
570 boolean confirmed;
571 confirmed = confirmProjectDeletion(projectsToDelete);
572 if (!confirmed) {
573 return;
577 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
579 public void run(IProgressMonitor monitor)
580 throws CoreException {
582 for (IProject prj : ResourcesPlugin.getWorkspace()
583 .getRoot().getProjects()) {
584 if (wdPath.isPrefixOf(prj.getLocation())) {
585 prj.delete(false, false, monitor);
589 removeDir(repo.getDirectory());
590 scheduleRefresh();
594 try {
595 ResourcesPlugin.getWorkspace().run(wsr,
596 ResourcesPlugin.getWorkspace().getRoot(),
597 IWorkspace.AVOID_UPDATE,
598 new NullProgressMonitor());
599 } catch (CoreException e1) {
600 Activator.logError(e1.getMessage(), e1);
607 // TODO delete does not work because of file locks on .pack-files
608 // Shawn Pearce has added the following thoughts:
610 // Hmm. We probably can't active detect file locks on pack files on
611 // Windows, can we?
612 // It would be nice if we could support a delete, but only if the
613 // repository is
614 // reasonably believed to be not-in-use right now.
616 // Within EGit you might be able to check GitProjectData and its
617 // repositoryCache to
618 // see if the repository is open by this workspace. If it is, then
619 // we know we shouldn't
620 // try to delete it.
622 // Some coding might look like this:
624 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
625 // deleteRepo.setText("Delete");
626 // deleteRepo.addSelectionListener(new SelectionAdapter() {
628 // @Override
629 // public void widgetSelected(SelectionEvent e) {
631 // boolean confirmed = MessageDialog.openConfirm(getSite()
632 // .getShell(), "Confirm",
633 // "This will delete the repository, continue?");
635 // if (!confirmed)
636 // return;
638 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
640 // public void run(IProgressMonitor monitor)
641 // throws CoreException {
642 // File workDir = repos.get(0).getRepository()
643 // .getWorkDir();
645 // File gitDir = repos.get(0).getRepository()
646 // .getDirectory();
648 // IPath wdPath = new Path(workDir.getAbsolutePath());
649 // for (IProject prj : ResourcesPlugin.getWorkspace()
650 // .getRoot().getProjects()) {
651 // if (wdPath.isPrefixOf(prj.getLocation())) {
652 // prj.delete(false, false, monitor);
653 // }
654 // }
656 // repos.get(0).getRepository().close();
658 // boolean deleted = deleteRecursively(gitDir, monitor);
659 // if (!deleted) {
660 // MessageDialog.openError(getSite().getShell(),
661 // "Error",
662 // "Could not delete Git Repository");
663 // }
665 // deleted = deleteRecursively(workDir, monitor);
666 // if (!deleted) {
667 // MessageDialog
668 // .openError(getSite().getShell(),
669 // "Error",
670 // "Could not delete Git Working Directory");
671 // }
673 // scheduleRefresh();
674 // }
676 // private boolean deleteRecursively(File fileToDelete,
677 // IProgressMonitor monitor) {
678 // if (fileToDelete.isDirectory()) {
679 // for (File file : fileToDelete.listFiles()) {
680 // if (!deleteRecursively(file, monitor)) {
681 // return false;
682 // }
683 // }
684 // }
685 // monitor.setTaskName(fileToDelete.getAbsolutePath());
686 // boolean deleted = fileToDelete.delete();
687 // if (!deleted) {
688 // System.err.println("Could not delete "
689 // + fileToDelete.getAbsolutePath());
690 // }
691 // return deleted;
692 // }
693 // };
695 // try {
696 // ResourcesPlugin.getWorkspace().run(wsr,
697 // ResourcesPlugin.getWorkspace().getRoot(),
698 // IWorkspace.AVOID_UPDATE,
699 // new NullProgressMonitor());
700 // } catch (CoreException e1) {
701 // // TODO Exception handling
702 // e1.printStackTrace();
703 // }
705 // }
707 // });
709 new MenuItem(men, SWT.SEPARATOR);
711 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
712 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
713 openPropsView.addSelectionListener(new SelectionAdapter() {
715 @Override
716 public void widgetSelected(SelectionEvent e) {
717 try {
718 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
719 .getActivePage().showView(
720 IPageLayout.ID_PROP_SHEET);
721 } catch (PartInitException e1) {
722 // just ignore
729 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
731 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
732 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
733 remoteConfig.addSelectionListener(new SelectionAdapter() {
735 @Override
736 public void widgetSelected(SelectionEvent e) {
738 WizardDialog dlg = new WizardDialog(getSite().getShell(),
739 new NewRemoteWizard(node.getRepository()));
740 if (dlg.open() == Window.OK)
741 scheduleRefresh();
748 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
750 final String configName = (String) node.getObject();
752 RemoteConfig rconfig;
753 try {
754 rconfig = new RemoteConfig(node.getRepository().getConfig(),
755 configName);
756 } catch (URISyntaxException e2) {
757 // TODO Exception handling
758 rconfig = null;
761 boolean fetchExists = rconfig != null
762 && !rconfig.getURIs().isEmpty();
763 boolean pushExists = rconfig != null
764 && !rconfig.getPushURIs().isEmpty();
766 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
767 if (fetchExists)
768 configureUrlFetch
769 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
770 else
771 configureUrlFetch
772 .setText(UIText.RepositoriesView_CreateFetch_menu);
773 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
775 @Override
776 public void widgetSelected(SelectionEvent e) {
778 WizardDialog dlg = new WizardDialog(getSite().getShell(),
779 new ConfigureRemoteWizard(node.getRepository(),
780 configName, false));
781 if (dlg.open() == Window.OK)
782 scheduleRefresh();
788 if (fetchExists) {
789 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
790 deleteFetch.setText(UIText.RepositoriesView_RemoveFetch_menu);
791 deleteFetch.addSelectionListener(new SelectionAdapter() {
793 @Override
794 public void widgetSelected(SelectionEvent e) {
795 RepositoryConfig config = node.getRepository()
796 .getConfig();
797 config.unset("remote", configName, "url"); //$NON-NLS-1$ //$NON-NLS-2$
798 config.unset("remote", configName, "fetch"); //$NON-NLS-1$//$NON-NLS-2$
799 try {
800 config.save();
801 scheduleRefresh();
802 } catch (IOException e1) {
803 MessageDialog.openError(getSite().getShell(),
804 UIText.RepositoriesView_ErrorHeader, e1
805 .getMessage());
812 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
813 if (pushExists)
814 configureUrlPush
815 .setText(UIText.RepositoriesView_ConfigurePushMenu);
816 else
817 configureUrlPush
818 .setText(UIText.RepositoriesView_CreatePush_menu);
819 configureUrlPush.addSelectionListener(new SelectionAdapter() {
821 @Override
822 public void widgetSelected(SelectionEvent e) {
824 WizardDialog dlg = new WizardDialog(getSite().getShell(),
825 new ConfigureRemoteWizard(node.getRepository(),
826 configName, true));
827 if (dlg.open() == Window.OK)
828 scheduleRefresh();
833 if (pushExists) {
834 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
835 deleteFetch.setText(UIText.RepositoriesView_RemovePush_menu);
836 deleteFetch.addSelectionListener(new SelectionAdapter() {
838 @Override
839 public void widgetSelected(SelectionEvent e) {
840 RepositoryConfig config = node.getRepository()
841 .getConfig();
842 config.unset("remote", configName, "pushurl"); //$NON-NLS-1$ //$NON-NLS-2$
843 config.unset("remote", configName, "push"); //$NON-NLS-1$ //$NON-NLS-2$
844 try {
845 config.save();
846 scheduleRefresh();
847 } catch (IOException e1) {
848 MessageDialog.openError(getSite().getShell(),
849 UIText.RepositoriesView_ErrorHeader, e1
850 .getMessage());
857 new MenuItem(men, SWT.SEPARATOR);
859 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
860 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
861 removeRemote.addSelectionListener(new SelectionAdapter() {
863 @Override
864 public void widgetSelected(SelectionEvent e) {
866 boolean ok = MessageDialog
867 .openConfirm(
868 getSite().getShell(),
869 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
871 .bind(
872 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
873 configName));
874 if (ok) {
875 RepositoryConfig config = node.getRepository()
876 .getConfig();
877 config.unsetSection(REMOTE, configName);
878 try {
879 config.save();
880 scheduleRefresh();
881 } catch (IOException e1) {
882 MessageDialog.openError(getSite().getShell(),
883 UIText.RepositoriesView_ErrorHeader, e1
884 .getMessage());
892 new MenuItem(men, SWT.SEPARATOR);
894 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
895 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
896 openPropsView.addSelectionListener(new SelectionAdapter() {
898 @Override
899 public void widgetSelected(SelectionEvent e) {
900 try {
901 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
902 .getActivePage().showView(
903 IPageLayout.ID_PROP_SHEET);
904 } catch (PartInitException e1) {
905 // just ignore
912 if (node.getType() == RepositoryTreeNodeType.FILE) {
914 final File file = (File) node.getObject();
916 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
917 openInTextEditor
918 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
919 openInTextEditor.addSelectionListener(new SelectionAdapter() {
921 @Override
922 public void widgetSelected(SelectionEvent e) {
923 openFile(file);
929 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
930 String path = ((File) node.getObject()).getAbsolutePath();
931 createImportProjectItem(men, node.getRepository(), path);
934 if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
935 String path = node.getRepository().getWorkDir().getAbsolutePath();
936 createImportProjectItem(men, node.getRepository(), path);
941 private void openFile(File file) {
942 IFileStore store = EFS.getLocalFileSystem().getStore(
943 new Path(file.getAbsolutePath()));
944 try {
945 // TODO do we need a read-only editor here?
946 IDE.openEditor(getSite().getPage(),
947 new FileStoreEditorInput(store),
948 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
949 } catch (PartInitException e) {
950 Activator.handleError(UIText.RepositoriesView_Error_WindowTitle, e, true);
954 private void createImportProjectItem(Menu men, final Repository repo,
955 final String path) {
956 MenuItem importProjects;
957 importProjects = new MenuItem(men, SWT.PUSH);
958 importProjects
959 .setText(UIText.RepositoriesView_ImportExistingProjects_MenuItem);
960 importProjects.addSelectionListener(new SelectionAdapter() {
962 @Override
963 public void widgetSelected(SelectionEvent e) {
964 // Instead of the generic ExternalProjectImportWizard
965 // from the org.eclipse.ui.ide plug-in, we use our
966 // own wizard (the generic one does not allow to set the
967 // path in 3.4; in addition, we have added project filtering
968 // capabilities)
969 Wizard wiz = new GitImportProjectsWizard(repo, path);
971 WizardDialog dlg = new WizardDialog(getSite().getShell(), wiz);
972 if (dlg.open() == Window.OK)
973 // TODO if we drop the "existing projects" node, we can
974 // probably do without refresh
975 scheduleRefresh();
982 private void addActionsToToolbar() {
983 importAction = new Action(UIText.RepositoriesView_Import_Button) {
985 @Override
986 public void run() {
987 GitCloneWizard wiz = new GitCloneWizard();
988 wiz.init(null, null);
989 new WizardDialog(getSite().getShell(), wiz).open();
992 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
994 importAction.setImageDescriptor(UIIcons.IMPORT);
996 getViewSite().getActionBars().getToolBarManager().add(importAction);
998 addAction = new Action(UIText.RepositoriesView_Add_Button) {
1000 @Override
1001 public void run() {
1002 RepositorySearchDialog sd = new RepositorySearchDialog(
1003 getSite().getShell(), getDirs());
1004 if (sd.open() == Window.OK) {
1005 Set<String> dirs = new HashSet<String>();
1006 dirs.addAll(getDirs());
1007 if (dirs.addAll(sd.getDirectories()))
1008 saveDirs(dirs);
1009 scheduleRefresh();
1014 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
1016 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
1018 getViewSite().getActionBars().getToolBarManager().add(addAction);
1020 linkWithSelectionAction = new Action(
1021 UIText.RepositoriesView_LinkWithSelection_action,
1022 IAction.AS_CHECK_BOX) {
1024 @Override
1025 public void run() {
1026 IEclipsePreferences prefs = getPrefs();
1027 prefs.putBoolean(PREFS_SYNCED, isChecked());
1028 try {
1029 prefs.flush();
1030 } catch (BackingStoreException e) {
1031 // ignore here
1033 if (isChecked()) {
1034 ISelectionService srv = (ISelectionService) getSite()
1035 .getService(ISelectionService.class);
1036 reactOnSelection(srv.getSelection());
1043 linkWithSelectionAction
1044 .setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
1046 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1048 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
1049 false));
1051 getViewSite().getActionBars().getToolBarManager().add(
1052 linkWithSelectionAction);
1054 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
1056 @Override
1057 public void run() {
1058 scheduleRefresh();
1062 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1064 getViewSite().getActionBars().getToolBarManager().add(refreshAction);
1068 * @return the preferences
1070 protected static IEclipsePreferences getPrefs() {
1071 return new InstanceScope().getNode(Activator.getPluginId());
1074 @Override
1075 public void dispose() {
1076 // make sure to cancel the refresh job
1077 if (this.scheduledJob != null) {
1078 this.scheduledJob.cancel();
1079 this.scheduledJob = null;
1081 super.dispose();
1085 * Schedules a refreh
1087 public void scheduleRefresh() {
1089 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1091 @Override
1092 protected IStatus run(IProgressMonitor monitor) {
1094 final List<Repository> input;
1095 try {
1096 input = getRepositoriesFromDirs(monitor);
1097 } catch (InterruptedException e) {
1098 return new Status(IStatus.ERROR, Activator.getPluginId(), e
1099 .getMessage(), e);
1102 Display.getDefault().syncExec(new Runnable() {
1104 public void run() {
1105 // keep expansion state and selection so that we can
1106 // restore the tree
1107 // after update
1108 Object[] expanded = tv.getExpandedElements();
1109 IStructuredSelection sel = (IStructuredSelection) tv
1110 .getSelection();
1111 tv.setInput(input);
1112 tv.setExpandedElements(expanded);
1114 Object selected = sel.getFirstElement();
1115 if (selected != null)
1116 tv.reveal(selected);
1118 IViewPart part = PlatformUI.getWorkbench()
1119 .getActiveWorkbenchWindow().getActivePage()
1120 .findView(IPageLayout.ID_PROP_SHEET);
1121 if (part != null) {
1122 PropertySheet sheet = (PropertySheet) part;
1123 PropertySheetPage page = (PropertySheetPage) sheet
1124 .getCurrentPage();
1125 page.refresh();
1130 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1135 job.setSystem(true);
1137 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1138 .getService(IWorkbenchSiteProgressService.class);
1140 service.schedule(job);
1142 scheduledJob = job;
1146 private List<Repository> getRepositoriesFromDirs(IProgressMonitor monitor)
1147 throws InterruptedException {
1149 List<String> gitDirStrings = getDirs();
1150 List<Repository> input = new ArrayList<Repository>();
1151 for (String dirString : gitDirStrings) {
1152 if (monitor.isCanceled()) {
1153 throw new InterruptedException(
1154 UIText.RepositoriesView_ActionCanceled_Message);
1156 try {
1157 File dir = new File(dirString);
1158 if (dir.exists() && dir.isDirectory()) {
1159 input.add(new Repository(dir));
1161 } catch (IOException e) {
1162 IStatus error = new Status(IStatus.ERROR, Activator
1163 .getPluginId(), e.getMessage(), e);
1164 Activator.getDefault().getLog().log(error);
1167 return input;
1171 * Adds a directory to the list if it is not already there
1173 * @param file
1174 * @return see {@link Collection#add(Object)}
1176 public static boolean addDir(File file) {
1178 String dirString;
1179 try {
1180 dirString = file.getCanonicalPath();
1181 } catch (IOException e) {
1182 dirString = file.getAbsolutePath();
1185 List<String> dirStrings = getDirs();
1186 if (dirStrings.contains(dirString)) {
1187 return false;
1188 } else {
1189 Set<String> dirs = new HashSet<String>();
1190 dirs.addAll(dirStrings);
1191 dirs.add(dirString);
1192 saveDirs(dirs);
1193 return true;
1197 private static void saveDirs(Set<String> gitDirStrings) {
1198 StringBuilder sb = new StringBuilder();
1199 for (String gitDirString : gitDirStrings) {
1200 sb.append(gitDirString);
1201 sb.append(File.pathSeparatorChar);
1204 IEclipsePreferences prefs = getPrefs();
1205 prefs.put(PREFS_DIRECTORIES, sb.toString());
1206 try {
1207 prefs.flush();
1208 } catch (BackingStoreException e) {
1209 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1210 e.getMessage(), e);
1211 Activator.getDefault().getLog().log(error);
1215 @Override
1216 public void setFocus() {
1217 tv.getTree().setFocus();
1220 @SuppressWarnings("boxing")
1221 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1222 boolean confirmed;
1223 confirmed = MessageDialog
1224 .openConfirm(
1225 getSite().getShell(),
1226 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1228 .bind(
1229 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1230 projectsToDelete.size()));
1231 return confirmed;
1234 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1235 selectionListeners.add(listener);
1238 public ISelection getSelection() {
1239 return currentSelection;
1242 public void removeSelectionChangedListener(
1243 ISelectionChangedListener listener) {
1244 selectionListeners.remove(listener);
1248 public void setSelection(ISelection selection) {
1249 currentSelection = selection;
1250 for (ISelectionChangedListener listener : selectionListeners) {
1251 listener.selectionChanged(new SelectionChangedEvent(
1252 RepositoriesView.this, selection));
1257 * Opens the tree and marks the folder to which a project is pointing
1259 * @param resource
1260 * TODO exceptions?
1262 @SuppressWarnings("unchecked")
1263 public void showResource(final IResource resource) {
1264 IProject project = resource.getProject();
1265 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1266 if (mapping == null)
1267 return;
1269 if (addDir(mapping.getRepository().getDirectory())) {
1270 scheduleRefresh();
1273 boolean doSetSelection = false;
1275 if (this.scheduledJob != null) {
1276 int state = this.scheduledJob.getState();
1277 if (state == Job.WAITING || state == Job.RUNNING) {
1278 this.scheduledJob.addJobChangeListener(new JobChangeAdapter() {
1280 @Override
1281 public void done(IJobChangeEvent event) {
1282 showResource(resource);
1285 } else {
1286 doSetSelection = true;
1290 if (doSetSelection) {
1291 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1292 .getContentProvider();
1293 RepositoryTreeNode currentNode = null;
1294 Object[] repos = cp.getElements(tv.getInput());
1295 for (Object repo : repos) {
1296 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1297 // TODO equals implementation of Repository?
1298 if (mapping.getRepository().getDirectory().equals(
1299 ((Repository) node.getObject()).getDirectory())) {
1300 for (Object child : cp.getChildren(node)) {
1301 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1302 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1303 currentNode = childNode;
1304 break;
1307 break;
1311 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1313 for (String segment : relPath.segments()) {
1314 for (Object child : cp.getChildren(currentNode)) {
1315 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1316 if (childNode.getObject().getName().equals(segment)) {
1317 currentNode = childNode;
1318 break;
1323 final RepositoryTreeNode selNode = currentNode;
1325 Display.getDefault().asyncExec(new Runnable() {
1327 public void run() {
1328 tv.setSelection(new StructuredSelection(selNode), true);