Improve RepositoriesView performance part 2
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blobc94ee2f4f66b55637d0ac982f53d94718c8630f8
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.lang.reflect.InvocationTargetException;
16 import java.net.URISyntaxException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.StringTokenizer;
24 import java.util.TreeSet;
26 import org.eclipse.core.filesystem.EFS;
27 import org.eclipse.core.filesystem.IFileStore;
28 import org.eclipse.core.resources.IProject;
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.jobs.IJobChangeEvent;
42 import org.eclipse.core.runtime.jobs.Job;
43 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
44 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
45 import org.eclipse.core.runtime.preferences.InstanceScope;
46 import org.eclipse.egit.core.op.BranchOperation;
47 import org.eclipse.egit.core.project.RepositoryMapping;
48 import org.eclipse.egit.ui.Activator;
49 import org.eclipse.egit.ui.UIIcons;
50 import org.eclipse.egit.ui.UIText;
51 import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
52 import org.eclipse.egit.ui.internal.clone.GitCreateProjectViaWizardWizard;
53 import org.eclipse.egit.ui.internal.fetch.FetchConfiguredRemoteAction;
54 import org.eclipse.egit.ui.internal.fetch.FetchWizard;
55 import org.eclipse.egit.ui.internal.push.PushConfiguredRemoteAction;
56 import org.eclipse.egit.ui.internal.push.PushWizard;
57 import org.eclipse.egit.ui.internal.repository.tree.RepositoryNode;
58 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
59 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType;
60 import org.eclipse.jface.action.Action;
61 import org.eclipse.jface.action.IAction;
62 import org.eclipse.jface.action.IToolBarManager;
63 import org.eclipse.jface.action.Separator;
64 import org.eclipse.jface.dialogs.MessageDialog;
65 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
66 import org.eclipse.jface.operation.IRunnableWithProgress;
67 import org.eclipse.jface.viewers.IOpenListener;
68 import org.eclipse.jface.viewers.ISelection;
69 import org.eclipse.jface.viewers.ISelectionChangedListener;
70 import org.eclipse.jface.viewers.ISelectionProvider;
71 import org.eclipse.jface.viewers.IStructuredSelection;
72 import org.eclipse.jface.viewers.ITreeContentProvider;
73 import org.eclipse.jface.viewers.OpenEvent;
74 import org.eclipse.jface.viewers.SelectionChangedEvent;
75 import org.eclipse.jface.viewers.StructuredSelection;
76 import org.eclipse.jface.viewers.TreeViewer;
77 import org.eclipse.jface.window.Window;
78 import org.eclipse.jface.wizard.Wizard;
79 import org.eclipse.jface.wizard.WizardDialog;
80 import org.eclipse.jgit.lib.Constants;
81 import org.eclipse.jgit.lib.IndexChangedEvent;
82 import org.eclipse.jgit.lib.Ref;
83 import org.eclipse.jgit.lib.RefUpdate;
84 import org.eclipse.jgit.lib.RefsChangedEvent;
85 import org.eclipse.jgit.lib.Repository;
86 import org.eclipse.jgit.lib.RepositoryCache;
87 import org.eclipse.jgit.lib.RepositoryConfig;
88 import org.eclipse.jgit.lib.RepositoryListener;
89 import org.eclipse.jgit.transport.RemoteConfig;
90 import org.eclipse.osgi.util.NLS;
91 import org.eclipse.swt.SWT;
92 import org.eclipse.swt.dnd.Clipboard;
93 import org.eclipse.swt.dnd.TextTransfer;
94 import org.eclipse.swt.dnd.Transfer;
95 import org.eclipse.swt.events.MenuDetectEvent;
96 import org.eclipse.swt.events.MenuDetectListener;
97 import org.eclipse.swt.events.SelectionAdapter;
98 import org.eclipse.swt.events.SelectionEvent;
99 import org.eclipse.swt.graphics.Point;
100 import org.eclipse.swt.widgets.Composite;
101 import org.eclipse.swt.widgets.Display;
102 import org.eclipse.swt.widgets.Menu;
103 import org.eclipse.swt.widgets.MenuItem;
104 import org.eclipse.swt.widgets.TreeItem;
105 import org.eclipse.ui.IEditorInput;
106 import org.eclipse.ui.IEditorPart;
107 import org.eclipse.ui.IFileEditorInput;
108 import org.eclipse.ui.IPageLayout;
109 import org.eclipse.ui.ISelectionListener;
110 import org.eclipse.ui.ISelectionService;
111 import org.eclipse.ui.IViewPart;
112 import org.eclipse.ui.IWorkbenchPart;
113 import org.eclipse.ui.PartInitException;
114 import org.eclipse.ui.PlatformUI;
115 import org.eclipse.ui.actions.ActionFactory;
116 import org.eclipse.ui.editors.text.EditorsUI;
117 import org.eclipse.ui.ide.FileStoreEditorInput;
118 import org.eclipse.ui.ide.IDE;
119 import org.eclipse.ui.part.IShowInTarget;
120 import org.eclipse.ui.part.ShowInContext;
121 import org.eclipse.ui.part.ViewPart;
122 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
123 import org.eclipse.ui.views.properties.IPropertySheetPage;
124 import org.eclipse.ui.views.properties.PropertySheet;
125 import org.eclipse.ui.views.properties.PropertySheetPage;
126 import org.osgi.service.prefs.BackingStoreException;
130 * The Git Repositories view.
131 * <p>
132 * This keeps track of a bunch of local directory names each of which represent
133 * a Git Repository. This list is stored in some Preferences object and used to
134 * build the tree in the view.
135 * <p>
136 * Implements {@link ISelectionProvider} in order to integrate with the
137 * Properties view.
138 * <p>
139 * This periodically refreshes itself in order to react on Repository changes.
141 public class RepositoriesView extends ViewPart implements ISelectionProvider,
142 IShowInTarget {
144 /** The view ID */
145 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
147 // TODO central constants? RemoteConfig ones are private
148 static final String REMOTE = "remote"; //$NON-NLS-1$
150 static final String URL = "url"; //$NON-NLS-1$
152 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
154 static final String FETCH = "fetch"; //$NON-NLS-1$
156 static final String PUSH = "push"; //$NON-NLS-1$
158 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
160 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
162 private final Set<Repository> repositories = new HashSet<Repository>();
164 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
166 private RepositoryListener repositoryListener;
168 private ISelection currentSelection = new StructuredSelection();
170 private Job scheduledJob;
172 private TreeViewer tv;
174 private IAction importAction;
176 private IAction addAction;
178 private IAction refreshAction;
180 private IAction linkWithSelectionAction;
182 private IAction copyAction;
184 private IAction pasteAction;
187 * TODO move to utility class
189 * @return the directories as configured for this view
191 public static List<String> getDirs() {
192 List<String> resultStrings = new ArrayList<String>();
193 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
194 if (dirs != null && dirs.length() > 0) {
195 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
196 while (tok.hasMoreTokens()) {
197 String dirName = tok.nextToken();
198 File testFile = new File(dirName);
199 if (testFile.exists() && !resultStrings.contains(dirName)) {
200 resultStrings.add(dirName);
204 Collections.sort(resultStrings);
205 return resultStrings;
208 private static void removeDir(File file) {
210 String dir;
211 try {
212 dir = file.getCanonicalPath();
213 } catch (IOException e1) {
214 dir = file.getAbsolutePath();
217 IEclipsePreferences prefs = getPrefs();
219 TreeSet<String> resultStrings = new TreeSet<String>();
220 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
221 if (dirs != null && dirs.length() > 0) {
222 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
223 while (tok.hasMoreTokens()) {
224 String dirName = tok.nextToken();
225 File testFile = new File(dirName);
226 if (testFile.exists()) {
227 try {
228 resultStrings.add(testFile.getCanonicalPath());
229 } catch (IOException e) {
230 resultStrings.add(testFile.getAbsolutePath());
236 if (resultStrings.remove(dir)) {
237 StringBuilder sb = new StringBuilder();
238 for (String gitDirString : resultStrings) {
239 sb.append(gitDirString);
240 sb.append(File.pathSeparatorChar);
243 prefs.put(PREFS_DIRECTORIES, sb.toString());
244 try {
245 prefs.flush();
246 } catch (BackingStoreException e) {
247 IStatus error = new Status(IStatus.ERROR, Activator
248 .getPluginId(), e.getMessage(), e);
249 Activator.getDefault().getLog().log(error);
255 @Override
256 public Object getAdapter(Class adapter) {
257 // integrate with Properties view
258 if (adapter == IPropertySheetPage.class) {
259 PropertySheetPage page = new PropertySheetPage();
260 page
261 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
262 page));
263 return page;
266 return super.getAdapter(adapter);
269 @Override
270 public void createPartControl(Composite parent) {
271 tv = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
272 tv.setContentProvider(new RepositoriesViewContentProvider());
273 // the label provider registers itself
274 new RepositoriesViewLabelProvider(tv);
276 getSite().setSelectionProvider(this);
278 tv.addSelectionChangedListener(new ISelectionChangedListener() {
280 public void selectionChanged(SelectionChangedEvent event) {
282 copyAction.setEnabled(false);
284 IStructuredSelection ssel = (IStructuredSelection) event
285 .getSelection();
286 if (ssel.size() == 1) {
287 RepositoryTreeNode node = (RepositoryTreeNode) ssel
288 .getFirstElement();
289 // allow copy on repository, file, or folder (copying the
290 // directory)
291 if (node.getType() == RepositoryTreeNodeType.REPO
292 || node.getType() == RepositoryTreeNodeType.WORKINGDIR
293 || node.getType() == RepositoryTreeNodeType.FOLDER
294 || node.getType() == RepositoryTreeNodeType.FILE) {
295 copyAction.setEnabled(true);
297 setSelection(new StructuredSelection(ssel.getFirstElement()));
298 } else {
299 setSelection(new StructuredSelection());
304 tv.addOpenListener(new IOpenListener() {
305 public void open(OpenEvent event) {
306 IStructuredSelection selection = (IStructuredSelection) event
307 .getSelection();
308 if (selection.isEmpty()) {
309 // nothing selected, ignore
310 return;
313 Object element = selection.getFirstElement();
314 ITreeContentProvider contentProvider = (ITreeContentProvider) tv
315 .getContentProvider();
316 if (contentProvider.hasChildren(element)) {
317 // this element has children, expand/collapse it
318 tv.setExpandedState(element, !tv.getExpandedState(element));
319 } else {
320 Object[] selectionArray = selection.toArray();
321 for (Object selectedElement : selectionArray) {
322 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
323 // if any of the selected elements are not files, ignore
324 // the open request
325 if (node.getType() != RepositoryTreeNodeType.FILE
326 && node.getType() != RepositoryTreeNodeType.REF
327 && node.getType() != RepositoryTreeNodeType.TAG) {
328 return;
332 // open the files the user has selected
333 for (Object selectedElement : selectionArray) {
334 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
335 if (node.getType() == RepositoryTreeNodeType.FILE)
336 openFile((File) node.getObject());
337 else if (node.getType() == RepositoryTreeNodeType.REF
338 || node.getType() == RepositoryTreeNodeType.TAG) {
339 Ref ref = (Ref) node.getObject();
340 if (!isBare(node.getRepository())
341 && ref.getName().startsWith(
342 Constants.R_REFS))
343 checkoutBranch(node, ref.getName());
350 createRepositoryChangedListener();
352 addContextMenu();
354 addActionsToToolbar();
356 scheduleRefresh();
358 ISelectionService srv = (ISelectionService) getSite().getService(
359 ISelectionService.class);
360 srv.addPostSelectionListener(new ISelectionListener() {
362 public void selectionChanged(IWorkbenchPart part,
363 ISelection selection) {
365 // if the "link with selection" toggle is off, we're done
366 if (linkWithSelectionAction == null
367 || !linkWithSelectionAction.isChecked())
368 return;
370 // this may happen if we switch between editors
371 if (part instanceof IEditorPart) {
372 IEditorInput input = ((IEditorPart) part).getEditorInput();
373 if (input instanceof IFileEditorInput)
374 reactOnSelection(new StructuredSelection(
375 ((IFileEditorInput) input).getFile()));
377 } else {
378 reactOnSelection(selection);
385 private void reactOnSelection(ISelection selection) {
386 if (selection instanceof StructuredSelection) {
387 StructuredSelection ssel = (StructuredSelection) selection;
388 if (ssel.size() != 1)
389 return;
390 if (ssel.getFirstElement() instanceof IResource) {
391 showResource((IResource) ssel.getFirstElement());
393 if (ssel.getFirstElement() instanceof IAdaptable) {
394 IResource adapted = (IResource) ((IAdaptable) ssel
395 .getFirstElement()).getAdapter(IResource.class);
396 if (adapted != null)
397 showResource(adapted);
402 private void addContextMenu() {
403 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
405 public void menuDetected(MenuDetectEvent e) {
406 Menu men = tv.getTree().getMenu();
407 if (men != null) {
408 men.dispose();
410 men = new Menu(tv.getTree());
412 TreeItem testItem = tv.getTree().getItem(
413 tv.getTree().toControl(new Point(e.x, e.y)));
414 if (testItem == null) {
415 addMenuItemsForPanel(men);
416 } else {
417 addMenuItemsForTreeSelection(men);
420 tv.getTree().setMenu(men);
425 private void addMenuItemsForPanel(Menu men) {
427 MenuItem importItem = new MenuItem(men, SWT.PUSH);
428 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
429 importItem.addSelectionListener(new SelectionAdapter() {
431 @Override
432 public void widgetSelected(SelectionEvent e) {
433 importAction.run();
438 MenuItem addItem = new MenuItem(men, SWT.PUSH);
439 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
440 addItem.addSelectionListener(new SelectionAdapter() {
442 @Override
443 public void widgetSelected(SelectionEvent e) {
444 addAction.run();
449 MenuItem pasteItem = new MenuItem(men, SWT.PUSH);
450 pasteItem.setText(UIText.RepositoriesView_PasteMenu);
451 pasteItem.addSelectionListener(new SelectionAdapter() {
453 @Override
454 public void widgetSelected(SelectionEvent e) {
455 pasteAction.run();
460 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
461 refreshItem.setText(refreshAction.getText());
462 refreshItem.addSelectionListener(new SelectionAdapter() {
464 @Override
465 public void widgetSelected(SelectionEvent e) {
466 refreshAction.run();
473 private void addMenuItemsForTreeSelection(Menu men) {
475 final IStructuredSelection sel = (IStructuredSelection) tv
476 .getSelection();
478 boolean repoOnly = true;
479 for (Object selected : sel.toArray()) {
481 if (((RepositoryTreeNode) selected).getType() != RepositoryTreeNodeType.REPO) {
482 repoOnly = false;
483 break;
487 if (sel.size() > 1 && repoOnly) {
488 List nodes = sel.toList();
489 final Repository[] repos = new Repository[nodes.size()];
490 for (int i = 0; i < sel.size(); i++)
491 repos[i] = ((RepositoryTreeNode) nodes.get(i)).getRepository();
493 MenuItem remove = new MenuItem(men, SWT.PUSH);
494 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
495 remove.addSelectionListener(new SelectionAdapter() {
497 @Override
498 public void widgetSelected(SelectionEvent e) {
499 // TODO progress monitoring/cancellation
500 removeRepository(new NullProgressMonitor(), repos);
506 // from here on, we only deal with single selection
507 if (sel.size() > 1)
508 return;
510 final RepositoryTreeNode node = (RepositoryTreeNode) sel
511 .getFirstElement();
513 final boolean isBare = isBare(node.getRepository());
515 if (node.getType() == RepositoryTreeNodeType.REF) {
517 final Ref ref = (Ref) node.getObject();
519 // we don't check out symbolic references
520 if (!ref.isSymbolic()) {
522 if (!isBare) {
523 MenuItem checkout = new MenuItem(men, SWT.PUSH);
524 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
526 checkout.setEnabled(!isRefCheckedOut(node.getRepository(),
527 ref.getName()));
529 checkout.addSelectionListener(new SelectionAdapter() {
531 @Override
532 public void widgetSelected(SelectionEvent e) {
533 checkoutBranch(node, ref.getLeaf().getName());
537 new MenuItem(men, SWT.SEPARATOR);
540 createCreateBranchItem(men, node);
541 createDeleteBranchItem(men, node);
546 if (node.getType() == RepositoryTreeNodeType.TAG) {
548 final Ref ref = (Ref) node.getObject();
550 MenuItem checkout = new MenuItem(men, SWT.PUSH);
551 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
553 checkout.setEnabled(!isRefCheckedOut(node.getRepository(), ref
554 .getName()));
556 checkout.addSelectionListener(new SelectionAdapter() {
558 @Override
559 public void widgetSelected(SelectionEvent e) {
560 checkoutBranch(node, ref.getLeaf().getName());
565 if (node.getType() == RepositoryTreeNodeType.BRANCHES
566 || node.getType() == RepositoryTreeNodeType.LOCALBRANCHES)
567 // offering this on the "Remote Branches" node would probably be
568 // confusing
569 createCreateBranchItem(men, node);
571 // for Repository: import existing projects, remove, (delete), open
572 // properties
573 if (node.getType() == RepositoryTreeNodeType.REPO) {
575 final Repository repo = (Repository) node.getObject();
577 // TODO "import existing plug-in" menu item
579 MenuItem remove = new MenuItem(men, SWT.PUSH);
580 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
581 remove.addSelectionListener(new SelectionAdapter() {
583 @Override
584 public void widgetSelected(SelectionEvent e) {
585 // TODO progress monitoring/cancellation
586 removeRepository(new NullProgressMonitor(), repo);
590 new MenuItem(men, SWT.SEPARATOR);
592 MenuItem fetchItem = new MenuItem(men, SWT.PUSH);
593 fetchItem.setText(UIText.RepositoriesView_FetchMenu);
594 fetchItem.setImage(UIIcons.FETCH.createImage());
595 fetchItem.addSelectionListener(new SelectionAdapter() {
597 @Override
598 public void widgetSelected(SelectionEvent e) {
599 try {
600 new WizardDialog(getSite().getShell(), new FetchWizard(
601 repo)).open();
602 } catch (URISyntaxException e1) {
603 Activator.handleError(e1.getMessage(), e1, true);
609 MenuItem pushItem = new MenuItem(men, SWT.PUSH);
610 pushItem.setText(UIText.RepositoriesView_PushMenu);
611 pushItem.setImage(UIIcons.PUSH.createImage());
612 pushItem.addSelectionListener(new SelectionAdapter() {
614 @Override
615 public void widgetSelected(SelectionEvent e) {
616 try {
617 new WizardDialog(getSite().getShell(), new PushWizard(
618 repo)).open();
619 } catch (URISyntaxException e1) {
620 Activator.handleError(e1.getMessage(), e1, true);
626 // TODO delete does not work because of file locks on .pack-files
627 // Shawn Pearce has added the following thoughts:
629 // Hmm. We probably can't active detect file locks on pack files on
630 // Windows, can we?
631 // It would be nice if we could support a delete, but only if the
632 // repository is
633 // reasonably believed to be not-in-use right now.
635 // Within EGit you might be able to check GitProjectData and its
636 // repositoryCache to
637 // see if the repository is open by this workspace. If it is, then
638 // we know we shouldn't
639 // try to delete it.
641 // Some coding might look like this:
643 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
644 // deleteRepo.setText("Delete");
645 // deleteRepo.addSelectionListener(new SelectionAdapter() {
647 // @Override
648 // public void widgetSelected(SelectionEvent e) {
650 // boolean confirmed = MessageDialog.openConfirm(getSite()
651 // .getShell(), "Confirm",
652 // "This will delete the repository, continue?");
654 // if (!confirmed)
655 // return;
657 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
659 // public void run(IProgressMonitor monitor)
660 // throws CoreException {
661 // File workDir = repos.get(0).getRepository()
662 // .getWorkDir();
664 // File gitDir = repos.get(0).getRepository()
665 // .getDirectory();
667 // IPath wdPath = new Path(workDir.getAbsolutePath());
668 // for (IProject prj : ResourcesPlugin.getWorkspace()
669 // .getRoot().getProjects()) {
670 // if (wdPath.isPrefixOf(prj.getLocation())) {
671 // prj.delete(false, false, monitor);
672 // }
673 // }
675 // repos.get(0).getRepository().close();
677 // boolean deleted = deleteRecursively(gitDir, monitor);
678 // if (!deleted) {
679 // MessageDialog.openError(getSite().getShell(),
680 // "Error",
681 // "Could not delete Git Repository");
682 // }
684 // deleted = deleteRecursively(workDir, monitor);
685 // if (!deleted) {
686 // MessageDialog
687 // .openError(getSite().getShell(),
688 // "Error",
689 // "Could not delete Git Working Directory");
690 // }
692 // scheduleRefresh();
693 // }
695 // private boolean deleteRecursively(File fileToDelete,
696 // IProgressMonitor monitor) {
697 // if (fileToDelete.isDirectory()) {
698 // for (File file : fileToDelete.listFiles()) {
699 // if (!deleteRecursively(file, monitor)) {
700 // return false;
701 // }
702 // }
703 // }
704 // monitor.setTaskName(fileToDelete.getAbsolutePath());
705 // boolean deleted = fileToDelete.delete();
706 // if (!deleted) {
707 // System.err.println("Could not delete "
708 // + fileToDelete.getAbsolutePath());
709 // }
710 // return deleted;
711 // }
712 // };
714 // try {
715 // ResourcesPlugin.getWorkspace().run(wsr,
716 // ResourcesPlugin.getWorkspace().getRoot(),
717 // IWorkspace.AVOID_UPDATE,
718 // new NullProgressMonitor());
719 // } catch (CoreException e1) {
720 // // TODO Exception handling
721 // e1.printStackTrace();
722 // }
724 // }
726 // });
728 new MenuItem(men, SWT.SEPARATOR);
730 if (!isBare) {
731 createImportProjectItem(men, repo, repo.getWorkDir().getPath());
733 new MenuItem(men, SWT.SEPARATOR);
736 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
737 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
738 openPropsView.addSelectionListener(new SelectionAdapter() {
740 @Override
741 public void widgetSelected(SelectionEvent e) {
742 try {
743 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
744 .getActivePage().showView(
745 IPageLayout.ID_PROP_SHEET);
746 } catch (PartInitException e1) {
747 // just ignore
753 new MenuItem(men, SWT.SEPARATOR);
755 createCopyPathItem(men, repo.getDirectory().getPath());
758 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
760 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
761 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
762 remoteConfig.addSelectionListener(new SelectionAdapter() {
764 @Override
765 public void widgetSelected(SelectionEvent e) {
767 WizardDialog dlg = new WizardDialog(getSite().getShell(),
768 new NewRemoteWizard(node.getRepository()));
769 if (dlg.open() == Window.OK)
770 tv.refresh();
777 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
779 final String configName = (String) node.getObject();
781 RemoteConfig rconfig;
782 try {
783 rconfig = new RemoteConfig(node.getRepository().getConfig(),
784 configName);
785 } catch (URISyntaxException e2) {
786 // TODO Exception handling
787 rconfig = null;
790 boolean fetchExists = rconfig != null
791 && !rconfig.getURIs().isEmpty();
792 boolean pushExists = rconfig != null
793 && !rconfig.getPushURIs().isEmpty();
795 if (!fetchExists) {
796 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
797 configureUrlFetch
798 .setText(UIText.RepositoriesView_CreateFetch_menu);
800 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
802 @Override
803 public void widgetSelected(SelectionEvent e) {
805 WizardDialog dlg = new WizardDialog(getSite()
806 .getShell(), new ConfigureRemoteWizard(node
807 .getRepository(), configName, false));
808 if (dlg.open() == Window.OK)
809 tv.refresh();
816 if (!pushExists) {
817 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
819 configureUrlPush
820 .setText(UIText.RepositoriesView_CreatePush_menu);
822 configureUrlPush.addSelectionListener(new SelectionAdapter() {
824 @Override
825 public void widgetSelected(SelectionEvent e) {
827 WizardDialog dlg = new WizardDialog(getSite()
828 .getShell(), new ConfigureRemoteWizard(node
829 .getRepository(), configName, true));
830 if (dlg.open() == Window.OK)
831 tv.refresh();
838 if (!fetchExists || !pushExists)
839 // add a separator dynamically
840 new MenuItem(men, SWT.SEPARATOR);
842 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
843 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
844 removeRemote.addSelectionListener(new SelectionAdapter() {
846 @Override
847 public void widgetSelected(SelectionEvent e) {
849 boolean ok = MessageDialog
850 .openConfirm(
851 getSite().getShell(),
852 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
854 .bind(
855 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
856 configName));
857 if (ok) {
858 RepositoryConfig config = node.getRepository()
859 .getConfig();
860 config.unsetSection(REMOTE, configName);
861 try {
862 config.save();
863 tv.refresh();
864 } catch (IOException e1) {
865 Activator.handleError(
866 UIText.RepositoriesView_ErrorHeader, e1,
867 true);
875 new MenuItem(men, SWT.SEPARATOR);
877 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
878 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
879 openPropsView.addSelectionListener(new SelectionAdapter() {
881 @Override
882 public void widgetSelected(SelectionEvent e) {
883 try {
884 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
885 .getActivePage().showView(
886 IPageLayout.ID_PROP_SHEET);
887 } catch (PartInitException e1) {
888 // just ignore
895 if (node.getType() == RepositoryTreeNodeType.FETCH) {
897 final String configName = (String) node.getParent().getObject();
899 MenuItem doFetch = new MenuItem(men, SWT.PUSH);
900 doFetch.setText(UIText.RepositoriesView_DoFetchMenu);
901 doFetch.addSelectionListener(new SelectionAdapter() {
903 @Override
904 public void widgetSelected(SelectionEvent evt) {
905 new FetchConfiguredRemoteAction(node.getRepository(),
906 configName).run(getSite().getShell());
911 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
912 configureUrlFetch
913 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
915 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
917 @Override
918 public void widgetSelected(SelectionEvent e) {
920 WizardDialog dlg = new WizardDialog(getSite().getShell(),
921 new ConfigureRemoteWizard(node.getRepository(),
922 configName, false));
923 if (dlg.open() == Window.OK)
924 tv.refresh();
930 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
931 deleteFetch.setText(UIText.RepositoriesView_RemoveFetch_menu);
932 deleteFetch.addSelectionListener(new SelectionAdapter() {
934 @Override
935 public void widgetSelected(SelectionEvent e) {
936 RepositoryConfig config = node.getRepository().getConfig();
937 config.unset("remote", configName, "url"); //$NON-NLS-1$ //$NON-NLS-2$
938 config.unset("remote", configName, "fetch"); //$NON-NLS-1$//$NON-NLS-2$
939 try {
940 config.save();
941 tv.refresh();
942 } catch (IOException e1) {
943 MessageDialog.openError(getSite().getShell(),
944 UIText.RepositoriesView_ErrorHeader, e1
945 .getMessage());
953 if (node.getType() == RepositoryTreeNodeType.PUSH) {
955 final String configName = (String) node.getParent().getObject();
957 MenuItem doPush = new MenuItem(men, SWT.PUSH);
958 doPush.setText(UIText.RepositoriesView_DoPushMenuItem);
959 doPush.addSelectionListener(new SelectionAdapter() {
961 @Override
962 public void widgetSelected(SelectionEvent evt) {
963 new PushConfiguredRemoteAction(node.getRepository(),
964 configName).run(getSite().getShell(), false);
968 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
969 configureUrlPush.setText(UIText.RepositoriesView_ConfigurePushMenu);
971 configureUrlPush.addSelectionListener(new SelectionAdapter() {
973 @Override
974 public void widgetSelected(SelectionEvent e) {
976 WizardDialog dlg = new WizardDialog(getSite().getShell(),
977 new ConfigureRemoteWizard(node.getRepository(),
978 configName, true));
979 if (dlg.open() == Window.OK)
980 tv.refresh();
985 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
986 deleteFetch.setText(UIText.RepositoriesView_RemovePush_menu);
987 deleteFetch.addSelectionListener(new SelectionAdapter() {
989 @Override
990 public void widgetSelected(SelectionEvent e) {
991 RepositoryConfig config = node.getRepository().getConfig();
992 config.unset("remote", configName, "pushurl"); //$NON-NLS-1$ //$NON-NLS-2$
993 config.unset("remote", configName, "push"); //$NON-NLS-1$ //$NON-NLS-2$
994 try {
995 config.save();
996 tv.refresh();
997 } catch (IOException e1) {
998 MessageDialog.openError(getSite().getShell(),
999 UIText.RepositoriesView_ErrorHeader, e1
1000 .getMessage());
1007 if (node.getType() == RepositoryTreeNodeType.FILE) {
1009 final File file = (File) node.getObject();
1011 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
1012 openInTextEditor
1013 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
1014 openInTextEditor.addSelectionListener(new SelectionAdapter() {
1016 @Override
1017 public void widgetSelected(SelectionEvent e) {
1018 openFile(file);
1023 new MenuItem(men, SWT.SEPARATOR);
1024 createCopyPathItem(men, file.getPath());
1027 if (!isBare && node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1028 String path = node.getRepository().getWorkDir().getAbsolutePath();
1029 createImportProjectItem(men, node.getRepository(), path);
1030 new MenuItem(men, SWT.SEPARATOR);
1031 createCopyPathItem(men, path);
1034 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
1035 String path = ((File) node.getObject()).getPath();
1036 createImportProjectItem(men, node.getRepository(), path);
1037 new MenuItem(men, SWT.SEPARATOR);
1038 createCopyPathItem(men, path);
1043 private boolean isBare(Repository repository) {
1044 return repository.getConfig().getBoolean("core", "bare", false); //$NON-NLS-1$ //$NON-NLS-2$
1047 private boolean isRefCheckedOut(Repository repository, String refName) {
1048 String branchName;
1049 String compareString;
1051 try {
1052 branchName = repository.getFullBranch();
1053 if (branchName == null)
1054 return false;
1055 if (refName.startsWith(Constants.R_HEADS)) {
1056 // local branch: HEAD would be on the branch
1057 compareString = refName;
1058 } else if (refName.startsWith(Constants.R_TAGS)) {
1059 // tag: HEAD would be on the commit id to which the tag is
1060 // pointing
1061 compareString = repository.mapTag(refName).getObjId().getName();
1062 } else if (refName.startsWith(Constants.R_REMOTES)) {
1063 // remote branch: HEAD would be on the commit id to which
1064 // the branch is pointing
1065 compareString = repository.mapCommit(refName).getCommitId()
1066 .getName();
1067 } else {
1068 // some other symbolic reference
1069 return false;
1071 } catch (IOException e1) {
1072 return false;
1075 return compareString.equals(branchName);
1078 private void createCopyPathItem(Menu men, final String path) {
1080 MenuItem copyPath;
1081 copyPath = new MenuItem(men, SWT.PUSH);
1082 copyPath.setText(UIText.RepositoriesView_CopyPathToClipboardMenu);
1083 copyPath.addSelectionListener(new SelectionAdapter() {
1085 @Override
1086 public void widgetSelected(SelectionEvent e) {
1087 Clipboard clipboard = new Clipboard(null);
1088 TextTransfer textTransfer = TextTransfer.getInstance();
1089 Transfer[] transfers = new Transfer[] { textTransfer };
1090 Object[] data = new Object[] { path };
1091 clipboard.setContents(data, transfers);
1092 clipboard.dispose();
1099 private void createCreateBranchItem(Menu men, final RepositoryTreeNode node) {
1100 final Ref ref;
1101 if (node.getType() == RepositoryTreeNodeType.REF)
1102 ref = (Ref) node.getObject();
1103 else
1104 ref = null;
1106 MenuItem createLocal = new MenuItem(men, SWT.PUSH);
1107 createLocal.setText(UIText.RepositoriesView_NewBranchMenu);
1109 createLocal.addSelectionListener(new SelectionAdapter() {
1111 @Override
1112 public void widgetSelected(SelectionEvent e) {
1114 Wizard wiz = new Wizard() {
1116 @Override
1117 public void addPages() {
1118 addPage(new CreateBranchPage(node.getRepository(), ref));
1119 setWindowTitle(UIText.RepositoriesView_NewBranchTitle);
1122 @Override
1123 public boolean performFinish() {
1125 try {
1126 getContainer().run(false, true,
1127 new IRunnableWithProgress() {
1129 public void run(IProgressMonitor monitor)
1130 throws InvocationTargetException,
1131 InterruptedException {
1132 CreateBranchPage cp = (CreateBranchPage) getPages()[0];
1133 try {
1134 cp.createBranch(monitor);
1135 } catch (CoreException ce) {
1136 throw new InvocationTargetException(
1137 ce);
1138 } catch (IOException ioe) {
1139 throw new InvocationTargetException(
1140 ioe);
1145 } catch (InvocationTargetException ite) {
1146 Activator
1147 .handleError(
1148 UIText.RepositoriesView_BranchCreationFailureMessage,
1149 ite.getCause(), true);
1150 return false;
1151 } catch (InterruptedException ie) {
1152 // ignore here
1154 return true;
1157 if (new WizardDialog(getSite().getShell(), wiz).open() == Window.OK)
1158 tv.refresh();
1164 private void createDeleteBranchItem(Menu men, final RepositoryTreeNode node) {
1166 final Ref ref = (Ref) node.getObject();
1168 MenuItem deleteBranch = new MenuItem(men, SWT.PUSH);
1169 deleteBranch.setText(UIText.RepositoriesView_DeleteBranchMenu);
1171 deleteBranch.setEnabled(!isRefCheckedOut(node.getRepository(), ref
1172 .getName()));
1174 deleteBranch.addSelectionListener(new SelectionAdapter() {
1176 @Override
1177 public void widgetSelected(SelectionEvent e) {
1179 if (!MessageDialog
1180 .openConfirm(
1181 getSite().getShell(),
1182 UIText.RepositoriesView_ConfirmDeleteTitle,
1184 .bind(
1185 UIText.RepositoriesView_ConfirmBranchDeletionMessage,
1186 ref.getName())))
1187 return;
1189 try {
1190 new ProgressMonitorDialog(getSite().getShell()).run(false,
1191 false, new IRunnableWithProgress() {
1193 public void run(IProgressMonitor monitor)
1194 throws InvocationTargetException,
1195 InterruptedException {
1197 try {
1198 RefUpdate op = node.getRepository()
1199 .updateRef(ref.getName());
1200 op.setRefLogMessage("branch deleted", //$NON-NLS-1$
1201 false);
1202 // we set the force update in order
1203 // to avoid having this rejected
1204 // due to minor issues
1205 op.setForceUpdate(true);
1206 op.delete();
1207 tv.refresh();
1208 } catch (IOException ioe) {
1209 throw new InvocationTargetException(ioe);
1214 } catch (InvocationTargetException e1) {
1215 Activator
1216 .handleError(
1217 UIText.RepositoriesView_BranchDeletionFailureMessage,
1218 e1.getCause(), true);
1219 e1.printStackTrace();
1220 } catch (InterruptedException e1) {
1221 // ignore
1229 private void openFile(File file) {
1230 IFileStore store = EFS.getLocalFileSystem().getStore(
1231 new Path(file.getAbsolutePath()));
1232 try {
1233 // TODO do we need a read-only editor here?
1234 IDE.openEditor(getSite().getPage(),
1235 new FileStoreEditorInput(store),
1236 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
1237 } catch (PartInitException e) {
1238 Activator.handleError(UIText.RepositoriesView_Error_WindowTitle, e,
1239 true);
1243 private void checkoutBranch(final RepositoryTreeNode node,
1244 final String refName) {
1245 // for the sake of UI responsiveness, let's start a job
1246 Job job = new Job(NLS.bind(UIText.RepositoriesView_CheckingOutMessage,
1247 refName)) {
1249 @Override
1250 protected IStatus run(IProgressMonitor monitor) {
1252 Repository repo = node.getRepository();
1254 final BranchOperation op = new BranchOperation(repo, refName);
1255 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1257 public void run(IProgressMonitor myMonitor)
1258 throws CoreException {
1259 op.execute(myMonitor);
1263 try {
1264 ResourcesPlugin.getWorkspace().run(wsr,
1265 ResourcesPlugin.getWorkspace().getRoot(),
1266 IWorkspace.AVOID_UPDATE, monitor);
1267 Display.getDefault().syncExec(new Runnable() {
1269 public void run() {
1270 tv.refresh();
1274 } catch (CoreException e1) {
1275 return new Status(IStatus.ERROR, Activator.getPluginId(),
1276 e1.getMessage(), e1);
1279 return Status.OK_STATUS;
1283 job.setUser(true);
1284 job.schedule();
1287 private void createImportProjectItem(Menu men, final Repository repo,
1288 final String path) {
1290 MenuItem startWizard;
1291 startWizard = new MenuItem(men, SWT.PUSH);
1292 startWizard.setText(UIText.RepositoriesView_ImportProjectsMenu);
1293 startWizard.addSelectionListener(new SelectionAdapter() {
1295 @Override
1296 public void widgetSelected(SelectionEvent e) {
1297 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1298 new GitCreateProjectViaWizardWizard(repo, path));
1299 dlg.open();
1304 // we could start the ImportWizard here,
1305 // unfortunately, this fails within a wizard
1306 // startWizard = new MenuItem(men, SWT.PUSH);
1307 // startWizard.setText("Start the Import wizard...");
1308 // startWizard.addSelectionListener(new SelectionAdapter() {
1310 // @Override
1311 // public void widgetSelected(SelectionEvent e) {
1313 // IHandlerService handlerService = (IHandlerService) getSite()
1314 // .getWorkbenchWindow().getWorkbench().getService(
1315 // IHandlerService.class);
1317 // try {
1318 // handlerService.executeCommand("org.eclipse.ui.file.import", //$NON-NLS-1$
1319 // null);
1320 // } catch (ExecutionException e1) {
1321 // Activator.handleError(e1.getMessage(), e1, true);
1322 // } catch (NotDefinedException e1) {
1323 // Activator.handleError(e1.getMessage(), e1, true);
1324 // } catch (NotEnabledException e1) {
1325 // Activator.handleError(e1.getMessage(), e1, true);
1326 // } catch (NotHandledException e1) {
1327 // Activator.handleError(e1.getMessage(), e1, true);
1328 // }
1329 // }
1331 // });
1334 private void addActionsToToolbar() {
1336 IToolBarManager manager = getViewSite().getActionBars()
1337 .getToolBarManager();
1339 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
1341 @Override
1342 public void run() {
1343 scheduleRefresh();
1346 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1347 manager.add(refreshAction);
1349 linkWithSelectionAction = new Action(
1350 UIText.RepositoriesView_LinkWithSelection_action,
1351 IAction.AS_CHECK_BOX) {
1353 @Override
1354 public void run() {
1355 IEclipsePreferences prefs = getPrefs();
1356 prefs.putBoolean(PREFS_SYNCED, isChecked());
1357 try {
1358 prefs.flush();
1359 } catch (BackingStoreException e) {
1360 // ignore here
1362 if (isChecked()) {
1363 ISelectionService srv = (ISelectionService) getSite()
1364 .getService(ISelectionService.class);
1365 reactOnSelection(srv.getSelection());
1372 linkWithSelectionAction
1373 .setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
1374 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1375 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
1376 false));
1378 manager.add(linkWithSelectionAction);
1380 manager.add(new Separator());
1382 IAction collapseAllAction = new Action(
1383 UIText.RepositoriesView_CollapseAllMenu) {
1385 @Override
1386 public void run() {
1387 tv.collapseAll();
1390 collapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1391 manager.add(collapseAllAction);
1393 manager.add(new Separator());
1395 importAction = new Action(UIText.RepositoriesView_Import_Button) {
1397 @Override
1398 public void run() {
1399 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1400 new GitCloneWizard());
1401 if (dlg.open() == Window.OK)
1402 scheduleRefresh();
1405 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
1406 importAction.setImageDescriptor(UIIcons.CLONEGIT);
1408 manager.add(importAction);
1410 addAction = new Action(UIText.RepositoriesView_Add_Button) {
1412 @Override
1413 public void run() {
1414 RepositorySearchDialog sd = new RepositorySearchDialog(
1415 getSite().getShell(), getDirs());
1416 if (sd.open() == Window.OK) {
1417 Set<String> dirs = new HashSet<String>();
1418 dirs.addAll(getDirs());
1419 if (dirs.addAll(sd.getDirectories()))
1420 saveDirs(dirs);
1421 scheduleRefresh();
1426 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
1427 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
1429 manager.add(addAction);
1431 // copy and paste are global actions; we just implement them
1432 // and register them with the global action handler
1433 // we enable/disable them upon tree selection changes
1435 copyAction = new Action("") { //$NON-NLS-1$
1437 @Override
1438 public void run() {
1439 // for REPO, WORKINGDIR, FILE, FOLDER: copy directory
1440 IStructuredSelection sel = (IStructuredSelection) tv
1441 .getSelection();
1442 if (sel.size() == 1) {
1443 RepositoryTreeNode node = (RepositoryTreeNode) sel
1444 .getFirstElement();
1445 String dir = null;
1446 if (node.getType() == RepositoryTreeNodeType.REPO) {
1447 dir = node.getRepository().getDirectory().getPath();
1448 } else if (node.getType() == RepositoryTreeNodeType.FILE
1449 || node.getType() == RepositoryTreeNodeType.FOLDER) {
1450 dir = ((File) node.getObject()).getPath();
1451 } else if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1452 if (!isBare(node.getRepository()))
1453 dir = node.getRepository().getWorkDir().getPath();
1455 if (dir != null) {
1456 Clipboard clip = null;
1457 try {
1458 clip = new Clipboard(getSite().getShell()
1459 .getDisplay());
1460 clip
1461 .setContents(new Object[] { dir },
1462 new Transfer[] { TextTransfer
1463 .getInstance() });
1464 } finally {
1465 if (clip != null)
1466 // we must dispose ourselves
1467 clip.dispose();
1474 copyAction.setEnabled(false);
1476 getViewSite().getActionBars().setGlobalActionHandler(
1477 ActionFactory.COPY.getId(), copyAction);
1479 pasteAction = new Action("") { //$NON-NLS-1$
1481 @Override
1482 public void run() {
1483 // we check if the pasted content is a directory
1484 // repository location and try to add this
1485 String errorMessage = null;
1487 Clipboard clip = null;
1488 try {
1489 clip = new Clipboard(getSite().getShell().getDisplay());
1490 String content = (String) clip.getContents(TextTransfer
1491 .getInstance());
1492 if (content == null) {
1493 errorMessage = UIText.RepositoriesView_NothingToPasteMessage;
1494 return;
1497 File file = new File(content);
1498 if (!file.exists() || !file.isDirectory()) {
1499 errorMessage = UIText.RepositoriesView_ClipboardContentNotDirectoryMessage;
1500 return;
1503 if (!RepositoryCache.FileKey.isGitRepository(file)) {
1504 errorMessage = NLS
1505 .bind(
1506 UIText.RepositoriesView_ClipboardContentNoGitRepoMessage,
1507 content);
1508 return;
1511 if (addDir(file))
1512 scheduleRefresh();
1513 else
1514 errorMessage = NLS.bind(
1515 UIText.RepositoriesView_PasteRepoAlreadyThere,
1516 content);
1517 } finally {
1518 if (clip != null)
1519 // we must dispose ourselves
1520 clip.dispose();
1521 if (errorMessage != null)
1522 MessageDialog.openWarning(getSite().getShell(),
1523 UIText.RepositoriesView_PasteFailureTitle,
1524 errorMessage);
1530 getViewSite().getActionBars().setGlobalActionHandler(
1531 ActionFactory.PASTE.getId(), pasteAction);
1536 * @return the preferences
1538 protected static IEclipsePreferences getPrefs() {
1539 return new InstanceScope().getNode(Activator.getPluginId());
1542 @Override
1543 public void dispose() {
1544 // make sure to cancel the refresh job
1545 if (this.scheduledJob != null) {
1546 this.scheduledJob.cancel();
1547 this.scheduledJob = null;
1549 // remove RepositoryChangedListener
1550 unregisterRepositoryListener();
1551 repositories.clear();
1552 super.dispose();
1556 * Schedules a refresh
1558 private void scheduleRefresh() {
1560 if (scheduledJob != null && scheduledJob.getState() == Job.RUNNING)
1561 return;
1563 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1565 @SuppressWarnings("unchecked")
1566 @Override
1567 protected IStatus run(IProgressMonitor monitor) {
1568 // first, let's check if the list of Directories has changed
1569 final List<String> directories = getDirs();
1571 boolean needsNewInput = tv.getInput() == null;
1572 List<RepositoryTreeNode<Repository>> oldInput = (List) tv
1573 .getInput();
1574 if (!needsNewInput)
1575 needsNewInput = oldInput.size() != directories.size();
1577 if (!needsNewInput) {
1578 List<String> oldDirectories = new ArrayList<String>();
1579 for (RepositoryTreeNode<Repository> node : oldInput) {
1580 oldDirectories.add(node.getRepository().getDirectory()
1581 .getPath());
1583 needsNewInput = !directories.containsAll(oldDirectories);
1586 final boolean updateInput = needsNewInput;
1587 final List<RepositoryTreeNode<Repository>> newInput;
1588 if (updateInput) {
1589 unregisterRepositoryListener();
1590 try {
1591 newInput = getRepositoriesFromDirs(monitor);
1592 } catch (InterruptedException e) {
1593 return new Status(IStatus.ERROR, Activator
1594 .getPluginId(), e.getMessage(), e);
1596 repositories.clear();
1597 for (RepositoryTreeNode<Repository> node: newInput) {
1598 Repository repo = node.getRepository();
1599 repositories.add(repo);
1600 // add listener if not already added
1601 repo.removeRepositoryChangedListener(repositoryListener);
1602 repo.addRepositoryChangedListener(repositoryListener);
1604 } else {
1605 newInput = null;
1607 Display.getDefault().asyncExec(new Runnable() {
1608 public void run() {
1609 // keep expansion state and selection so that we can
1610 // restore the tree
1611 // after update
1612 Object[] expanded = tv.getExpandedElements();
1613 IStructuredSelection sel = (IStructuredSelection) tv
1614 .getSelection();
1616 if (updateInput)
1617 tv.setInput(newInput);
1618 else
1619 tv.refresh();
1620 tv.setExpandedElements(expanded);
1622 Object selected = sel.getFirstElement();
1623 if (selected != null)
1624 tv.reveal(selected);
1626 IViewPart part = PlatformUI.getWorkbench()
1627 .getActiveWorkbenchWindow().getActivePage()
1628 .findView(IPageLayout.ID_PROP_SHEET);
1629 if (part != null) {
1630 PropertySheet sheet = (PropertySheet) part;
1631 PropertySheetPage page = (PropertySheetPage) sheet
1632 .getCurrentPage();
1633 page.refresh();
1637 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1641 job.setSystem(true);
1643 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1644 .getService(IWorkbenchSiteProgressService.class);
1646 service.schedule(job);
1648 scheduledJob = job;
1652 private void createRepositoryChangedListener() {
1653 repositoryListener = new RepositoryListener() {
1654 public void refsChanged(RefsChangedEvent e) {
1655 scheduleRefresh();
1658 public void indexChanged(IndexChangedEvent e) {
1659 scheduleRefresh();
1664 private void unregisterRepositoryListener() {
1665 for (Repository repo:repositories)
1666 repo.removeRepositoryChangedListener(repositoryListener);
1671 * Adds a directory to the list if it is not already there
1673 * @param file
1674 * @return see {@link Collection#add(Object)}
1676 public static boolean addDir(File file) {
1678 String dirString;
1679 try {
1680 dirString = file.getCanonicalPath();
1681 } catch (IOException e) {
1682 dirString = file.getAbsolutePath();
1685 List<String> dirStrings = getDirs();
1686 if (dirStrings.contains(dirString)) {
1687 return false;
1688 } else {
1689 Set<String> dirs = new HashSet<String>();
1690 dirs.addAll(dirStrings);
1691 dirs.add(dirString);
1692 saveDirs(dirs);
1693 return true;
1698 * Converts the directories as configured for this view into a list of
1699 * {@link Repository} objects suitable for the tree content provider
1700 * <p>
1701 * TODO move to some utility class
1703 * @param monitor
1704 * @return a list of nodes
1705 * @throws InterruptedException
1707 public static List<RepositoryTreeNode<Repository>> getRepositoriesFromDirs(
1708 IProgressMonitor monitor) throws InterruptedException {
1710 List<String> gitDirStrings = getDirs();
1711 List<RepositoryTreeNode<Repository>> input = new ArrayList<RepositoryTreeNode<Repository>>();
1713 for (String dirString : gitDirStrings) {
1714 if (monitor != null && monitor.isCanceled()) {
1715 throw new InterruptedException(
1716 UIText.RepositoriesView_ActionCanceled_Message);
1718 try {
1719 File dir = new File(dirString);
1720 if (dir.exists() && dir.isDirectory()) {
1721 Repository repo = org.eclipse.egit.core.Activator
1722 .getDefault().getRepositoryCache()
1723 .lookupRepository(dir);
1724 RepositoryNode node = new RepositoryNode(null, repo);
1725 input.add(node);
1727 } catch (IOException e) {
1728 IStatus error = new Status(IStatus.ERROR, Activator
1729 .getPluginId(), e.getMessage(), e);
1730 Activator.getDefault().getLog().log(error);
1733 Collections.sort(input);
1734 return input;
1737 private static void saveDirs(Set<String> gitDirStrings) {
1738 StringBuilder sb = new StringBuilder();
1739 for (String gitDirString : gitDirStrings) {
1740 sb.append(gitDirString);
1741 sb.append(File.pathSeparatorChar);
1744 IEclipsePreferences prefs = getPrefs();
1745 prefs.put(PREFS_DIRECTORIES, sb.toString());
1746 try {
1747 prefs.flush();
1748 } catch (BackingStoreException e) {
1749 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1750 e.getMessage(), e);
1751 Activator.getDefault().getLog().log(error);
1755 @Override
1756 public void setFocus() {
1757 tv.getTree().setFocus();
1760 @SuppressWarnings("boxing")
1761 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1762 boolean confirmed;
1763 confirmed = MessageDialog
1764 .openConfirm(
1765 getSite().getShell(),
1766 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1768 .bind(
1769 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1770 projectsToDelete.size()));
1771 return confirmed;
1774 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1775 selectionListeners.add(listener);
1778 public ISelection getSelection() {
1779 return currentSelection;
1782 public void removeSelectionChangedListener(
1783 ISelectionChangedListener listener) {
1784 selectionListeners.remove(listener);
1788 public void setSelection(ISelection selection) {
1789 currentSelection = selection;
1790 for (ISelectionChangedListener listener : selectionListeners) {
1791 listener.selectionChanged(new SelectionChangedEvent(
1792 RepositoriesView.this, selection));
1797 * Opens the tree and marks the folder to which a project is pointing
1799 * @param resource
1800 * TODO exceptions?
1802 @SuppressWarnings("unchecked")
1803 public void showResource(final IResource resource) {
1804 IProject project = resource.getProject();
1805 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1806 if (mapping == null)
1807 return;
1809 if (addDir(mapping.getRepository().getDirectory())) {
1810 scheduleRefresh();
1813 boolean doSetSelection = false;
1815 if (this.scheduledJob != null) {
1816 int state = this.scheduledJob.getState();
1817 if (state == Job.WAITING || state == Job.RUNNING) {
1818 this.scheduledJob.addJobChangeListener(new JobChangeAdapter() {
1820 @Override
1821 public void done(IJobChangeEvent event) {
1822 showResource(resource);
1825 } else {
1826 doSetSelection = true;
1830 if (doSetSelection) {
1831 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1832 .getContentProvider();
1833 RepositoryTreeNode currentNode = null;
1834 Object[] repos = cp.getElements(tv.getInput());
1835 for (Object repo : repos) {
1836 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1837 // TODO equals implementation of Repository?
1838 if (mapping.getRepository().getDirectory().equals(
1839 ((Repository) node.getObject()).getDirectory())) {
1840 for (Object child : cp.getChildren(node)) {
1841 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1842 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1843 currentNode = childNode;
1844 break;
1847 break;
1851 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1853 for (String segment : relPath.segments()) {
1854 for (Object child : cp.getChildren(currentNode)) {
1855 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1856 if (childNode.getObject().getName().equals(segment)) {
1857 currentNode = childNode;
1858 break;
1863 final RepositoryTreeNode selNode = currentNode;
1865 Display.getDefault().asyncExec(new Runnable() {
1867 public void run() {
1868 tv.setSelection(new StructuredSelection(selNode), true);
1876 public boolean show(ShowInContext context) {
1877 ISelection selection = context.getSelection();
1878 if (selection instanceof IStructuredSelection) {
1879 IStructuredSelection ss = (IStructuredSelection) selection;
1880 if (ss.size() == 1) {
1881 Object element = ss.getFirstElement();
1882 if (element instanceof IAdaptable) {
1883 IResource resource = (IResource) ((IAdaptable) element)
1884 .getAdapter(IResource.class);
1885 if (resource != null) {
1886 showResource(resource);
1887 return true;
1892 return false;
1895 private void removeRepository(final IProgressMonitor monitor,
1896 final Repository... repository) {
1897 final List<IProject> projectsToDelete = new ArrayList<IProject>();
1899 monitor
1900 .setTaskName(UIText.RepositoriesView_DeleteRepoDeterminProjectsMessage);
1902 for (Repository repo : repository) {
1903 File workDir = repo.getWorkDir();
1904 final IPath wdPath = new Path(workDir.getAbsolutePath());
1905 for (IProject prj : ResourcesPlugin.getWorkspace().getRoot()
1906 .getProjects()) {
1907 if (monitor.isCanceled())
1908 return;
1909 if (wdPath.isPrefixOf(prj.getLocation())) {
1910 projectsToDelete.add(prj);
1913 repo.removeRepositoryChangedListener(repositoryListener);
1916 if (!projectsToDelete.isEmpty()) {
1917 boolean confirmed;
1918 confirmed = confirmProjectDeletion(projectsToDelete);
1919 if (!confirmed) {
1920 return;
1924 if (monitor.isCanceled())
1925 return;
1927 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1929 public void run(IProgressMonitor actMonitor) throws CoreException {
1931 for (IProject prj : projectsToDelete) {
1932 prj.delete(false, false, actMonitor);
1934 for (Repository repo : repository)
1935 removeDir(repo.getDirectory());
1936 scheduleRefresh();
1940 try {
1941 ResourcesPlugin.getWorkspace().run(wsr,
1942 ResourcesPlugin.getWorkspace().getRoot(),
1943 IWorkspace.AVOID_UPDATE, monitor);
1944 } catch (CoreException e1) {
1945 Activator.logError(e1.getMessage(), e1);