Git Repositories View: auto-refresh
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob703421c0f30b57f78ff4defa01127f6b082ff781
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.repository.RepositoryTreeNode.RepositoryTreeNodeType;
54 import org.eclipse.jface.action.Action;
55 import org.eclipse.jface.action.IAction;
56 import org.eclipse.jface.action.IToolBarManager;
57 import org.eclipse.jface.action.Separator;
58 import org.eclipse.jface.dialogs.MessageDialog;
59 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
60 import org.eclipse.jface.operation.IRunnableWithProgress;
61 import org.eclipse.jface.viewers.IOpenListener;
62 import org.eclipse.jface.viewers.ISelection;
63 import org.eclipse.jface.viewers.ISelectionChangedListener;
64 import org.eclipse.jface.viewers.ISelectionProvider;
65 import org.eclipse.jface.viewers.IStructuredSelection;
66 import org.eclipse.jface.viewers.ITreeContentProvider;
67 import org.eclipse.jface.viewers.OpenEvent;
68 import org.eclipse.jface.viewers.SelectionChangedEvent;
69 import org.eclipse.jface.viewers.StructuredSelection;
70 import org.eclipse.jface.viewers.TreeViewer;
71 import org.eclipse.jface.window.Window;
72 import org.eclipse.jface.wizard.Wizard;
73 import org.eclipse.jface.wizard.WizardDialog;
74 import org.eclipse.jgit.lib.Constants;
75 import org.eclipse.jgit.lib.IndexChangedEvent;
76 import org.eclipse.jgit.lib.Ref;
77 import org.eclipse.jgit.lib.RefUpdate;
78 import org.eclipse.jgit.lib.RefsChangedEvent;
79 import org.eclipse.jgit.lib.Repository;
80 import org.eclipse.jgit.lib.RepositoryCache;
81 import org.eclipse.jgit.lib.RepositoryConfig;
82 import org.eclipse.jgit.lib.RepositoryListener;
83 import org.eclipse.jgit.transport.RemoteConfig;
84 import org.eclipse.osgi.util.NLS;
85 import org.eclipse.swt.SWT;
86 import org.eclipse.swt.dnd.Clipboard;
87 import org.eclipse.swt.dnd.TextTransfer;
88 import org.eclipse.swt.dnd.Transfer;
89 import org.eclipse.swt.events.MenuDetectEvent;
90 import org.eclipse.swt.events.MenuDetectListener;
91 import org.eclipse.swt.events.SelectionAdapter;
92 import org.eclipse.swt.events.SelectionEvent;
93 import org.eclipse.swt.graphics.Point;
94 import org.eclipse.swt.widgets.Composite;
95 import org.eclipse.swt.widgets.Display;
96 import org.eclipse.swt.widgets.Menu;
97 import org.eclipse.swt.widgets.MenuItem;
98 import org.eclipse.swt.widgets.TreeItem;
99 import org.eclipse.ui.IEditorInput;
100 import org.eclipse.ui.IEditorPart;
101 import org.eclipse.ui.IFileEditorInput;
102 import org.eclipse.ui.IPageLayout;
103 import org.eclipse.ui.ISelectionListener;
104 import org.eclipse.ui.ISelectionService;
105 import org.eclipse.ui.IViewPart;
106 import org.eclipse.ui.IWorkbenchPart;
107 import org.eclipse.ui.PartInitException;
108 import org.eclipse.ui.PlatformUI;
109 import org.eclipse.ui.actions.ActionFactory;
110 import org.eclipse.ui.editors.text.EditorsUI;
111 import org.eclipse.ui.ide.FileStoreEditorInput;
112 import org.eclipse.ui.ide.IDE;
113 import org.eclipse.ui.part.IShowInTarget;
114 import org.eclipse.ui.part.ShowInContext;
115 import org.eclipse.ui.part.ViewPart;
116 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
117 import org.eclipse.ui.views.properties.IPropertySheetPage;
118 import org.eclipse.ui.views.properties.PropertySheet;
119 import org.eclipse.ui.views.properties.PropertySheetPage;
120 import org.osgi.service.prefs.BackingStoreException;
124 * The Git Repositories view.
125 * <p>
126 * This keeps track of a bunch of local directory names each of which represent
127 * a Git Repository. This list is stored in some Preferences object and used to
128 * build the tree in the view.
129 * <p>
130 * Implements {@link ISelectionProvider} in order to integrate with the
131 * Properties view.
132 * <p>
133 * This periodically refreshes itself in order to react on Repository changes.
135 public class RepositoriesView extends ViewPart implements ISelectionProvider,
136 IShowInTarget {
138 /** The view ID */
139 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
141 // TODO central constants? RemoteConfig ones are private
142 static final String REMOTE = "remote"; //$NON-NLS-1$
144 static final String URL = "url"; //$NON-NLS-1$
146 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
148 static final String FETCH = "fetch"; //$NON-NLS-1$
150 static final String PUSH = "push"; //$NON-NLS-1$
152 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
154 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
156 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
158 private final static long AUTO_REFRESH_INTERVAL_MILLISECONDS = 10000l;
160 private ISelection currentSelection = new StructuredSelection();
162 private Job scheduledJob;
164 private Job autoRefreshJob;
166 private TreeViewer tv;
168 private IAction importAction;
170 private IAction addAction;
172 private IAction refreshAction;
174 private IAction linkWithSelectionAction;
176 private IAction copyAction;
178 private IAction pasteAction;
181 * TODO move to utility class
183 * @return the directories as configured for this view
185 public static List<String> getDirs() {
186 List<String> resultStrings = new ArrayList<String>();
187 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
188 if (dirs != null && dirs.length() > 0) {
189 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
190 while (tok.hasMoreTokens()) {
191 String dirName = tok.nextToken();
192 File testFile = new File(dirName);
193 if (testFile.exists() && !resultStrings.contains(dirName)) {
194 resultStrings.add(dirName);
198 Collections.sort(resultStrings);
199 return resultStrings;
202 private static void removeDir(File file) {
204 String dir;
205 try {
206 dir = file.getCanonicalPath();
207 } catch (IOException e1) {
208 dir = file.getAbsolutePath();
211 IEclipsePreferences prefs = getPrefs();
213 TreeSet<String> resultStrings = new TreeSet<String>();
214 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
215 if (dirs != null && dirs.length() > 0) {
216 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
217 while (tok.hasMoreTokens()) {
218 String dirName = tok.nextToken();
219 File testFile = new File(dirName);
220 if (testFile.exists()) {
221 try {
222 resultStrings.add(testFile.getCanonicalPath());
223 } catch (IOException e) {
224 resultStrings.add(testFile.getAbsolutePath());
230 if (resultStrings.remove(dir)) {
231 StringBuilder sb = new StringBuilder();
232 for (String gitDirString : resultStrings) {
233 sb.append(gitDirString);
234 sb.append(File.pathSeparatorChar);
237 prefs.put(PREFS_DIRECTORIES, sb.toString());
238 try {
239 prefs.flush();
240 } catch (BackingStoreException e) {
241 IStatus error = new Status(IStatus.ERROR, Activator
242 .getPluginId(), e.getMessage(), e);
243 Activator.getDefault().getLog().log(error);
249 @Override
250 public Object getAdapter(Class adapter) {
251 // integrate with Properties view
252 if (adapter == IPropertySheetPage.class) {
253 PropertySheetPage page = new PropertySheetPage();
254 page
255 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
256 page));
257 return page;
260 return super.getAdapter(adapter);
263 @Override
264 public void createPartControl(Composite parent) {
265 tv = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
266 tv.setContentProvider(new RepositoriesViewContentProvider());
267 // the label provider registers itself
268 new RepositoriesViewLabelProvider(tv);
270 getSite().setSelectionProvider(this);
272 tv.addSelectionChangedListener(new ISelectionChangedListener() {
274 public void selectionChanged(SelectionChangedEvent event) {
276 copyAction.setEnabled(false);
278 IStructuredSelection ssel = (IStructuredSelection) event
279 .getSelection();
280 if (ssel.size() == 1) {
281 RepositoryTreeNode node = (RepositoryTreeNode) ssel
282 .getFirstElement();
283 // allow copy on repository, file, or folder (copying the
284 // directory)
285 if (node.getType() == RepositoryTreeNodeType.REPO
286 || node.getType() == RepositoryTreeNodeType.WORKINGDIR
287 || node.getType() == RepositoryTreeNodeType.FOLDER
288 || node.getType() == RepositoryTreeNodeType.FILE) {
289 copyAction.setEnabled(true);
291 setSelection(new StructuredSelection(ssel.getFirstElement()));
292 } else {
293 setSelection(new StructuredSelection());
298 tv.addOpenListener(new IOpenListener() {
299 public void open(OpenEvent event) {
300 IStructuredSelection selection = (IStructuredSelection) event
301 .getSelection();
302 if (selection.isEmpty()) {
303 // nothing selected, ignore
304 return;
307 Object element = selection.getFirstElement();
308 ITreeContentProvider contentProvider = (ITreeContentProvider) tv
309 .getContentProvider();
310 if (contentProvider.hasChildren(element)) {
311 // this element has children, expand/collapse it
312 tv.setExpandedState(element, !tv.getExpandedState(element));
313 } else {
314 Object[] selectionArray = selection.toArray();
315 for (Object selectedElement : selectionArray) {
316 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
317 // if any of the selected elements are not files, ignore
318 // the open request
319 if (node.getType() != RepositoryTreeNodeType.FILE
320 && node.getType() != RepositoryTreeNodeType.REF
321 && node.getType() != RepositoryTreeNodeType.TAG) {
322 return;
326 // open the files the user has selected
327 for (Object selectedElement : selectionArray) {
328 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
329 if (node.getType() == RepositoryTreeNodeType.FILE)
330 openFile((File) node.getObject());
331 else if (node.getType() == RepositoryTreeNodeType.REF
332 || node.getType() == RepositoryTreeNodeType.TAG) {
333 Ref ref = (Ref) node.getObject();
334 if (!isBare(node.getRepository())
335 && ref.getName().startsWith(
336 Constants.R_REFS))
337 checkoutBranch(node, ref.getName());
344 addContextMenu();
346 addActionsToToolbar();
348 scheduleRefresh();
350 // schedule the auto-refresh job
351 autoRefreshJob = new Job("Git Repositories View Auto-Refresh") { //$NON-NLS-1$
353 @Override
354 protected IStatus run(IProgressMonitor monitor) {
355 scheduleRefresh();
356 schedule(AUTO_REFRESH_INTERVAL_MILLISECONDS);
357 return Status.OK_STATUS;
361 autoRefreshJob.setSystem(true);
362 autoRefreshJob.schedule(AUTO_REFRESH_INTERVAL_MILLISECONDS);
364 ISelectionService srv = (ISelectionService) getSite().getService(
365 ISelectionService.class);
366 srv.addPostSelectionListener(new ISelectionListener() {
368 public void selectionChanged(IWorkbenchPart part,
369 ISelection selection) {
371 // if the "link with selection" toggle is off, we're done
372 if (linkWithSelectionAction == null
373 || !linkWithSelectionAction.isChecked())
374 return;
376 // this may happen if we switch between editors
377 if (part instanceof IEditorPart) {
378 IEditorInput input = ((IEditorPart) part).getEditorInput();
379 if (input instanceof IFileEditorInput)
380 reactOnSelection(new StructuredSelection(
381 ((IFileEditorInput) input).getFile()));
383 } else {
384 reactOnSelection(selection);
391 private void reactOnSelection(ISelection selection) {
392 if (selection instanceof StructuredSelection) {
393 StructuredSelection ssel = (StructuredSelection) selection;
394 if (ssel.size() != 1)
395 return;
396 if (ssel.getFirstElement() instanceof IResource) {
397 showResource((IResource) ssel.getFirstElement());
399 if (ssel.getFirstElement() instanceof IAdaptable) {
400 IResource adapted = (IResource) ((IAdaptable) ssel
401 .getFirstElement()).getAdapter(IResource.class);
402 if (adapted != null)
403 showResource(adapted);
408 private void addContextMenu() {
409 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
411 public void menuDetected(MenuDetectEvent e) {
412 Menu men = tv.getTree().getMenu();
413 if (men != null) {
414 men.dispose();
416 men = new Menu(tv.getTree());
418 TreeItem testItem = tv.getTree().getItem(
419 tv.getTree().toControl(new Point(e.x, e.y)));
420 if (testItem == null) {
421 addMenuItemsForPanel(men);
422 } else {
423 addMenuItemsForTreeSelection(men);
426 tv.getTree().setMenu(men);
431 private void addMenuItemsForPanel(Menu men) {
433 MenuItem importItem = new MenuItem(men, SWT.PUSH);
434 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
435 importItem.addSelectionListener(new SelectionAdapter() {
437 @Override
438 public void widgetSelected(SelectionEvent e) {
439 importAction.run();
444 MenuItem addItem = new MenuItem(men, SWT.PUSH);
445 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
446 addItem.addSelectionListener(new SelectionAdapter() {
448 @Override
449 public void widgetSelected(SelectionEvent e) {
450 addAction.run();
455 MenuItem pasteItem = new MenuItem(men, SWT.PUSH);
456 pasteItem.setText(UIText.RepositoriesView_PasteMenu);
457 pasteItem.addSelectionListener(new SelectionAdapter() {
459 @Override
460 public void widgetSelected(SelectionEvent e) {
461 pasteAction.run();
466 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
467 refreshItem.setText(refreshAction.getText());
468 refreshItem.addSelectionListener(new SelectionAdapter() {
470 @Override
471 public void widgetSelected(SelectionEvent e) {
472 refreshAction.run();
479 private void addMenuItemsForTreeSelection(Menu men) {
481 final IStructuredSelection sel = (IStructuredSelection) tv
482 .getSelection();
484 boolean repoOnly = true;
485 for (Object selected : sel.toArray()) {
487 if (((RepositoryTreeNode) selected).getType() != RepositoryTreeNodeType.REPO) {
488 repoOnly = false;
489 break;
493 if (sel.size() > 1 && repoOnly) {
494 List nodes = sel.toList();
495 final Repository[] repos = new Repository[nodes.size()];
496 for (int i = 0; i < sel.size(); i++)
497 repos[i] = ((RepositoryTreeNode) nodes.get(i)).getRepository();
499 MenuItem remove = new MenuItem(men, SWT.PUSH);
500 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
501 remove.addSelectionListener(new SelectionAdapter() {
503 @Override
504 public void widgetSelected(SelectionEvent e) {
505 // TODO progress monitoring/cancellation
506 removeRepository(new NullProgressMonitor(), repos);
512 // from here on, we only deal with single selection
513 if (sel.size() > 1)
514 return;
516 final RepositoryTreeNode node = (RepositoryTreeNode) sel
517 .getFirstElement();
519 final boolean isBare = isBare(node.getRepository());
521 if (node.getType() == RepositoryTreeNodeType.REF) {
523 final Ref ref = (Ref) node.getObject();
525 // we don't check out symbolic references
526 if (!ref.isSymbolic()) {
528 if (!isBare) {
529 MenuItem checkout = new MenuItem(men, SWT.PUSH);
530 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
532 checkout.setEnabled(!isRefCheckedOut(node.getRepository(),
533 ref.getName()));
535 checkout.addSelectionListener(new SelectionAdapter() {
537 @Override
538 public void widgetSelected(SelectionEvent e) {
539 checkoutBranch(node, ref.getLeaf().getName());
543 new MenuItem(men, SWT.SEPARATOR);
546 createCreateBranchItem(men, node);
547 createDeleteBranchItem(men, node);
552 if (node.getType() == RepositoryTreeNodeType.TAG) {
554 final Ref ref = (Ref) node.getObject();
556 MenuItem checkout = new MenuItem(men, SWT.PUSH);
557 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
559 checkout.setEnabled(!isRefCheckedOut(node.getRepository(), ref
560 .getName()));
562 checkout.addSelectionListener(new SelectionAdapter() {
564 @Override
565 public void widgetSelected(SelectionEvent e) {
566 checkoutBranch(node, ref.getLeaf().getName());
571 if (node.getType() == RepositoryTreeNodeType.BRANCHES
572 || node.getType() == RepositoryTreeNodeType.LOCALBRANCHES)
573 // offering this on the "Remote Branches" node would probably be
574 // confusing
575 createCreateBranchItem(men, node);
577 // for Repository: import existing projects, remove, (delete), open
578 // properties
579 if (node.getType() == RepositoryTreeNodeType.REPO) {
581 final Repository repo = (Repository) node.getObject();
583 // TODO "import existing plug-in" menu item
585 MenuItem remove = new MenuItem(men, SWT.PUSH);
586 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
587 remove.addSelectionListener(new SelectionAdapter() {
589 @Override
590 public void widgetSelected(SelectionEvent e) {
591 // TODO progress monitoring/cancellation
592 removeRepository(new NullProgressMonitor(), repo);
596 // TODO delete does not work because of file locks on .pack-files
597 // Shawn Pearce has added the following thoughts:
599 // Hmm. We probably can't active detect file locks on pack files on
600 // Windows, can we?
601 // It would be nice if we could support a delete, but only if the
602 // repository is
603 // reasonably believed to be not-in-use right now.
605 // Within EGit you might be able to check GitProjectData and its
606 // repositoryCache to
607 // see if the repository is open by this workspace. If it is, then
608 // we know we shouldn't
609 // try to delete it.
611 // Some coding might look like this:
613 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
614 // deleteRepo.setText("Delete");
615 // deleteRepo.addSelectionListener(new SelectionAdapter() {
617 // @Override
618 // public void widgetSelected(SelectionEvent e) {
620 // boolean confirmed = MessageDialog.openConfirm(getSite()
621 // .getShell(), "Confirm",
622 // "This will delete the repository, continue?");
624 // if (!confirmed)
625 // return;
627 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
629 // public void run(IProgressMonitor monitor)
630 // throws CoreException {
631 // File workDir = repos.get(0).getRepository()
632 // .getWorkDir();
634 // File gitDir = repos.get(0).getRepository()
635 // .getDirectory();
637 // IPath wdPath = new Path(workDir.getAbsolutePath());
638 // for (IProject prj : ResourcesPlugin.getWorkspace()
639 // .getRoot().getProjects()) {
640 // if (wdPath.isPrefixOf(prj.getLocation())) {
641 // prj.delete(false, false, monitor);
642 // }
643 // }
645 // repos.get(0).getRepository().close();
647 // boolean deleted = deleteRecursively(gitDir, monitor);
648 // if (!deleted) {
649 // MessageDialog.openError(getSite().getShell(),
650 // "Error",
651 // "Could not delete Git Repository");
652 // }
654 // deleted = deleteRecursively(workDir, monitor);
655 // if (!deleted) {
656 // MessageDialog
657 // .openError(getSite().getShell(),
658 // "Error",
659 // "Could not delete Git Working Directory");
660 // }
662 // scheduleRefresh();
663 // }
665 // private boolean deleteRecursively(File fileToDelete,
666 // IProgressMonitor monitor) {
667 // if (fileToDelete.isDirectory()) {
668 // for (File file : fileToDelete.listFiles()) {
669 // if (!deleteRecursively(file, monitor)) {
670 // return false;
671 // }
672 // }
673 // }
674 // monitor.setTaskName(fileToDelete.getAbsolutePath());
675 // boolean deleted = fileToDelete.delete();
676 // if (!deleted) {
677 // System.err.println("Could not delete "
678 // + fileToDelete.getAbsolutePath());
679 // }
680 // return deleted;
681 // }
682 // };
684 // try {
685 // ResourcesPlugin.getWorkspace().run(wsr,
686 // ResourcesPlugin.getWorkspace().getRoot(),
687 // IWorkspace.AVOID_UPDATE,
688 // new NullProgressMonitor());
689 // } catch (CoreException e1) {
690 // // TODO Exception handling
691 // e1.printStackTrace();
692 // }
694 // }
696 // });
698 new MenuItem(men, SWT.SEPARATOR);
700 if (!isBare) {
701 createImportProjectItem(men, repo, repo.getWorkDir().getPath());
703 new MenuItem(men, SWT.SEPARATOR);
706 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
707 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
708 openPropsView.addSelectionListener(new SelectionAdapter() {
710 @Override
711 public void widgetSelected(SelectionEvent e) {
712 try {
713 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
714 .getActivePage().showView(
715 IPageLayout.ID_PROP_SHEET);
716 } catch (PartInitException e1) {
717 // just ignore
723 new MenuItem(men, SWT.SEPARATOR);
725 createCopyPathItem(men, repo.getDirectory().getPath());
728 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
730 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
731 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
732 remoteConfig.addSelectionListener(new SelectionAdapter() {
734 @Override
735 public void widgetSelected(SelectionEvent e) {
737 WizardDialog dlg = new WizardDialog(getSite().getShell(),
738 new NewRemoteWizard(node.getRepository()));
739 if (dlg.open() == Window.OK)
740 tv.refresh();
747 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
749 final String configName = (String) node.getObject();
751 RemoteConfig rconfig;
752 try {
753 rconfig = new RemoteConfig(node.getRepository().getConfig(),
754 configName);
755 } catch (URISyntaxException e2) {
756 // TODO Exception handling
757 rconfig = null;
760 boolean fetchExists = rconfig != null
761 && !rconfig.getURIs().isEmpty();
762 boolean pushExists = rconfig != null
763 && !rconfig.getPushURIs().isEmpty();
765 if (!fetchExists) {
766 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
767 configureUrlFetch
768 .setText(UIText.RepositoriesView_CreateFetch_menu);
770 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
772 @Override
773 public void widgetSelected(SelectionEvent e) {
775 WizardDialog dlg = new WizardDialog(getSite()
776 .getShell(), new ConfigureRemoteWizard(node
777 .getRepository(), configName, false));
778 if (dlg.open() == Window.OK)
779 tv.refresh();
786 if (!pushExists) {
787 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
789 configureUrlPush
790 .setText(UIText.RepositoriesView_CreatePush_menu);
792 configureUrlPush.addSelectionListener(new SelectionAdapter() {
794 @Override
795 public void widgetSelected(SelectionEvent e) {
797 WizardDialog dlg = new WizardDialog(getSite()
798 .getShell(), new ConfigureRemoteWizard(node
799 .getRepository(), configName, true));
800 if (dlg.open() == Window.OK)
801 tv.refresh();
808 if (!fetchExists || !pushExists)
809 // add a separator dynamically
810 new MenuItem(men, SWT.SEPARATOR);
812 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
813 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
814 removeRemote.addSelectionListener(new SelectionAdapter() {
816 @Override
817 public void widgetSelected(SelectionEvent e) {
819 boolean ok = MessageDialog
820 .openConfirm(
821 getSite().getShell(),
822 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
824 .bind(
825 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
826 configName));
827 if (ok) {
828 RepositoryConfig config = node.getRepository()
829 .getConfig();
830 config.unsetSection(REMOTE, configName);
831 try {
832 config.save();
833 tv.refresh();
834 } catch (IOException e1) {
835 Activator.handleError(
836 UIText.RepositoriesView_ErrorHeader, e1,
837 true);
845 new MenuItem(men, SWT.SEPARATOR);
847 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
848 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
849 openPropsView.addSelectionListener(new SelectionAdapter() {
851 @Override
852 public void widgetSelected(SelectionEvent e) {
853 try {
854 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
855 .getActivePage().showView(
856 IPageLayout.ID_PROP_SHEET);
857 } catch (PartInitException e1) {
858 // just ignore
865 if (node.getType() == RepositoryTreeNodeType.FETCH) {
867 final String configName = (String) node.getParent().getObject();
869 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
870 configureUrlFetch
871 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
873 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
875 @Override
876 public void widgetSelected(SelectionEvent e) {
878 WizardDialog dlg = new WizardDialog(getSite().getShell(),
879 new ConfigureRemoteWizard(node.getRepository(),
880 configName, false));
881 if (dlg.open() == Window.OK)
882 tv.refresh();
888 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
889 deleteFetch.setText(UIText.RepositoriesView_RemoveFetch_menu);
890 deleteFetch.addSelectionListener(new SelectionAdapter() {
892 @Override
893 public void widgetSelected(SelectionEvent e) {
894 RepositoryConfig config = node.getRepository().getConfig();
895 config.unset("remote", configName, "url"); //$NON-NLS-1$ //$NON-NLS-2$
896 config.unset("remote", configName, "fetch"); //$NON-NLS-1$//$NON-NLS-2$
897 try {
898 config.save();
899 tv.refresh();
900 } catch (IOException e1) {
901 MessageDialog.openError(getSite().getShell(),
902 UIText.RepositoriesView_ErrorHeader, e1
903 .getMessage());
911 if (node.getType() == RepositoryTreeNodeType.PUSH) {
913 final String configName = (String) node.getParent().getObject();
915 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
917 configureUrlPush.setText(UIText.RepositoriesView_ConfigurePushMenu);
919 configureUrlPush.addSelectionListener(new SelectionAdapter() {
921 @Override
922 public void widgetSelected(SelectionEvent e) {
924 WizardDialog dlg = new WizardDialog(getSite().getShell(),
925 new ConfigureRemoteWizard(node.getRepository(),
926 configName, true));
927 if (dlg.open() == Window.OK)
928 tv.refresh();
933 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
934 deleteFetch.setText(UIText.RepositoriesView_RemovePush_menu);
935 deleteFetch.addSelectionListener(new SelectionAdapter() {
937 @Override
938 public void widgetSelected(SelectionEvent e) {
939 RepositoryConfig config = node.getRepository().getConfig();
940 config.unset("remote", configName, "pushurl"); //$NON-NLS-1$ //$NON-NLS-2$
941 config.unset("remote", configName, "push"); //$NON-NLS-1$ //$NON-NLS-2$
942 try {
943 config.save();
944 tv.refresh();
945 } catch (IOException e1) {
946 MessageDialog.openError(getSite().getShell(),
947 UIText.RepositoriesView_ErrorHeader, e1
948 .getMessage());
955 if (node.getType() == RepositoryTreeNodeType.FILE) {
957 final File file = (File) node.getObject();
959 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
960 openInTextEditor
961 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
962 openInTextEditor.addSelectionListener(new SelectionAdapter() {
964 @Override
965 public void widgetSelected(SelectionEvent e) {
966 openFile(file);
971 new MenuItem(men, SWT.SEPARATOR);
972 createCopyPathItem(men, file.getPath());
975 if (!isBare && node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
976 String path = node.getRepository().getWorkDir().getAbsolutePath();
977 createImportProjectItem(men, node.getRepository(), path);
978 new MenuItem(men, SWT.SEPARATOR);
979 createCopyPathItem(men, path);
982 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
983 String path = ((File) node.getObject()).getPath();
984 createImportProjectItem(men, node.getRepository(), path);
985 new MenuItem(men, SWT.SEPARATOR);
986 createCopyPathItem(men, path);
991 private boolean isBare(Repository repository) {
992 return repository.getConfig().getBoolean("core", "bare", false); //$NON-NLS-1$ //$NON-NLS-2$
995 private boolean isRefCheckedOut(Repository repository, String refName) {
996 String branchName;
997 String compareString;
999 try {
1000 branchName = repository.getFullBranch();
1001 if (branchName == null)
1002 return false;
1003 if (refName.startsWith(Constants.R_HEADS)) {
1004 // local branch: HEAD would be on the branch
1005 compareString = refName;
1006 } else if (refName.startsWith(Constants.R_TAGS)) {
1007 // tag: HEAD would be on the commit id to which the tag is
1008 // pointing
1009 compareString = repository.mapTag(refName).getObjId().getName();
1010 } else if (refName.startsWith(Constants.R_REMOTES)) {
1011 // remote branch: HEAD would be on the commit id to which
1012 // the branch is pointing
1013 compareString = repository.mapCommit(refName).getCommitId()
1014 .getName();
1015 } else {
1016 // some other symbolic reference
1017 return false;
1019 } catch (IOException e1) {
1020 return false;
1023 return compareString.equals(branchName);
1026 private void createCopyPathItem(Menu men, final String path) {
1028 MenuItem copyPath;
1029 copyPath = new MenuItem(men, SWT.PUSH);
1030 copyPath.setText(UIText.RepositoriesView_CopyPathToClipboardMenu);
1031 copyPath.addSelectionListener(new SelectionAdapter() {
1033 @Override
1034 public void widgetSelected(SelectionEvent e) {
1035 Clipboard clipboard = new Clipboard(null);
1036 TextTransfer textTransfer = TextTransfer.getInstance();
1037 Transfer[] transfers = new Transfer[] { textTransfer };
1038 Object[] data = new Object[] { path };
1039 clipboard.setContents(data, transfers);
1040 clipboard.dispose();
1047 private void createCreateBranchItem(Menu men, final RepositoryTreeNode node) {
1048 final Ref ref;
1049 if (node.getType() == RepositoryTreeNodeType.REF)
1050 ref = (Ref) node.getObject();
1051 else
1052 ref = null;
1054 MenuItem createLocal = new MenuItem(men, SWT.PUSH);
1055 createLocal.setText(UIText.RepositoriesView_NewBranchMenu);
1057 createLocal.addSelectionListener(new SelectionAdapter() {
1059 @Override
1060 public void widgetSelected(SelectionEvent e) {
1062 Wizard wiz = new Wizard() {
1064 @Override
1065 public void addPages() {
1066 addPage(new CreateBranchPage(node.getRepository(), ref));
1067 setWindowTitle(UIText.RepositoriesView_NewBranchTitle);
1070 @Override
1071 public boolean performFinish() {
1073 try {
1074 getContainer().run(false, true,
1075 new IRunnableWithProgress() {
1077 public void run(IProgressMonitor monitor)
1078 throws InvocationTargetException,
1079 InterruptedException {
1080 CreateBranchPage cp = (CreateBranchPage) getPages()[0];
1081 try {
1082 cp.createBranch(monitor);
1083 } catch (CoreException ce) {
1084 throw new InvocationTargetException(
1085 ce);
1086 } catch (IOException ioe) {
1087 throw new InvocationTargetException(
1088 ioe);
1093 } catch (InvocationTargetException ite) {
1094 Activator
1095 .handleError(
1096 UIText.RepositoriesView_BranchCreationFailureMessage,
1097 ite.getCause(), true);
1098 return false;
1099 } catch (InterruptedException ie) {
1100 // ignore here
1102 return true;
1105 if (new WizardDialog(getSite().getShell(), wiz).open() == Window.OK)
1106 tv.refresh();
1112 private void createDeleteBranchItem(Menu men, final RepositoryTreeNode node) {
1114 final Ref ref = (Ref) node.getObject();
1116 MenuItem deleteBranch = new MenuItem(men, SWT.PUSH);
1117 deleteBranch.setText(UIText.RepositoriesView_DeleteBranchMenu);
1119 deleteBranch.setEnabled(!isRefCheckedOut(node.getRepository(), ref
1120 .getName()));
1122 deleteBranch.addSelectionListener(new SelectionAdapter() {
1124 @Override
1125 public void widgetSelected(SelectionEvent e) {
1127 if (!MessageDialog
1128 .openConfirm(
1129 getSite().getShell(),
1130 UIText.RepositoriesView_ConfirmDeleteTitle,
1132 .bind(
1133 UIText.RepositoriesView_ConfirmBranchDeletionMessage,
1134 ref.getName())))
1135 return;
1137 try {
1138 new ProgressMonitorDialog(getSite().getShell()).run(false,
1139 false, new IRunnableWithProgress() {
1141 public void run(IProgressMonitor monitor)
1142 throws InvocationTargetException,
1143 InterruptedException {
1145 try {
1146 RefUpdate op = node.getRepository()
1147 .updateRef(ref.getName());
1148 op.setRefLogMessage("branch deleted", //$NON-NLS-1$
1149 false);
1150 // we set the force update in order
1151 // to avoid having this rejected
1152 // due to minor issues
1153 op.setForceUpdate(true);
1154 op.delete();
1155 tv.refresh();
1156 } catch (IOException ioe) {
1157 throw new InvocationTargetException(ioe);
1162 } catch (InvocationTargetException e1) {
1163 Activator
1164 .handleError(
1165 UIText.RepositoriesView_BranchDeletionFailureMessage,
1166 e1.getCause(), true);
1167 e1.printStackTrace();
1168 } catch (InterruptedException e1) {
1169 // ignore
1177 private void openFile(File file) {
1178 IFileStore store = EFS.getLocalFileSystem().getStore(
1179 new Path(file.getAbsolutePath()));
1180 try {
1181 // TODO do we need a read-only editor here?
1182 IDE.openEditor(getSite().getPage(),
1183 new FileStoreEditorInput(store),
1184 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
1185 } catch (PartInitException e) {
1186 Activator.handleError(UIText.RepositoriesView_Error_WindowTitle, e,
1187 true);
1191 private void checkoutBranch(final RepositoryTreeNode node,
1192 final String refName) {
1193 // for the sake of UI responsiveness, let's start a job
1194 Job job = new Job(NLS.bind(UIText.RepositoriesView_CheckingOutMessage,
1195 refName)) {
1197 @Override
1198 protected IStatus run(IProgressMonitor monitor) {
1200 Repository repo = node.getRepository();
1202 final BranchOperation op = new BranchOperation(repo, refName);
1203 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1205 public void run(IProgressMonitor myMonitor)
1206 throws CoreException {
1207 op.execute(myMonitor);
1211 try {
1212 ResourcesPlugin.getWorkspace().run(wsr,
1213 ResourcesPlugin.getWorkspace().getRoot(),
1214 IWorkspace.AVOID_UPDATE, monitor);
1215 Display.getDefault().syncExec(new Runnable() {
1217 public void run() {
1218 tv.refresh();
1222 } catch (CoreException e1) {
1223 return new Status(IStatus.ERROR, Activator.getPluginId(),
1224 e1.getMessage(), e1);
1227 return Status.OK_STATUS;
1231 job.setUser(true);
1232 job.schedule();
1235 private void createImportProjectItem(Menu men, final Repository repo,
1236 final String path) {
1238 MenuItem startWizard;
1239 startWizard = new MenuItem(men, SWT.PUSH);
1240 startWizard.setText(UIText.RepositoriesView_ImportProjectsMenu);
1241 startWizard.addSelectionListener(new SelectionAdapter() {
1243 @Override
1244 public void widgetSelected(SelectionEvent e) {
1245 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1246 new GitCreateProjectViaWizardWizard(repo, path));
1247 dlg.open();
1252 // we could start the ImportWizard here,
1253 // unfortunately, this fails within a wizard
1254 // startWizard = new MenuItem(men, SWT.PUSH);
1255 // startWizard.setText("Start the Import wizard...");
1256 // startWizard.addSelectionListener(new SelectionAdapter() {
1258 // @Override
1259 // public void widgetSelected(SelectionEvent e) {
1261 // IHandlerService handlerService = (IHandlerService) getSite()
1262 // .getWorkbenchWindow().getWorkbench().getService(
1263 // IHandlerService.class);
1265 // try {
1266 // handlerService.executeCommand("org.eclipse.ui.file.import", //$NON-NLS-1$
1267 // null);
1268 // } catch (ExecutionException e1) {
1269 // Activator.handleError(e1.getMessage(), e1, true);
1270 // } catch (NotDefinedException e1) {
1271 // Activator.handleError(e1.getMessage(), e1, true);
1272 // } catch (NotEnabledException e1) {
1273 // Activator.handleError(e1.getMessage(), e1, true);
1274 // } catch (NotHandledException e1) {
1275 // Activator.handleError(e1.getMessage(), e1, true);
1276 // }
1277 // }
1279 // });
1282 private void addActionsToToolbar() {
1284 IToolBarManager manager = getViewSite().getActionBars()
1285 .getToolBarManager();
1287 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
1289 @Override
1290 public void run() {
1291 scheduleRefresh();
1294 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1295 manager.add(refreshAction);
1297 linkWithSelectionAction = new Action(
1298 UIText.RepositoriesView_LinkWithSelection_action,
1299 IAction.AS_CHECK_BOX) {
1301 @Override
1302 public void run() {
1303 IEclipsePreferences prefs = getPrefs();
1304 prefs.putBoolean(PREFS_SYNCED, isChecked());
1305 try {
1306 prefs.flush();
1307 } catch (BackingStoreException e) {
1308 // ignore here
1310 if (isChecked()) {
1311 ISelectionService srv = (ISelectionService) getSite()
1312 .getService(ISelectionService.class);
1313 reactOnSelection(srv.getSelection());
1320 linkWithSelectionAction
1321 .setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
1322 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1323 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
1324 false));
1326 manager.add(linkWithSelectionAction);
1328 manager.add(new Separator());
1330 IAction collapseAllAction = new Action(
1331 UIText.RepositoriesView_CollapseAllMenu) {
1333 @Override
1334 public void run() {
1335 tv.collapseAll();
1338 collapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1339 manager.add(collapseAllAction);
1341 manager.add(new Separator());
1343 importAction = new Action(UIText.RepositoriesView_Import_Button) {
1345 @Override
1346 public void run() {
1347 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1348 new GitCloneWizard());
1349 if (dlg.open() == Window.OK)
1350 scheduleRefresh();
1353 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
1354 importAction.setImageDescriptor(UIIcons.CLONEGIT);
1356 manager.add(importAction);
1358 addAction = new Action(UIText.RepositoriesView_Add_Button) {
1360 @Override
1361 public void run() {
1362 RepositorySearchDialog sd = new RepositorySearchDialog(
1363 getSite().getShell(), getDirs());
1364 if (sd.open() == Window.OK) {
1365 Set<String> dirs = new HashSet<String>();
1366 dirs.addAll(getDirs());
1367 if (dirs.addAll(sd.getDirectories()))
1368 saveDirs(dirs);
1369 scheduleRefresh();
1374 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
1375 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
1377 manager.add(addAction);
1379 // copy and paste are global actions; we just implement them
1380 // and register them with the global action handler
1381 // we enable/disable them upon tree selection changes
1383 copyAction = new Action("") { //$NON-NLS-1$
1385 @Override
1386 public void run() {
1387 // for REPO, WORKINGDIR, FILE, FOLDER: copy directory
1388 IStructuredSelection sel = (IStructuredSelection) tv
1389 .getSelection();
1390 if (sel.size() == 1) {
1391 RepositoryTreeNode node = (RepositoryTreeNode) sel
1392 .getFirstElement();
1393 String dir = null;
1394 if (node.getType() == RepositoryTreeNodeType.REPO) {
1395 dir = node.getRepository().getDirectory().getPath();
1396 } else if (node.getType() == RepositoryTreeNodeType.FILE
1397 || node.getType() == RepositoryTreeNodeType.FOLDER) {
1398 dir = ((File) node.getObject()).getPath();
1399 } else if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1400 if (!isBare(node.getRepository()))
1401 dir = node.getRepository().getWorkDir().getPath();
1403 if (dir != null) {
1404 Clipboard clip = null;
1405 try {
1406 clip = new Clipboard(getSite().getShell()
1407 .getDisplay());
1408 clip
1409 .setContents(new Object[] { dir },
1410 new Transfer[] { TextTransfer
1411 .getInstance() });
1412 } finally {
1413 if (clip != null)
1414 // we must dispose ourselves
1415 clip.dispose();
1422 copyAction.setEnabled(false);
1424 getViewSite().getActionBars().setGlobalActionHandler(
1425 ActionFactory.COPY.getId(), copyAction);
1427 pasteAction = new Action("") { //$NON-NLS-1$
1429 @Override
1430 public void run() {
1431 // we check if the pasted content is a directory
1432 // repository location and try to add this
1433 String errorMessage = null;
1435 Clipboard clip = null;
1436 try {
1437 clip = new Clipboard(getSite().getShell().getDisplay());
1438 String content = (String) clip.getContents(TextTransfer
1439 .getInstance());
1440 if (content == null) {
1441 errorMessage = UIText.RepositoriesView_NothingToPasteMessage;
1442 return;
1445 File file = new File(content);
1446 if (!file.exists() || !file.isDirectory()) {
1447 errorMessage = UIText.RepositoriesView_ClipboardContentNotDirectoryMessage;
1448 return;
1451 if (!RepositoryCache.FileKey.isGitRepository(file)) {
1452 errorMessage = NLS
1453 .bind(
1454 UIText.RepositoriesView_ClipboardContentNoGitRepoMessage,
1455 content);
1456 return;
1459 if (addDir(file))
1460 scheduleRefresh();
1461 else
1462 errorMessage = NLS.bind(
1463 UIText.RepositoriesView_PasteRepoAlreadyThere,
1464 content);
1465 } finally {
1466 if (clip != null)
1467 // we must dispose ourselves
1468 clip.dispose();
1469 if (errorMessage != null)
1470 // TODO String ext
1471 MessageDialog.openWarning(getSite().getShell(),
1472 UIText.RepositoriesView_PasteFailureTitle,
1473 errorMessage);
1479 getViewSite().getActionBars().setGlobalActionHandler(
1480 ActionFactory.PASTE.getId(), pasteAction);
1485 * @return the preferences
1487 protected static IEclipsePreferences getPrefs() {
1488 return new InstanceScope().getNode(Activator.getPluginId());
1491 @Override
1492 public void dispose() {
1493 // make sure to cancel the refresh job
1494 if (this.scheduledJob != null) {
1495 this.scheduledJob.cancel();
1496 this.scheduledJob = null;
1498 // and the auto refresh job, too
1499 if (this.autoRefreshJob != null) {
1500 this.autoRefreshJob.cancel();
1501 this.autoRefreshJob = null;
1503 super.dispose();
1506 @SuppressWarnings("unchecked")
1507 private boolean checkForRepositoryChanges() {
1509 if (tv.getInput() == null)
1510 return false;
1512 final Set<Repository> reposToRefresh = new HashSet<Repository>();
1514 RepositoryListener listener = new RepositoryListener() {
1516 public void refsChanged(RefsChangedEvent e) {
1517 reposToRefresh.add(e.getRepository());
1520 public void indexChanged(IndexChangedEvent e) {
1521 reposToRefresh.add(e.getRepository());
1525 List<RepositoryTreeNode<Repository>> input = (List<RepositoryTreeNode<Repository>>) tv
1526 .getInput();
1528 for (final RepositoryTreeNode<Repository> node : input) {
1530 Repository repository = node.getRepository();
1531 repository.addRepositoryChangedListener(listener);
1532 try {
1533 repository.scanForRepoChanges();
1534 } catch (IOException e1) {
1535 // ignore
1536 } finally {
1537 repository.removeRepositoryChangedListener(listener);
1541 return !reposToRefresh.isEmpty();
1545 * Schedules a refresh
1547 private void scheduleRefresh() {
1549 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1551 @SuppressWarnings("unchecked")
1552 @Override
1553 protected IStatus run(IProgressMonitor monitor) {
1554 // first, let's check if the list of Directories has changed
1555 final List<String> directories = getDirs();
1557 boolean needsNewInput = tv.getInput() == null;
1558 List<RepositoryTreeNode<Repository>> oldInput = (List) tv
1559 .getInput();
1560 if (!needsNewInput)
1561 needsNewInput = oldInput.size() != directories.size();
1563 if (!needsNewInput) {
1564 List<String> oldDirectories = new ArrayList<String>();
1565 for (RepositoryTreeNode<Repository> node : oldInput) {
1566 oldDirectories.add(node.getRepository().getDirectory()
1567 .getPath());
1569 needsNewInput = !directories.containsAll(oldDirectories);
1572 final boolean updateInput = needsNewInput;
1573 final List newInput;
1574 if (updateInput)
1575 try {
1576 newInput = getRepositoriesFromDirs(monitor);
1577 } catch (InterruptedException e) {
1578 return new Status(IStatus.ERROR, Activator
1579 .getPluginId(), e.getMessage(), e);
1581 else
1582 newInput = null;
1584 // we only check for Repository changes if we don't
1585 // have a new input
1586 if (updateInput || checkForRepositoryChanges()) {
1587 Display.getDefault().asyncExec(new Runnable() {
1588 public void run() {
1589 // keep expansion state and selection so that we can
1590 // restore the tree
1591 // after update
1592 Object[] expanded = tv.getExpandedElements();
1593 IStructuredSelection sel = (IStructuredSelection) tv
1594 .getSelection();
1596 if (updateInput)
1597 tv.setInput(newInput);
1598 else
1599 tv.refresh();
1600 tv.setExpandedElements(expanded);
1602 Object selected = sel.getFirstElement();
1603 if (selected != null)
1604 tv.reveal(selected);
1606 IViewPart part = PlatformUI.getWorkbench()
1607 .getActiveWorkbenchWindow().getActivePage()
1608 .findView(IPageLayout.ID_PROP_SHEET);
1609 if (part != null) {
1610 PropertySheet sheet = (PropertySheet) part;
1611 PropertySheetPage page = (PropertySheetPage) sheet
1612 .getCurrentPage();
1613 page.refresh();
1618 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1622 job.setSystem(true);
1624 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1625 .getService(IWorkbenchSiteProgressService.class);
1627 service.schedule(job);
1629 scheduledJob = job;
1634 * Adds a directory to the list if it is not already there
1636 * @param file
1637 * @return see {@link Collection#add(Object)}
1639 public static boolean addDir(File file) {
1641 String dirString;
1642 try {
1643 dirString = file.getCanonicalPath();
1644 } catch (IOException e) {
1645 dirString = file.getAbsolutePath();
1648 List<String> dirStrings = getDirs();
1649 if (dirStrings.contains(dirString)) {
1650 return false;
1651 } else {
1652 Set<String> dirs = new HashSet<String>();
1653 dirs.addAll(dirStrings);
1654 dirs.add(dirString);
1655 saveDirs(dirs);
1656 return true;
1661 * Converts the directories as configured for this view into a list of
1662 * {@link Repository} objects suitable for the tree content provider
1663 * <p>
1664 * TODO move to some utility class
1666 * @param monitor
1667 * @return a list of nodes
1668 * @throws InterruptedException
1670 public static List<RepositoryTreeNode<Repository>> getRepositoriesFromDirs(
1671 IProgressMonitor monitor) throws InterruptedException {
1673 List<String> gitDirStrings = getDirs();
1674 List<RepositoryTreeNode<Repository>> input = new ArrayList<RepositoryTreeNode<Repository>>();
1676 for (String dirString : gitDirStrings) {
1677 if (monitor != null && monitor.isCanceled()) {
1678 throw new InterruptedException(
1679 UIText.RepositoriesView_ActionCanceled_Message);
1681 try {
1682 File dir = new File(dirString);
1683 if (dir.exists() && dir.isDirectory()) {
1684 Repository repo = new Repository(dir);
1685 // reset repository change events here so that check for
1686 // repository changes does not trigger an unnecessary
1687 // refresh
1688 repo.scanForRepoChanges();
1689 RepositoryTreeNode<Repository> node = new RepositoryTreeNode<Repository>(
1690 null, RepositoryTreeNodeType.REPO, repo, repo);
1691 input.add(node);
1693 } catch (IOException e) {
1694 IStatus error = new Status(IStatus.ERROR, Activator
1695 .getPluginId(), e.getMessage(), e);
1696 Activator.getDefault().getLog().log(error);
1699 Collections.sort(input);
1700 return input;
1703 private static void saveDirs(Set<String> gitDirStrings) {
1704 StringBuilder sb = new StringBuilder();
1705 for (String gitDirString : gitDirStrings) {
1706 sb.append(gitDirString);
1707 sb.append(File.pathSeparatorChar);
1710 IEclipsePreferences prefs = getPrefs();
1711 prefs.put(PREFS_DIRECTORIES, sb.toString());
1712 try {
1713 prefs.flush();
1714 } catch (BackingStoreException e) {
1715 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1716 e.getMessage(), e);
1717 Activator.getDefault().getLog().log(error);
1721 @Override
1722 public void setFocus() {
1723 tv.getTree().setFocus();
1726 @SuppressWarnings("boxing")
1727 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1728 boolean confirmed;
1729 confirmed = MessageDialog
1730 .openConfirm(
1731 getSite().getShell(),
1732 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1734 .bind(
1735 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1736 projectsToDelete.size()));
1737 return confirmed;
1740 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1741 selectionListeners.add(listener);
1744 public ISelection getSelection() {
1745 return currentSelection;
1748 public void removeSelectionChangedListener(
1749 ISelectionChangedListener listener) {
1750 selectionListeners.remove(listener);
1754 public void setSelection(ISelection selection) {
1755 currentSelection = selection;
1756 for (ISelectionChangedListener listener : selectionListeners) {
1757 listener.selectionChanged(new SelectionChangedEvent(
1758 RepositoriesView.this, selection));
1763 * Opens the tree and marks the folder to which a project is pointing
1765 * @param resource
1766 * TODO exceptions?
1768 @SuppressWarnings("unchecked")
1769 public void showResource(final IResource resource) {
1770 IProject project = resource.getProject();
1771 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1772 if (mapping == null)
1773 return;
1775 if (addDir(mapping.getRepository().getDirectory())) {
1776 scheduleRefresh();
1779 boolean doSetSelection = false;
1781 if (this.scheduledJob != null) {
1782 int state = this.scheduledJob.getState();
1783 if (state == Job.WAITING || state == Job.RUNNING) {
1784 this.scheduledJob.addJobChangeListener(new JobChangeAdapter() {
1786 @Override
1787 public void done(IJobChangeEvent event) {
1788 showResource(resource);
1791 } else {
1792 doSetSelection = true;
1796 if (doSetSelection) {
1797 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1798 .getContentProvider();
1799 RepositoryTreeNode currentNode = null;
1800 Object[] repos = cp.getElements(tv.getInput());
1801 for (Object repo : repos) {
1802 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1803 // TODO equals implementation of Repository?
1804 if (mapping.getRepository().getDirectory().equals(
1805 ((Repository) node.getObject()).getDirectory())) {
1806 for (Object child : cp.getChildren(node)) {
1807 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1808 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1809 currentNode = childNode;
1810 break;
1813 break;
1817 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1819 for (String segment : relPath.segments()) {
1820 for (Object child : cp.getChildren(currentNode)) {
1821 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1822 if (childNode.getObject().getName().equals(segment)) {
1823 currentNode = childNode;
1824 break;
1829 final RepositoryTreeNode selNode = currentNode;
1831 Display.getDefault().asyncExec(new Runnable() {
1833 public void run() {
1834 tv.setSelection(new StructuredSelection(selNode), true);
1842 public boolean show(ShowInContext context) {
1843 ISelection selection = context.getSelection();
1844 if (selection instanceof IStructuredSelection) {
1845 IStructuredSelection ss = (IStructuredSelection) selection;
1846 if (ss.size() == 1) {
1847 Object element = ss.getFirstElement();
1848 if (element instanceof IAdaptable) {
1849 IResource resource = (IResource) ((IAdaptable) element)
1850 .getAdapter(IResource.class);
1851 if (resource != null) {
1852 showResource(resource);
1853 return true;
1858 return false;
1861 private void removeRepository(final IProgressMonitor monitor,
1862 final Repository... repository) {
1863 final List<IProject> projectsToDelete = new ArrayList<IProject>();
1865 monitor
1866 .setTaskName(UIText.RepositoriesView_DeleteRepoDeterminProjectsMessage);
1868 for (Repository repo : repository) {
1869 File workDir = repo.getWorkDir();
1870 final IPath wdPath = new Path(workDir.getAbsolutePath());
1871 for (IProject prj : ResourcesPlugin.getWorkspace().getRoot()
1872 .getProjects()) {
1873 if (monitor.isCanceled())
1874 return;
1875 if (wdPath.isPrefixOf(prj.getLocation())) {
1876 projectsToDelete.add(prj);
1881 if (!projectsToDelete.isEmpty()) {
1882 boolean confirmed;
1883 confirmed = confirmProjectDeletion(projectsToDelete);
1884 if (!confirmed) {
1885 return;
1889 if (monitor.isCanceled())
1890 return;
1892 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1894 public void run(IProgressMonitor actMonitor) throws CoreException {
1896 for (IProject prj : projectsToDelete) {
1897 prj.delete(false, false, actMonitor);
1899 for (Repository repo : repository)
1900 removeDir(repo.getDirectory());
1901 scheduleRefresh();
1905 try {
1906 ResourcesPlugin.getWorkspace().run(wsr,
1907 ResourcesPlugin.getWorkspace().getRoot(),
1908 IWorkspace.AVOID_UPDATE, monitor);
1909 } catch (CoreException e1) {
1910 Activator.logError(e1.getMessage(), e1);