Git Repositories View: Simple fetch and push
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob947d0d3e42b2d7ea5a015e19b8659afd6325e1a2
1 /*******************************************************************************
2 * Copyright (c) 2010 SAP AG.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Mathias Kinzler (SAP AG) - initial implementation
10 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.repository;
13 import java.io.File;
14 import java.io.IOException;
15 import java.lang.reflect.InvocationTargetException;
16 import java.net.URISyntaxException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.StringTokenizer;
24 import java.util.TreeSet;
26 import org.eclipse.core.filesystem.EFS;
27 import org.eclipse.core.filesystem.IFileStore;
28 import org.eclipse.core.resources.IProject;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.resources.IWorkspace;
31 import org.eclipse.core.resources.IWorkspaceRunnable;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IAdaptable;
35 import org.eclipse.core.runtime.IPath;
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.core.runtime.IStatus;
38 import org.eclipse.core.runtime.NullProgressMonitor;
39 import org.eclipse.core.runtime.Path;
40 import org.eclipse.core.runtime.Status;
41 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
42 import org.eclipse.core.runtime.jobs.Job;
43 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
44 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
45 import org.eclipse.core.runtime.preferences.InstanceScope;
46 import org.eclipse.egit.core.op.BranchOperation;
47 import org.eclipse.egit.core.project.RepositoryMapping;
48 import org.eclipse.egit.ui.Activator;
49 import org.eclipse.egit.ui.UIIcons;
50 import org.eclipse.egit.ui.UIText;
51 import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
52 import org.eclipse.egit.ui.internal.clone.GitCreateProjectViaWizardWizard;
53 import org.eclipse.egit.ui.internal.fetch.FetchConfiguredRemoteAction;
54 import org.eclipse.egit.ui.internal.push.PushConfiguredRemoteAction;
55 import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode.RepositoryTreeNodeType;
56 import org.eclipse.jface.action.Action;
57 import org.eclipse.jface.action.IAction;
58 import org.eclipse.jface.action.IToolBarManager;
59 import org.eclipse.jface.action.Separator;
60 import org.eclipse.jface.dialogs.MessageDialog;
61 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
62 import org.eclipse.jface.operation.IRunnableWithProgress;
63 import org.eclipse.jface.viewers.IOpenListener;
64 import org.eclipse.jface.viewers.ISelection;
65 import org.eclipse.jface.viewers.ISelectionChangedListener;
66 import org.eclipse.jface.viewers.ISelectionProvider;
67 import org.eclipse.jface.viewers.IStructuredSelection;
68 import org.eclipse.jface.viewers.ITreeContentProvider;
69 import org.eclipse.jface.viewers.OpenEvent;
70 import org.eclipse.jface.viewers.SelectionChangedEvent;
71 import org.eclipse.jface.viewers.StructuredSelection;
72 import org.eclipse.jface.viewers.TreeViewer;
73 import org.eclipse.jface.window.Window;
74 import org.eclipse.jface.wizard.Wizard;
75 import org.eclipse.jface.wizard.WizardDialog;
76 import org.eclipse.jgit.lib.Constants;
77 import org.eclipse.jgit.lib.IndexChangedEvent;
78 import org.eclipse.jgit.lib.Ref;
79 import org.eclipse.jgit.lib.RefUpdate;
80 import org.eclipse.jgit.lib.RefsChangedEvent;
81 import org.eclipse.jgit.lib.Repository;
82 import org.eclipse.jgit.lib.RepositoryCache;
83 import org.eclipse.jgit.lib.RepositoryConfig;
84 import org.eclipse.jgit.lib.RepositoryListener;
85 import org.eclipse.jgit.transport.RemoteConfig;
86 import org.eclipse.osgi.util.NLS;
87 import org.eclipse.swt.SWT;
88 import org.eclipse.swt.dnd.Clipboard;
89 import org.eclipse.swt.dnd.TextTransfer;
90 import org.eclipse.swt.dnd.Transfer;
91 import org.eclipse.swt.events.MenuDetectEvent;
92 import org.eclipse.swt.events.MenuDetectListener;
93 import org.eclipse.swt.events.SelectionAdapter;
94 import org.eclipse.swt.events.SelectionEvent;
95 import org.eclipse.swt.graphics.Point;
96 import org.eclipse.swt.widgets.Composite;
97 import org.eclipse.swt.widgets.Display;
98 import org.eclipse.swt.widgets.Menu;
99 import org.eclipse.swt.widgets.MenuItem;
100 import org.eclipse.swt.widgets.TreeItem;
101 import org.eclipse.ui.IEditorInput;
102 import org.eclipse.ui.IEditorPart;
103 import org.eclipse.ui.IFileEditorInput;
104 import org.eclipse.ui.IPageLayout;
105 import org.eclipse.ui.ISelectionListener;
106 import org.eclipse.ui.ISelectionService;
107 import org.eclipse.ui.IViewPart;
108 import org.eclipse.ui.IWorkbenchPart;
109 import org.eclipse.ui.PartInitException;
110 import org.eclipse.ui.PlatformUI;
111 import org.eclipse.ui.actions.ActionFactory;
112 import org.eclipse.ui.editors.text.EditorsUI;
113 import org.eclipse.ui.ide.FileStoreEditorInput;
114 import org.eclipse.ui.ide.IDE;
115 import org.eclipse.ui.part.IShowInTarget;
116 import org.eclipse.ui.part.ShowInContext;
117 import org.eclipse.ui.part.ViewPart;
118 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
119 import org.eclipse.ui.views.properties.IPropertySheetPage;
120 import org.eclipse.ui.views.properties.PropertySheet;
121 import org.eclipse.ui.views.properties.PropertySheetPage;
122 import org.osgi.service.prefs.BackingStoreException;
126 * The Git Repositories view.
127 * <p>
128 * This keeps track of a bunch of local directory names each of which represent
129 * a Git Repository. This list is stored in some Preferences object and used to
130 * build the tree in the view.
131 * <p>
132 * Implements {@link ISelectionProvider} in order to integrate with the
133 * Properties view.
134 * <p>
135 * This periodically refreshes itself in order to react on Repository changes.
137 public class RepositoriesView extends ViewPart implements ISelectionProvider,
138 IShowInTarget {
140 /** The view ID */
141 public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
143 // TODO central constants? RemoteConfig ones are private
144 static final String REMOTE = "remote"; //$NON-NLS-1$
146 static final String URL = "url"; //$NON-NLS-1$
148 static final String PUSHURL = "pushurl"; //$NON-NLS-1$
150 static final String FETCH = "fetch"; //$NON-NLS-1$
152 static final String PUSH = "push"; //$NON-NLS-1$
154 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
156 private static final String PREFS_SYNCED = "GitRepositoriesView.SyncWithSelection"; //$NON-NLS-1$
158 private final List<ISelectionChangedListener> selectionListeners = new ArrayList<ISelectionChangedListener>();
160 private final static long AUTO_REFRESH_INTERVAL_MILLISECONDS = 10000l;
162 private ISelection currentSelection = new StructuredSelection();
164 private Job scheduledJob;
166 private Job autoRefreshJob;
168 private TreeViewer tv;
170 private IAction importAction;
172 private IAction addAction;
174 private IAction refreshAction;
176 private IAction linkWithSelectionAction;
178 private IAction copyAction;
180 private IAction pasteAction;
183 * TODO move to utility class
185 * @return the directories as configured for this view
187 public static List<String> getDirs() {
188 List<String> resultStrings = new ArrayList<String>();
189 String dirs = getPrefs().get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
190 if (dirs != null && dirs.length() > 0) {
191 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
192 while (tok.hasMoreTokens()) {
193 String dirName = tok.nextToken();
194 File testFile = new File(dirName);
195 if (testFile.exists() && !resultStrings.contains(dirName)) {
196 resultStrings.add(dirName);
200 Collections.sort(resultStrings);
201 return resultStrings;
204 private static void removeDir(File file) {
206 String dir;
207 try {
208 dir = file.getCanonicalPath();
209 } catch (IOException e1) {
210 dir = file.getAbsolutePath();
213 IEclipsePreferences prefs = getPrefs();
215 TreeSet<String> resultStrings = new TreeSet<String>();
216 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
217 if (dirs != null && dirs.length() > 0) {
218 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
219 while (tok.hasMoreTokens()) {
220 String dirName = tok.nextToken();
221 File testFile = new File(dirName);
222 if (testFile.exists()) {
223 try {
224 resultStrings.add(testFile.getCanonicalPath());
225 } catch (IOException e) {
226 resultStrings.add(testFile.getAbsolutePath());
232 if (resultStrings.remove(dir)) {
233 StringBuilder sb = new StringBuilder();
234 for (String gitDirString : resultStrings) {
235 sb.append(gitDirString);
236 sb.append(File.pathSeparatorChar);
239 prefs.put(PREFS_DIRECTORIES, sb.toString());
240 try {
241 prefs.flush();
242 } catch (BackingStoreException e) {
243 IStatus error = new Status(IStatus.ERROR, Activator
244 .getPluginId(), e.getMessage(), e);
245 Activator.getDefault().getLog().log(error);
251 @Override
252 public Object getAdapter(Class adapter) {
253 // integrate with Properties view
254 if (adapter == IPropertySheetPage.class) {
255 PropertySheetPage page = new PropertySheetPage();
256 page
257 .setPropertySourceProvider(new RepositoryPropertySourceProvider(
258 page));
259 return page;
262 return super.getAdapter(adapter);
265 @Override
266 public void createPartControl(Composite parent) {
267 tv = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
268 tv.setContentProvider(new RepositoriesViewContentProvider());
269 // the label provider registers itself
270 new RepositoriesViewLabelProvider(tv);
272 getSite().setSelectionProvider(this);
274 tv.addSelectionChangedListener(new ISelectionChangedListener() {
276 public void selectionChanged(SelectionChangedEvent event) {
278 copyAction.setEnabled(false);
280 IStructuredSelection ssel = (IStructuredSelection) event
281 .getSelection();
282 if (ssel.size() == 1) {
283 RepositoryTreeNode node = (RepositoryTreeNode) ssel
284 .getFirstElement();
285 // allow copy on repository, file, or folder (copying the
286 // directory)
287 if (node.getType() == RepositoryTreeNodeType.REPO
288 || node.getType() == RepositoryTreeNodeType.WORKINGDIR
289 || node.getType() == RepositoryTreeNodeType.FOLDER
290 || node.getType() == RepositoryTreeNodeType.FILE) {
291 copyAction.setEnabled(true);
293 setSelection(new StructuredSelection(ssel.getFirstElement()));
294 } else {
295 setSelection(new StructuredSelection());
300 tv.addOpenListener(new IOpenListener() {
301 public void open(OpenEvent event) {
302 IStructuredSelection selection = (IStructuredSelection) event
303 .getSelection();
304 if (selection.isEmpty()) {
305 // nothing selected, ignore
306 return;
309 Object element = selection.getFirstElement();
310 ITreeContentProvider contentProvider = (ITreeContentProvider) tv
311 .getContentProvider();
312 if (contentProvider.hasChildren(element)) {
313 // this element has children, expand/collapse it
314 tv.setExpandedState(element, !tv.getExpandedState(element));
315 } else {
316 Object[] selectionArray = selection.toArray();
317 for (Object selectedElement : selectionArray) {
318 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
319 // if any of the selected elements are not files, ignore
320 // the open request
321 if (node.getType() != RepositoryTreeNodeType.FILE
322 && node.getType() != RepositoryTreeNodeType.REF
323 && node.getType() != RepositoryTreeNodeType.TAG) {
324 return;
328 // open the files the user has selected
329 for (Object selectedElement : selectionArray) {
330 RepositoryTreeNode node = (RepositoryTreeNode) selectedElement;
331 if (node.getType() == RepositoryTreeNodeType.FILE)
332 openFile((File) node.getObject());
333 else if (node.getType() == RepositoryTreeNodeType.REF
334 || node.getType() == RepositoryTreeNodeType.TAG) {
335 Ref ref = (Ref) node.getObject();
336 if (!isBare(node.getRepository())
337 && ref.getName().startsWith(
338 Constants.R_REFS))
339 checkoutBranch(node, ref.getName());
346 addContextMenu();
348 addActionsToToolbar();
350 scheduleRefresh();
352 // schedule the auto-refresh job
353 autoRefreshJob = new Job("Git Repositories View Auto-Refresh") { //$NON-NLS-1$
355 @Override
356 protected IStatus run(IProgressMonitor monitor) {
357 scheduleRefresh();
358 schedule(AUTO_REFRESH_INTERVAL_MILLISECONDS);
359 return Status.OK_STATUS;
363 autoRefreshJob.setSystem(true);
364 autoRefreshJob.schedule(AUTO_REFRESH_INTERVAL_MILLISECONDS);
366 ISelectionService srv = (ISelectionService) getSite().getService(
367 ISelectionService.class);
368 srv.addPostSelectionListener(new ISelectionListener() {
370 public void selectionChanged(IWorkbenchPart part,
371 ISelection selection) {
373 // if the "link with selection" toggle is off, we're done
374 if (linkWithSelectionAction == null
375 || !linkWithSelectionAction.isChecked())
376 return;
378 // this may happen if we switch between editors
379 if (part instanceof IEditorPart) {
380 IEditorInput input = ((IEditorPart) part).getEditorInput();
381 if (input instanceof IFileEditorInput)
382 reactOnSelection(new StructuredSelection(
383 ((IFileEditorInput) input).getFile()));
385 } else {
386 reactOnSelection(selection);
393 private void reactOnSelection(ISelection selection) {
394 if (selection instanceof StructuredSelection) {
395 StructuredSelection ssel = (StructuredSelection) selection;
396 if (ssel.size() != 1)
397 return;
398 if (ssel.getFirstElement() instanceof IResource) {
399 showResource((IResource) ssel.getFirstElement());
401 if (ssel.getFirstElement() instanceof IAdaptable) {
402 IResource adapted = (IResource) ((IAdaptable) ssel
403 .getFirstElement()).getAdapter(IResource.class);
404 if (adapted != null)
405 showResource(adapted);
410 private void addContextMenu() {
411 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
413 public void menuDetected(MenuDetectEvent e) {
414 Menu men = tv.getTree().getMenu();
415 if (men != null) {
416 men.dispose();
418 men = new Menu(tv.getTree());
420 TreeItem testItem = tv.getTree().getItem(
421 tv.getTree().toControl(new Point(e.x, e.y)));
422 if (testItem == null) {
423 addMenuItemsForPanel(men);
424 } else {
425 addMenuItemsForTreeSelection(men);
428 tv.getTree().setMenu(men);
433 private void addMenuItemsForPanel(Menu men) {
435 MenuItem importItem = new MenuItem(men, SWT.PUSH);
436 importItem.setText(UIText.RepositoriesView_ImportRepository_MenuItem);
437 importItem.addSelectionListener(new SelectionAdapter() {
439 @Override
440 public void widgetSelected(SelectionEvent e) {
441 importAction.run();
446 MenuItem addItem = new MenuItem(men, SWT.PUSH);
447 addItem.setText(UIText.RepositoriesView_AddRepository_MenuItem);
448 addItem.addSelectionListener(new SelectionAdapter() {
450 @Override
451 public void widgetSelected(SelectionEvent e) {
452 addAction.run();
457 MenuItem pasteItem = new MenuItem(men, SWT.PUSH);
458 pasteItem.setText(UIText.RepositoriesView_PasteMenu);
459 pasteItem.addSelectionListener(new SelectionAdapter() {
461 @Override
462 public void widgetSelected(SelectionEvent e) {
463 pasteAction.run();
468 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
469 refreshItem.setText(refreshAction.getText());
470 refreshItem.addSelectionListener(new SelectionAdapter() {
472 @Override
473 public void widgetSelected(SelectionEvent e) {
474 refreshAction.run();
481 private void addMenuItemsForTreeSelection(Menu men) {
483 final IStructuredSelection sel = (IStructuredSelection) tv
484 .getSelection();
486 boolean repoOnly = true;
487 for (Object selected : sel.toArray()) {
489 if (((RepositoryTreeNode) selected).getType() != RepositoryTreeNodeType.REPO) {
490 repoOnly = false;
491 break;
495 if (sel.size() > 1 && repoOnly) {
496 List nodes = sel.toList();
497 final Repository[] repos = new Repository[nodes.size()];
498 for (int i = 0; i < sel.size(); i++)
499 repos[i] = ((RepositoryTreeNode) nodes.get(i)).getRepository();
501 MenuItem remove = new MenuItem(men, SWT.PUSH);
502 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
503 remove.addSelectionListener(new SelectionAdapter() {
505 @Override
506 public void widgetSelected(SelectionEvent e) {
507 // TODO progress monitoring/cancellation
508 removeRepository(new NullProgressMonitor(), repos);
514 // from here on, we only deal with single selection
515 if (sel.size() > 1)
516 return;
518 final RepositoryTreeNode node = (RepositoryTreeNode) sel
519 .getFirstElement();
521 final boolean isBare = isBare(node.getRepository());
523 if (node.getType() == RepositoryTreeNodeType.REF) {
525 final Ref ref = (Ref) node.getObject();
527 // we don't check out symbolic references
528 if (!ref.isSymbolic()) {
530 if (!isBare) {
531 MenuItem checkout = new MenuItem(men, SWT.PUSH);
532 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
534 checkout.setEnabled(!isRefCheckedOut(node.getRepository(),
535 ref.getName()));
537 checkout.addSelectionListener(new SelectionAdapter() {
539 @Override
540 public void widgetSelected(SelectionEvent e) {
541 checkoutBranch(node, ref.getLeaf().getName());
545 new MenuItem(men, SWT.SEPARATOR);
548 createCreateBranchItem(men, node);
549 createDeleteBranchItem(men, node);
554 if (node.getType() == RepositoryTreeNodeType.TAG) {
556 final Ref ref = (Ref) node.getObject();
558 MenuItem checkout = new MenuItem(men, SWT.PUSH);
559 checkout.setText(UIText.RepositoriesView_CheckOut_MenuItem);
561 checkout.setEnabled(!isRefCheckedOut(node.getRepository(), ref
562 .getName()));
564 checkout.addSelectionListener(new SelectionAdapter() {
566 @Override
567 public void widgetSelected(SelectionEvent e) {
568 checkoutBranch(node, ref.getLeaf().getName());
573 if (node.getType() == RepositoryTreeNodeType.BRANCHES
574 || node.getType() == RepositoryTreeNodeType.LOCALBRANCHES)
575 // offering this on the "Remote Branches" node would probably be
576 // confusing
577 createCreateBranchItem(men, node);
579 // for Repository: import existing projects, remove, (delete), open
580 // properties
581 if (node.getType() == RepositoryTreeNodeType.REPO) {
583 final Repository repo = (Repository) node.getObject();
585 // TODO "import existing plug-in" menu item
587 MenuItem remove = new MenuItem(men, SWT.PUSH);
588 remove.setText(UIText.RepositoriesView_Remove_MenuItem);
589 remove.addSelectionListener(new SelectionAdapter() {
591 @Override
592 public void widgetSelected(SelectionEvent e) {
593 // TODO progress monitoring/cancellation
594 removeRepository(new NullProgressMonitor(), repo);
598 // TODO delete does not work because of file locks on .pack-files
599 // Shawn Pearce has added the following thoughts:
601 // Hmm. We probably can't active detect file locks on pack files on
602 // Windows, can we?
603 // It would be nice if we could support a delete, but only if the
604 // repository is
605 // reasonably believed to be not-in-use right now.
607 // Within EGit you might be able to check GitProjectData and its
608 // repositoryCache to
609 // see if the repository is open by this workspace. If it is, then
610 // we know we shouldn't
611 // try to delete it.
613 // Some coding might look like this:
615 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
616 // deleteRepo.setText("Delete");
617 // deleteRepo.addSelectionListener(new SelectionAdapter() {
619 // @Override
620 // public void widgetSelected(SelectionEvent e) {
622 // boolean confirmed = MessageDialog.openConfirm(getSite()
623 // .getShell(), "Confirm",
624 // "This will delete the repository, continue?");
626 // if (!confirmed)
627 // return;
629 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
631 // public void run(IProgressMonitor monitor)
632 // throws CoreException {
633 // File workDir = repos.get(0).getRepository()
634 // .getWorkDir();
636 // File gitDir = repos.get(0).getRepository()
637 // .getDirectory();
639 // IPath wdPath = new Path(workDir.getAbsolutePath());
640 // for (IProject prj : ResourcesPlugin.getWorkspace()
641 // .getRoot().getProjects()) {
642 // if (wdPath.isPrefixOf(prj.getLocation())) {
643 // prj.delete(false, false, monitor);
644 // }
645 // }
647 // repos.get(0).getRepository().close();
649 // boolean deleted = deleteRecursively(gitDir, monitor);
650 // if (!deleted) {
651 // MessageDialog.openError(getSite().getShell(),
652 // "Error",
653 // "Could not delete Git Repository");
654 // }
656 // deleted = deleteRecursively(workDir, monitor);
657 // if (!deleted) {
658 // MessageDialog
659 // .openError(getSite().getShell(),
660 // "Error",
661 // "Could not delete Git Working Directory");
662 // }
664 // scheduleRefresh();
665 // }
667 // private boolean deleteRecursively(File fileToDelete,
668 // IProgressMonitor monitor) {
669 // if (fileToDelete.isDirectory()) {
670 // for (File file : fileToDelete.listFiles()) {
671 // if (!deleteRecursively(file, monitor)) {
672 // return false;
673 // }
674 // }
675 // }
676 // monitor.setTaskName(fileToDelete.getAbsolutePath());
677 // boolean deleted = fileToDelete.delete();
678 // if (!deleted) {
679 // System.err.println("Could not delete "
680 // + fileToDelete.getAbsolutePath());
681 // }
682 // return deleted;
683 // }
684 // };
686 // try {
687 // ResourcesPlugin.getWorkspace().run(wsr,
688 // ResourcesPlugin.getWorkspace().getRoot(),
689 // IWorkspace.AVOID_UPDATE,
690 // new NullProgressMonitor());
691 // } catch (CoreException e1) {
692 // // TODO Exception handling
693 // e1.printStackTrace();
694 // }
696 // }
698 // });
700 new MenuItem(men, SWT.SEPARATOR);
702 if (!isBare) {
703 createImportProjectItem(men, repo, repo.getWorkDir().getPath());
705 new MenuItem(men, SWT.SEPARATOR);
708 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
709 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
710 openPropsView.addSelectionListener(new SelectionAdapter() {
712 @Override
713 public void widgetSelected(SelectionEvent e) {
714 try {
715 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
716 .getActivePage().showView(
717 IPageLayout.ID_PROP_SHEET);
718 } catch (PartInitException e1) {
719 // just ignore
725 new MenuItem(men, SWT.SEPARATOR);
727 createCopyPathItem(men, repo.getDirectory().getPath());
730 if (node.getType() == RepositoryTreeNodeType.REMOTES) {
732 MenuItem remoteConfig = new MenuItem(men, SWT.PUSH);
733 remoteConfig.setText(UIText.RepositoriesView_NewRemoteMenu);
734 remoteConfig.addSelectionListener(new SelectionAdapter() {
736 @Override
737 public void widgetSelected(SelectionEvent e) {
739 WizardDialog dlg = new WizardDialog(getSite().getShell(),
740 new NewRemoteWizard(node.getRepository()));
741 if (dlg.open() == Window.OK)
742 tv.refresh();
749 if (node.getType() == RepositoryTreeNodeType.REMOTE) {
751 final String configName = (String) node.getObject();
753 RemoteConfig rconfig;
754 try {
755 rconfig = new RemoteConfig(node.getRepository().getConfig(),
756 configName);
757 } catch (URISyntaxException e2) {
758 // TODO Exception handling
759 rconfig = null;
762 boolean fetchExists = rconfig != null
763 && !rconfig.getURIs().isEmpty();
764 boolean pushExists = rconfig != null
765 && !rconfig.getPushURIs().isEmpty();
767 if (!fetchExists) {
768 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
769 configureUrlFetch
770 .setText(UIText.RepositoriesView_CreateFetch_menu);
772 configureUrlFetch.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, false));
780 if (dlg.open() == Window.OK)
781 tv.refresh();
788 if (!pushExists) {
789 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
791 configureUrlPush
792 .setText(UIText.RepositoriesView_CreatePush_menu);
794 configureUrlPush.addSelectionListener(new SelectionAdapter() {
796 @Override
797 public void widgetSelected(SelectionEvent e) {
799 WizardDialog dlg = new WizardDialog(getSite()
800 .getShell(), new ConfigureRemoteWizard(node
801 .getRepository(), configName, true));
802 if (dlg.open() == Window.OK)
803 tv.refresh();
810 if (!fetchExists || !pushExists)
811 // add a separator dynamically
812 new MenuItem(men, SWT.SEPARATOR);
814 MenuItem removeRemote = new MenuItem(men, SWT.PUSH);
815 removeRemote.setText(UIText.RepositoriesView_RemoveRemoteMenu);
816 removeRemote.addSelectionListener(new SelectionAdapter() {
818 @Override
819 public void widgetSelected(SelectionEvent e) {
821 boolean ok = MessageDialog
822 .openConfirm(
823 getSite().getShell(),
824 UIText.RepositoriesView_ConfirmDeleteRemoteHeader,
826 .bind(
827 UIText.RepositoriesView_ConfirmDeleteRemoteMessage,
828 configName));
829 if (ok) {
830 RepositoryConfig config = node.getRepository()
831 .getConfig();
832 config.unsetSection(REMOTE, configName);
833 try {
834 config.save();
835 tv.refresh();
836 } catch (IOException e1) {
837 Activator.handleError(
838 UIText.RepositoriesView_ErrorHeader, e1,
839 true);
847 new MenuItem(men, SWT.SEPARATOR);
849 MenuItem openPropsView = new MenuItem(men, SWT.PUSH);
850 openPropsView.setText(UIText.RepositoriesView_OpenPropertiesMenu);
851 openPropsView.addSelectionListener(new SelectionAdapter() {
853 @Override
854 public void widgetSelected(SelectionEvent e) {
855 try {
856 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
857 .getActivePage().showView(
858 IPageLayout.ID_PROP_SHEET);
859 } catch (PartInitException e1) {
860 // just ignore
867 if (node.getType() == RepositoryTreeNodeType.FETCH) {
869 final String configName = (String) node.getParent().getObject();
871 MenuItem doFetch = new MenuItem(men, SWT.PUSH);
872 doFetch.setText(UIText.RepositoriesView_FetchMenu);
873 doFetch.addSelectionListener(new SelectionAdapter() {
875 @Override
876 public void widgetSelected(SelectionEvent evt) {
877 new FetchConfiguredRemoteAction(node.getRepository(),
878 configName).run(getSite().getShell());
883 MenuItem configureUrlFetch = new MenuItem(men, SWT.PUSH);
884 configureUrlFetch
885 .setText(UIText.RepositoriesView_ConfigureFetchMenu);
887 configureUrlFetch.addSelectionListener(new SelectionAdapter() {
889 @Override
890 public void widgetSelected(SelectionEvent e) {
892 WizardDialog dlg = new WizardDialog(getSite().getShell(),
893 new ConfigureRemoteWizard(node.getRepository(),
894 configName, false));
895 if (dlg.open() == Window.OK)
896 tv.refresh();
902 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
903 deleteFetch.setText(UIText.RepositoriesView_RemoveFetch_menu);
904 deleteFetch.addSelectionListener(new SelectionAdapter() {
906 @Override
907 public void widgetSelected(SelectionEvent e) {
908 RepositoryConfig config = node.getRepository().getConfig();
909 config.unset("remote", configName, "url"); //$NON-NLS-1$ //$NON-NLS-2$
910 config.unset("remote", configName, "fetch"); //$NON-NLS-1$//$NON-NLS-2$
911 try {
912 config.save();
913 tv.refresh();
914 } catch (IOException e1) {
915 MessageDialog.openError(getSite().getShell(),
916 UIText.RepositoriesView_ErrorHeader, e1
917 .getMessage());
925 if (node.getType() == RepositoryTreeNodeType.PUSH) {
927 final String configName = (String) node.getParent().getObject();
929 MenuItem doPush = new MenuItem(men, SWT.PUSH);
930 doPush.setText(UIText.RepositoriesView_DoPushMenuItem);
931 doPush.addSelectionListener(new SelectionAdapter() {
933 @Override
934 public void widgetSelected(SelectionEvent evt) {
935 new PushConfiguredRemoteAction(node.getRepository(),
936 configName).run(getSite().getShell(), false);
940 MenuItem configureUrlPush = new MenuItem(men, SWT.PUSH);
941 configureUrlPush.setText(UIText.RepositoriesView_ConfigurePushMenu);
943 configureUrlPush.addSelectionListener(new SelectionAdapter() {
945 @Override
946 public void widgetSelected(SelectionEvent e) {
948 WizardDialog dlg = new WizardDialog(getSite().getShell(),
949 new ConfigureRemoteWizard(node.getRepository(),
950 configName, true));
951 if (dlg.open() == Window.OK)
952 tv.refresh();
957 MenuItem deleteFetch = new MenuItem(men, SWT.PUSH);
958 deleteFetch.setText(UIText.RepositoriesView_RemovePush_menu);
959 deleteFetch.addSelectionListener(new SelectionAdapter() {
961 @Override
962 public void widgetSelected(SelectionEvent e) {
963 RepositoryConfig config = node.getRepository().getConfig();
964 config.unset("remote", configName, "pushurl"); //$NON-NLS-1$ //$NON-NLS-2$
965 config.unset("remote", configName, "push"); //$NON-NLS-1$ //$NON-NLS-2$
966 try {
967 config.save();
968 tv.refresh();
969 } catch (IOException e1) {
970 MessageDialog.openError(getSite().getShell(),
971 UIText.RepositoriesView_ErrorHeader, e1
972 .getMessage());
979 if (node.getType() == RepositoryTreeNodeType.FILE) {
981 final File file = (File) node.getObject();
983 MenuItem openInTextEditor = new MenuItem(men, SWT.PUSH);
984 openInTextEditor
985 .setText(UIText.RepositoriesView_OpenInTextEditor_menu);
986 openInTextEditor.addSelectionListener(new SelectionAdapter() {
988 @Override
989 public void widgetSelected(SelectionEvent e) {
990 openFile(file);
995 new MenuItem(men, SWT.SEPARATOR);
996 createCopyPathItem(men, file.getPath());
999 if (!isBare && node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1000 String path = node.getRepository().getWorkDir().getAbsolutePath();
1001 createImportProjectItem(men, node.getRepository(), path);
1002 new MenuItem(men, SWT.SEPARATOR);
1003 createCopyPathItem(men, path);
1006 if (node.getType() == RepositoryTreeNodeType.FOLDER) {
1007 String path = ((File) node.getObject()).getPath();
1008 createImportProjectItem(men, node.getRepository(), path);
1009 new MenuItem(men, SWT.SEPARATOR);
1010 createCopyPathItem(men, path);
1015 private boolean isBare(Repository repository) {
1016 return repository.getConfig().getBoolean("core", "bare", false); //$NON-NLS-1$ //$NON-NLS-2$
1019 private boolean isRefCheckedOut(Repository repository, String refName) {
1020 String branchName;
1021 String compareString;
1023 try {
1024 branchName = repository.getFullBranch();
1025 if (branchName == null)
1026 return false;
1027 if (refName.startsWith(Constants.R_HEADS)) {
1028 // local branch: HEAD would be on the branch
1029 compareString = refName;
1030 } else if (refName.startsWith(Constants.R_TAGS)) {
1031 // tag: HEAD would be on the commit id to which the tag is
1032 // pointing
1033 compareString = repository.mapTag(refName).getObjId().getName();
1034 } else if (refName.startsWith(Constants.R_REMOTES)) {
1035 // remote branch: HEAD would be on the commit id to which
1036 // the branch is pointing
1037 compareString = repository.mapCommit(refName).getCommitId()
1038 .getName();
1039 } else {
1040 // some other symbolic reference
1041 return false;
1043 } catch (IOException e1) {
1044 return false;
1047 return compareString.equals(branchName);
1050 private void createCopyPathItem(Menu men, final String path) {
1052 MenuItem copyPath;
1053 copyPath = new MenuItem(men, SWT.PUSH);
1054 copyPath.setText(UIText.RepositoriesView_CopyPathToClipboardMenu);
1055 copyPath.addSelectionListener(new SelectionAdapter() {
1057 @Override
1058 public void widgetSelected(SelectionEvent e) {
1059 Clipboard clipboard = new Clipboard(null);
1060 TextTransfer textTransfer = TextTransfer.getInstance();
1061 Transfer[] transfers = new Transfer[] { textTransfer };
1062 Object[] data = new Object[] { path };
1063 clipboard.setContents(data, transfers);
1064 clipboard.dispose();
1071 private void createCreateBranchItem(Menu men, final RepositoryTreeNode node) {
1072 final Ref ref;
1073 if (node.getType() == RepositoryTreeNodeType.REF)
1074 ref = (Ref) node.getObject();
1075 else
1076 ref = null;
1078 MenuItem createLocal = new MenuItem(men, SWT.PUSH);
1079 createLocal.setText(UIText.RepositoriesView_NewBranchMenu);
1081 createLocal.addSelectionListener(new SelectionAdapter() {
1083 @Override
1084 public void widgetSelected(SelectionEvent e) {
1086 Wizard wiz = new Wizard() {
1088 @Override
1089 public void addPages() {
1090 addPage(new CreateBranchPage(node.getRepository(), ref));
1091 setWindowTitle(UIText.RepositoriesView_NewBranchTitle);
1094 @Override
1095 public boolean performFinish() {
1097 try {
1098 getContainer().run(false, true,
1099 new IRunnableWithProgress() {
1101 public void run(IProgressMonitor monitor)
1102 throws InvocationTargetException,
1103 InterruptedException {
1104 CreateBranchPage cp = (CreateBranchPage) getPages()[0];
1105 try {
1106 cp.createBranch(monitor);
1107 } catch (CoreException ce) {
1108 throw new InvocationTargetException(
1109 ce);
1110 } catch (IOException ioe) {
1111 throw new InvocationTargetException(
1112 ioe);
1117 } catch (InvocationTargetException ite) {
1118 Activator
1119 .handleError(
1120 UIText.RepositoriesView_BranchCreationFailureMessage,
1121 ite.getCause(), true);
1122 return false;
1123 } catch (InterruptedException ie) {
1124 // ignore here
1126 return true;
1129 if (new WizardDialog(getSite().getShell(), wiz).open() == Window.OK)
1130 tv.refresh();
1136 private void createDeleteBranchItem(Menu men, final RepositoryTreeNode node) {
1138 final Ref ref = (Ref) node.getObject();
1140 MenuItem deleteBranch = new MenuItem(men, SWT.PUSH);
1141 deleteBranch.setText(UIText.RepositoriesView_DeleteBranchMenu);
1143 deleteBranch.setEnabled(!isRefCheckedOut(node.getRepository(), ref
1144 .getName()));
1146 deleteBranch.addSelectionListener(new SelectionAdapter() {
1148 @Override
1149 public void widgetSelected(SelectionEvent e) {
1151 if (!MessageDialog
1152 .openConfirm(
1153 getSite().getShell(),
1154 UIText.RepositoriesView_ConfirmDeleteTitle,
1156 .bind(
1157 UIText.RepositoriesView_ConfirmBranchDeletionMessage,
1158 ref.getName())))
1159 return;
1161 try {
1162 new ProgressMonitorDialog(getSite().getShell()).run(false,
1163 false, new IRunnableWithProgress() {
1165 public void run(IProgressMonitor monitor)
1166 throws InvocationTargetException,
1167 InterruptedException {
1169 try {
1170 RefUpdate op = node.getRepository()
1171 .updateRef(ref.getName());
1172 op.setRefLogMessage("branch deleted", //$NON-NLS-1$
1173 false);
1174 // we set the force update in order
1175 // to avoid having this rejected
1176 // due to minor issues
1177 op.setForceUpdate(true);
1178 op.delete();
1179 tv.refresh();
1180 } catch (IOException ioe) {
1181 throw new InvocationTargetException(ioe);
1186 } catch (InvocationTargetException e1) {
1187 Activator
1188 .handleError(
1189 UIText.RepositoriesView_BranchDeletionFailureMessage,
1190 e1.getCause(), true);
1191 e1.printStackTrace();
1192 } catch (InterruptedException e1) {
1193 // ignore
1201 private void openFile(File file) {
1202 IFileStore store = EFS.getLocalFileSystem().getStore(
1203 new Path(file.getAbsolutePath()));
1204 try {
1205 // TODO do we need a read-only editor here?
1206 IDE.openEditor(getSite().getPage(),
1207 new FileStoreEditorInput(store),
1208 EditorsUI.DEFAULT_TEXT_EDITOR_ID);
1209 } catch (PartInitException e) {
1210 Activator.handleError(UIText.RepositoriesView_Error_WindowTitle, e,
1211 true);
1215 private void checkoutBranch(final RepositoryTreeNode node,
1216 final String refName) {
1217 // for the sake of UI responsiveness, let's start a job
1218 Job job = new Job(NLS.bind(UIText.RepositoriesView_CheckingOutMessage,
1219 refName)) {
1221 @Override
1222 protected IStatus run(IProgressMonitor monitor) {
1224 Repository repo = node.getRepository();
1226 final BranchOperation op = new BranchOperation(repo, refName);
1227 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1229 public void run(IProgressMonitor myMonitor)
1230 throws CoreException {
1231 op.execute(myMonitor);
1235 try {
1236 ResourcesPlugin.getWorkspace().run(wsr,
1237 ResourcesPlugin.getWorkspace().getRoot(),
1238 IWorkspace.AVOID_UPDATE, monitor);
1239 Display.getDefault().syncExec(new Runnable() {
1241 public void run() {
1242 tv.refresh();
1246 } catch (CoreException e1) {
1247 return new Status(IStatus.ERROR, Activator.getPluginId(),
1248 e1.getMessage(), e1);
1251 return Status.OK_STATUS;
1255 job.setUser(true);
1256 job.schedule();
1259 private void createImportProjectItem(Menu men, final Repository repo,
1260 final String path) {
1262 MenuItem startWizard;
1263 startWizard = new MenuItem(men, SWT.PUSH);
1264 startWizard.setText(UIText.RepositoriesView_ImportProjectsMenu);
1265 startWizard.addSelectionListener(new SelectionAdapter() {
1267 @Override
1268 public void widgetSelected(SelectionEvent e) {
1269 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1270 new GitCreateProjectViaWizardWizard(repo, path));
1271 dlg.open();
1276 // we could start the ImportWizard here,
1277 // unfortunately, this fails within a wizard
1278 // startWizard = new MenuItem(men, SWT.PUSH);
1279 // startWizard.setText("Start the Import wizard...");
1280 // startWizard.addSelectionListener(new SelectionAdapter() {
1282 // @Override
1283 // public void widgetSelected(SelectionEvent e) {
1285 // IHandlerService handlerService = (IHandlerService) getSite()
1286 // .getWorkbenchWindow().getWorkbench().getService(
1287 // IHandlerService.class);
1289 // try {
1290 // handlerService.executeCommand("org.eclipse.ui.file.import", //$NON-NLS-1$
1291 // null);
1292 // } catch (ExecutionException e1) {
1293 // Activator.handleError(e1.getMessage(), e1, true);
1294 // } catch (NotDefinedException e1) {
1295 // Activator.handleError(e1.getMessage(), e1, true);
1296 // } catch (NotEnabledException e1) {
1297 // Activator.handleError(e1.getMessage(), e1, true);
1298 // } catch (NotHandledException e1) {
1299 // Activator.handleError(e1.getMessage(), e1, true);
1300 // }
1301 // }
1303 // });
1306 private void addActionsToToolbar() {
1308 IToolBarManager manager = getViewSite().getActionBars()
1309 .getToolBarManager();
1311 refreshAction = new Action(UIText.RepositoriesView_Refresh_Button) {
1313 @Override
1314 public void run() {
1315 scheduleRefresh();
1318 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1319 manager.add(refreshAction);
1321 linkWithSelectionAction = new Action(
1322 UIText.RepositoriesView_LinkWithSelection_action,
1323 IAction.AS_CHECK_BOX) {
1325 @Override
1326 public void run() {
1327 IEclipsePreferences prefs = getPrefs();
1328 prefs.putBoolean(PREFS_SYNCED, isChecked());
1329 try {
1330 prefs.flush();
1331 } catch (BackingStoreException e) {
1332 // ignore here
1334 if (isChecked()) {
1335 ISelectionService srv = (ISelectionService) getSite()
1336 .getService(ISelectionService.class);
1337 reactOnSelection(srv.getSelection());
1344 linkWithSelectionAction
1345 .setToolTipText(UIText.RepositoriesView_LinkWithSelection_action);
1346 linkWithSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1347 linkWithSelectionAction.setChecked(getPrefs().getBoolean(PREFS_SYNCED,
1348 false));
1350 manager.add(linkWithSelectionAction);
1352 manager.add(new Separator());
1354 IAction collapseAllAction = new Action(
1355 UIText.RepositoriesView_CollapseAllMenu) {
1357 @Override
1358 public void run() {
1359 tv.collapseAll();
1362 collapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1363 manager.add(collapseAllAction);
1365 manager.add(new Separator());
1367 importAction = new Action(UIText.RepositoriesView_Import_Button) {
1369 @Override
1370 public void run() {
1371 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1372 new GitCloneWizard());
1373 if (dlg.open() == Window.OK)
1374 scheduleRefresh();
1377 importAction.setToolTipText(UIText.RepositoriesView_Clone_Tooltip);
1378 importAction.setImageDescriptor(UIIcons.CLONEGIT);
1380 manager.add(importAction);
1382 addAction = new Action(UIText.RepositoriesView_Add_Button) {
1384 @Override
1385 public void run() {
1386 RepositorySearchDialog sd = new RepositorySearchDialog(
1387 getSite().getShell(), getDirs());
1388 if (sd.open() == Window.OK) {
1389 Set<String> dirs = new HashSet<String>();
1390 dirs.addAll(getDirs());
1391 if (dirs.addAll(sd.getDirectories()))
1392 saveDirs(dirs);
1393 scheduleRefresh();
1398 addAction.setToolTipText(UIText.RepositoriesView_AddRepository_Tooltip);
1399 addAction.setImageDescriptor(UIIcons.NEW_REPOSITORY);
1401 manager.add(addAction);
1403 // copy and paste are global actions; we just implement them
1404 // and register them with the global action handler
1405 // we enable/disable them upon tree selection changes
1407 copyAction = new Action("") { //$NON-NLS-1$
1409 @Override
1410 public void run() {
1411 // for REPO, WORKINGDIR, FILE, FOLDER: copy directory
1412 IStructuredSelection sel = (IStructuredSelection) tv
1413 .getSelection();
1414 if (sel.size() == 1) {
1415 RepositoryTreeNode node = (RepositoryTreeNode) sel
1416 .getFirstElement();
1417 String dir = null;
1418 if (node.getType() == RepositoryTreeNodeType.REPO) {
1419 dir = node.getRepository().getDirectory().getPath();
1420 } else if (node.getType() == RepositoryTreeNodeType.FILE
1421 || node.getType() == RepositoryTreeNodeType.FOLDER) {
1422 dir = ((File) node.getObject()).getPath();
1423 } else if (node.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1424 if (!isBare(node.getRepository()))
1425 dir = node.getRepository().getWorkDir().getPath();
1427 if (dir != null) {
1428 Clipboard clip = null;
1429 try {
1430 clip = new Clipboard(getSite().getShell()
1431 .getDisplay());
1432 clip
1433 .setContents(new Object[] { dir },
1434 new Transfer[] { TextTransfer
1435 .getInstance() });
1436 } finally {
1437 if (clip != null)
1438 // we must dispose ourselves
1439 clip.dispose();
1446 copyAction.setEnabled(false);
1448 getViewSite().getActionBars().setGlobalActionHandler(
1449 ActionFactory.COPY.getId(), copyAction);
1451 pasteAction = new Action("") { //$NON-NLS-1$
1453 @Override
1454 public void run() {
1455 // we check if the pasted content is a directory
1456 // repository location and try to add this
1457 String errorMessage = null;
1459 Clipboard clip = null;
1460 try {
1461 clip = new Clipboard(getSite().getShell().getDisplay());
1462 String content = (String) clip.getContents(TextTransfer
1463 .getInstance());
1464 if (content == null) {
1465 errorMessage = UIText.RepositoriesView_NothingToPasteMessage;
1466 return;
1469 File file = new File(content);
1470 if (!file.exists() || !file.isDirectory()) {
1471 errorMessage = UIText.RepositoriesView_ClipboardContentNotDirectoryMessage;
1472 return;
1475 if (!RepositoryCache.FileKey.isGitRepository(file)) {
1476 errorMessage = NLS
1477 .bind(
1478 UIText.RepositoriesView_ClipboardContentNoGitRepoMessage,
1479 content);
1480 return;
1483 if (addDir(file))
1484 scheduleRefresh();
1485 else
1486 errorMessage = NLS.bind(
1487 UIText.RepositoriesView_PasteRepoAlreadyThere,
1488 content);
1489 } finally {
1490 if (clip != null)
1491 // we must dispose ourselves
1492 clip.dispose();
1493 if (errorMessage != null)
1494 // TODO String ext
1495 MessageDialog.openWarning(getSite().getShell(),
1496 UIText.RepositoriesView_PasteFailureTitle,
1497 errorMessage);
1503 getViewSite().getActionBars().setGlobalActionHandler(
1504 ActionFactory.PASTE.getId(), pasteAction);
1509 * @return the preferences
1511 protected static IEclipsePreferences getPrefs() {
1512 return new InstanceScope().getNode(Activator.getPluginId());
1515 @Override
1516 public void dispose() {
1517 // make sure to cancel the refresh job
1518 if (this.scheduledJob != null) {
1519 this.scheduledJob.cancel();
1520 this.scheduledJob = null;
1522 // and the auto refresh job, too
1523 if (this.autoRefreshJob != null) {
1524 this.autoRefreshJob.cancel();
1525 this.autoRefreshJob = null;
1527 super.dispose();
1530 @SuppressWarnings("unchecked")
1531 private boolean checkForRepositoryChanges() {
1533 if (tv.getInput() == null)
1534 return false;
1536 final Set<Repository> reposToRefresh = new HashSet<Repository>();
1538 RepositoryListener listener = new RepositoryListener() {
1540 public void refsChanged(RefsChangedEvent e) {
1541 reposToRefresh.add(e.getRepository());
1544 public void indexChanged(IndexChangedEvent e) {
1545 reposToRefresh.add(e.getRepository());
1549 List<RepositoryTreeNode<Repository>> input = (List<RepositoryTreeNode<Repository>>) tv
1550 .getInput();
1552 for (final RepositoryTreeNode<Repository> node : input) {
1554 Repository repository = node.getRepository();
1555 repository.addRepositoryChangedListener(listener);
1556 try {
1557 repository.scanForRepoChanges();
1558 } catch (IOException e1) {
1559 // ignore
1560 } finally {
1561 repository.removeRepositoryChangedListener(listener);
1565 return !reposToRefresh.isEmpty();
1569 * Schedules a refresh
1571 private void scheduleRefresh() {
1573 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1575 @SuppressWarnings("unchecked")
1576 @Override
1577 protected IStatus run(IProgressMonitor monitor) {
1578 // first, let's check if the list of Directories has changed
1579 final List<String> directories = getDirs();
1581 boolean needsNewInput = tv.getInput() == null;
1582 List<RepositoryTreeNode<Repository>> oldInput = (List) tv
1583 .getInput();
1584 if (!needsNewInput)
1585 needsNewInput = oldInput.size() != directories.size();
1587 if (!needsNewInput) {
1588 List<String> oldDirectories = new ArrayList<String>();
1589 for (RepositoryTreeNode<Repository> node : oldInput) {
1590 oldDirectories.add(node.getRepository().getDirectory()
1591 .getPath());
1593 needsNewInput = !directories.containsAll(oldDirectories);
1596 final boolean updateInput = needsNewInput;
1597 final List newInput;
1598 if (updateInput)
1599 try {
1600 newInput = getRepositoriesFromDirs(monitor);
1601 } catch (InterruptedException e) {
1602 return new Status(IStatus.ERROR, Activator
1603 .getPluginId(), e.getMessage(), e);
1605 else
1606 newInput = null;
1608 // we only check for Repository changes if we don't
1609 // have a new input
1610 if (updateInput || checkForRepositoryChanges()) {
1611 Display.getDefault().asyncExec(new Runnable() {
1612 public void run() {
1613 // keep expansion state and selection so that we can
1614 // restore the tree
1615 // after update
1616 Object[] expanded = tv.getExpandedElements();
1617 IStructuredSelection sel = (IStructuredSelection) tv
1618 .getSelection();
1620 if (updateInput)
1621 tv.setInput(newInput);
1622 else
1623 tv.refresh();
1624 tv.setExpandedElements(expanded);
1626 Object selected = sel.getFirstElement();
1627 if (selected != null)
1628 tv.reveal(selected);
1630 IViewPart part = PlatformUI.getWorkbench()
1631 .getActiveWorkbenchWindow().getActivePage()
1632 .findView(IPageLayout.ID_PROP_SHEET);
1633 if (part != null) {
1634 PropertySheet sheet = (PropertySheet) part;
1635 PropertySheetPage page = (PropertySheetPage) sheet
1636 .getCurrentPage();
1637 page.refresh();
1642 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1646 job.setSystem(true);
1648 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1649 .getService(IWorkbenchSiteProgressService.class);
1651 service.schedule(job);
1653 scheduledJob = job;
1658 * Adds a directory to the list if it is not already there
1660 * @param file
1661 * @return see {@link Collection#add(Object)}
1663 public static boolean addDir(File file) {
1665 String dirString;
1666 try {
1667 dirString = file.getCanonicalPath();
1668 } catch (IOException e) {
1669 dirString = file.getAbsolutePath();
1672 List<String> dirStrings = getDirs();
1673 if (dirStrings.contains(dirString)) {
1674 return false;
1675 } else {
1676 Set<String> dirs = new HashSet<String>();
1677 dirs.addAll(dirStrings);
1678 dirs.add(dirString);
1679 saveDirs(dirs);
1680 return true;
1685 * Converts the directories as configured for this view into a list of
1686 * {@link Repository} objects suitable for the tree content provider
1687 * <p>
1688 * TODO move to some utility class
1690 * @param monitor
1691 * @return a list of nodes
1692 * @throws InterruptedException
1694 public static List<RepositoryTreeNode<Repository>> getRepositoriesFromDirs(
1695 IProgressMonitor monitor) throws InterruptedException {
1697 List<String> gitDirStrings = getDirs();
1698 List<RepositoryTreeNode<Repository>> input = new ArrayList<RepositoryTreeNode<Repository>>();
1700 for (String dirString : gitDirStrings) {
1701 if (monitor != null && monitor.isCanceled()) {
1702 throw new InterruptedException(
1703 UIText.RepositoriesView_ActionCanceled_Message);
1705 try {
1706 File dir = new File(dirString);
1707 if (dir.exists() && dir.isDirectory()) {
1708 Repository repo = new Repository(dir);
1709 // reset repository change events here so that check for
1710 // repository changes does not trigger an unnecessary
1711 // refresh
1712 repo.scanForRepoChanges();
1713 RepositoryTreeNode<Repository> node = new RepositoryTreeNode<Repository>(
1714 null, RepositoryTreeNodeType.REPO, repo, repo);
1715 input.add(node);
1717 } catch (IOException e) {
1718 IStatus error = new Status(IStatus.ERROR, Activator
1719 .getPluginId(), e.getMessage(), e);
1720 Activator.getDefault().getLog().log(error);
1723 Collections.sort(input);
1724 return input;
1727 private static void saveDirs(Set<String> gitDirStrings) {
1728 StringBuilder sb = new StringBuilder();
1729 for (String gitDirString : gitDirStrings) {
1730 sb.append(gitDirString);
1731 sb.append(File.pathSeparatorChar);
1734 IEclipsePreferences prefs = getPrefs();
1735 prefs.put(PREFS_DIRECTORIES, sb.toString());
1736 try {
1737 prefs.flush();
1738 } catch (BackingStoreException e) {
1739 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1740 e.getMessage(), e);
1741 Activator.getDefault().getLog().log(error);
1745 @Override
1746 public void setFocus() {
1747 tv.getTree().setFocus();
1750 @SuppressWarnings("boxing")
1751 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1752 boolean confirmed;
1753 confirmed = MessageDialog
1754 .openConfirm(
1755 getSite().getShell(),
1756 UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1758 .bind(
1759 UIText.RepositoriesView_ConfirmProjectDeletion_Question,
1760 projectsToDelete.size()));
1761 return confirmed;
1764 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1765 selectionListeners.add(listener);
1768 public ISelection getSelection() {
1769 return currentSelection;
1772 public void removeSelectionChangedListener(
1773 ISelectionChangedListener listener) {
1774 selectionListeners.remove(listener);
1778 public void setSelection(ISelection selection) {
1779 currentSelection = selection;
1780 for (ISelectionChangedListener listener : selectionListeners) {
1781 listener.selectionChanged(new SelectionChangedEvent(
1782 RepositoriesView.this, selection));
1787 * Opens the tree and marks the folder to which a project is pointing
1789 * @param resource
1790 * TODO exceptions?
1792 @SuppressWarnings("unchecked")
1793 public void showResource(final IResource resource) {
1794 IProject project = resource.getProject();
1795 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
1796 if (mapping == null)
1797 return;
1799 if (addDir(mapping.getRepository().getDirectory())) {
1800 scheduleRefresh();
1803 boolean doSetSelection = false;
1805 if (this.scheduledJob != null) {
1806 int state = this.scheduledJob.getState();
1807 if (state == Job.WAITING || state == Job.RUNNING) {
1808 this.scheduledJob.addJobChangeListener(new JobChangeAdapter() {
1810 @Override
1811 public void done(IJobChangeEvent event) {
1812 showResource(resource);
1815 } else {
1816 doSetSelection = true;
1820 if (doSetSelection) {
1821 RepositoriesViewContentProvider cp = (RepositoriesViewContentProvider) tv
1822 .getContentProvider();
1823 RepositoryTreeNode currentNode = null;
1824 Object[] repos = cp.getElements(tv.getInput());
1825 for (Object repo : repos) {
1826 RepositoryTreeNode node = (RepositoryTreeNode) repo;
1827 // TODO equals implementation of Repository?
1828 if (mapping.getRepository().getDirectory().equals(
1829 ((Repository) node.getObject()).getDirectory())) {
1830 for (Object child : cp.getChildren(node)) {
1831 RepositoryTreeNode childNode = (RepositoryTreeNode) child;
1832 if (childNode.getType() == RepositoryTreeNodeType.WORKINGDIR) {
1833 currentNode = childNode;
1834 break;
1837 break;
1841 IPath relPath = new Path(mapping.getRepoRelativePath(resource));
1843 for (String segment : relPath.segments()) {
1844 for (Object child : cp.getChildren(currentNode)) {
1845 RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
1846 if (childNode.getObject().getName().equals(segment)) {
1847 currentNode = childNode;
1848 break;
1853 final RepositoryTreeNode selNode = currentNode;
1855 Display.getDefault().asyncExec(new Runnable() {
1857 public void run() {
1858 tv.setSelection(new StructuredSelection(selNode), true);
1866 public boolean show(ShowInContext context) {
1867 ISelection selection = context.getSelection();
1868 if (selection instanceof IStructuredSelection) {
1869 IStructuredSelection ss = (IStructuredSelection) selection;
1870 if (ss.size() == 1) {
1871 Object element = ss.getFirstElement();
1872 if (element instanceof IAdaptable) {
1873 IResource resource = (IResource) ((IAdaptable) element)
1874 .getAdapter(IResource.class);
1875 if (resource != null) {
1876 showResource(resource);
1877 return true;
1882 return false;
1885 private void removeRepository(final IProgressMonitor monitor,
1886 final Repository... repository) {
1887 final List<IProject> projectsToDelete = new ArrayList<IProject>();
1889 monitor
1890 .setTaskName(UIText.RepositoriesView_DeleteRepoDeterminProjectsMessage);
1892 for (Repository repo : repository) {
1893 File workDir = repo.getWorkDir();
1894 final IPath wdPath = new Path(workDir.getAbsolutePath());
1895 for (IProject prj : ResourcesPlugin.getWorkspace().getRoot()
1896 .getProjects()) {
1897 if (monitor.isCanceled())
1898 return;
1899 if (wdPath.isPrefixOf(prj.getLocation())) {
1900 projectsToDelete.add(prj);
1905 if (!projectsToDelete.isEmpty()) {
1906 boolean confirmed;
1907 confirmed = confirmProjectDeletion(projectsToDelete);
1908 if (!confirmed) {
1909 return;
1913 if (monitor.isCanceled())
1914 return;
1916 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1918 public void run(IProgressMonitor actMonitor) throws CoreException {
1920 for (IProject prj : projectsToDelete) {
1921 prj.delete(false, false, actMonitor);
1923 for (Repository repo : repository)
1924 removeDir(repo.getDirectory());
1925 scheduleRefresh();
1929 try {
1930 ResourcesPlugin.getWorkspace().run(wsr,
1931 ResourcesPlugin.getWorkspace().getRoot(),
1932 IWorkspace.AVOID_UPDATE, monitor);
1933 } catch (CoreException e1) {
1934 Activator.logError(e1.getMessage(), e1);