Add a "Git Repository Exploring" perspective
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob38667d307d59a85e11aa95ad3c517080f5b4ba74
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.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.StringTokenizer;
22 import java.util.TreeSet;
24 import org.eclipse.core.filesystem.EFS;
25 import org.eclipse.core.filesystem.IFileStore;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IProjectDescription;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.resources.IWorkspace;
30 import org.eclipse.core.resources.IWorkspaceRunnable;
31 import org.eclipse.core.resources.ResourcesPlugin;
32 import org.eclipse.core.runtime.CoreException;
33 import org.eclipse.core.runtime.IAdaptable;
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.NullProgressMonitor;
38 import org.eclipse.core.runtime.Path;
39 import org.eclipse.core.runtime.Status;
40 import org.eclipse.core.runtime.SubProgressMonitor;
41 import org.eclipse.core.runtime.jobs.Job;
42 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
43 import org.eclipse.core.runtime.preferences.InstanceScope;
44 import org.eclipse.egit.core.op.BranchOperation;
45 import org.eclipse.egit.core.op.ConnectProviderOperation;
46 import org.eclipse.egit.core.project.RepositoryMapping;
47 import org.eclipse.egit.ui.Activator;
48 import org.eclipse.egit.ui.UIIcons;
49 import org.eclipse.egit.ui.UIText;
50 import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
51 import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode.RepositoryTreeNodeType;
52 import org.eclipse.jface.action.Action;
53 import org.eclipse.jface.action.IAction;
54 import org.eclipse.jface.dialogs.MessageDialog;
55 import org.eclipse.jface.viewers.ISelection;
56 import org.eclipse.jface.viewers.ISelectionChangedListener;
57 import org.eclipse.jface.viewers.ISelectionProvider;
58 import org.eclipse.jface.viewers.IStructuredSelection;
59 import org.eclipse.jface.viewers.SelectionChangedEvent;
60 import org.eclipse.jface.viewers.StructuredSelection;
61 import org.eclipse.jface.viewers.TreeViewer;
62 import org.eclipse.jface.window.Window;
63 import org.eclipse.jface.wizard.Wizard;
64 import org.eclipse.jface.wizard.WizardDialog;
65 import org.eclipse.jgit.lib.Ref;
66 import org.eclipse.jgit.lib.Repository;
67 import org.eclipse.jgit.lib.RepositoryConfig;
68 import org.eclipse.osgi.util.NLS;
69 import org.eclipse.swt.SWT;
70 import org.eclipse.swt.events.MenuDetectEvent;
71 import org.eclipse.swt.events.MenuDetectListener;
72 import org.eclipse.swt.events.SelectionAdapter;
73 import org.eclipse.swt.events.SelectionEvent;
74 import org.eclipse.swt.graphics.Point;
75 import org.eclipse.swt.layout.FillLayout;
76 import org.eclipse.swt.widgets.Composite;
77 import org.eclipse.swt.widgets.Display;
78 import org.eclipse.swt.widgets.Menu;
79 import org.eclipse.swt.widgets.MenuItem;
80 import org.eclipse.swt.widgets.TreeItem;
81 import org.eclipse.ui.IPageLayout;
82 import org.eclipse.ui.ISelectionListener;
83 import org.eclipse.ui.ISelectionService;
84 import org.eclipse.ui.IViewPart;
85 import org.eclipse.ui.IWorkbenchPart;
86 import org.eclipse.ui.PartInitException;
87 import org.eclipse.ui.PlatformUI;
88 import org.eclipse.ui.editors.text.EditorsUI;
89 import org.eclipse.ui.ide.FileStoreEditorInput;
90 import org.eclipse.ui.ide.IDE;
91 import org.eclipse.ui.part.ViewPart;
92 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
93 import org.eclipse.ui.views.properties.IPropertySheetPage;
94 import org.eclipse.ui.views.properties.PropertySheet;
95 import org.eclipse.ui.views.properties.PropertySheetPage;
96 import org.eclipse.ui.wizards.datatransfer.ExternalProjectImportWizard;
97 import org.osgi.service.prefs.BackingStoreException;
99 /**
101 * The Git Repositories view.
102 * <p>
103 * This keeps track of a bunch of local directory names each of which represent
104 * a Git Repository. This list is stored in some Preferences object and used to
105 * build the tree in the view.
106 * <p>
107 * Implements {@link ISelectionProvider} in order to integrate with the
108 * Properties view.
109 * <p>
110 * TODO
111 * <li>Clarification whether to show projects, perhaps configurable switch</li>
114 public class RepositoriesView extends ViewPart implements ISelectionProvider {
116 /** The view ID */
117 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
118 // TODO central constants? RemoteConfig ones are private
119 static final String REMOTE = "remote"; //$NON-NLS-1$
121 static final String URL = "url"; //$NON-NLS-1$
123 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
125 static final String FETCH = "fetch"; //$NON-NLS-1$
127 static final String PUSH = "push"; //$NON-NLS-1$
129 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
131 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
133 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
135 private ISelection currentSelection = new StructuredSelection();
137 private Job scheduledJob;
139 private TreeViewer tv;
141 private IAction importAction;
143 private IAction addAction;
145 private IAction refreshAction;
147 private IAction linkWithSelectionAction;
149 private static List<String> getDirs() {
150 List<String> resultStrings = new ArrayList<String>();
151 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
152 if (dirs != null && dirs.length() > 0) {
153 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
154 while (tok.hasMoreTokens()) {
155 String dirName = tok.nextToken();
156 File testFile = new File(dirName);
157 if (testFile.exists()) {
158 resultStrings.add(dirName);
162 Collections.sort(resultStrings);
163 return resultStrings;
166 private static void removeDir(File file) {
168 String dir;
169 try {
170 dir = file.getCanonicalPath();
171 } catch (IOException e1) {
172 dir = file.getAbsolutePath();
175 IEclipsePreferences prefs = getPrefs();
177 TreeSet<String> resultStrings = new TreeSet<String>();
178 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
179 if (dirs != null && dirs.length() > 0) {
180 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
181 while (tok.hasMoreTokens()) {
182 String dirName = tok.nextToken();
183 File testFile = new File(dirName);
184 if (testFile.exists()) {
185 try {
186 resultStrings.add(testFile.getCanonicalPath());
187 } catch (IOException e) {
188 resultStrings.add(testFile.getAbsolutePath());
194 if (resultStrings.remove(dir)) {
195 StringBuilder sb = new StringBuilder();
196 for (String gitDirString : resultStrings) {
197 sb.append(gitDirString);
198 sb.append(File.pathSeparatorChar);
201 prefs.put(PREFS_DIRECTORIES, sb.toString());
202 try {
203 prefs.flush();
204 } catch (BackingStoreException e) {
205 IStatus error = new Status(IStatus.ERROR, Activator
206 .getPluginId(), e.getMessage(), e);
207 Activator.getDefault().getLog().log(error);
213 @Override
214 public Object getAdapter(Class adapter) {
215 // integrate with Properties view
216 if (adapter == IPropertySheetPage.class) {
217 PropertySheetPage page = new PropertySheetPage();
218 page
219 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
220 page));
221 return page;
224 return super.getAdapter(adapter);
227 @Override
228 public void createPartControl(Composite parent) {
230 Composite main = new Composite(parent, SWT.NONE);
231 main.setLayout(new FillLayout());
233 tv = new TreeViewer(main);
234 tv.setContentProvider(new RepositoriesViewContentProvider());
235 // the label provider registers itself
236 new RepositoriesViewLabelProvider(tv);
238 getSite().setSelectionProvider(this);
240 tv.addSelectionChangedListener(new ISelectionChangedListener() {
242 public void selectionChanged(SelectionChangedEvent event) {
244 IStructuredSelection ssel = (IStructuredSelection) event
245 .getSelection();
246 if (ssel.size() == 1) {
247 setSelection(new StructuredSelection(ssel.getFirstElement()));
248 } else {
249 setSelection(new StructuredSelection());
254 // make the tree rather wide to accommodate long directory names
255 tv.getTree().getColumn(0).setWidth(700);
257 addContextMenu();
259 addActionsToToolbar();
261 scheduleRefresh();
263 ISelectionService srv = (ISelectionService) getSite().getService(
264 ISelectionService.class);
265 srv.addPostSelectionListener(new ISelectionListener() {
267 public void selectionChanged(IWorkbenchPart part,
268 ISelection selection) {
270 if (linkWithSelectionAction == null
271 || !linkWithSelectionAction.isChecked())
272 return;
274 reactOnSelection(selection);
280 private void reactOnSelection(ISelection selection) {
281 if (selection instanceof StructuredSelection) {
282 StructuredSelection ssel = (StructuredSelection) selection;
283 if (ssel.size() != 1)
284 return;
285 if (ssel.getFirstElement() instanceof IResource) {
286 showResource((IResource) ssel.getFirstElement());
288 if (ssel.getFirstElement() instanceof IAdaptable) {
289 IResource adapted = (IResource) ((IAdaptable) ssel
290 .getFirstElement()).getAdapter(IResource.class);
291 if (adapted != null)
292 showResource(adapted);
297 private void addContextMenu() {
298 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
300 public void menuDetected(MenuDetectEvent e) {
302 tv.getTree().setMenu(null);
303 Menu men = new Menu(tv.getTree());
305 TreeItem testItem = tv.getTree().getItem(
306 tv.getTree().toControl(new Point(e.x, e.y)));
307 if (testItem == null) {
308 addMenuItemsForPanel(men);
309 } else {
310 addMenuItemsForTreeSelection(men);
313 tv.getTree().setMenu(men);
318 private void addMenuItemsForPanel(Menu men) {
320 MenuItem importItem = new MenuItem(men, SWT.PUSH);
321 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
322 importItem.addSelectionListener(new SelectionAdapter() {
324 @Override
325 public void widgetSelected(SelectionEvent e) {
326 importAction.run();
331 MenuItem addItem = new MenuItem(men, SWT.PUSH);
332 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
333 addItem.addSelectionListener(new SelectionAdapter() {
335 @Override
336 public void widgetSelected(SelectionEvent e) {
337 addAction.run();
342 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
343 refreshItem.setText(refreshAction.getText());
344 refreshItem.addSelectionListener(new SelectionAdapter() {
346 @Override
347 public void widgetSelected(SelectionEvent e) {
348 refreshAction.run();
355 @SuppressWarnings("unchecked")
356 private void addMenuItemsForTreeSelection(Menu men) {
358 final IStructuredSelection sel = (IStructuredSelection) tv
359 .getSelection();
361 boolean importableProjectsOnly = true;
363 for (Object node : sel.toArray()) {
364 RepositoryTreeNode tnode = (RepositoryTreeNode) node;
365 importableProjectsOnly = tnode.getType() == RepositoryTreeNodeType.PROJ;
366 if (!importableProjectsOnly)
367 break;
370 if (importableProjectsOnly) {
371 MenuItem sync = new MenuItem(men, SWT.PUSH);
372 sync.setText(UIText.RepositoriesView_ImportProject_MenuItem);
374 sync.addSelectionListener(new SelectionAdapter() {
376 @Override
377 public void widgetSelected(SelectionEvent e) {
379 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
381 public void run(IProgressMonitor monitor)
382 throws CoreException {
384 for (Object selected : sel.toArray()) {
385 RepositoryTreeNode<File> projectNode = (RepositoryTreeNode<File>) selected;
386 File file = projectNode.getObject();
388 IProjectDescription pd = ResourcesPlugin
389 .getWorkspace().newProjectDescription(
390 file.getName());
391 IPath locationPath = new Path(file
392 .getAbsolutePath());
394 pd.setLocation(locationPath);
396 ResourcesPlugin.getWorkspace().getRoot()
397 .getProject(pd.getName()).create(pd,
398 monitor);
399 IProject project = ResourcesPlugin
400 .getWorkspace().getRoot().getProject(
401 pd.getName());
402 project.open(monitor);
404 File gitDir = projectNode.getRepository()
405 .getDirectory();
407 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
408 project, gitDir);
409 connectProviderOperation
410 .run(new SubProgressMonitor(monitor, 20));
417 try {
419 ResourcesPlugin.getWorkspace().run(wsr,
420 ResourcesPlugin.getWorkspace().getRoot(),
421 IWorkspace.AVOID_UPDATE,
422 new NullProgressMonitor());
424 scheduleRefresh();
425 } catch (CoreException e1) {
426 Activator.getDefault().getLog().log(e1.getStatus());
434 // from here on, we only deal with single selection
435 if (sel.size() > 1)
436 return;
438 final RepositoryTreeNode node = (RepositoryTreeNode) sel
439 .getFirstElement();
441 // for Refs (branches): checkout
442 if (node.getType() == RepositoryTreeNodeType.REF) {
444 final Ref ref = (Ref) node.getObject();
446 MenuItem checkout = new MenuItem(men, SWT.PUSH);
447 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
448 checkout.addSelectionListener(new SelectionAdapter() {
450 @Override
451 public void widgetSelected(SelectionEvent e) {
452 Repository repo = node.getRepository();
453 String refName = ref.getLeaf().getName();
454 final BranchOperation op = new BranchOperation(repo,
455 refName);
456 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
458 public void run(IProgressMonitor monitor)
459 throws CoreException {
460 op.run(monitor);
463 try {
464 ResourcesPlugin.getWorkspace().run(wsr,
465 ResourcesPlugin.getWorkspace().getRoot(),
466 IWorkspace.AVOID_UPDATE,
467 new NullProgressMonitor());
468 scheduleRefresh();
469 } catch (CoreException e1) {
470 MessageDialog.openError(getSite().getShell(),
471 UIText.RepositoriesView_Error_WindowTitle, e1
472 .getMessage());
480 // for Repository: import existing projects, remove, (delete), open
481 // properties
482 if (node.getType() == RepositoryTreeNodeType.REPO) {
484 final Repository repo = (Repository) node.getObject();
486 // TODO "import existing plug-in" menu item
488 MenuItem remove = new MenuItem(men, SWT.PUSH);
489 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
490 remove.addSelectionListener(new SelectionAdapter() {
492 @Override
493 public void widgetSelected(SelectionEvent e) {
495 List<IProject> projectsToDelete = new ArrayList<IProject>();
496 File workDir = repo.getWorkDir();
497 final IPath wdPath = new Path(workDir.getAbsolutePath());
498 for (IProject prj : ResourcesPlugin.getWorkspace()
499 .getRoot().getProjects()) {
500 if (wdPath.isPrefixOf(prj.getLocation())) {
501 projectsToDelete.add(prj);
505 if (!projectsToDelete.isEmpty()) {
506 boolean confirmed;
507 confirmed = confirmProjectDeletion(projectsToDelete);
508 if (!confirmed) {
509 return;
513 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
515 public void run(IProgressMonitor monitor)
516 throws CoreException {
518 for (IProject prj : ResourcesPlugin.getWorkspace()
519 .getRoot().getProjects()) {
520 if (wdPath.isPrefixOf(prj.getLocation())) {
521 prj.delete(false, false, monitor);
525 removeDir(repo.getDirectory());
526 scheduleRefresh();
530 try {
531 ResourcesPlugin.getWorkspace().run(wsr,
532 ResourcesPlugin.getWorkspace().getRoot(),
533 IWorkspace.AVOID_UPDATE,
534 new NullProgressMonitor());
535 } catch (CoreException e1) {
536 Activator.getDefault().getLog().log(e1.getStatus());
543 // TODO delete does not work because of file locks on .pack-files
544 // Shawn Pearce has added the following thoughts:
546 // Hmm. We probably can't active detect file locks on pack files on
547 // Windows, can we?
548 // It would be nice if we could support a delete, but only if the
549 // repository is
550 // reasonably believed to be not-in-use right now.
552 // Within EGit you might be able to check GitProjectData and its
553 // repositoryCache to
554 // see if the repository is open by this workspace. If it is, then
555 // we know we shouldn't
556 // try to delete it.
558 // Some coding might look like this:
560 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
561 // deleteRepo.setText("Delete");
562 // deleteRepo.addSelectionListener(new SelectionAdapter() {
564 // @Override
565 // public void widgetSelected(SelectionEvent e) {
567 // boolean confirmed = MessageDialog.openConfirm(getSite()
568 // .getShell(), "Confirm",
569 // "This will delete the repository, continue?");
571 // if (!confirmed)
572 // return;
574 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
576 // public void run(IProgressMonitor monitor)
577 // throws CoreException {
578 // File workDir = repos.get(0).getRepository()
579 // .getWorkDir();
581 // File gitDir = repos.get(0).getRepository()
582 // .getDirectory();
584 // IPath wdPath = new Path(workDir.getAbsolutePath());
585 // for (IProject prj : ResourcesPlugin.getWorkspace()
586 // .getRoot().getProjects()) {
587 // if (wdPath.isPrefixOf(prj.getLocation())) {
588 // prj.delete(false, false, monitor);
589 // }
590 // }
592 // repos.get(0).getRepository().close();
594 // boolean deleted = deleteRecursively(gitDir, monitor);
595 // if (!deleted) {
596 // MessageDialog.openError(getSite().getShell(),
597 // "Error",
598 // "Could not delete Git Repository");
599 // }
601 // deleted = deleteRecursively(workDir, monitor);
602 // if (!deleted) {
603 // MessageDialog
604 // .openError(getSite().getShell(),
605 // "Error",
606 // "Could not delete Git Working Directory");
607 // }
609 // scheduleRefresh();
610 // }
612 // private boolean deleteRecursively(File fileToDelete,
613 // IProgressMonitor monitor) {
614 // if (fileToDelete.isDirectory()) {
615 // for (File file : fileToDelete.listFiles()) {
616 // if (!deleteRecursively(file, monitor)) {
617 // return false;
618 // }
619 // }
620 // }
621 // monitor.setTaskName(fileToDelete.getAbsolutePath());
622 // boolean deleted = fileToDelete.delete();
623 // if (!deleted) {
624 // System.err.println("Could not delete "
625 // + fileToDelete.getAbsolutePath());
626 // }
627 // return deleted;
628 // }
629 // };
631 // try {
632 // ResourcesPlugin.getWorkspace().run(wsr,
633 // ResourcesPlugin.getWorkspace().getRoot(),
634 // IWorkspace.AVOID_UPDATE,
635 // new NullProgressMonitor());
636 // } catch (CoreException e1) {
637 // // TODO Exception handling
638 // e1.printStackTrace();
639 // }
641 // }
643 // });
645 new MenuItem(men, SWT.SEPARATOR);
647 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
648 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
649 openPropsView.addSelectionListener(new SelectionAdapter() {
651 @Override
652 public void widgetSelected(SelectionEvent e) {
653 try {
654 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
655 .getActivePage().showView(
656 IPageLayout.ID_PROP_SHEET);
657 } catch (PartInitException e1) {
658 // just ignore
665 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
667 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
668 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
669 remoteConfig.addSelectionListener(new SelectionAdapter() {
671 @Override
672 public void widgetSelected(SelectionEvent e) {
673 new WizardDialog(getSite().getShell(),
674 new ConfigureRemoteWizard(node.getRepository()))
675 .open();
676 scheduleRefresh();
683 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
685 final String name = (String) node.getObject();
687 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
688 configureUrlFetch
689 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
690 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
692 @Override
693 public void widgetSelected(SelectionEvent e) {
695 new WizardDialog(getSite().getShell(),
696 new ConfigureRemoteWizard(node.getRepository(),
697 name, false)).open();
698 scheduleRefresh();
704 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
705 configureUrlPush.setText(UIText.RepositoriesView_ConfigurePushMenu);
706 configureUrlPush.addSelectionListener(new SelectionAdapter() {
708 @Override
709 public void widgetSelected(SelectionEvent e) {
711 new WizardDialog(getSite().getShell(),
712 new ConfigureRemoteWizard(node.getRepository(),
713 name, true)).open();
714 scheduleRefresh();
720 new MenuItem(men, SWT.SEPARATOR);
722 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
723 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
724 removeRemote.addSelectionListener(new SelectionAdapter() {
726 @Override
727 public void widgetSelected(SelectionEvent e) {
729 boolean ok = MessageDialog
730 .openConfirm(
731 getSite().getShell(),
732 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
734 .bind(
735 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
736 name));
737 if (ok) {
738 RepositoryConfig config = node.getRepository()
739 .getConfig();
740 config.unsetSection(REMOTE, name);
741 try {
742 config.save();
743 scheduleRefresh();
744 } catch (IOException e1) {
745 MessageDialog.openError(getSite().getShell(),
746 UIText.RepositoriesView_ErrorHeader, e1
747 .getMessage());
755 new MenuItem(men, SWT.SEPARATOR);
757 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
758 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
759 openPropsView.addSelectionListener(new SelectionAdapter() {
761 @Override
762 public void widgetSelected(SelectionEvent e) {
763 try {
764 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
765 .getActivePage().showView(
766 IPageLayout.ID_PROP_SHEET);
767 } catch (PartInitException e1) {
768 // just ignore
775 if (node.getType() == RepositoryTreeNodeType.FILE) {
777 final File file = (File) node.getObject();
779 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
780 openInTextEditor
781 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
782 openInTextEditor.addSelectionListener(new SelectionAdapter() {
784 @Override
785 public void widgetSelected(SelectionEvent e) {
786 IFileStore store = EFS.getLocalFileSystem().getStore(
787 new Path(file.getAbsolutePath()));
788 try {
789 // TODO do we need a read-only editor here?
790 IDE.openEditor(getSite().getPage(),
791 new FileStoreEditorInput(store),
792 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
794 } catch (PartInitException e1) {
795 MessageDialog.openError(getSite().getShell(),
796 UIText.RepositoriesView_Error_WindowTitle, e1
797 .getMessage());
805 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
806 String path = ((File) node.getObject()).getAbsolutePath();
807 createImportProjectItem(men, node.getRepository(), path);
810 if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
811 String path = node.getRepository().getWorkDir().getAbsolutePath();
812 createImportProjectItem(men, node.getRepository(), path);
817 private void createImportProjectItem(Menu men, final Repository repo,
818 final String path) {
819 MenuItem importProjects;
820 importProjects = new MenuItem(men, SWT.PUSH);
821 importProjects
822 .setText(UIText.RepositoriesView_ImportExistingProjects_MenuItem);
823 importProjects.addSelectionListener(new SelectionAdapter() {
825 @Override
826 public void widgetSelected(SelectionEvent e) {
827 Wizard wiz = new ExternalProjectImportWizard(path) {
829 @Override
830 public void addPages() {
831 super.addPages();
832 // we could add some page with a single
835 @Override
836 public boolean performFinish() {
838 final Set<IPath> previousLocations = new HashSet<IPath>();
839 // we want to share only new projects
840 for (IProject project : ResourcesPlugin.getWorkspace()
841 .getRoot().getProjects()) {
842 previousLocations.add(project.getLocation());
845 boolean success = super.performFinish();
846 if (success) {
847 // IWizardPage page = getPage("Share");
848 // TODO evaluate checkbox or such, but
849 // if we do share
850 // always, we don't even need another
851 // page
853 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
855 public void run(IProgressMonitor monitor)
856 throws CoreException {
857 File gitDir = repo.getDirectory();
858 File gitWorkDir = repo.getWorkDir();
859 Path workPath = new Path(gitWorkDir
860 .getAbsolutePath());
862 // we check which projects are
863 // in the workspace
864 // pointing to a location in the
865 // repo's
866 // working directory
867 // and share them
868 for (IProject prj : ResourcesPlugin
869 .getWorkspace().getRoot()
870 .getProjects()) {
872 if (workPath.isPrefixOf(prj
873 .getLocation())) {
874 if (previousLocations.contains(prj
875 .getLocation())) {
876 continue;
878 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
879 prj, gitDir);
880 connectProviderOperation
881 .run(new SubProgressMonitor(
882 monitor, 20));
890 try {
891 ResourcesPlugin.getWorkspace().run(
892 wsr,
893 ResourcesPlugin.getWorkspace()
894 .getRoot(),
895 IWorkspace.AVOID_UPDATE,
896 new NullProgressMonitor());
897 scheduleRefresh();
898 } catch (CoreException ce) {
899 MessageDialog
900 .openError(
901 getShell(),
902 UIText.RepositoriesView_Error_WindowTitle,
903 ce.getMessage());
907 return success;
912 WizardDialog dlg = new WizardDialog(getSite().getShell(), wiz);
913 dlg.open();
919 private void addActionsToToolbar() {
920 importAction = new Action(UIText.RepositoriesView_Import_Button) {
922 @Override
923 public void run() {
924 GitCloneWizard wiz = new GitCloneWizard();
925 wiz.init(null, null);
926 new WizardDialog(getSite().getShell(), wiz).open();
929 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
931 importAction.setImageDescriptor(UIIcons.IMPORT);
933 getViewSite().getActionBars().getToolBarManager().add(importAction);
935 addAction = new Action(UIText.RepositoriesView_Add_Button) {
937 @Override
938 public void run() {
939 RepositorySearchDialog sd = new RepositorySearchDialog(
940 getSite().getShell(), getDirs());
941 if (sd.open() == Window.OK) {
942 Set<String> dirs = new HashSet<String>();
943 dirs.addAll(getDirs());
944 if (dirs.addAll(sd.getDirectories()))
945 saveDirs(dirs);
946 scheduleRefresh();
951 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
953 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
955 getViewSite().getActionBars().getToolBarManager().add(addAction);
957 linkWithSelectionAction = new Action(UIText.RepositoriesView_LinkWithSelection_action,
958 IAction.AS_CHECK_BOX) {
960 @Override
961 public void run() {
962 IEclipsePreferences prefs = getPrefs();
963 prefs.putBoolean(PREFS_SYNCED, isChecked());
964 try {
965 prefs.flush();
966 } catch (BackingStoreException e) {
967 // ignore here
969 if (isChecked()) {
970 ISelectionService srv = (ISelectionService) getSite()
971 .getService(ISelectionService.class);
972 reactOnSelection(srv.getSelection());
979 linkWithSelectionAction.setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
981 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
983 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
984 false));
986 getViewSite().getActionBars().getToolBarManager().add(
987 linkWithSelectionAction);
989 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
991 @Override
992 public void run() {
993 scheduleRefresh();
997 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
999 getViewSite().getActionBars().getToolBarManager().add(refreshAction);
1003 * @return the preferences
1005 protected static IEclipsePreferences getPrefs() {
1006 return new InstanceScope().getNode(Activator.getPluginId());
1009 @Override
1010 public void dispose() {
1011 // make sure to cancel the refresh job
1012 if (this.scheduledJob != null) {
1013 this.scheduledJob.cancel();
1014 this.scheduledJob = null;
1016 super.dispose();
1020 * Schedules a refreh
1022 public void scheduleRefresh() {
1024 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1026 @Override
1027 protected IStatus run(IProgressMonitor monitor) {
1029 final List<Repository> input;
1030 try {
1031 input = getRepositoriesFromDirs(monitor);
1032 } catch (InterruptedException e) {
1033 return new Status(IStatus.ERROR, Activator.getPluginId(), e
1034 .getMessage(), e);
1037 Display.getDefault().syncExec(new Runnable() {
1039 public void run() {
1040 // keep expansion state and selection so that we can
1041 // restore the tree
1042 // after update
1043 Object[] expanded = tv.getExpandedElements();
1044 IStructuredSelection sel = (IStructuredSelection) tv
1045 .getSelection();
1046 tv.setInput(input);
1047 tv.setExpandedElements(expanded);
1049 Object selected = sel.getFirstElement();
1050 if (selected != null)
1051 tv.reveal(selected);
1053 IViewPart part = PlatformUI.getWorkbench()
1054 .getActiveWorkbenchWindow().getActivePage()
1055 .findView(IPageLayout.ID_PROP_SHEET);
1056 if (part != null) {
1057 PropertySheet sheet = (PropertySheet) part;
1058 PropertySheetPage page = (PropertySheetPage) sheet
1059 .getCurrentPage();
1060 page.refresh();
1065 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1070 job.setSystem(true);
1072 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1073 .getService(IWorkbenchSiteProgressService.class);
1075 service.schedule(job);
1077 scheduledJob = job;
1081 private List<Repository> getRepositoriesFromDirs(IProgressMonitor monitor)
1082 throws InterruptedException {
1084 List<String> gitDirStrings = getDirs();
1085 List<Repository> input = new ArrayList<Repository>();
1086 for (String dirString : gitDirStrings) {
1087 if (monitor.isCanceled()) {
1088 throw new InterruptedException(
1089 UIText.RepositoriesView_ActionCanceled_Message);
1091 try {
1092 File dir = new File(dirString);
1093 if (dir.exists() && dir.isDirectory()) {
1094 input.add(new Repository(dir));
1096 } catch (IOException e) {
1097 IStatus error = new Status(IStatus.ERROR, Activator
1098 .getPluginId(), e.getMessage(), e);
1099 Activator.getDefault().getLog().log(error);
1102 return input;
1106 * Adds a directory to the list if it is not already there
1108 * @param file
1109 * @return see {@link Collection#add(Object)}
1111 public static boolean addDir(File file) {
1113 String dirString;
1114 try {
1115 dirString = file.getCanonicalPath();
1116 } catch (IOException e) {
1117 dirString = file.getAbsolutePath();
1120 List<String> dirStrings = getDirs();
1121 if (dirStrings.contains(dirString)) {
1122 return false;
1123 } else {
1124 Set<String> dirs = new HashSet<String>();
1125 dirs.addAll(dirStrings);
1126 dirs.add(dirString);
1127 saveDirs(dirs);
1128 return true;
1132 private static void saveDirs(Set<String> gitDirStrings) {
1133 StringBuilder sb = new StringBuilder();
1134 for (String gitDirString : gitDirStrings) {
1135 sb.append(gitDirString);
1136 sb.append(File.pathSeparatorChar);
1139 IEclipsePreferences prefs = getPrefs();
1140 prefs.put(PREFS_DIRECTORIES, sb.toString());
1141 try {
1142 prefs.flush();
1143 } catch (BackingStoreException e) {
1144 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1145 e.getMessage(), e);
1146 Activator.getDefault().getLog().log(error);
1150 @Override
1151 public void setFocus() {
1152 // nothing special
1155 @SuppressWarnings("boxing")
1156 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1157 boolean confirmed;
1158 confirmed = MessageDialog
1159 .openConfirm(
1160 getSite().getShell(),
1161 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1163 .bind(
1164 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1165 projectsToDelete.size()));
1166 return confirmed;
1169 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1170 selectionListeners.add(listener);
1173 public ISelection getSelection() {
1174 return currentSelection;
1177 public void removeSelectionChangedListener(
1178 ISelectionChangedListener listener) {
1179 selectionListeners.remove(listener);
1183 public void setSelection(ISelection selection) {
1184 currentSelection = selection;
1185 for (ISelectionChangedListener listener : selectionListeners) {
1186 listener.selectionChanged(new SelectionChangedEvent(
1187 RepositoriesView.this, selection));
1192 * Opens the tree and marks the folder to which a project is pointing
1194 * @param resource
1195 * TODO exceptions?
1197 @SuppressWarnings("unchecked")
1198 public void showResource(IResource resource) {
1199 IProject project = resource.getProject();
1200 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1201 if (mapping == null)
1202 return;
1204 if (addDir(mapping.getRepository().getDirectory())) {
1205 scheduleRefresh();
1206 try {
1207 scheduledJob.join();
1208 } catch (InterruptedException e) {
1209 // ignore here
1213 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1214 .getContentProvider();
1215 RepositoryTreeNode currentNode = null;
1216 Object[] repos = cp.getElements(tv.getInput());
1217 for (Object repo : repos) {
1218 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1219 // TODO equals implementation of Repository?
1220 if (mapping.getRepository().getDirectory().equals(
1221 ((Repository) node.getObject()).getDirectory())) {
1222 for (Object child : cp.getChildren(node)) {
1223 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1224 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1225 currentNode = childNode;
1226 break;
1229 break;
1233 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1235 for (String segment : relPath.segments()) {
1236 for (Object child : cp.getChildren(currentNode)) {
1237 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1238 if (childNode.getObject().getName().equals(segment)) {
1239 currentNode = childNode;
1240 break;
1245 tv.setSelection(new StructuredSelection(currentNode), true);