Git Repositories View: allow tag check-out
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob8133d8298dfacec827ac28ad538cc83ecbeb05b1
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.Ref;
76 import org.eclipse.jgit.lib.RefUpdate;
77 import org.eclipse.jgit.lib.Repository;
78 import org.eclipse.jgit.lib.RepositoryCache;
79 import org.eclipse.jgit.lib.RepositoryConfig;
80 import org.eclipse.jgit.transport.RemoteConfig;
81 import org.eclipse.osgi.util.NLS;
82 import org.eclipse.swt.SWT;
83 import org.eclipse.swt.dnd.Clipboard;
84 import org.eclipse.swt.dnd.TextTransfer;
85 import org.eclipse.swt.dnd.Transfer;
86 import org.eclipse.swt.events.MenuDetectEvent;
87 import org.eclipse.swt.events.MenuDetectListener;
88 import org.eclipse.swt.events.SelectionAdapter;
89 import org.eclipse.swt.events.SelectionEvent;
90 import org.eclipse.swt.graphics.Point;
91 import org.eclipse.swt.widgets.Composite;
92 import org.eclipse.swt.widgets.Display;
93 import org.eclipse.swt.widgets.Menu;
94 import org.eclipse.swt.widgets.MenuItem;
95 import org.eclipse.swt.widgets.TreeItem;
96 import org.eclipse.ui.IEditorInput;
97 import org.eclipse.ui.IEditorPart;
98 import org.eclipse.ui.IFileEditorInput;
99 import org.eclipse.ui.IPageLayout;
100 import org.eclipse.ui.ISelectionListener;
101 import org.eclipse.ui.ISelectionService;
102 import org.eclipse.ui.IViewPart;
103 import org.eclipse.ui.IWorkbenchPart;
104 import org.eclipse.ui.PartInitException;
105 import org.eclipse.ui.PlatformUI;
106 import org.eclipse.ui.actions.ActionFactory;
107 import org.eclipse.ui.editors.text.EditorsUI;
108 import org.eclipse.ui.ide.FileStoreEditorInput;
109 import org.eclipse.ui.ide.IDE;
110 import org.eclipse.ui.part.IShowInTarget;
111 import org.eclipse.ui.part.ShowInContext;
112 import org.eclipse.ui.part.ViewPart;
113 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
114 import org.eclipse.ui.views.properties.IPropertySheetPage;
115 import org.eclipse.ui.views.properties.PropertySheet;
116 import org.eclipse.ui.views.properties.PropertySheetPage;
117 import org.osgi.service.prefs.BackingStoreException;
121 * The Git Repositories view.
122 * <p>
123 * This keeps track of a bunch of local directory names each of which represent
124 * a Git Repository. This list is stored in some Preferences object and used to
125 * build the tree in the view.
126 * <p>
127 * Implements {@link ISelectionProvider} in order to integrate with the
128 * Properties view.
129 * <p>
130 * TODO
131 * <li>Clarification whether to show projects, perhaps configurable switch</li>
134 public class RepositoriesView extends ViewPart implements ISelectionProvider,
135 IShowInTarget {
137 /** The view ID */
138 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
140 // TODO central constants? RemoteConfig ones are private
141 static final String REMOTE = "remote"; //$NON-NLS-1$
143 static final String URL = "url"; //$NON-NLS-1$
145 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
147 static final String FETCH = "fetch"; //$NON-NLS-1$
149 static final String PUSH = "push"; //$NON-NLS-1$
151 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
153 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
155 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
157 private ISelection currentSelection = new StructuredSelection();
159 private Job scheduledJob;
161 private TreeViewer tv;
163 private IAction importAction;
165 private IAction addAction;
167 private IAction refreshAction;
169 private IAction linkWithSelectionAction;
171 private IAction copyAction;
173 private IAction pasteAction;
176 * TODO move to utility class
178 * @return the directories as configured for this view
180 public static List<String> getDirs() {
181 List<String> resultStrings = new ArrayList<String>();
182 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
183 if (dirs != null && dirs.length() > 0) {
184 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
185 while (tok.hasMoreTokens()) {
186 String dirName = tok.nextToken();
187 File testFile = new File(dirName);
188 if (testFile.exists()) {
189 resultStrings.add(dirName);
193 Collections.sort(resultStrings);
194 return resultStrings;
197 private static void removeDir(File file) {
199 String dir;
200 try {
201 dir = file.getCanonicalPath();
202 } catch (IOException e1) {
203 dir = file.getAbsolutePath();
206 IEclipsePreferences prefs = getPrefs();
208 TreeSet<String> resultStrings = new TreeSet<String>();
209 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
210 if (dirs != null && dirs.length() > 0) {
211 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
212 while (tok.hasMoreTokens()) {
213 String dirName = tok.nextToken();
214 File testFile = new File(dirName);
215 if (testFile.exists()) {
216 try {
217 resultStrings.add(testFile.getCanonicalPath());
218 } catch (IOException e) {
219 resultStrings.add(testFile.getAbsolutePath());
225 if (resultStrings.remove(dir)) {
226 StringBuilder sb = new StringBuilder();
227 for (String gitDirString : resultStrings) {
228 sb.append(gitDirString);
229 sb.append(File.pathSeparatorChar);
232 prefs.put(PREFS_DIRECTORIES, sb.toString());
233 try {
234 prefs.flush();
235 } catch (BackingStoreException e) {
236 IStatus error = new Status(IStatus.ERROR, Activator
237 .getPluginId(), e.getMessage(), e);
238 Activator.getDefault().getLog().log(error);
244 @Override
245 public Object getAdapter(Class adapter) {
246 // integrate with Properties view
247 if (adapter == IPropertySheetPage.class) {
248 PropertySheetPage page = new PropertySheetPage();
249 page
250 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
251 page));
252 return page;
255 return super.getAdapter(adapter);
258 @Override
259 public void createPartControl(Composite parent) {
260 tv = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
261 tv.setContentProvider(new RepositoriesViewContentProvider());
262 // the label provider registers itself
263 new RepositoriesViewLabelProvider(tv);
265 getSite().setSelectionProvider(this);
267 tv.addSelectionChangedListener(new ISelectionChangedListener() {
269 public void selectionChanged(SelectionChangedEvent event) {
271 copyAction.setEnabled(false);
273 IStructuredSelection ssel = (IStructuredSelection) event
274 .getSelection();
275 if (ssel.size() == 1) {
276 RepositoryTreeNode node = (RepositoryTreeNode) ssel
277 .getFirstElement();
278 // allow copy on repository, file, or folder (copying the
279 // directory)
280 if (node.getType() == RepositoryTreeNodeType.REPO
281 || node.getType() == RepositoryTreeNodeType.WORKINGDIR
282 || node.getType() == RepositoryTreeNodeType.FOLDER
283 || node.getType() == RepositoryTreeNodeType.FILE) {
284 copyAction.setEnabled(true);
286 setSelection(new StructuredSelection(ssel.getFirstElement()));
287 } else {
288 setSelection(new StructuredSelection());
293 tv.addOpenListener(new IOpenListener() {
294 public void open(OpenEvent event) {
295 IStructuredSelection selection = (IStructuredSelection) event
296 .getSelection();
297 if (selection.isEmpty()) {
298 // nothing selected, ignore
299 return;
302 Object element = selection.getFirstElement();
303 ITreeContentProvider contentProvider = (ITreeContentProvider) tv
304 .getContentProvider();
305 if (contentProvider.hasChildren(element)) {
306 // this element has children, expand/collapse it
307 tv.setExpandedState(element, !tv.getExpandedState(element));
308 } else {
309 Object[] selectionArray = selection.toArray();
310 for (Object selectedElement : selectionArray) {
311 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
312 // if any of the selected elements are not files, ignore
313 // the open request
314 if (node.getType() != RepositoryTreeNodeType.FILE
315 && node.getType() != RepositoryTreeNodeType.REF
316 && node.getType() != RepositoryTreeNodeType.TAG) {
317 return;
321 // open the files the user has selected
322 for (Object selectedElement : selectionArray) {
323 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
324 if (node.getType() == RepositoryTreeNodeType.FILE)
325 openFile((File) node.getObject());
326 else if (node.getType() == RepositoryTreeNodeType.REF
327 || node.getType() == RepositoryTreeNodeType.TAG) {
328 Ref ref = (Ref) node.getObject();
329 if (!isBare(node.getRepository())
330 && ref.getName().startsWith(
331 Constants.R_REFS))
332 checkoutBranch(node, ref.getName());
339 addContextMenu();
341 addActionsToToolbar();
343 scheduleRefresh();
345 ISelectionService srv = (ISelectionService) getSite().getService(
346 ISelectionService.class);
347 srv.addPostSelectionListener(new ISelectionListener() {
349 public void selectionChanged(IWorkbenchPart part,
350 ISelection selection) {
352 // if the "link with selection" toggle is off, we're done
353 if (linkWithSelectionAction == null
354 || !linkWithSelectionAction.isChecked())
355 return;
357 // this may happen if we switch between editors
358 if (part instanceof IEditorPart) {
359 IEditorInput input = ((IEditorPart) part).getEditorInput();
360 if (input instanceof IFileEditorInput)
361 reactOnSelection(new StructuredSelection(
362 ((IFileEditorInput) input).getFile()));
364 } else {
365 reactOnSelection(selection);
372 private void reactOnSelection(ISelection selection) {
373 if (selection instanceof StructuredSelection) {
374 StructuredSelection ssel = (StructuredSelection) selection;
375 if (ssel.size() != 1)
376 return;
377 if (ssel.getFirstElement() instanceof IResource) {
378 showResource((IResource) ssel.getFirstElement());
380 if (ssel.getFirstElement() instanceof IAdaptable) {
381 IResource adapted = (IResource) ((IAdaptable) ssel
382 .getFirstElement()).getAdapter(IResource.class);
383 if (adapted != null)
384 showResource(adapted);
389 private void addContextMenu() {
390 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
392 public void menuDetected(MenuDetectEvent e) {
393 Menu men = tv.getTree().getMenu();
394 if (men != null) {
395 men.dispose();
397 men = new Menu(tv.getTree());
399 TreeItem testItem = tv.getTree().getItem(
400 tv.getTree().toControl(new Point(e.x, e.y)));
401 if (testItem == null) {
402 addMenuItemsForPanel(men);
403 } else {
404 addMenuItemsForTreeSelection(men);
407 tv.getTree().setMenu(men);
412 private void addMenuItemsForPanel(Menu men) {
414 MenuItem importItem = new MenuItem(men, SWT.PUSH);
415 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
416 importItem.addSelectionListener(new SelectionAdapter() {
418 @Override
419 public void widgetSelected(SelectionEvent e) {
420 importAction.run();
425 MenuItem addItem = new MenuItem(men, SWT.PUSH);
426 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
427 addItem.addSelectionListener(new SelectionAdapter() {
429 @Override
430 public void widgetSelected(SelectionEvent e) {
431 addAction.run();
436 MenuItem pasteItem = new MenuItem(men, SWT.PUSH);
437 pasteItem.setText(UIText.RepositoriesView_PasteMenu);
438 pasteItem.addSelectionListener(new SelectionAdapter() {
440 @Override
441 public void widgetSelected(SelectionEvent e) {
442 pasteAction.run();
447 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
448 refreshItem.setText(refreshAction.getText());
449 refreshItem.addSelectionListener(new SelectionAdapter() {
451 @Override
452 public void widgetSelected(SelectionEvent e) {
453 refreshAction.run();
460 private void addMenuItemsForTreeSelection(Menu men) {
462 final IStructuredSelection sel = (IStructuredSelection) tv
463 .getSelection();
465 boolean repoOnly = true;
466 for (Object selected : sel.toArray()) {
468 if (((RepositoryTreeNode) selected).getType() != RepositoryTreeNodeType.REPO) {
469 repoOnly = false;
470 break;
474 if (sel.size() > 1 && repoOnly) {
475 List nodes = sel.toList();
476 final Repository[] repos = new Repository[nodes.size()];
477 for (int i = 0; i < sel.size(); i++)
478 repos[i] = ((RepositoryTreeNode) nodes.get(i)).getRepository();
480 MenuItem remove = new MenuItem(men, SWT.PUSH);
481 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
482 remove.addSelectionListener(new SelectionAdapter() {
484 @Override
485 public void widgetSelected(SelectionEvent e) {
486 // TODO progress monitoring/cancellation
487 removeRepository(new NullProgressMonitor(), repos);
493 // from here on, we only deal with single selection
494 if (sel.size() > 1)
495 return;
497 final RepositoryTreeNode node = (RepositoryTreeNode) sel
498 .getFirstElement();
500 final boolean isBare = isBare(node.getRepository());
502 if (node.getType() == RepositoryTreeNodeType.REF) {
504 final Ref ref = (Ref) node.getObject();
506 // we don't check out symbolic references
507 if (!ref.isSymbolic()) {
509 if (!isBare) {
510 MenuItem checkout = new MenuItem(men, SWT.PUSH);
511 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
513 checkout.setEnabled(!isRefCheckedOut(node.getRepository(),
514 ref.getName()));
516 checkout.addSelectionListener(new SelectionAdapter() {
518 @Override
519 public void widgetSelected(SelectionEvent e) {
520 checkoutBranch(node, ref.getLeaf().getName());
524 new MenuItem(men, SWT.SEPARATOR);
527 createCreateBranchItem(men, node);
528 createDeleteBranchItem(men, node);
533 if (node.getType() == RepositoryTreeNodeType.TAG) {
535 final Ref ref = (Ref) node.getObject();
537 MenuItem checkout = new MenuItem(men, SWT.PUSH);
538 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
540 checkout.setEnabled(!isRefCheckedOut(node.getRepository(), ref
541 .getName()));
543 checkout.addSelectionListener(new SelectionAdapter() {
545 @Override
546 public void widgetSelected(SelectionEvent e) {
547 checkoutBranch(node, ref.getLeaf().getName());
553 if (node.getType() == RepositoryTreeNodeType.LOCALBRANCHES
554 || node.getType() == RepositoryTreeNodeType.REMOTEBRANCHES)
555 createCreateBranchItem(men, node);
557 // for Repository: import existing projects, remove, (delete), open
558 // properties
559 if (node.getType() == RepositoryTreeNodeType.REPO) {
561 final Repository repo = (Repository) node.getObject();
563 // TODO "import existing plug-in" menu item
565 MenuItem remove = new MenuItem(men, SWT.PUSH);
566 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
567 remove.addSelectionListener(new SelectionAdapter() {
569 @Override
570 public void widgetSelected(SelectionEvent e) {
571 // TODO progress monitoring/cancellation
572 removeRepository(new NullProgressMonitor(), repo);
576 // TODO delete does not work because of file locks on .pack-files
577 // Shawn Pearce has added the following thoughts:
579 // Hmm. We probably can't active detect file locks on pack files on
580 // Windows, can we?
581 // It would be nice if we could support a delete, but only if the
582 // repository is
583 // reasonably believed to be not-in-use right now.
585 // Within EGit you might be able to check GitProjectData and its
586 // repositoryCache to
587 // see if the repository is open by this workspace. If it is, then
588 // we know we shouldn't
589 // try to delete it.
591 // Some coding might look like this:
593 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
594 // deleteRepo.setText("Delete");
595 // deleteRepo.addSelectionListener(new SelectionAdapter() {
597 // @Override
598 // public void widgetSelected(SelectionEvent e) {
600 // boolean confirmed = MessageDialog.openConfirm(getSite()
601 // .getShell(), "Confirm",
602 // "This will delete the repository, continue?");
604 // if (!confirmed)
605 // return;
607 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
609 // public void run(IProgressMonitor monitor)
610 // throws CoreException {
611 // File workDir = repos.get(0).getRepository()
612 // .getWorkDir();
614 // File gitDir = repos.get(0).getRepository()
615 // .getDirectory();
617 // IPath wdPath = new Path(workDir.getAbsolutePath());
618 // for (IProject prj : ResourcesPlugin.getWorkspace()
619 // .getRoot().getProjects()) {
620 // if (wdPath.isPrefixOf(prj.getLocation())) {
621 // prj.delete(false, false, monitor);
622 // }
623 // }
625 // repos.get(0).getRepository().close();
627 // boolean deleted = deleteRecursively(gitDir, monitor);
628 // if (!deleted) {
629 // MessageDialog.openError(getSite().getShell(),
630 // "Error",
631 // "Could not delete Git Repository");
632 // }
634 // deleted = deleteRecursively(workDir, monitor);
635 // if (!deleted) {
636 // MessageDialog
637 // .openError(getSite().getShell(),
638 // "Error",
639 // "Could not delete Git Working Directory");
640 // }
642 // scheduleRefresh();
643 // }
645 // private boolean deleteRecursively(File fileToDelete,
646 // IProgressMonitor monitor) {
647 // if (fileToDelete.isDirectory()) {
648 // for (File file : fileToDelete.listFiles()) {
649 // if (!deleteRecursively(file, monitor)) {
650 // return false;
651 // }
652 // }
653 // }
654 // monitor.setTaskName(fileToDelete.getAbsolutePath());
655 // boolean deleted = fileToDelete.delete();
656 // if (!deleted) {
657 // System.err.println("Could not delete "
658 // + fileToDelete.getAbsolutePath());
659 // }
660 // return deleted;
661 // }
662 // };
664 // try {
665 // ResourcesPlugin.getWorkspace().run(wsr,
666 // ResourcesPlugin.getWorkspace().getRoot(),
667 // IWorkspace.AVOID_UPDATE,
668 // new NullProgressMonitor());
669 // } catch (CoreException e1) {
670 // // TODO Exception handling
671 // e1.printStackTrace();
672 // }
674 // }
676 // });
678 new MenuItem(men, SWT.SEPARATOR);
680 if (!isBare) {
681 createImportProjectItem(men, repo, repo.getWorkDir().getPath());
683 new MenuItem(men, SWT.SEPARATOR);
686 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
687 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
688 openPropsView.addSelectionListener(new SelectionAdapter() {
690 @Override
691 public void widgetSelected(SelectionEvent e) {
692 try {
693 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
694 .getActivePage().showView(
695 IPageLayout.ID_PROP_SHEET);
696 } catch (PartInitException e1) {
697 // just ignore
703 new MenuItem(men, SWT.SEPARATOR);
705 createCopyPathItem(men, repo.getDirectory().getPath());
708 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
710 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
711 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
712 remoteConfig.addSelectionListener(new SelectionAdapter() {
714 @Override
715 public void widgetSelected(SelectionEvent e) {
717 WizardDialog dlg = new WizardDialog(getSite().getShell(),
718 new NewRemoteWizard(node.getRepository()));
719 if (dlg.open() == Window.OK)
720 scheduleRefresh();
727 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
729 final String configName = (String) node.getObject();
731 RemoteConfig rconfig;
732 try {
733 rconfig = new RemoteConfig(node.getRepository().getConfig(),
734 configName);
735 } catch (URISyntaxException e2) {
736 // TODO Exception handling
737 rconfig = null;
740 boolean fetchExists = rconfig != null
741 && !rconfig.getURIs().isEmpty();
742 boolean pushExists = rconfig != null
743 && !rconfig.getPushURIs().isEmpty();
745 if (!fetchExists) {
746 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
747 configureUrlFetch
748 .setText(UIText.RepositoriesView_CreateFetch_menu);
750 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
752 @Override
753 public void widgetSelected(SelectionEvent e) {
755 WizardDialog dlg = new WizardDialog(getSite()
756 .getShell(), new ConfigureRemoteWizard(node
757 .getRepository(), configName, false));
758 if (dlg.open() == Window.OK)
759 scheduleRefresh();
766 if (!pushExists) {
767 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
769 configureUrlPush
770 .setText(UIText.RepositoriesView_CreatePush_menu);
772 configureUrlPush.addSelectionListener(new SelectionAdapter() {
774 @Override
775 public void widgetSelected(SelectionEvent e) {
777 WizardDialog dlg = new WizardDialog(getSite()
778 .getShell(), new ConfigureRemoteWizard(node
779 .getRepository(), configName, true));
780 if (dlg.open() == Window.OK)
781 scheduleRefresh();
788 if (!fetchExists || !pushExists)
789 // add a separator dynamically
790 new MenuItem(men, SWT.SEPARATOR);
792 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
793 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
794 removeRemote.addSelectionListener(new SelectionAdapter() {
796 @Override
797 public void widgetSelected(SelectionEvent e) {
799 boolean ok = MessageDialog
800 .openConfirm(
801 getSite().getShell(),
802 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
804 .bind(
805 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
806 configName));
807 if (ok) {
808 RepositoryConfig config = node.getRepository()
809 .getConfig();
810 config.unsetSection(REMOTE, configName);
811 try {
812 config.save();
813 scheduleRefresh();
814 } catch (IOException e1) {
815 Activator.handleError(
816 UIText.RepositoriesView_ErrorHeader, e1,
817 true);
825 new MenuItem(men, SWT.SEPARATOR);
827 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
828 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
829 openPropsView.addSelectionListener(new SelectionAdapter() {
831 @Override
832 public void widgetSelected(SelectionEvent e) {
833 try {
834 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
835 .getActivePage().showView(
836 IPageLayout.ID_PROP_SHEET);
837 } catch (PartInitException e1) {
838 // just ignore
845 if (node.getType() == RepositoryTreeNodeType.FETCH) {
847 final String configName = (String) node.getParent().getObject();
849 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
850 configureUrlFetch
851 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
853 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
855 @Override
856 public void widgetSelected(SelectionEvent e) {
858 WizardDialog dlg = new WizardDialog(getSite().getShell(),
859 new ConfigureRemoteWizard(node.getRepository(),
860 configName, false));
861 if (dlg.open() == Window.OK)
862 scheduleRefresh();
868 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
869 deleteFetch.setText(UIText.RepositoriesView_RemoveFetch_menu);
870 deleteFetch.addSelectionListener(new SelectionAdapter() {
872 @Override
873 public void widgetSelected(SelectionEvent e) {
874 RepositoryConfig config = node.getRepository().getConfig();
875 config.unset("remote", configName, "url"); //$NON-NLS-1$ //$NON-NLS-2$
876 config.unset("remote", configName, "fetch"); //$NON-NLS-1$//$NON-NLS-2$
877 try {
878 config.save();
879 scheduleRefresh();
880 } catch (IOException e1) {
881 MessageDialog.openError(getSite().getShell(),
882 UIText.RepositoriesView_ErrorHeader, e1
883 .getMessage());
891 if (node.getType() == RepositoryTreeNodeType.PUSH) {
893 final String configName = (String) node.getParent().getObject();
895 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
897 configureUrlPush.setText(UIText.RepositoriesView_ConfigurePushMenu);
899 configureUrlPush.addSelectionListener(new SelectionAdapter() {
901 @Override
902 public void widgetSelected(SelectionEvent e) {
904 WizardDialog dlg = new WizardDialog(getSite().getShell(),
905 new ConfigureRemoteWizard(node.getRepository(),
906 configName, true));
907 if (dlg.open() == Window.OK)
908 scheduleRefresh();
913 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
914 deleteFetch.setText(UIText.RepositoriesView_RemovePush_menu);
915 deleteFetch.addSelectionListener(new SelectionAdapter() {
917 @Override
918 public void widgetSelected(SelectionEvent e) {
919 RepositoryConfig config = node.getRepository().getConfig();
920 config.unset("remote", configName, "pushurl"); //$NON-NLS-1$ //$NON-NLS-2$
921 config.unset("remote", configName, "push"); //$NON-NLS-1$ //$NON-NLS-2$
922 try {
923 config.save();
924 scheduleRefresh();
925 } catch (IOException e1) {
926 MessageDialog.openError(getSite().getShell(),
927 UIText.RepositoriesView_ErrorHeader, e1
928 .getMessage());
935 if (node.getType() == RepositoryTreeNodeType.FILE) {
937 final File file = (File) node.getObject();
939 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
940 openInTextEditor
941 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
942 openInTextEditor.addSelectionListener(new SelectionAdapter() {
944 @Override
945 public void widgetSelected(SelectionEvent e) {
946 openFile(file);
951 new MenuItem(men, SWT.SEPARATOR);
952 createCopyPathItem(men, file.getPath());
955 if (!isBare && node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
956 String path = node.getRepository().getWorkDir().getAbsolutePath();
957 createImportProjectItem(men, node.getRepository(), path);
958 new MenuItem(men, SWT.SEPARATOR);
959 createCopyPathItem(men, path);
962 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
963 String path = ((File) node.getObject()).getPath();
964 createImportProjectItem(men, node.getRepository(), path);
965 new MenuItem(men, SWT.SEPARATOR);
966 createCopyPathItem(men, path);
971 private boolean isBare(Repository repository) {
972 return repository.getConfig().getBoolean("core", "bare", false); //$NON-NLS-1$ //$NON-NLS-2$
975 private boolean isRefCheckedOut(Repository repository, String refName) {
976 String branchName;
977 String compareString;
979 try {
980 branchName = repository.getFullBranch();
981 if (branchName == null)
982 return false;
983 if (refName.startsWith(Constants.R_HEADS)) {
984 // local branch: HEAD would be on the branch
985 compareString = refName;
986 } else if (refName.startsWith(Constants.R_TAGS)) {
987 // tag: HEAD would be on the commit id to which the tag is
988 // pointing
989 compareString = repository.mapTag(refName).getObjId().getName();
990 } else if (refName.startsWith(Constants.R_REMOTES)) {
991 // remote branch: HEAD would be on the commit id to which
992 // the branch is pointing
993 compareString = repository.mapCommit(refName).getCommitId()
994 .getName();
995 } else {
996 // some other symbolic reference
997 return false;
999 } catch (IOException e1) {
1000 return false;
1003 return compareString.equals(branchName);
1006 private void createCopyPathItem(Menu men, final String path) {
1008 MenuItem copyPath;
1009 copyPath = new MenuItem(men, SWT.PUSH);
1010 copyPath.setText(UIText.RepositoriesView_CopyPathToClipboardMenu);
1011 copyPath.addSelectionListener(new SelectionAdapter() {
1013 @Override
1014 public void widgetSelected(SelectionEvent e) {
1015 Clipboard clipboard = new Clipboard(null);
1016 TextTransfer textTransfer = TextTransfer.getInstance();
1017 Transfer[] transfers = new Transfer[] { textTransfer };
1018 Object[] data = new Object[] { path };
1019 clipboard.setContents(data, transfers);
1020 clipboard.dispose();
1027 private void createCreateBranchItem(Menu men, final RepositoryTreeNode node) {
1029 final boolean remoteMode;
1030 final Ref ref;
1031 if (node.getType() == RepositoryTreeNodeType.REF) {
1032 remoteMode = node.getParent().getType() == RepositoryTreeNodeType.REMOTEBRANCHES;
1033 ref = (Ref) node.getObject();
1034 } else if (node.getType() == RepositoryTreeNodeType.LOCALBRANCHES) {
1035 remoteMode = false;
1036 ref = null;
1037 } else if (node.getType() == RepositoryTreeNodeType.REMOTEBRANCHES) {
1038 remoteMode = true;
1039 ref = null;
1040 } else
1041 return;
1043 MenuItem createLocal = new MenuItem(men, SWT.PUSH);
1044 if (remoteMode)
1045 createLocal.setText(UIText.RepositoriesView_NewRemoteBranchMenu);
1046 else
1047 createLocal.setText(UIText.RepositoriesView_NewLocalBranchMenu);
1049 createLocal.addSelectionListener(new SelectionAdapter() {
1051 @Override
1052 public void widgetSelected(SelectionEvent e) {
1054 Wizard wiz = new Wizard() {
1056 @Override
1057 public void addPages() {
1058 addPage(new CreateBranchPage(node.getRepository(), ref,
1059 remoteMode));
1060 setWindowTitle(UIText.RepositoriesView_NewBranchTitle);
1063 @Override
1064 public boolean performFinish() {
1066 try {
1067 getContainer().run(false, true,
1068 new IRunnableWithProgress() {
1070 public void run(IProgressMonitor monitor)
1071 throws InvocationTargetException,
1072 InterruptedException {
1073 CreateBranchPage cp = (CreateBranchPage) getPages()[0];
1074 try {
1075 cp.createBranch(monitor);
1076 } catch (CoreException ce) {
1077 throw new InvocationTargetException(
1078 ce);
1079 } catch (IOException ioe) {
1080 throw new InvocationTargetException(
1081 ioe);
1086 } catch (InvocationTargetException ite) {
1087 Activator
1088 .handleError(
1089 UIText.RepositoriesView_BranchCreationFailureMessage,
1090 ite.getCause(), true);
1091 return false;
1092 } catch (InterruptedException ie) {
1093 // ignore here
1095 return true;
1098 if (new WizardDialog(getSite().getShell(), wiz).open() == Window.OK)
1099 scheduleRefresh();
1106 private void createDeleteBranchItem(Menu men, final RepositoryTreeNode node) {
1108 final Ref ref = (Ref) node.getObject();
1110 MenuItem deleteBranch = new MenuItem(men, SWT.PUSH);
1111 deleteBranch.setText(UIText.RepositoriesView_DeleteBranchMenu);
1113 deleteBranch.setEnabled(!isRefCheckedOut(node.getRepository(), ref
1114 .getName()));
1116 deleteBranch.addSelectionListener(new SelectionAdapter() {
1118 @Override
1119 public void widgetSelected(SelectionEvent e) {
1121 if (!MessageDialog
1122 .openConfirm(
1123 getSite().getShell(),
1124 UIText.RepositoriesView_ConfirmDeleteTitle,
1126 .bind(
1127 UIText.RepositoriesView_ConfirmBranchDeletionMessage,
1128 ref.getName())))
1129 return;
1131 try {
1132 new ProgressMonitorDialog(getSite().getShell()).run(false,
1133 false, new IRunnableWithProgress() {
1135 public void run(IProgressMonitor monitor)
1136 throws InvocationTargetException,
1137 InterruptedException {
1139 try {
1140 RefUpdate op = node.getRepository()
1141 .updateRef(ref.getName());
1142 op.setRefLogMessage("branch deleted", //$NON-NLS-1$
1143 false);
1144 // we set the force update in order
1145 // to avoid having this rejected
1146 // due to minor issues
1147 op.setForceUpdate(true);
1148 op.delete();
1149 scheduleRefresh();
1150 } catch (IOException ioe) {
1151 throw new InvocationTargetException(ioe);
1156 } catch (InvocationTargetException e1) {
1157 Activator
1158 .handleError(
1159 UIText.RepositoriesView_BranchDeletionFailureMessage,
1160 e1.getCause(), true);
1161 e1.printStackTrace();
1162 } catch (InterruptedException e1) {
1163 // ignore
1171 private void openFile(File file) {
1172 IFileStore store = EFS.getLocalFileSystem().getStore(
1173 new Path(file.getAbsolutePath()));
1174 try {
1175 // TODO do we need a read-only editor here?
1176 IDE.openEditor(getSite().getPage(),
1177 new FileStoreEditorInput(store),
1178 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
1179 } catch (PartInitException e) {
1180 Activator.handleError(UIText.RepositoriesView_Error_WindowTitle, e,
1181 true);
1185 private void checkoutBranch(final RepositoryTreeNode node,
1186 final String refName) {
1187 // for the sake of UI responsiveness, let's start a job
1188 Job job = new Job(NLS.bind(UIText.RepositoriesView_CheckingOutMessage,
1189 refName)) {
1191 @Override
1192 protected IStatus run(IProgressMonitor monitor) {
1194 Repository repo = node.getRepository();
1196 final BranchOperation op = new BranchOperation(repo, refName);
1197 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1199 public void run(IProgressMonitor myMonitor)
1200 throws CoreException {
1201 op.execute(myMonitor);
1205 try {
1206 ResourcesPlugin.getWorkspace().run(wsr,
1207 ResourcesPlugin.getWorkspace().getRoot(),
1208 IWorkspace.AVOID_UPDATE, monitor);
1209 scheduleRefresh();
1210 } catch (CoreException e1) {
1211 return new Status(IStatus.ERROR, Activator.getPluginId(),
1212 e1.getMessage(), e1);
1215 return Status.OK_STATUS;
1219 job.setUser(true);
1220 job.schedule();
1223 private void createImportProjectItem(Menu men, final Repository repo,
1224 final String path) {
1226 MenuItem startWizard;
1227 startWizard = new MenuItem(men, SWT.PUSH);
1228 startWizard.setText(UIText.RepositoriesView_ImportProjectsMenu);
1229 startWizard.addSelectionListener(new SelectionAdapter() {
1231 @Override
1232 public void widgetSelected(SelectionEvent e) {
1233 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1234 new GitCreateProjectViaWizardWizard(repo, path));
1235 if (dlg.open() == Window.OK)
1236 scheduleRefresh();
1242 // we could start the ImportWizard here,
1243 // unfortunately, this fails within a wizard
1244 // startWizard = new MenuItem(men, SWT.PUSH);
1245 // startWizard.setText("Start the Import wizard...");
1246 // startWizard.addSelectionListener(new SelectionAdapter() {
1248 // @Override
1249 // public void widgetSelected(SelectionEvent e) {
1251 // IHandlerService handlerService = (IHandlerService) getSite()
1252 // .getWorkbenchWindow().getWorkbench().getService(
1253 // IHandlerService.class);
1255 // try {
1256 // handlerService.executeCommand("org.eclipse.ui.file.import", //$NON-NLS-1$
1257 // null);
1258 // } catch (ExecutionException e1) {
1259 // Activator.handleError(e1.getMessage(), e1, true);
1260 // } catch (NotDefinedException e1) {
1261 // Activator.handleError(e1.getMessage(), e1, true);
1262 // } catch (NotEnabledException e1) {
1263 // Activator.handleError(e1.getMessage(), e1, true);
1264 // } catch (NotHandledException e1) {
1265 // Activator.handleError(e1.getMessage(), e1, true);
1266 // }
1267 // }
1269 // });
1272 private void addActionsToToolbar() {
1274 IToolBarManager manager = getViewSite().getActionBars()
1275 .getToolBarManager();
1277 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
1279 @Override
1280 public void run() {
1281 scheduleRefresh();
1284 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1285 manager.add(refreshAction);
1287 linkWithSelectionAction = new Action(
1288 UIText.RepositoriesView_LinkWithSelection_action,
1289 IAction.AS_CHECK_BOX) {
1291 @Override
1292 public void run() {
1293 IEclipsePreferences prefs = getPrefs();
1294 prefs.putBoolean(PREFS_SYNCED, isChecked());
1295 try {
1296 prefs.flush();
1297 } catch (BackingStoreException e) {
1298 // ignore here
1300 if (isChecked()) {
1301 ISelectionService srv = (ISelectionService) getSite()
1302 .getService(ISelectionService.class);
1303 reactOnSelection(srv.getSelection());
1310 linkWithSelectionAction
1311 .setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
1312 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1313 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
1314 false));
1316 manager.add(linkWithSelectionAction);
1318 manager.add(new Separator());
1320 IAction collapseAllAction = new Action(
1321 UIText.RepositoriesView_CollapseAllMenu) {
1323 @Override
1324 public void run() {
1325 tv.collapseAll();
1328 collapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1329 manager.add(collapseAllAction);
1331 manager.add(new Separator());
1333 importAction = new Action(UIText.RepositoriesView_Import_Button) {
1335 @Override
1336 public void run() {
1337 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1338 new GitCloneWizard());
1339 if (dlg.open() == Window.OK)
1340 scheduleRefresh();
1343 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
1344 importAction.setImageDescriptor(UIIcons.CLONEGIT);
1346 manager.add(importAction);
1348 addAction = new Action(UIText.RepositoriesView_Add_Button) {
1350 @Override
1351 public void run() {
1352 RepositorySearchDialog sd = new RepositorySearchDialog(
1353 getSite().getShell(), getDirs());
1354 if (sd.open() == Window.OK) {
1355 Set<String> dirs = new HashSet<String>();
1356 dirs.addAll(getDirs());
1357 if (dirs.addAll(sd.getDirectories()))
1358 saveDirs(dirs);
1359 scheduleRefresh();
1364 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
1365 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
1367 manager.add(addAction);
1369 // copy and paste are global actions; we just implement them
1370 // and register them with the global action handler
1371 // we enable/disable them upon tree selection changes
1373 copyAction = new Action("") { //$NON-NLS-1$
1375 @Override
1376 public void run() {
1377 // for REPO, WORKINGDIR, FILE, FOLDER: copy directory
1378 IStructuredSelection sel = (IStructuredSelection) tv
1379 .getSelection();
1380 if (sel.size() == 1) {
1381 RepositoryTreeNode node = (RepositoryTreeNode) sel
1382 .getFirstElement();
1383 String dir = null;
1384 if (node.getType() == RepositoryTreeNodeType.REPO) {
1385 dir = node.getRepository().getDirectory().getPath();
1386 } else if (node.getType() == RepositoryTreeNodeType.FILE
1387 || node.getType() == RepositoryTreeNodeType.FOLDER) {
1388 dir = ((File) node.getObject()).getPath();
1389 } else if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1390 if (!isBare(node.getRepository()))
1391 dir = node.getRepository().getWorkDir().getPath();
1393 if (dir != null) {
1394 Clipboard clip = null;
1395 try {
1396 clip = new Clipboard(getSite().getShell()
1397 .getDisplay());
1398 clip
1399 .setContents(new Object[] { dir },
1400 new Transfer[] { TextTransfer
1401 .getInstance() });
1402 } finally {
1403 if (clip != null)
1404 // we must dispose ourselves
1405 clip.dispose();
1412 copyAction.setEnabled(false);
1414 getViewSite().getActionBars().setGlobalActionHandler(
1415 ActionFactory.COPY.getId(), copyAction);
1417 pasteAction = new Action("") { //$NON-NLS-1$
1419 @Override
1420 public void run() {
1421 // we check if the pasted content is a directory
1422 // repository location and try to add this
1423 String errorMessage = null;
1425 Clipboard clip = null;
1426 try {
1427 clip = new Clipboard(getSite().getShell().getDisplay());
1428 String content = (String) clip.getContents(TextTransfer
1429 .getInstance());
1430 if (content == null) {
1431 errorMessage = UIText.RepositoriesView_NothingToPasteMessage;
1432 return;
1435 File file = new File(content);
1436 if (!file.exists() || !file.isDirectory()) {
1437 errorMessage = UIText.RepositoriesView_ClipboardContentNotDirectoryMessage;
1438 return;
1441 if (!RepositoryCache.FileKey.isGitRepository(file)) {
1442 errorMessage = NLS
1443 .bind(
1444 UIText.RepositoriesView_ClipboardContentNoGitRepoMessage,
1445 content);
1446 return;
1449 if (addDir(file))
1450 scheduleRefresh();
1451 else
1452 errorMessage = NLS.bind(
1453 UIText.RepositoriesView_PasteRepoAlreadyThere,
1454 content);
1455 } finally {
1456 if (clip != null)
1457 // we must dispose ourselves
1458 clip.dispose();
1459 if (errorMessage != null)
1460 // TODO String ext
1461 MessageDialog.openWarning(getSite().getShell(),
1462 UIText.RepositoriesView_PasteFailureTitle,
1463 errorMessage);
1469 getViewSite().getActionBars().setGlobalActionHandler(
1470 ActionFactory.PASTE.getId(), pasteAction);
1475 * @return the preferences
1477 protected static IEclipsePreferences getPrefs() {
1478 return new InstanceScope().getNode(Activator.getPluginId());
1481 @Override
1482 public void dispose() {
1483 // make sure to cancel the refresh job
1484 if (this.scheduledJob != null) {
1485 this.scheduledJob.cancel();
1486 this.scheduledJob = null;
1488 super.dispose();
1492 * Schedules a refreh
1494 public void scheduleRefresh() {
1496 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1498 @Override
1499 protected IStatus run(IProgressMonitor monitor) {
1501 final List<RepositoryTreeNode<Repository>> input;
1502 try {
1503 input = getRepositoriesFromDirs(monitor);
1504 } catch (InterruptedException e) {
1505 return new Status(IStatus.ERROR, Activator.getPluginId(), e
1506 .getMessage(), e);
1509 boolean needsNewInput = tv.getInput() == null;
1510 List oldInput = (List) tv.getInput();
1511 if (!needsNewInput)
1512 needsNewInput = oldInput.size() != input.size();
1514 if (!needsNewInput) {
1515 for (int i = 0; i < input.size(); i++) {
1516 needsNewInput = !input.get(i).equals(oldInput.get(i));
1517 if (needsNewInput)
1518 break;
1522 final boolean updateInput = needsNewInput;
1524 Display.getDefault().syncExec(new Runnable() {
1526 public void run() {
1527 // keep expansion state and selection so that we can
1528 // restore the tree
1529 // after update
1530 Object[] expanded = tv.getExpandedElements();
1531 IStructuredSelection sel = (IStructuredSelection) tv
1532 .getSelection();
1533 if (updateInput)
1534 tv.setInput(input);
1535 else
1536 tv.refresh();
1537 tv.setExpandedElements(expanded);
1539 Object selected = sel.getFirstElement();
1540 if (selected != null)
1541 tv.reveal(selected);
1543 IViewPart part = PlatformUI.getWorkbench()
1544 .getActiveWorkbenchWindow().getActivePage()
1545 .findView(IPageLayout.ID_PROP_SHEET);
1546 if (part != null) {
1547 PropertySheet sheet = (PropertySheet) part;
1548 PropertySheetPage page = (PropertySheetPage) sheet
1549 .getCurrentPage();
1550 page.refresh();
1555 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1560 job.setSystem(true);
1562 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1563 .getService(IWorkbenchSiteProgressService.class);
1565 service.schedule(job);
1567 scheduledJob = job;
1572 * Adds a directory to the list if it is not already there
1574 * @param file
1575 * @return see {@link Collection#add(Object)}
1577 public static boolean addDir(File file) {
1579 String dirString;
1580 try {
1581 dirString = file.getCanonicalPath();
1582 } catch (IOException e) {
1583 dirString = file.getAbsolutePath();
1586 List<String> dirStrings = getDirs();
1587 if (dirStrings.contains(dirString)) {
1588 return false;
1589 } else {
1590 Set<String> dirs = new HashSet<String>();
1591 dirs.addAll(dirStrings);
1592 dirs.add(dirString);
1593 saveDirs(dirs);
1594 return true;
1599 * Converts the directories as configured for this view into a list of
1600 * {@link Repository} objects suitable for the tree content provider
1601 * <p>
1602 * TODO move to some utility class
1604 * @param monitor
1605 * @return a list of nodes
1606 * @throws InterruptedException
1608 public static List<RepositoryTreeNode<Repository>> getRepositoriesFromDirs(
1609 IProgressMonitor monitor) throws InterruptedException {
1611 List<String> gitDirStrings = getDirs();
1612 List<RepositoryTreeNode<Repository>> input = new ArrayList<RepositoryTreeNode<Repository>>();
1614 for (String dirString : gitDirStrings) {
1615 if (monitor != null && monitor.isCanceled()) {
1616 throw new InterruptedException(
1617 UIText.RepositoriesView_ActionCanceled_Message);
1619 try {
1620 File dir = new File(dirString);
1621 if (dir.exists() && dir.isDirectory()) {
1622 Repository repo = new Repository(dir);
1623 RepositoryTreeNode<Repository> node = new RepositoryTreeNode<Repository>(
1624 null, RepositoryTreeNodeType.REPO, repo, repo);
1625 input.add(node);
1627 } catch (IOException e) {
1628 IStatus error = new Status(IStatus.ERROR, Activator
1629 .getPluginId(), e.getMessage(), e);
1630 Activator.getDefault().getLog().log(error);
1633 Collections.sort(input);
1634 return input;
1637 private static void saveDirs(Set<String> gitDirStrings) {
1638 StringBuilder sb = new StringBuilder();
1639 for (String gitDirString : gitDirStrings) {
1640 sb.append(gitDirString);
1641 sb.append(File.pathSeparatorChar);
1644 IEclipsePreferences prefs = getPrefs();
1645 prefs.put(PREFS_DIRECTORIES, sb.toString());
1646 try {
1647 prefs.flush();
1648 } catch (BackingStoreException e) {
1649 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1650 e.getMessage(), e);
1651 Activator.getDefault().getLog().log(error);
1655 @Override
1656 public void setFocus() {
1657 tv.getTree().setFocus();
1660 @SuppressWarnings("boxing")
1661 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1662 boolean confirmed;
1663 confirmed = MessageDialog
1664 .openConfirm(
1665 getSite().getShell(),
1666 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1668 .bind(
1669 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1670 projectsToDelete.size()));
1671 return confirmed;
1674 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1675 selectionListeners.add(listener);
1678 public ISelection getSelection() {
1679 return currentSelection;
1682 public void removeSelectionChangedListener(
1683 ISelectionChangedListener listener) {
1684 selectionListeners.remove(listener);
1688 public void setSelection(ISelection selection) {
1689 currentSelection = selection;
1690 for (ISelectionChangedListener listener : selectionListeners) {
1691 listener.selectionChanged(new SelectionChangedEvent(
1692 RepositoriesView.this, selection));
1697 * Opens the tree and marks the folder to which a project is pointing
1699 * @param resource
1700 * TODO exceptions?
1702 @SuppressWarnings("unchecked")
1703 public void showResource(final IResource resource) {
1704 IProject project = resource.getProject();
1705 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1706 if (mapping == null)
1707 return;
1709 if (addDir(mapping.getRepository().getDirectory())) {
1710 scheduleRefresh();
1713 boolean doSetSelection = false;
1715 if (this.scheduledJob != null) {
1716 int state = this.scheduledJob.getState();
1717 if (state == Job.WAITING || state == Job.RUNNING) {
1718 this.scheduledJob.addJobChangeListener(new JobChangeAdapter() {
1720 @Override
1721 public void done(IJobChangeEvent event) {
1722 showResource(resource);
1725 } else {
1726 doSetSelection = true;
1730 if (doSetSelection) {
1731 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1732 .getContentProvider();
1733 RepositoryTreeNode currentNode = null;
1734 Object[] repos = cp.getElements(tv.getInput());
1735 for (Object repo : repos) {
1736 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1737 // TODO equals implementation of Repository?
1738 if (mapping.getRepository().getDirectory().equals(
1739 ((Repository) node.getObject()).getDirectory())) {
1740 for (Object child : cp.getChildren(node)) {
1741 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1742 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1743 currentNode = childNode;
1744 break;
1747 break;
1751 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1753 for (String segment : relPath.segments()) {
1754 for (Object child : cp.getChildren(currentNode)) {
1755 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1756 if (childNode.getObject().getName().equals(segment)) {
1757 currentNode = childNode;
1758 break;
1763 final RepositoryTreeNode selNode = currentNode;
1765 Display.getDefault().asyncExec(new Runnable() {
1767 public void run() {
1768 tv.setSelection(new StructuredSelection(selNode), true);
1776 public boolean show(ShowInContext context) {
1777 ISelection selection = context.getSelection();
1778 if (selection instanceof IStructuredSelection) {
1779 IStructuredSelection ss = (IStructuredSelection) selection;
1780 if (ss.size() == 1) {
1781 Object element = ss.getFirstElement();
1782 if (element instanceof IAdaptable) {
1783 IResource resource = (IResource) ((IAdaptable) element)
1784 .getAdapter(IResource.class);
1785 if (resource != null) {
1786 showResource(resource);
1787 return true;
1792 return false;
1795 private void removeRepository(final IProgressMonitor monitor,
1796 final Repository... repository) {
1797 final List<IProject> projectsToDelete = new ArrayList<IProject>();
1799 monitor
1800 .setTaskName(UIText.RepositoriesView_DeleteRepoDeterminProjectsMessage);
1802 for (Repository repo : repository) {
1803 File workDir = repo.getWorkDir();
1804 final IPath wdPath = new Path(workDir.getAbsolutePath());
1805 for (IProject prj : ResourcesPlugin.getWorkspace().getRoot()
1806 .getProjects()) {
1807 if (monitor.isCanceled())
1808 return;
1809 if (wdPath.isPrefixOf(prj.getLocation())) {
1810 projectsToDelete.add(prj);
1815 if (!projectsToDelete.isEmpty()) {
1816 boolean confirmed;
1817 confirmed = confirmProjectDeletion(projectsToDelete);
1818 if (!confirmed) {
1819 return;
1823 if (monitor.isCanceled())
1824 return;
1826 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1828 public void run(IProgressMonitor actMonitor) throws CoreException {
1830 for (IProject prj : projectsToDelete) {
1831 prj.delete(false, false, actMonitor);
1833 for (Repository repo : repository)
1834 removeDir(repo.getDirectory());
1835 scheduleRefresh();
1839 try {
1840 ResourcesPlugin.getWorkspace().run(wsr,
1841 ResourcesPlugin.getWorkspace().getRoot(),
1842 IWorkspace.AVOID_UPDATE, monitor);
1843 } catch (CoreException e1) {
1844 Activator.logError(e1.getMessage(), e1);