Add a "Git Repositories View"
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoriesView.java
blob25a9b940867a0e922a4ec0bd3cd394ef878c6ee4
1 /*******************************************************************************
2 * Copyright (c) 2010 SAP AG.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Mathias Kinzler (SAP AG) - initial implementation
10 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.repository;
13 import java.io.File;
14 import java.io.IOException;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Comparator;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.StringTokenizer;
23 import java.util.TreeSet;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IProjectDescription;
27 import org.eclipse.core.resources.IWorkspace;
28 import org.eclipse.core.resources.IWorkspaceRunnable;
29 import org.eclipse.core.resources.ResourcesPlugin;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IPath;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.core.runtime.IStatus;
34 import org.eclipse.core.runtime.NullProgressMonitor;
35 import org.eclipse.core.runtime.Path;
36 import org.eclipse.core.runtime.Status;
37 import org.eclipse.core.runtime.SubProgressMonitor;
38 import org.eclipse.core.runtime.jobs.Job;
39 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
40 import org.eclipse.core.runtime.preferences.InstanceScope;
41 import org.eclipse.egit.core.op.BranchOperation;
42 import org.eclipse.egit.core.op.ConnectProviderOperation;
43 import org.eclipse.egit.ui.Activator;
44 import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
45 import org.eclipse.egit.ui.internal.clone.GitProjectsImportPage;
46 import org.eclipse.jface.action.Action;
47 import org.eclipse.jface.action.IAction;
48 import org.eclipse.jface.dialogs.MessageDialog;
49 import org.eclipse.jface.layout.GridDataFactory;
50 import org.eclipse.jface.resource.CompositeImageDescriptor;
51 import org.eclipse.jface.resource.ImageDescriptor;
52 import org.eclipse.jface.text.DefaultInformationControl;
53 import org.eclipse.jface.text.TextPresentation;
54 import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter;
55 import org.eclipse.jface.viewers.BaseLabelProvider;
56 import org.eclipse.jface.viewers.IStructuredSelection;
57 import org.eclipse.jface.viewers.ITableLabelProvider;
58 import org.eclipse.jface.viewers.ITreeContentProvider;
59 import org.eclipse.jface.viewers.TreeViewer;
60 import org.eclipse.jface.viewers.Viewer;
61 import org.eclipse.jface.viewers.ViewerCell;
62 import org.eclipse.jface.window.Window;
63 import org.eclipse.jface.wizard.Wizard;
64 import org.eclipse.jface.wizard.WizardDialog;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.Ref;
67 import org.eclipse.jgit.lib.Repository;
68 import org.eclipse.jgit.lib.RepositoryCache;
69 import org.eclipse.osgi.util.NLS;
70 import org.eclipse.swt.SWT;
71 import org.eclipse.swt.events.MenuDetectEvent;
72 import org.eclipse.swt.events.MenuDetectListener;
73 import org.eclipse.swt.events.MouseEvent;
74 import org.eclipse.swt.events.MouseMoveListener;
75 import org.eclipse.swt.events.MouseTrackAdapter;
76 import org.eclipse.swt.events.SelectionAdapter;
77 import org.eclipse.swt.events.SelectionEvent;
78 import org.eclipse.swt.graphics.GC;
79 import org.eclipse.swt.graphics.Image;
80 import org.eclipse.swt.graphics.Point;
81 import org.eclipse.swt.layout.GridLayout;
82 import org.eclipse.swt.widgets.Composite;
83 import org.eclipse.swt.widgets.Display;
84 import org.eclipse.swt.widgets.Menu;
85 import org.eclipse.swt.widgets.MenuItem;
86 import org.eclipse.swt.widgets.Tree;
87 import org.eclipse.swt.widgets.TreeColumn;
88 import org.eclipse.swt.widgets.TreeItem;
89 import org.eclipse.ui.ISharedImages;
90 import org.eclipse.ui.PlatformUI;
91 import org.eclipse.ui.ide.IDE.SharedImages;
92 import org.eclipse.ui.part.ViewPart;
93 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
94 import org.eclipse.ui.statushandlers.StatusManager;
95 import org.eclipse.ui.wizards.datatransfer.ExternalProjectImportWizard;
96 import org.osgi.service.prefs.BackingStoreException;
98 /**
100 * The Git Repositories view.
101 * <p>
102 * This keeps track of a bunch of local directory names each of which represent
103 * a Git Repository. This list is stored in some Preferences object and used to
104 * build the tree in the view.
105 * <p>
106 * TODO
107 * <li>Icons</li>
108 * <li>String externalization</li>
109 * <li>Clarification whether to show projects, perhaps configurable switch</li>
112 public class RepositoriesView extends ViewPart {
114 private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
116 private static final ImageDescriptor CHECKEDOUT_OVERLAY = Activator
117 .getDefault().getImageRegistry().getDescriptor(
118 Activator.ICON_CHECKEDOUT_OVR);
120 private Job scheduledJob;
122 private TreeViewer tv;
124 private IAction importAction;
126 private IAction addAction;
128 private IAction refreshAction;
130 enum RepositoryTreeNodeType {
132 REPO(Activator.ICON_REPOSITORY), REF(PlatformUI.getWorkbench()
133 .getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER)), PROJ(
134 PlatformUI.getWorkbench().getSharedImages().getImage(
135 SharedImages.IMG_OBJ_PROJECT_CLOSED)), BRANCHES(
136 Activator.ICON_BRANCHES), PROJECTS(PlatformUI.getWorkbench()
137 .getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER));
139 private final Image myImage;
141 private RepositoryTreeNodeType(String iconName) {
143 if (iconName != null) {
144 myImage = Activator.getDefault().getImageRegistry().get(
145 iconName);
146 } else {
147 myImage = null;
152 private RepositoryTreeNodeType(Image icon) {
153 myImage = icon;
157 public Image getIcon() {
158 return myImage;
163 private static final class RepositoryTreeNode<T> {
165 private final Repository myRepository;
167 private final T myObject;
169 private final RepositoryTreeNodeType myType;
171 private final RepositoryTreeNode myParent;
173 private String branch;
175 public RepositoryTreeNode(RepositoryTreeNode parent,
176 RepositoryTreeNodeType type, Repository repository, T treeObject) {
177 myParent = parent;
178 myRepository = repository;
179 myType = type;
180 myObject = treeObject;
183 @SuppressWarnings("unchecked")
184 private RepositoryTreeNode<Repository> getRepositoryNode() {
185 if (myType == RepositoryTreeNodeType.REPO) {
186 return (RepositoryTreeNode<Repository>) this;
187 } else {
188 return getParent().getRepositoryNode();
193 * We keep this cached in the repository node to avoid repeated lookups
195 * @return the full branch
196 * @throws IOException
198 public String getBranch() throws IOException {
199 if (myType != RepositoryTreeNodeType.REPO) {
200 return getRepositoryNode().getBranch();
202 if (branch == null) {
203 branch = getRepository().getBranch();
205 return branch;
208 public RepositoryTreeNode getParent() {
209 return myParent;
212 public RepositoryTreeNodeType getType() {
213 return myType;
216 public Repository getRepository() {
217 return myRepository;
220 public T getObject() {
221 return myObject;
224 @Override
225 public int hashCode() {
226 final int prime = 31;
227 int result = 1;
228 switch (myType) {
229 case REPO:
230 case PROJECTS:
231 case BRANCHES:
232 result = prime
233 * result
234 + ((myObject == null) ? 0 : ((Repository) myObject)
235 .getDirectory().hashCode());
236 break;
237 case REF:
238 result = prime
239 * result
240 + ((myObject == null) ? 0 : ((Ref) myObject).getName()
241 .hashCode());
242 break;
243 case PROJ:
244 result = prime
245 * result
246 + ((myObject == null) ? 0 : ((File) myObject).getPath()
247 .hashCode());
248 break;
250 default:
251 break;
254 result = prime * result
255 + ((myParent == null) ? 0 : myParent.hashCode());
256 result = prime
257 * result
258 + ((myRepository == null) ? 0 : myRepository.getDirectory()
259 .hashCode());
260 result = prime * result
261 + ((myType == null) ? 0 : myType.hashCode());
262 return result;
265 @Override
266 public boolean equals(Object obj) {
267 if (this == obj)
268 return true;
269 if (obj == null)
270 return false;
271 if (getClass() != obj.getClass())
272 return false;
274 RepositoryTreeNode other = (RepositoryTreeNode) obj;
276 if (myType == null) {
277 if (other.myType != null)
278 return false;
279 } else if (!myType.equals(other.myType))
280 return false;
281 if (myParent == null) {
282 if (other.myParent != null)
283 return false;
284 } else if (!myParent.equals(other.myParent))
285 return false;
286 if (myRepository == null) {
287 if (other.myRepository != null)
288 return false;
289 } else if (!myRepository.getDirectory().equals(
290 other.myRepository.getDirectory()))
291 return false;
292 if (myObject == null) {
293 if (other.myObject != null)
294 return false;
295 } else if (!checkObjectsEqual(other.myObject))
296 return false;
298 return true;
301 private boolean checkObjectsEqual(Object otherObject) {
302 switch (myType) {
303 case REPO:
304 case PROJECTS:
305 case BRANCHES:
306 return ((Repository) myObject).getDirectory().equals(
307 ((Repository) otherObject).getDirectory());
308 case REF:
309 return ((Ref) myObject).getName().equals(
310 ((Ref) otherObject).getName());
311 case PROJ:
312 return ((File) myObject).getPath().equals(
313 ((File) otherObject).getPath());
314 default:
315 return false;
320 private static final class ContentProvider implements ITreeContentProvider {
322 @SuppressWarnings("unchecked")
323 public Object[] getElements(Object inputElement) {
325 Comparator<RepositoryTreeNode<Repository>> sorter = new Comparator<RepositoryTreeNode<Repository>>() {
327 public int compare(RepositoryTreeNode<Repository> o1,
328 RepositoryTreeNode<Repository> o2) {
329 return getRepositoryName(o1.getObject()).compareTo(
330 getRepositoryName(o2.getObject()));
335 Set<RepositoryTreeNode<Repository>> output = new TreeSet<RepositoryTreeNode<Repository>>(
336 sorter);
338 for (Repository repo : ((List<Repository>) inputElement)) {
339 output.add(new RepositoryTreeNode<Repository>(null,
340 RepositoryTreeNodeType.REPO, repo, repo));
343 return output.toArray();
346 public void dispose() {
347 // nothing
350 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
351 // nothing
354 public Object[] getChildren(Object parentElement) {
356 RepositoryTreeNode node = (RepositoryTreeNode) parentElement;
357 Repository repo = node.getRepository();
359 switch (node.getType()) {
361 case BRANCHES:
363 List<RepositoryTreeNode<Ref>> refs = new ArrayList<RepositoryTreeNode<Ref>>();
365 Repository rep = node.getRepository();
366 for (Ref ref : rep.getAllRefs().values()) {
367 refs.add(new RepositoryTreeNode<Ref>(node,
368 RepositoryTreeNodeType.REF, repo, ref));
371 return refs.toArray();
373 case REPO:
375 List<RepositoryTreeNode<Repository>> branches = new ArrayList<RepositoryTreeNode<Repository>>();
377 branches.add(new RepositoryTreeNode<Repository>(node,
378 RepositoryTreeNodeType.BRANCHES, node.getRepository(),
379 node.getRepository()));
381 branches.add(new RepositoryTreeNode<Repository>(node,
382 RepositoryTreeNodeType.PROJECTS, node.getRepository(),
383 node.getRepository()));
385 return branches.toArray();
387 case PROJECTS:
389 List<RepositoryTreeNode<File>> projects = new ArrayList<RepositoryTreeNode<File>>();
391 // TODO do we want to show the projects here?
392 Collection<File> result = new HashSet<File>();
393 Set<String> traversed = new HashSet<String>();
394 collectProjectFilesFromDirectory(result, repo.getDirectory()
395 .getParentFile(), traversed, new NullProgressMonitor());
396 for (File file : result) {
397 projects.add(new RepositoryTreeNode<File>(node,
398 RepositoryTreeNodeType.PROJ, repo, file));
401 Comparator<RepositoryTreeNode<File>> sorter = new Comparator<RepositoryTreeNode<File>>() {
403 public int compare(RepositoryTreeNode<File> o1,
404 RepositoryTreeNode<File> o2) {
405 return o1.getObject().getName().compareTo(
406 o2.getObject().getName());
409 Collections.sort(projects, sorter);
411 return projects.toArray();
413 default:
414 return null;
419 public Object getParent(Object element) {
421 return ((RepositoryTreeNode) element).getParent();
424 public boolean hasChildren(Object element) {
425 Object[] children = getChildren(element);
426 return children != null && children.length > 0;
429 private boolean collectProjectFilesFromDirectory(
430 Collection<File> files, File directory,
431 Set<String> directoriesVisited, IProgressMonitor monitor) {
433 // stolen from the GitCloneWizard; perhaps we should completely drop
434 // the projects from this view, though
435 if (monitor.isCanceled()) {
436 return false;
438 monitor.subTask(NLS.bind(
439 RepositoryViewUITexts.RepositoriesView_Checking_Message,
440 directory.getPath()));
441 File[] contents = directory.listFiles();
442 if (contents == null)
443 return false;
445 // first look for project description files
446 final String dotProject = IProjectDescription.DESCRIPTION_FILE_NAME;
447 for (int i = 0; i < contents.length; i++) {
448 File file = contents[i];
449 if (file.isFile() && file.getName().equals(dotProject)) {
450 files.add(file.getParentFile());
451 // don't search sub-directories since we can't have nested
452 // projects
453 return true;
456 // no project description found, so recurse into sub-directories
457 for (int i = 0; i < contents.length; i++) {
458 if (contents[i].isDirectory()) {
459 if (!contents[i].getName().equals(
460 GitProjectsImportPage.METADATA_FOLDER)) {
461 try {
462 String canonicalPath = contents[i]
463 .getCanonicalPath();
464 if (!directoriesVisited.add(canonicalPath)) {
465 // already been here --> do not recurse
466 continue;
468 } catch (IOException exception) {
469 StatusManager.getManager().handle(
470 new Status(IStatus.ERROR, Activator
471 .getPluginId(), exception
472 .getLocalizedMessage(), exception));
475 collectProjectFilesFromDirectory(files, contents[i],
476 directoriesVisited, monitor);
480 return true;
485 private static final class LabelProvider extends BaseLabelProvider
486 implements ITableLabelProvider {
488 private DefaultInformationControl infoControl;
492 * @param viewer
494 LabelProvider(final TreeViewer viewer) {
496 viewer.setLabelProvider(this);
497 Tree tree = viewer.getTree();
498 TreeColumn col = new TreeColumn(tree, SWT.NONE);
499 col.setWidth(400);
500 viewer.getTree().addMouseTrackListener(new MouseTrackAdapter() {
502 @Override
503 public void mouseHover(MouseEvent e) {
505 Point eventPoint = new Point(e.x, e.y);
507 TreeItem item = viewer.getTree().getItem(eventPoint);
508 if (item != null) {
510 RepositoryTreeNode node = (RepositoryTreeNode) item
511 .getData();
512 String text = node.getRepository().getDirectory()
513 .getAbsolutePath();
515 final ViewerCell cell = viewer.getCell(eventPoint);
517 if (infoControl != null && infoControl.isVisible()) {
518 infoControl.setVisible(false);
521 GC testGc = new GC(cell.getControl());
522 final Point textExtent = testGc.textExtent(text);
523 testGc.dispose();
525 if (infoControl == null || !infoControl.isVisible()) {
527 IInformationPresenter ips = new IInformationPresenter() {
529 public String updatePresentation(
530 Display display, String hoverInfo,
531 TextPresentation presentation,
532 int maxWidth, int maxHeight) {
533 return hoverInfo;
538 infoControl = new DefaultInformationControl(Display
539 .getCurrent().getActiveShell().getShell(),
540 ips) {
542 @Override
543 public void setInformation(String content) {
544 super.setInformation(content);
545 super.setSize(textExtent.x, textExtent.y);
551 Point dispPoint = viewer.getControl().toDisplay(
552 eventPoint);
554 infoControl.setLocation(dispPoint);
556 // the default info provider works better with \r ...
557 infoControl.setInformation(text);
559 final MouseMoveListener moveListener = new MouseMoveListener() {
561 public void mouseMove(MouseEvent evt) {
562 infoControl.setVisible(false);
563 cell.getControl().removeMouseMoveListener(this);
568 cell.getControl().addMouseMoveListener(moveListener);
570 infoControl.setVisible(true);
580 public Image getColumnImage(Object element, int columnIndex) {
581 return decorateImage(((RepositoryTreeNode) element).getType()
582 .getIcon(), element);
585 public String getColumnText(Object element, int columnIndex) {
587 RepositoryTreeNode node = (RepositoryTreeNode) element;
588 switch (node.getType()) {
589 case REPO:
590 File directory = ((Repository) node.getObject()).getDirectory()
591 .getParentFile();
592 return (directory.getName() + " - " + directory //$NON-NLS-1$
593 .getAbsolutePath());
594 case BRANCHES:
595 return RepositoryViewUITexts.RepositoriesView_Branches_Nodetext;
596 case PROJECTS:
597 return RepositoryViewUITexts.RepositoriesView_ExistingProjects_Nodetext;
598 case REF:
599 Ref ref = (Ref) node.getObject();
600 // shorten the name
601 String refName = node.getRepository().shortenRefName(
602 ref.getName());
603 if (ref.isSymbolic()) {
604 refName = refName
605 + " - " //$NON-NLS-1$
606 + node.getRepository().shortenRefName(
607 ref.getLeaf().getName());
609 return refName;
610 case PROJ:
612 File file = (File) node.getObject();
613 return file.getName();
615 default:
616 return null;
620 public Image decorateImage(final Image image, Object element) {
622 RepositoryTreeNode node = (RepositoryTreeNode) element;
623 switch (node.getType()) {
625 case REF:
626 Ref ref = (Ref) node.getObject();
627 // shorten the name
628 String refName = node.getRepository().shortenRefName(
629 ref.getName());
630 try {
631 String branch = node.getBranch();
632 if (refName.equals(branch)) {
633 CompositeImageDescriptor cd = new CompositeImageDescriptor() {
635 @Override
636 protected Point getSize() {
637 return new Point(image.getBounds().width, image
638 .getBounds().width);
641 @Override
642 protected void drawCompositeImage(int width,
643 int height) {
644 drawImage(image.getImageData(), 0, 0);
645 drawImage(CHECKEDOUT_OVERLAY.getImageData(), 0,
650 return cd.createImage();
652 } catch (IOException e1) {
653 // simply ignore here
655 return image;
657 case PROJ:
659 File file = (File) node.getObject();
661 for (IProject proj : ResourcesPlugin.getWorkspace().getRoot()
662 .getProjects()) {
663 if (proj.getLocation().equals(
664 new Path(file.getAbsolutePath()))) {
665 CompositeImageDescriptor cd = new CompositeImageDescriptor() {
667 @Override
668 protected Point getSize() {
669 return new Point(image.getBounds().width, image
670 .getBounds().width);
673 @Override
674 protected void drawCompositeImage(int width,
675 int height) {
676 drawImage(image.getImageData(), 0, 0);
677 drawImage(CHECKEDOUT_OVERLAY.getImageData(), 0,
682 return cd.createImage();
685 return image;
687 default:
688 return image;
694 private List<String> getGitDirs() {
695 List<String> resultStrings = new ArrayList<String>();
696 String dirs = new InstanceScope().getNode(Activator.getPluginId()).get(
697 PREFS_DIRECTORIES, ""); //$NON-NLS-1$
698 if (dirs != null && dirs.length() > 0) {
699 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
700 while (tok.hasMoreTokens()) {
701 String dirName = tok.nextToken();
702 File testFile = new File(dirName);
703 if (testFile.exists()) {
704 resultStrings.add(dirName);
708 Collections.sort(resultStrings);
709 return resultStrings;
712 private void removeDir(String dir) {
714 IEclipsePreferences prefs = new InstanceScope().getNode(Activator
715 .getPluginId());
717 TreeSet<String> resultStrings = new TreeSet<String>();
718 String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
719 if (dirs != null && dirs.length() > 0) {
720 StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
721 while (tok.hasMoreTokens()) {
722 String dirName = tok.nextToken();
723 File testFile = new File(dirName);
724 if (testFile.exists()) {
725 resultStrings.add(dirName);
730 if (resultStrings.remove(dir)) {
731 StringBuilder sb = new StringBuilder();
732 for (String gitDirString : resultStrings) {
733 sb.append(gitDirString);
734 sb.append(File.pathSeparatorChar);
737 prefs.put(PREFS_DIRECTORIES, sb.toString());
738 try {
739 prefs.flush();
740 } catch (BackingStoreException e) {
741 IStatus error = new Status(IStatus.ERROR, Activator
742 .getPluginId(), e.getMessage(), e);
743 Activator.getDefault().getLog().log(error);
749 @Override
750 public Object getAdapter(Class adapter) {
751 return super.getAdapter(adapter);
754 @Override
755 public void createPartControl(Composite parent) {
757 Composite main = new Composite(parent, SWT.NONE);
758 GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
759 main.setLayout(new GridLayout(1, false));
761 tv = new TreeViewer(main);
762 tv.setContentProvider(new ContentProvider());
763 new LabelProvider(tv);
765 GridDataFactory.fillDefaults().grab(true, true).applyTo(tv.getTree());
767 addContextMenu();
769 addActionsToToolbar();
771 scheduleRefresh();
774 private void addContextMenu() {
775 tv.getTree().addMenuDetectListener(new MenuDetectListener() {
777 public void menuDetected(MenuDetectEvent e) {
779 tv.getTree().setMenu(null);
780 Menu men = new Menu(tv.getTree());
782 TreeItem testItem = tv.getTree().getItem(
783 tv.getTree().toControl(new Point(e.x, e.y)));
784 if (testItem == null) {
785 addMenuItemsForPanel(men);
786 } else {
787 addMenuItemsForTreeSelection(men);
788 if (men.getItemCount() > 0)
789 new MenuItem(men, SWT.SEPARATOR);
790 addMenuItemsForPanel(men);
793 tv.getTree().setMenu(men);
798 private void addMenuItemsForPanel(Menu men) {
800 MenuItem importItem = new MenuItem(men, SWT.PUSH);
801 importItem
802 .setText(RepositoryViewUITexts.RepositoriesView_ImportRepository_MenuItem);
803 importItem.addSelectionListener(new SelectionAdapter() {
805 @Override
806 public void widgetSelected(SelectionEvent e) {
807 importAction.run();
812 MenuItem addItem = new MenuItem(men, SWT.PUSH);
813 addItem
814 .setText(RepositoryViewUITexts.RepositoriesView_AddRepository_MenuItem);
815 addItem.addSelectionListener(new SelectionAdapter() {
817 @Override
818 public void widgetSelected(SelectionEvent e) {
819 addAction.run();
824 MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
825 refreshItem.setText(refreshAction.getText());
826 refreshItem.addSelectionListener(new SelectionAdapter() {
828 @Override
829 public void widgetSelected(SelectionEvent e) {
830 refreshAction.run();
837 @SuppressWarnings("unchecked")
838 private void addMenuItemsForTreeSelection(Menu men) {
839 final List<RepositoryTreeNode<Ref>> refs = new ArrayList<RepositoryTreeNode<Ref>>();
840 final List<RepositoryTreeNode<File>> projects = new ArrayList<RepositoryTreeNode<File>>();
841 final List<RepositoryTreeNode<Repository>> repos = new ArrayList<RepositoryTreeNode<Repository>>();
843 TreeItem[] selectedItems = tv.getTree().getSelection();
844 for (TreeItem item : selectedItems) {
845 RepositoryTreeNode node = (RepositoryTreeNode) item.getData();
846 switch (node.getType()) {
847 case PROJ:
848 projects.add(node);
849 break;
850 case REF:
851 refs.add(node);
852 break;
853 case REPO:
854 repos.add(node);
855 break;
856 default:
857 break;
861 boolean importableProjectsOnly = !projects.isEmpty() && repos.isEmpty()
862 && refs.isEmpty();
864 for (RepositoryTreeNode<File> prj : projects) {
865 if (!importableProjectsOnly)
866 break;
868 for (IProject proj : ResourcesPlugin.getWorkspace().getRoot()
869 .getProjects()) {
870 if (proj.getLocation().equals(
871 new Path(prj.getObject().getAbsolutePath())))
872 importableProjectsOnly = false;
878 boolean singleRef = refs.size() == 1 && projects.isEmpty()
879 && repos.isEmpty();
880 boolean singleRepo = repos.size() == 1 && projects.isEmpty()
881 && refs.isEmpty();
883 try {
884 singleRef = singleRef
885 && !refs.get(0).getObject().getName()
886 .equals(Constants.HEAD)
887 && (refs.get(0).getRepository().mapCommit(
888 refs.get(0).getObject().getLeaf().getObjectId()) != null);
889 } catch (IOException e2) {
890 singleRef = false;
893 if (importableProjectsOnly) {
894 MenuItem sync = new MenuItem(men, SWT.PUSH);
895 sync
896 .setText(RepositoryViewUITexts.RepositoriesView_ImportProject_MenuItem);
898 sync.addSelectionListener(new SelectionAdapter() {
900 @Override
901 public void widgetSelected(SelectionEvent e) {
903 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
905 public void run(IProgressMonitor monitor)
906 throws CoreException {
908 for (RepositoryTreeNode<File> projectNode : projects) {
909 File file = projectNode.getObject();
911 IProjectDescription pd = ResourcesPlugin
912 .getWorkspace().newProjectDescription(
913 file.getName());
914 IPath locationPath = new Path(file
915 .getAbsolutePath());
917 pd.setLocation(locationPath);
919 ResourcesPlugin.getWorkspace().getRoot()
920 .getProject(pd.getName()).create(pd,
921 monitor);
922 IProject project = ResourcesPlugin
923 .getWorkspace().getRoot().getProject(
924 pd.getName());
925 project.open(monitor);
927 File gitDir = projectNode.getRepository()
928 .getDirectory();
930 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
931 project, gitDir);
932 connectProviderOperation
933 .run(new SubProgressMonitor(monitor, 20));
940 try {
942 ResourcesPlugin.getWorkspace().run(wsr,
943 ResourcesPlugin.getWorkspace().getRoot(),
944 IWorkspace.AVOID_UPDATE,
945 new NullProgressMonitor());
947 scheduleRefresh();
948 } catch (CoreException e1) {
949 Activator.getDefault().getLog().log(e1.getStatus());
957 if (singleRef) {
959 MenuItem checkout = new MenuItem(men, SWT.PUSH);
960 checkout
961 .setText(RepositoryViewUITexts.RepositoriesView_CheckOut_MenuItem);
962 checkout.addSelectionListener(new SelectionAdapter() {
964 @Override
965 public void widgetSelected(SelectionEvent e) {
966 Repository repo = refs.get(0).getRepository();
967 String refName = refs.get(0).myObject.getLeaf().getName();
968 final BranchOperation op = new BranchOperation(repo,
969 refName);
970 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
972 public void run(IProgressMonitor monitor)
973 throws CoreException {
974 op.run(monitor);
977 try {
978 ResourcesPlugin.getWorkspace().run(wsr,
979 ResourcesPlugin.getWorkspace().getRoot(),
980 IWorkspace.AVOID_UPDATE,
981 new NullProgressMonitor());
982 scheduleRefresh();
983 } catch (CoreException e1) {
984 MessageDialog
985 .openError(
986 getSite().getShell(),
987 RepositoryViewUITexts.RepositoriesView_Error_WindowTitle,
988 e1.getMessage());
996 if (singleRepo) {
998 MenuItem importProjects = new MenuItem(men, SWT.PUSH);
999 importProjects
1000 .setText(RepositoryViewUITexts.RepositoriesView_ImportExistingProjects_MenuItem);
1001 importProjects.addSelectionListener(new SelectionAdapter() {
1003 @Override
1004 public void widgetSelected(SelectionEvent e) {
1005 Wizard wiz = new ExternalProjectImportWizard(repos.get(0)
1006 .getRepository().getWorkDir().getAbsolutePath()) {
1008 @Override
1009 public void addPages() {
1010 super.addPages();
1011 // we could add some page with a single
1012 // checkbox to indicate if we wan
1013 // addPage(new WizardPage("Share") {
1015 // public void createControl(
1016 // Composite parent) {
1017 // Composite main = new Composite(
1018 // parent, SWT.NONE);
1019 // main.setLayout(new GridLayout(1,
1020 // false));
1021 // GridDataFactory.fillDefaults()
1022 // .grab(true, true).applyTo(
1023 // main);
1024 // Button but = new Button(main,
1025 // SWT.PUSH);
1026 // but.setText("Push me");
1027 // setControl(main);
1029 // }
1030 // });
1033 @Override
1034 public boolean performFinish() {
1036 final Set<IPath> previousLocations = new HashSet<IPath>();
1037 // we want to share only new projects
1038 for (IProject project : ResourcesPlugin
1039 .getWorkspace().getRoot().getProjects()) {
1040 previousLocations.add(project.getLocation());
1043 boolean success = super.performFinish();
1044 if (success) {
1045 // IWizardPage page = getPage("Share");
1046 // TODO evaluate checkbox or such, but
1047 // if we do share
1048 // always, we don't even need another
1049 // page
1051 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1053 public void run(IProgressMonitor monitor)
1054 throws CoreException {
1055 File gitDir = repos.get(0)
1056 .getRepository().getDirectory();
1057 File gitWorkDir = repos.get(0)
1058 .getRepository().getWorkDir();
1059 Path workPath = new Path(gitWorkDir
1060 .getAbsolutePath());
1062 // we check which projects are
1063 // in the workspace
1064 // pointing to a location in the
1065 // repo's
1066 // working directory
1067 // and share them
1068 for (IProject prj : ResourcesPlugin
1069 .getWorkspace().getRoot()
1070 .getProjects()) {
1072 if (workPath.isPrefixOf(prj
1073 .getLocation())) {
1074 if (previousLocations
1075 .contains(prj
1076 .getLocation())) {
1077 continue;
1079 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
1080 prj, gitDir);
1081 connectProviderOperation
1082 .run(new SubProgressMonitor(
1083 monitor, 20));
1091 try {
1092 ResourcesPlugin.getWorkspace().run(
1093 wsr,
1094 ResourcesPlugin.getWorkspace()
1095 .getRoot(),
1096 IWorkspace.AVOID_UPDATE,
1097 new NullProgressMonitor());
1098 scheduleRefresh();
1099 } catch (CoreException ce) {
1100 MessageDialog
1101 .openError(
1102 getShell(),
1103 RepositoryViewUITexts.RepositoriesView_Error_WindowTitle,
1104 ce.getMessage());
1108 return success;
1113 WizardDialog dlg = new WizardDialog(getSite().getShell(),
1114 wiz);
1115 dlg.open();
1120 // TODO "import existing plug-in" menu item
1121 // TODO "configure" menu item
1123 MenuItem remove = new MenuItem(men, SWT.PUSH);
1124 remove
1125 .setText(RepositoryViewUITexts.RepositoriesView_Remove_MenuItem);
1126 remove.addSelectionListener(new SelectionAdapter() {
1128 @Override
1129 public void widgetSelected(SelectionEvent e) {
1131 List<IProject> projectsToDelete = new ArrayList<IProject>();
1132 File workDir = repos.get(0).getRepository().getWorkDir();
1133 final IPath wdPath = new Path(workDir.getAbsolutePath());
1134 for (IProject prj : ResourcesPlugin.getWorkspace()
1135 .getRoot().getProjects()) {
1136 if (wdPath.isPrefixOf(prj.getLocation())) {
1137 projectsToDelete.add(prj);
1141 if (!projectsToDelete.isEmpty()) {
1142 boolean confirmed;
1143 confirmed = confirmProjectDeletion(projectsToDelete);
1144 if (!confirmed) {
1145 return;
1149 IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1151 public void run(IProgressMonitor monitor)
1152 throws CoreException {
1154 for (IProject prj : ResourcesPlugin.getWorkspace()
1155 .getRoot().getProjects()) {
1156 if (wdPath.isPrefixOf(prj.getLocation())) {
1157 prj.delete(false, false, monitor);
1161 Repository repo = repos.get(0).getRepository();
1162 removeDir(repo.getDirectory().getAbsolutePath());
1163 scheduleRefresh();
1167 try {
1168 ResourcesPlugin.getWorkspace().run(wsr,
1169 ResourcesPlugin.getWorkspace().getRoot(),
1170 IWorkspace.AVOID_UPDATE,
1171 new NullProgressMonitor());
1172 } catch (CoreException e1) {
1173 Activator.getDefault().getLog().log(e1.getStatus());
1180 // TODO delete does not work because of file locks on .pack-files
1181 // Shawn Pearce has added the following thoughts:
1183 // Hmm. We probably can't active detect file locks on pack files on
1184 // Windows, can we?
1185 // It would be nice if we could support a delete, but only if the
1186 // repository is
1187 // reasonably believed to be not-in-use right now.
1189 // Within EGit you might be able to check GitProjectData and its
1190 // repositoryCache to
1191 // see if the repository is open by this workspace. If it is, then
1192 // we know we shouldn't
1193 // try to delete it.
1195 // Some coding might look like this:
1197 // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
1198 // deleteRepo.setText("Delete");
1199 // deleteRepo.addSelectionListener(new SelectionAdapter() {
1201 // @Override
1202 // public void widgetSelected(SelectionEvent e) {
1204 // boolean confirmed = MessageDialog.openConfirm(getSite()
1205 // .getShell(), "Confirm",
1206 // "This will delete the repository, continue?");
1208 // if (!confirmed)
1209 // return;
1211 // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
1213 // public void run(IProgressMonitor monitor)
1214 // throws CoreException {
1215 // File workDir = repos.get(0).getRepository()
1216 // .getWorkDir();
1218 // File gitDir = repos.get(0).getRepository()
1219 // .getDirectory();
1221 // IPath wdPath = new Path(workDir.getAbsolutePath());
1222 // for (IProject prj : ResourcesPlugin.getWorkspace()
1223 // .getRoot().getProjects()) {
1224 // if (wdPath.isPrefixOf(prj.getLocation())) {
1225 // prj.delete(false, false, monitor);
1226 // }
1227 // }
1229 // repos.get(0).getRepository().close();
1231 // boolean deleted = deleteRecursively(gitDir, monitor);
1232 // if (!deleted) {
1233 // MessageDialog.openError(getSite().getShell(),
1234 // "Error",
1235 // "Could not delete Git Repository");
1236 // }
1238 // deleted = deleteRecursively(workDir, monitor);
1239 // if (!deleted) {
1240 // MessageDialog
1241 // .openError(getSite().getShell(),
1242 // "Error",
1243 // "Could not delete Git Working Directory");
1244 // }
1246 // scheduleRefresh();
1247 // }
1249 // private boolean deleteRecursively(File fileToDelete,
1250 // IProgressMonitor monitor) {
1251 // if (fileToDelete.isDirectory()) {
1252 // for (File file : fileToDelete.listFiles()) {
1253 // if (!deleteRecursively(file, monitor)) {
1254 // return false;
1255 // }
1256 // }
1257 // }
1258 // monitor.setTaskName(fileToDelete.getAbsolutePath());
1259 // boolean deleted = fileToDelete.delete();
1260 // if (!deleted) {
1261 // System.err.println("Could not delete "
1262 // + fileToDelete.getAbsolutePath());
1263 // }
1264 // return deleted;
1265 // }
1266 // };
1268 // try {
1269 // ResourcesPlugin.getWorkspace().run(wsr,
1270 // ResourcesPlugin.getWorkspace().getRoot(),
1271 // IWorkspace.AVOID_UPDATE,
1272 // new NullProgressMonitor());
1273 // } catch (CoreException e1) {
1274 // // TODO Exception handling
1275 // e1.printStackTrace();
1276 // }
1278 // }
1280 // });
1284 private void addActionsToToolbar() {
1285 importAction = new Action(
1286 RepositoryViewUITexts.RepositoriesView_Import_Button) {
1288 @Override
1289 public void run() {
1290 GitCloneWizard wiz = new GitCloneWizard();
1291 wiz.init(null, null);
1292 new WizardDialog(getSite().getShell(), wiz).open();
1293 updateDirStrings(new NullProgressMonitor());
1296 importAction
1297 .setToolTipText(RepositoryViewUITexts.RepositoriesView_Clone_Tooltip);
1299 getViewSite().getActionBars().getToolBarManager().add(importAction);
1301 addAction = new Action(
1302 RepositoryViewUITexts.RepositoriesView_Add_Button) {
1304 @Override
1305 public void run() {
1306 RepositorySearchDialog sd = new RepositorySearchDialog(
1307 getSite().getShell(), ResourcesPlugin.getWorkspace()
1308 .getRoot().getLocation().toOSString(),
1309 getGitDirs());
1310 if (sd.open() == Window.OK) {
1311 Set<String> dirs = new HashSet<String>();
1312 dirs.addAll(getGitDirs());
1313 if (dirs.addAll(sd.getDirectories()))
1314 saveDirStrings(dirs);
1315 scheduleRefresh();
1320 addAction
1321 .setToolTipText(RepositoryViewUITexts.RepositoriesView_AddRepository_Tooltip);
1323 getViewSite().getActionBars().getToolBarManager().add(addAction);
1325 // TODO if we don't show projects, then we probably don't need refresh
1327 refreshAction = new Action(
1328 RepositoryViewUITexts.RepositoriesView_Refresh_Button) {
1330 @Override
1331 public void run() {
1332 scheduleRefresh();
1336 getViewSite().getActionBars().getToolBarManager().add(refreshAction);
1339 @Override
1340 public void dispose() {
1341 // make sure to cancel the refresh job
1342 if (this.scheduledJob != null) {
1343 this.scheduledJob.cancel();
1344 this.scheduledJob = null;
1346 super.dispose();
1349 private void scheduleRefresh() {
1351 Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
1353 @Override
1354 protected IStatus run(IProgressMonitor monitor) {
1356 final List<Repository> input;
1357 try {
1358 input = getRepositoriesFromDirs(monitor);
1359 } catch (InterruptedException e) {
1360 return new Status(IStatus.ERROR, Activator.getPluginId(), e
1361 .getMessage(), e);
1364 Display.getDefault().syncExec(new Runnable() {
1366 public void run() {
1367 // keep expansion state and selection so that we can
1368 // restore the tree
1369 // after update
1370 Object[] expanded = tv.getExpandedElements();
1371 IStructuredSelection sel = (IStructuredSelection) tv
1372 .getSelection();
1373 tv.setInput(input);
1374 tv.setExpandedElements(expanded);
1376 Object selected = sel.getFirstElement();
1377 if (selected != null)
1378 tv.reveal(selected);
1382 return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
1387 job.setSystem(true);
1389 IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
1390 .getService(IWorkbenchSiteProgressService.class);
1392 service.schedule(job);
1394 scheduledJob = job;
1398 private List<Repository> getRepositoriesFromDirs(IProgressMonitor monitor)
1399 throws InterruptedException {
1401 List<String> gitDirStrings = getGitDirs();
1402 List<Repository> input = new ArrayList<Repository>();
1403 for (String dirString : gitDirStrings) {
1404 if (monitor.isCanceled()) {
1405 throw new InterruptedException(
1406 RepositoryViewUITexts.RepositoriesView_ActionCanceled_Message);
1408 try {
1409 File dir = new File(dirString);
1410 if (dir.exists() && dir.isDirectory()) {
1411 input.add(new Repository(dir));
1413 } catch (IOException e) {
1414 IStatus error = new Status(IStatus.ERROR, Activator
1415 .getPluginId(), e.getMessage(), e);
1416 Activator.getDefault().getLog().log(error);
1419 return input;
1422 private void updateDirStrings(IProgressMonitor monitor) {
1424 IPath path = ResourcesPlugin.getWorkspace().getRoot().getLocation();
1425 File root = path.toFile();
1426 TreeSet<String> dirStrings = new TreeSet<String>();
1427 recurseDir(root, dirStrings, monitor);
1428 saveDirStrings(dirStrings);
1429 scheduleRefresh();
1433 private void saveDirStrings(Set<String> gitDirStrings) {
1434 StringBuilder sb = new StringBuilder();
1435 for (String gitDirString : gitDirStrings) {
1436 sb.append(gitDirString);
1437 sb.append(File.pathSeparatorChar);
1440 IEclipsePreferences prefs = new InstanceScope().getNode(Activator
1441 .getPluginId());
1442 prefs.put(PREFS_DIRECTORIES, sb.toString());
1443 try {
1444 prefs.flush();
1445 } catch (BackingStoreException e) {
1446 IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
1447 e.getMessage(), e);
1448 Activator.getDefault().getLog().log(error);
1454 * @param root
1455 * @param strings
1456 * @param monitor
1458 public static void recurseDir(File root, TreeSet<String> strings,
1459 IProgressMonitor monitor) {
1461 if (!root.exists() || !root.isDirectory()) {
1462 return;
1464 File[] children = root.listFiles();
1465 for (File child : children) {
1466 if (monitor.isCanceled()) {
1467 return;
1470 if (child.exists() && child.isDirectory()
1471 && RepositoryCache.FileKey.isGitRepository(child)) {
1472 strings.add(child.getAbsolutePath());
1473 return;
1475 if (child.isDirectory()) {
1476 monitor.setTaskName(child.getPath());
1477 recurseDir(child, strings, monitor);
1483 private static String getRepositoryName(Repository repository) {
1484 return repository.getDirectory().getParentFile().getName();
1487 @Override
1488 public void setFocus() {
1489 // nothing special
1492 @SuppressWarnings("boxing")
1493 private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
1494 boolean confirmed;
1495 confirmed = MessageDialog
1496 .openConfirm(
1497 getSite().getShell(),
1498 RepositoryViewUITexts.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
1500 .bind(
1501 RepositoryViewUITexts.RepositoriesView_ConfirmProjectDeletion_Question,
1502 projectsToDelete.size()));
1503 return confirmed;