aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathias Kinzler2010-03-15 08:14:29 (EDT)
committerMathias Kinzler2010-03-15 08:14:29 (EDT)
commitf0325b28ed76b4115f3ed2cb2dde57fa2d9af38e (patch)
tree0b32c978971680a6cfa54362c69cff3fa2c35611
parent374af9a268a289429ae1b0c64e14116cff5d3916 (diff)
downloadegit-f0325b28ed76b4115f3ed2cb2dde57fa2d9af38e.zip
egit-f0325b28ed76b4115f3ed2cb2dde57fa2d9af38e.tar.gz
egit-f0325b28ed76b4115f3ed2cb2dde57fa2d9af38e.tar.bz2
Add a "Git Repositories View"refs/changes/44/344/2
This is a very first implementation of a "Git Repositories View" (analogous to e.g. CVS Repositories View). It allows addition and removal of local Git Repositories, cloning of Git Repositories as well as checking-out of branches. Project import can be done using the standard "Import Existing Projects" wizard. We should use bug 301168 to discuss this. Bug: 301168 (also: 281394, 302742, 303403) Change-Id: Icd0a7171f67c3180dc4a4a4246b490d6d4536845 Signed-off-by: Mathias Kinzler <mathias.kinzler@sap.com>
-rw-r--r--org.eclipse.egit.ui/icons/obj16/branches_rep.gifbin0 -> 121 bytes
-rw-r--r--org.eclipse.egit.ui/icons/obj16/repository_rep.gifbin0 -> 545 bytes
-rw-r--r--org.eclipse.egit.ui/icons/ovr/checkedout_ov.gifbin0 -> 173 bytes
-rw-r--r--org.eclipse.egit.ui/plugin.xml14
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java17
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoriesView.java1506
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositorySearchDialog.java243
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoryViewUITexts.java72
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/repositoryviewuitexts.properties18
9 files changed, 1870 insertions, 0 deletions
diff --git a/org.eclipse.egit.ui/icons/obj16/branches_rep.gif b/org.eclipse.egit.ui/icons/obj16/branches_rep.gif
new file mode 100644
index 0000000..717f3b5
--- /dev/null
+++ b/org.eclipse.egit.ui/icons/obj16/branches_rep.gif
Binary files differ
diff --git a/org.eclipse.egit.ui/icons/obj16/repository_rep.gif b/org.eclipse.egit.ui/icons/obj16/repository_rep.gif
new file mode 100644
index 0000000..0470e15
--- /dev/null
+++ b/org.eclipse.egit.ui/icons/obj16/repository_rep.gif
Binary files differ
diff --git a/org.eclipse.egit.ui/icons/ovr/checkedout_ov.gif b/org.eclipse.egit.ui/icons/ovr/checkedout_ov.gif
new file mode 100644
index 0000000..0053b56
--- /dev/null
+++ b/org.eclipse.egit.ui/icons/ovr/checkedout_ov.gif
Binary files differ
diff --git a/org.eclipse.egit.ui/plugin.xml b/org.eclipse.egit.ui/plugin.xml
index 50a3185..76719a6 100644
--- a/org.eclipse.egit.ui/plugin.xml
+++ b/org.eclipse.egit.ui/plugin.xml
@@ -448,4 +448,18 @@
</commandParameter>
</command>
</extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ category="org.eclipse.egit.ui.GitCategory"
+ class="org.eclipse.egit.ui.internal.repository.RepositoriesView"
+ id="org.eclipse.egit.ui.RepositoriesView"
+ name="Git Repositories"
+ restorable="true">
+ </view>
+ <category
+ id="org.eclipse.egit.ui.GitCategory"
+ name="Git">
+ </category>
+ </extension>
</plugin>
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java
index d9b05fa..cd8f92a 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java
@@ -51,6 +51,16 @@ import org.eclipse.jgit.transport.SshSessionFactory;
*/
public class Activator extends AbstractUIPlugin {
+ /** the repository icon */
+ public static final String ICON_REPOSITORY = "Icon_Repository"; //$NON-NLS-1$
+
+ /** the branches icon */
+ public static final String ICON_BRANCHES = "Icon_Branches"; //$NON-NLS-1$
+
+ /** the checked-out overlay icon */
+ public static final String ICON_CHECKEDOUT_OVR = "Icon_CheckedOut_Overlay"; //$NON-NLS-1$
+
+
/**
* The one and only instance
*/
@@ -196,6 +206,13 @@ public class Activator extends AbstractUIPlugin {
setupProxy(context);
setupRepoChangeScanner();
setupRepoIndexRefresh();
+ setupImageRegistry();
+ }
+
+ private void setupImageRegistry() {
+ getImageRegistry().put(ICON_REPOSITORY, imageDescriptorFromPlugin(getPluginId(), "icons/obj16/repository_rep.gif")); //$NON-NLS-1$
+ getImageRegistry().put(ICON_BRANCHES, imageDescriptorFromPlugin(getPluginId(), "icons/obj16/branches_rep.gif")); //$NON-NLS-1$
+ getImageRegistry().put(ICON_CHECKEDOUT_OVR, imageDescriptorFromPlugin(getPluginId(), "icons/ovr/checkedout_ov.gif")); //$NON-NLS-1$
}
private void setupRepoIndexRefresh() {
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoriesView.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoriesView.java
new file mode 100644
index 0000000..25a9b94
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoriesView.java
@@ -0,0 +1,1506 @@
+/*******************************************************************************
+ * Copyright (c) 2010 SAP AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Mathias Kinzler (SAP AG) - initial implementation
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.repository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.egit.core.op.BranchOperation;
+import org.eclipse.egit.core.op.ConnectProviderOperation;
+import org.eclipse.egit.ui.Activator;
+import org.eclipse.egit.ui.internal.clone.GitCloneWizard;
+import org.eclipse.egit.ui.internal.clone.GitProjectsImportPage;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.resource.CompositeImageDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.text.DefaultInformationControl;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter;
+import org.eclipse.jface.viewers.BaseLabelProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.window.Window;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MenuDetectEvent;
+import org.eclipse.swt.events.MenuDetectListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.MouseTrackAdapter;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE.SharedImages;
+import org.eclipse.ui.part.ViewPart;
+import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
+import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.ui.wizards.datatransfer.ExternalProjectImportWizard;
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ *
+ * The Git Repositories view.
+ * <p>
+ * This keeps track of a bunch of local directory names each of which represent
+ * a Git Repository. This list is stored in some Preferences object and used to
+ * build the tree in the view.
+ * <p>
+ * TODO
+ * <li>Icons</li>
+ * <li>String externalization</li>
+ * <li>Clarification whether to show projects, perhaps configurable switch</li>
+ *
+ */
+public class RepositoriesView extends ViewPart {
+
+ private static final String PREFS_DIRECTORIES = "GitRepositoriesView.GitDirectories"; //$NON-NLS-1$
+
+ private static final ImageDescriptor CHECKEDOUT_OVERLAY = Activator
+ .getDefault().getImageRegistry().getDescriptor(
+ Activator.ICON_CHECKEDOUT_OVR);
+
+ private Job scheduledJob;
+
+ private TreeViewer tv;
+
+ private IAction importAction;
+
+ private IAction addAction;
+
+ private IAction refreshAction;
+
+ enum RepositoryTreeNodeType {
+
+ REPO(Activator.ICON_REPOSITORY), REF(PlatformUI.getWorkbench()
+ .getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER)), PROJ(
+ PlatformUI.getWorkbench().getSharedImages().getImage(
+ SharedImages.IMG_OBJ_PROJECT_CLOSED)), BRANCHES(
+ Activator.ICON_BRANCHES), PROJECTS(PlatformUI.getWorkbench()
+ .getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER));
+
+ private final Image myImage;
+
+ private RepositoryTreeNodeType(String iconName) {
+
+ if (iconName != null) {
+ myImage = Activator.getDefault().getImageRegistry().get(
+ iconName);
+ } else {
+ myImage = null;
+ }
+
+ }
+
+ private RepositoryTreeNodeType(Image icon) {
+ myImage = icon;
+
+ }
+
+ public Image getIcon() {
+ return myImage;
+ }
+
+ }
+
+ private static final class RepositoryTreeNode<T> {
+
+ private final Repository myRepository;
+
+ private final T myObject;
+
+ private final RepositoryTreeNodeType myType;
+
+ private final RepositoryTreeNode myParent;
+
+ private String branch;
+
+ public RepositoryTreeNode(RepositoryTreeNode parent,
+ RepositoryTreeNodeType type, Repository repository, T treeObject) {
+ myParent = parent;
+ myRepository = repository;
+ myType = type;
+ myObject = treeObject;
+ }
+
+ @SuppressWarnings("unchecked")
+ private RepositoryTreeNode<Repository> getRepositoryNode() {
+ if (myType == RepositoryTreeNodeType.REPO) {
+ return (RepositoryTreeNode<Repository>) this;
+ } else {
+ return getParent().getRepositoryNode();
+ }
+ }
+
+ /**
+ * We keep this cached in the repository node to avoid repeated lookups
+ *
+ * @return the full branch
+ * @throws IOException
+ */
+ public String getBranch() throws IOException {
+ if (myType != RepositoryTreeNodeType.REPO) {
+ return getRepositoryNode().getBranch();
+ }
+ if (branch == null) {
+ branch = getRepository().getBranch();
+ }
+ return branch;
+ }
+
+ public RepositoryTreeNode getParent() {
+ return myParent;
+ }
+
+ public RepositoryTreeNodeType getType() {
+ return myType;
+ }
+
+ public Repository getRepository() {
+ return myRepository;
+ }
+
+ public T getObject() {
+ return myObject;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ switch (myType) {
+ case REPO:
+ case PROJECTS:
+ case BRANCHES:
+ result = prime
+ * result
+ + ((myObject == null) ? 0 : ((Repository) myObject)
+ .getDirectory().hashCode());
+ break;
+ case REF:
+ result = prime
+ * result
+ + ((myObject == null) ? 0 : ((Ref) myObject).getName()
+ .hashCode());
+ break;
+ case PROJ:
+ result = prime
+ * result
+ + ((myObject == null) ? 0 : ((File) myObject).getPath()
+ .hashCode());
+ break;
+
+ default:
+ break;
+ }
+
+ result = prime * result
+ + ((myParent == null) ? 0 : myParent.hashCode());
+ result = prime
+ * result
+ + ((myRepository == null) ? 0 : myRepository.getDirectory()
+ .hashCode());
+ result = prime * result
+ + ((myType == null) ? 0 : myType.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ RepositoryTreeNode other = (RepositoryTreeNode) obj;
+
+ if (myType == null) {
+ if (other.myType != null)
+ return false;
+ } else if (!myType.equals(other.myType))
+ return false;
+ if (myParent == null) {
+ if (other.myParent != null)
+ return false;
+ } else if (!myParent.equals(other.myParent))
+ return false;
+ if (myRepository == null) {
+ if (other.myRepository != null)
+ return false;
+ } else if (!myRepository.getDirectory().equals(
+ other.myRepository.getDirectory()))
+ return false;
+ if (myObject == null) {
+ if (other.myObject != null)
+ return false;
+ } else if (!checkObjectsEqual(other.myObject))
+ return false;
+
+ return true;
+ }
+
+ private boolean checkObjectsEqual(Object otherObject) {
+ switch (myType) {
+ case REPO:
+ case PROJECTS:
+ case BRANCHES:
+ return ((Repository) myObject).getDirectory().equals(
+ ((Repository) otherObject).getDirectory());
+ case REF:
+ return ((Ref) myObject).getName().equals(
+ ((Ref) otherObject).getName());
+ case PROJ:
+ return ((File) myObject).getPath().equals(
+ ((File) otherObject).getPath());
+ default:
+ return false;
+ }
+ }
+ }
+
+ private static final class ContentProvider implements ITreeContentProvider {
+
+ @SuppressWarnings("unchecked")
+ public Object[] getElements(Object inputElement) {
+
+ Comparator<RepositoryTreeNode<Repository>> sorter = new Comparator<RepositoryTreeNode<Repository>>() {
+
+ public int compare(RepositoryTreeNode<Repository> o1,
+ RepositoryTreeNode<Repository> o2) {
+ return getRepositoryName(o1.getObject()).compareTo(
+ getRepositoryName(o2.getObject()));
+ }
+
+ };
+
+ Set<RepositoryTreeNode<Repository>> output = new TreeSet<RepositoryTreeNode<Repository>>(
+ sorter);
+
+ for (Repository repo : ((List<Repository>) inputElement)) {
+ output.add(new RepositoryTreeNode<Repository>(null,
+ RepositoryTreeNodeType.REPO, repo, repo));
+ }
+
+ return output.toArray();
+ }
+
+ public void dispose() {
+ // nothing
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // nothing
+ }
+
+ public Object[] getChildren(Object parentElement) {
+
+ RepositoryTreeNode node = (RepositoryTreeNode) parentElement;
+ Repository repo = node.getRepository();
+
+ switch (node.getType()) {
+
+ case BRANCHES:
+
+ List<RepositoryTreeNode<Ref>> refs = new ArrayList<RepositoryTreeNode<Ref>>();
+
+ Repository rep = node.getRepository();
+ for (Ref ref : rep.getAllRefs().values()) {
+ refs.add(new RepositoryTreeNode<Ref>(node,
+ RepositoryTreeNodeType.REF, repo, ref));
+ }
+
+ return refs.toArray();
+
+ case REPO:
+
+ List<RepositoryTreeNode<Repository>> branches = new ArrayList<RepositoryTreeNode<Repository>>();
+
+ branches.add(new RepositoryTreeNode<Repository>(node,
+ RepositoryTreeNodeType.BRANCHES, node.getRepository(),
+ node.getRepository()));
+
+ branches.add(new RepositoryTreeNode<Repository>(node,
+ RepositoryTreeNodeType.PROJECTS, node.getRepository(),
+ node.getRepository()));
+
+ return branches.toArray();
+
+ case PROJECTS:
+
+ List<RepositoryTreeNode<File>> projects = new ArrayList<RepositoryTreeNode<File>>();
+
+ // TODO do we want to show the projects here?
+ Collection<File> result = new HashSet<File>();
+ Set<String> traversed = new HashSet<String>();
+ collectProjectFilesFromDirectory(result, repo.getDirectory()
+ .getParentFile(), traversed, new NullProgressMonitor());
+ for (File file : result) {
+ projects.add(new RepositoryTreeNode<File>(node,
+ RepositoryTreeNodeType.PROJ, repo, file));
+ }
+
+ Comparator<RepositoryTreeNode<File>> sorter = new Comparator<RepositoryTreeNode<File>>() {
+
+ public int compare(RepositoryTreeNode<File> o1,
+ RepositoryTreeNode<File> o2) {
+ return o1.getObject().getName().compareTo(
+ o2.getObject().getName());
+ }
+ };
+ Collections.sort(projects, sorter);
+
+ return projects.toArray();
+
+ default:
+ return null;
+ }
+
+ }
+
+ public Object getParent(Object element) {
+
+ return ((RepositoryTreeNode) element).getParent();
+ }
+
+ public boolean hasChildren(Object element) {
+ Object[] children = getChildren(element);
+ return children != null && children.length > 0;
+ }
+
+ private boolean collectProjectFilesFromDirectory(
+ Collection<File> files, File directory,
+ Set<String> directoriesVisited, IProgressMonitor monitor) {
+
+ // stolen from the GitCloneWizard; perhaps we should completely drop
+ // the projects from this view, though
+ if (monitor.isCanceled()) {
+ return false;
+ }
+ monitor.subTask(NLS.bind(
+ RepositoryViewUITexts.RepositoriesView_Checking_Message,
+ directory.getPath()));
+ File[] contents = directory.listFiles();
+ if (contents == null)
+ return false;
+
+ // first look for project description files
+ final String dotProject = IProjectDescription.DESCRIPTION_FILE_NAME;
+ for (int i = 0; i < contents.length; i++) {
+ File file = contents[i];
+ if (file.isFile() && file.getName().equals(dotProject)) {
+ files.add(file.getParentFile());
+ // don't search sub-directories since we can't have nested
+ // projects
+ return true;
+ }
+ }
+ // no project description found, so recurse into sub-directories
+ for (int i = 0; i < contents.length; i++) {
+ if (contents[i].isDirectory()) {
+ if (!contents[i].getName().equals(
+ GitProjectsImportPage.METADATA_FOLDER)) {
+ try {
+ String canonicalPath = contents[i]
+ .getCanonicalPath();
+ if (!directoriesVisited.add(canonicalPath)) {
+ // already been here --> do not recurse
+ continue;
+ }
+ } catch (IOException exception) {
+ StatusManager.getManager().handle(
+ new Status(IStatus.ERROR, Activator
+ .getPluginId(), exception
+ .getLocalizedMessage(), exception));
+
+ }
+ collectProjectFilesFromDirectory(files, contents[i],
+ directoriesVisited, monitor);
+ }
+ }
+ }
+ return true;
+ }
+
+ }
+
+ private static final class LabelProvider extends BaseLabelProvider
+ implements ITableLabelProvider {
+
+ private DefaultInformationControl infoControl;
+
+ /**
+ *
+ * @param viewer
+ */
+ LabelProvider(final TreeViewer viewer) {
+
+ viewer.setLabelProvider(this);
+ Tree tree = viewer.getTree();
+ TreeColumn col = new TreeColumn(tree, SWT.NONE);
+ col.setWidth(400);
+ viewer.getTree().addMouseTrackListener(new MouseTrackAdapter() {
+
+ @Override
+ public void mouseHover(MouseEvent e) {
+
+ Point eventPoint = new Point(e.x, e.y);
+
+ TreeItem item = viewer.getTree().getItem(eventPoint);
+ if (item != null) {
+
+ RepositoryTreeNode node = (RepositoryTreeNode) item
+ .getData();
+ String text = node.getRepository().getDirectory()
+ .getAbsolutePath();
+
+ final ViewerCell cell = viewer.getCell(eventPoint);
+
+ if (infoControl != null && infoControl.isVisible()) {
+ infoControl.setVisible(false);
+ }
+
+ GC testGc = new GC(cell.getControl());
+ final Point textExtent = testGc.textExtent(text);
+ testGc.dispose();
+
+ if (infoControl == null || !infoControl.isVisible()) {
+
+ IInformationPresenter ips = new IInformationPresenter() {
+
+ public String updatePresentation(
+ Display display, String hoverInfo,
+ TextPresentation presentation,
+ int maxWidth, int maxHeight) {
+ return hoverInfo;
+ }
+
+ };
+
+ infoControl = new DefaultInformationControl(Display
+ .getCurrent().getActiveShell().getShell(),
+ ips) {
+
+ @Override
+ public void setInformation(String content) {
+ super.setInformation(content);
+ super.setSize(textExtent.x, textExtent.y);
+ }
+
+ };
+ }
+
+ Point dispPoint = viewer.getControl().toDisplay(
+ eventPoint);
+
+ infoControl.setLocation(dispPoint);
+
+ // the default info provider works better with \r ...
+ infoControl.setInformation(text);
+
+ final MouseMoveListener moveListener = new MouseMoveListener() {
+
+ public void mouseMove(MouseEvent evt) {
+ infoControl.setVisible(false);
+ cell.getControl().removeMouseMoveListener(this);
+
+ }
+ };
+
+ cell.getControl().addMouseMoveListener(moveListener);
+
+ infoControl.setVisible(true);
+
+ }
+
+ }
+
+ });
+
+ }
+
+ public Image getColumnImage(Object element, int columnIndex) {
+ return decorateImage(((RepositoryTreeNode) element).getType()
+ .getIcon(), element);
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+
+ RepositoryTreeNode node = (RepositoryTreeNode) element;
+ switch (node.getType()) {
+ case REPO:
+ File directory = ((Repository) node.getObject()).getDirectory()
+ .getParentFile();
+ return (directory.getName() + " - " + directory //$NON-NLS-1$
+ .getAbsolutePath());
+ case BRANCHES:
+ return RepositoryViewUITexts.RepositoriesView_Branches_Nodetext;
+ case PROJECTS:
+ return RepositoryViewUITexts.RepositoriesView_ExistingProjects_Nodetext;
+ case REF:
+ Ref ref = (Ref) node.getObject();
+ // shorten the name
+ String refName = node.getRepository().shortenRefName(
+ ref.getName());
+ if (ref.isSymbolic()) {
+ refName = refName
+ + " - " //$NON-NLS-1$
+ + node.getRepository().shortenRefName(
+ ref.getLeaf().getName());
+ }
+ return refName;
+ case PROJ:
+
+ File file = (File) node.getObject();
+ return file.getName();
+
+ default:
+ return null;
+ }
+ }
+
+ public Image decorateImage(final Image image, Object element) {
+
+ RepositoryTreeNode node = (RepositoryTreeNode) element;
+ switch (node.getType()) {
+
+ case REF:
+ Ref ref = (Ref) node.getObject();
+ // shorten the name
+ String refName = node.getRepository().shortenRefName(
+ ref.getName());
+ try {
+ String branch = node.getBranch();
+ if (refName.equals(branch)) {
+ CompositeImageDescriptor cd = new CompositeImageDescriptor() {
+
+ @Override
+ protected Point getSize() {
+ return new Point(image.getBounds().width, image
+ .getBounds().width);
+ }
+
+ @Override
+ protected void drawCompositeImage(int width,
+ int height) {
+ drawImage(image.getImageData(), 0, 0);
+ drawImage(CHECKEDOUT_OVERLAY.getImageData(), 0,
+ 0);
+
+ }
+ };
+ return cd.createImage();
+ }
+ } catch (IOException e1) {
+ // simply ignore here
+ }
+ return image;
+
+ case PROJ:
+
+ File file = (File) node.getObject();
+
+ for (IProject proj : ResourcesPlugin.getWorkspace().getRoot()
+ .getProjects()) {
+ if (proj.getLocation().equals(
+ new Path(file.getAbsolutePath()))) {
+ CompositeImageDescriptor cd = new CompositeImageDescriptor() {
+
+ @Override
+ protected Point getSize() {
+ return new Point(image.getBounds().width, image
+ .getBounds().width);
+ }
+
+ @Override
+ protected void drawCompositeImage(int width,
+ int height) {
+ drawImage(image.getImageData(), 0, 0);
+ drawImage(CHECKEDOUT_OVERLAY.getImageData(), 0,
+ 0);
+
+ }
+ };
+ return cd.createImage();
+ }
+ }
+ return image;
+
+ default:
+ return image;
+ }
+ }
+
+ }
+
+ private List<String> getGitDirs() {
+ List<String> resultStrings = new ArrayList<String>();
+ String dirs = new InstanceScope().getNode(Activator.getPluginId()).get(
+ PREFS_DIRECTORIES, ""); //$NON-NLS-1$
+ if (dirs != null && dirs.length() > 0) {
+ StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
+ while (tok.hasMoreTokens()) {
+ String dirName = tok.nextToken();
+ File testFile = new File(dirName);
+ if (testFile.exists()) {
+ resultStrings.add(dirName);
+ }
+ }
+ }
+ Collections.sort(resultStrings);
+ return resultStrings;
+ }
+
+ private void removeDir(String dir) {
+
+ IEclipsePreferences prefs = new InstanceScope().getNode(Activator
+ .getPluginId());
+
+ TreeSet<String> resultStrings = new TreeSet<String>();
+ String dirs = prefs.get(PREFS_DIRECTORIES, ""); //$NON-NLS-1$
+ if (dirs != null && dirs.length() > 0) {
+ StringTokenizer tok = new StringTokenizer(dirs, File.pathSeparator);
+ while (tok.hasMoreTokens()) {
+ String dirName = tok.nextToken();
+ File testFile = new File(dirName);
+ if (testFile.exists()) {
+ resultStrings.add(dirName);
+ }
+ }
+ }
+
+ if (resultStrings.remove(dir)) {
+ StringBuilder sb = new StringBuilder();
+ for (String gitDirString : resultStrings) {
+ sb.append(gitDirString);
+ sb.append(File.pathSeparatorChar);
+ }
+
+ prefs.put(PREFS_DIRECTORIES, sb.toString());
+ try {
+ prefs.flush();
+ } catch (BackingStoreException e) {
+ IStatus error = new Status(IStatus.ERROR, Activator
+ .getPluginId(), e.getMessage(), e);
+ Activator.getDefault().getLog().log(error);
+ }
+ }
+
+ }
+
+ @Override
+ public Object getAdapter(Class adapter) {
+ return super.getAdapter(adapter);
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+
+ Composite main = new Composite(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
+ main.setLayout(new GridLayout(1, false));
+
+ tv = new TreeViewer(main);
+ tv.setContentProvider(new ContentProvider());
+ new LabelProvider(tv);
+
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(tv.getTree());
+
+ addContextMenu();
+
+ addActionsToToolbar();
+
+ scheduleRefresh();
+ }
+
+ private void addContextMenu() {
+ tv.getTree().addMenuDetectListener(new MenuDetectListener() {
+
+ public void menuDetected(MenuDetectEvent e) {
+
+ tv.getTree().setMenu(null);
+ Menu men = new Menu(tv.getTree());
+
+ TreeItem testItem = tv.getTree().getItem(
+ tv.getTree().toControl(new Point(e.x, e.y)));
+ if (testItem == null) {
+ addMenuItemsForPanel(men);
+ } else {
+ addMenuItemsForTreeSelection(men);
+ if (men.getItemCount() > 0)
+ new MenuItem(men, SWT.SEPARATOR);
+ addMenuItemsForPanel(men);
+ }
+
+ tv.getTree().setMenu(men);
+ }
+ });
+ }
+
+ private void addMenuItemsForPanel(Menu men) {
+
+ MenuItem importItem = new MenuItem(men, SWT.PUSH);
+ importItem
+ .setText(RepositoryViewUITexts.RepositoriesView_ImportRepository_MenuItem);
+ importItem.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ importAction.run();
+ }
+
+ });
+
+ MenuItem addItem = new MenuItem(men, SWT.PUSH);
+ addItem
+ .setText(RepositoryViewUITexts.RepositoriesView_AddRepository_MenuItem);
+ addItem.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ addAction.run();
+ }
+
+ });
+
+ MenuItem refreshItem = new MenuItem(men, SWT.PUSH);
+ refreshItem.setText(refreshAction.getText());
+ refreshItem.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ refreshAction.run();
+ }
+
+ });
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addMenuItemsForTreeSelection(Menu men) {
+ final List<RepositoryTreeNode<Ref>> refs = new ArrayList<RepositoryTreeNode<Ref>>();
+ final List<RepositoryTreeNode<File>> projects = new ArrayList<RepositoryTreeNode<File>>();
+ final List<RepositoryTreeNode<Repository>> repos = new ArrayList<RepositoryTreeNode<Repository>>();
+
+ TreeItem[] selectedItems = tv.getTree().getSelection();
+ for (TreeItem item : selectedItems) {
+ RepositoryTreeNode node = (RepositoryTreeNode) item.getData();
+ switch (node.getType()) {
+ case PROJ:
+ projects.add(node);
+ break;
+ case REF:
+ refs.add(node);
+ break;
+ case REPO:
+ repos.add(node);
+ break;
+ default:
+ break;
+ }
+ }
+
+ boolean importableProjectsOnly = !projects.isEmpty() && repos.isEmpty()
+ && refs.isEmpty();
+
+ for (RepositoryTreeNode<File> prj : projects) {
+ if (!importableProjectsOnly)
+ break;
+
+ for (IProject proj : ResourcesPlugin.getWorkspace().getRoot()
+ .getProjects()) {
+ if (proj.getLocation().equals(
+ new Path(prj.getObject().getAbsolutePath())))
+ importableProjectsOnly = false;
+
+ }
+
+ }
+
+ boolean singleRef = refs.size() == 1 && projects.isEmpty()
+ && repos.isEmpty();
+ boolean singleRepo = repos.size() == 1 && projects.isEmpty()
+ && refs.isEmpty();
+
+ try {
+ singleRef = singleRef
+ && !refs.get(0).getObject().getName()
+ .equals(Constants.HEAD)
+ && (refs.get(0).getRepository().mapCommit(
+ refs.get(0).getObject().getLeaf().getObjectId()) != null);
+ } catch (IOException e2) {
+ singleRef = false;
+ }
+
+ if (importableProjectsOnly) {
+ MenuItem sync = new MenuItem(men, SWT.PUSH);
+ sync
+ .setText(RepositoryViewUITexts.RepositoriesView_ImportProject_MenuItem);
+
+ sync.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+
+ IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
+
+ public void run(IProgressMonitor monitor)
+ throws CoreException {
+
+ for (RepositoryTreeNode<File> projectNode : projects) {
+ File file = projectNode.getObject();
+
+ IProjectDescription pd = ResourcesPlugin
+ .getWorkspace().newProjectDescription(
+ file.getName());
+ IPath locationPath = new Path(file
+ .getAbsolutePath());
+
+ pd.setLocation(locationPath);
+
+ ResourcesPlugin.getWorkspace().getRoot()
+ .getProject(pd.getName()).create(pd,
+ monitor);
+ IProject project = ResourcesPlugin
+ .getWorkspace().getRoot().getProject(
+ pd.getName());
+ project.open(monitor);
+
+ File gitDir = projectNode.getRepository()
+ .getDirectory();
+
+ ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
+ project, gitDir);
+ connectProviderOperation
+ .run(new SubProgressMonitor(monitor, 20));
+
+ }
+
+ }
+ };
+
+ try {
+
+ ResourcesPlugin.getWorkspace().run(wsr,
+ ResourcesPlugin.getWorkspace().getRoot(),
+ IWorkspace.AVOID_UPDATE,
+ new NullProgressMonitor());
+
+ scheduleRefresh();
+ } catch (CoreException e1) {
+ Activator.getDefault().getLog().log(e1.getStatus());
+ }
+
+ }
+
+ });
+ }
+
+ if (singleRef) {
+
+ MenuItem checkout = new MenuItem(men, SWT.PUSH);
+ checkout
+ .setText(RepositoryViewUITexts.RepositoriesView_CheckOut_MenuItem);
+ checkout.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Repository repo = refs.get(0).getRepository();
+ String refName = refs.get(0).myObject.getLeaf().getName();
+ final BranchOperation op = new BranchOperation(repo,
+ refName);
+ IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
+
+ public void run(IProgressMonitor monitor)
+ throws CoreException {
+ op.run(monitor);
+ }
+ };
+ try {
+ ResourcesPlugin.getWorkspace().run(wsr,
+ ResourcesPlugin.getWorkspace().getRoot(),
+ IWorkspace.AVOID_UPDATE,
+ new NullProgressMonitor());
+ scheduleRefresh();
+ } catch (CoreException e1) {
+ MessageDialog
+ .openError(
+ getSite().getShell(),
+ RepositoryViewUITexts.RepositoriesView_Error_WindowTitle,
+ e1.getMessage());
+ }
+
+ }
+
+ });
+ }
+
+ if (singleRepo) {
+
+ MenuItem importProjects = new MenuItem(men, SWT.PUSH);
+ importProjects
+ .setText(RepositoryViewUITexts.RepositoriesView_ImportExistingProjects_MenuItem);
+ importProjects.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Wizard wiz = new ExternalProjectImportWizard(repos.get(0)
+ .getRepository().getWorkDir().getAbsolutePath()) {
+
+ @Override
+ public void addPages() {
+ super.addPages();
+ // we could add some page with a single
+ // checkbox to indicate if we wan
+ // addPage(new WizardPage("Share") {
+ //
+ // public void createControl(
+ // Composite parent) {
+ // Composite main = new Composite(
+ // parent, SWT.NONE);
+ // main.setLayout(new GridLayout(1,
+ // false));
+ // GridDataFactory.fillDefaults()
+ // .grab(true, true).applyTo(
+ // main);
+ // Button but = new Button(main,
+ // SWT.PUSH);
+ // but.setText("Push me");
+ // setControl(main);
+ //
+ // }
+ // });
+ }
+
+ @Override
+ public boolean performFinish() {
+
+ final Set<IPath> previousLocations = new HashSet<IPath>();
+ // we want to share only new projects
+ for (IProject project : ResourcesPlugin
+ .getWorkspace().getRoot().getProjects()) {
+ previousLocations.add(project.getLocation());
+ }
+
+ boolean success = super.performFinish();
+ if (success) {
+ // IWizardPage page = getPage("Share");
+ // TODO evaluate checkbox or such, but
+ // if we do share
+ // always, we don't even need another
+ // page
+
+ IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
+
+ public void run(IProgressMonitor monitor)
+ throws CoreException {
+ File gitDir = repos.get(0)
+ .getRepository().getDirectory();
+ File gitWorkDir = repos.get(0)
+ .getRepository().getWorkDir();
+ Path workPath = new Path(gitWorkDir
+ .getAbsolutePath());
+
+ // we check which projects are
+ // in the workspace
+ // pointing to a location in the
+ // repo's
+ // working directory
+ // and share them
+ for (IProject prj : ResourcesPlugin
+ .getWorkspace().getRoot()
+ .getProjects()) {
+
+ if (workPath.isPrefixOf(prj
+ .getLocation())) {
+ if (previousLocations
+ .contains(prj
+ .getLocation())) {
+ continue;
+ }
+ ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
+ prj, gitDir);
+ connectProviderOperation
+ .run(new SubProgressMonitor(
+ monitor, 20));
+
+ }
+ }
+
+ }
+ };
+
+ try {
+ ResourcesPlugin.getWorkspace().run(
+ wsr,
+ ResourcesPlugin.getWorkspace()
+ .getRoot(),
+ IWorkspace.AVOID_UPDATE,
+ new NullProgressMonitor());
+ scheduleRefresh();
+ } catch (CoreException ce) {
+ MessageDialog
+ .openError(
+ getShell(),
+ RepositoryViewUITexts.RepositoriesView_Error_WindowTitle,
+ ce.getMessage());
+ }
+
+ }
+ return success;
+ }
+
+ };
+
+ WizardDialog dlg = new WizardDialog(getSite().getShell(),
+ wiz);
+ dlg.open();
+ }
+
+ });
+
+ // TODO "import existing plug-in" menu item
+ // TODO "configure" menu item
+
+ MenuItem remove = new MenuItem(men, SWT.PUSH);
+ remove
+ .setText(RepositoryViewUITexts.RepositoriesView_Remove_MenuItem);
+ remove.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+
+ List<IProject> projectsToDelete = new ArrayList<IProject>();
+ File workDir = repos.get(0).getRepository().getWorkDir();
+ final IPath wdPath = new Path(workDir.getAbsolutePath());
+ for (IProject prj : ResourcesPlugin.getWorkspace()
+ .getRoot().getProjects()) {
+ if (wdPath.isPrefixOf(prj.getLocation())) {
+ projectsToDelete.add(prj);
+ }
+ }
+
+ if (!projectsToDelete.isEmpty()) {
+ boolean confirmed;
+ confirmed = confirmProjectDeletion(projectsToDelete);
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
+
+ public void run(IProgressMonitor monitor)
+ throws CoreException {
+
+ for (IProject prj : ResourcesPlugin.getWorkspace()
+ .getRoot().getProjects()) {
+ if (wdPath.isPrefixOf(prj.getLocation())) {
+ prj.delete(false, false, monitor);
+ }
+ }
+
+ Repository repo = repos.get(0).getRepository();
+ removeDir(repo.getDirectory().getAbsolutePath());
+ scheduleRefresh();
+ }
+ };
+
+ try {
+ ResourcesPlugin.getWorkspace().run(wsr,
+ ResourcesPlugin.getWorkspace().getRoot(),
+ IWorkspace.AVOID_UPDATE,
+ new NullProgressMonitor());
+ } catch (CoreException e1) {
+ Activator.getDefault().getLog().log(e1.getStatus());
+ }
+
+ }
+
+ });
+
+ // TODO delete does not work because of file locks on .pack-files
+ // Shawn Pearce has added the following thoughts:
+
+ // Hmm. We probably can't active detect file locks on pack files on
+ // Windows, can we?
+ // It would be nice if we could support a delete, but only if the
+ // repository is
+ // reasonably believed to be not-in-use right now.
+ //
+ // Within EGit you might be able to check GitProjectData and its
+ // repositoryCache to
+ // see if the repository is open by this workspace. If it is, then
+ // we know we shouldn't
+ // try to delete it.
+ //
+ // Some coding might look like this:
+ //
+ // MenuItem deleteRepo = new MenuItem(men, SWT.PUSH);
+ // deleteRepo.setText("Delete");
+ // deleteRepo.addSelectionListener(new SelectionAdapter() {
+ //
+ // @Override
+ // public void widgetSelected(SelectionEvent e) {
+ //
+ // boolean confirmed = MessageDialog.openConfirm(getSite()
+ // .getShell(), "Confirm",
+ // "This will delete the repository, continue?");
+ //
+ // if (!confirmed)
+ // return;
+ //
+ // IWorkspaceRunnable wsr = new IWorkspaceRunnable() {
+ //
+ // public void run(IProgressMonitor monitor)
+ // throws CoreException {
+ // File workDir = repos.get(0).getRepository()
+ // .getWorkDir();
+ //
+ // File gitDir = repos.get(0).getRepository()
+ // .getDirectory();
+ //
+ // IPath wdPath = new Path(workDir.getAbsolutePath());
+ // for (IProject prj : ResourcesPlugin.getWorkspace()
+ // .getRoot().getProjects()) {
+ // if (wdPath.isPrefixOf(prj.getLocation())) {
+ // prj.delete(false, false, monitor);
+ // }
+ // }
+ //
+ // repos.get(0).getRepository().close();
+ //
+ // boolean deleted = deleteRecursively(gitDir, monitor);
+ // if (!deleted) {
+ // MessageDialog.openError(getSite().getShell(),
+ // "Error",
+ // "Could not delete Git Repository");
+ // }
+ //
+ // deleted = deleteRecursively(workDir, monitor);
+ // if (!deleted) {
+ // MessageDialog
+ // .openError(getSite().getShell(),
+ // "Error",
+ // "Could not delete Git Working Directory");
+ // }
+ //
+ // scheduleRefresh();
+ // }
+ //
+ // private boolean deleteRecursively(File fileToDelete,
+ // IProgressMonitor monitor) {
+ // if (fileToDelete.isDirectory()) {
+ // for (File file : fileToDelete.listFiles()) {
+ // if (!deleteRecursively(file, monitor)) {
+ // return false;
+ // }
+ // }
+ // }
+ // monitor.setTaskName(fileToDelete.getAbsolutePath());
+ // boolean deleted = fileToDelete.delete();
+ // if (!deleted) {
+ // System.err.println("Could not delete "
+ // + fileToDelete.getAbsolutePath());
+ // }
+ // return deleted;
+ // }
+ // };
+ //
+ // try {
+ // ResourcesPlugin.getWorkspace().run(wsr,
+ // ResourcesPlugin.getWorkspace().getRoot(),
+ // IWorkspace.AVOID_UPDATE,
+ // new NullProgressMonitor());
+ // } catch (CoreException e1) {
+ // // TODO Exception handling
+ // e1.printStackTrace();
+ // }
+ //
+ // }
+ //
+ // });
+ }
+ }
+
+ private void addActionsToToolbar() {
+ importAction = new Action(
+ RepositoryViewUITexts.RepositoriesView_Import_Button) {
+
+ @Override
+ public void run() {
+ GitCloneWizard wiz = new GitCloneWizard();
+ wiz.init(null, null);
+ new WizardDialog(getSite().getShell(), wiz).open();
+ updateDirStrings(new NullProgressMonitor());
+ }
+ };
+ importAction
+ .setToolTipText(RepositoryViewUITexts.RepositoriesView_Clone_Tooltip);
+
+ getViewSite().getActionBars().getToolBarManager().add(importAction);
+
+ addAction = new Action(
+ RepositoryViewUITexts.RepositoriesView_Add_Button) {
+
+ @Override
+ public void run() {
+ RepositorySearchDialog sd = new RepositorySearchDialog(
+ getSite().getShell(), ResourcesPlugin.getWorkspace()
+ .getRoot().getLocation().toOSString(),
+ getGitDirs());
+ if (sd.open() == Window.OK) {
+ Set<String> dirs = new HashSet<String>();
+ dirs.addAll(getGitDirs());
+ if (dirs.addAll(sd.getDirectories()))
+ saveDirStrings(dirs);
+ scheduleRefresh();
+ }
+
+ }
+ };
+ addAction
+ .setToolTipText(RepositoryViewUITexts.RepositoriesView_AddRepository_Tooltip);
+
+ getViewSite().getActionBars().getToolBarManager().add(addAction);
+
+ // TODO if we don't show projects, then we probably don't need refresh
+
+ refreshAction = new Action(
+ RepositoryViewUITexts.RepositoriesView_Refresh_Button) {
+
+ @Override
+ public void run() {
+ scheduleRefresh();
+ }
+ };
+
+ getViewSite().getActionBars().getToolBarManager().add(refreshAction);
+ }
+
+ @Override
+ public void dispose() {
+ // make sure to cancel the refresh job
+ if (this.scheduledJob != null) {
+ this.scheduledJob.cancel();
+ this.scheduledJob = null;
+ }
+ super.dispose();
+ }
+
+ private void scheduleRefresh() {
+
+ Job job = new Job("Refreshing Git Repositories view") { //$NON-NLS-1$
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+
+ final List<Repository> input;
+ try {
+ input = getRepositoriesFromDirs(monitor);
+ } catch (InterruptedException e) {
+ return new Status(IStatus.ERROR, Activator.getPluginId(), e
+ .getMessage(), e);
+ }
+
+ Display.getDefault().syncExec(new Runnable() {
+
+ public void run() {
+ // keep expansion state and selection so that we can
+ // restore the tree
+ // after update
+ Object[] expanded = tv.getExpandedElements();
+ IStructuredSelection sel = (IStructuredSelection) tv
+ .getSelection();
+ tv.setInput(input);
+ tv.setExpandedElements(expanded);
+
+ Object selected = sel.getFirstElement();
+ if (selected != null)
+ tv.reveal(selected);
+ }
+ });
+
+ return new Status(IStatus.OK, Activator.getPluginId(), ""); //$NON-NLS-1$
+
+ }
+
+ };
+ job.setSystem(true);
+
+ IWorkbenchSiteProgressService service = (IWorkbenchSiteProgressService) getSite()
+ .getService(IWorkbenchSiteProgressService.class);
+
+ service.schedule(job);
+
+ scheduledJob = job;
+
+ }
+
+ private List<Repository> getRepositoriesFromDirs(IProgressMonitor monitor)
+ throws InterruptedException {
+
+ List<String> gitDirStrings = getGitDirs();
+ List<Repository> input = new ArrayList<Repository>();
+ for (String dirString : gitDirStrings) {
+ if (monitor.isCanceled()) {
+ throw new InterruptedException(
+ RepositoryViewUITexts.RepositoriesView_ActionCanceled_Message);
+ }
+ try {
+ File dir = new File(dirString);
+ if (dir.exists() && dir.isDirectory()) {
+ input.add(new Repository(dir));
+ }
+ } catch (IOException e) {
+ IStatus error = new Status(IStatus.ERROR, Activator
+ .getPluginId(), e.getMessage(), e);
+ Activator.getDefault().getLog().log(error);
+ }
+ }
+ return input;
+ }
+
+ private void updateDirStrings(IProgressMonitor monitor) {
+
+ IPath path = ResourcesPlugin.getWorkspace().getRoot().getLocation();
+ File root = path.toFile();
+ TreeSet<String> dirStrings = new TreeSet<String>();
+ recurseDir(root, dirStrings, monitor);
+ saveDirStrings(dirStrings);
+ scheduleRefresh();
+
+ }
+
+ private void saveDirStrings(Set<String> gitDirStrings) {
+ StringBuilder sb = new StringBuilder();
+ for (String gitDirString : gitDirStrings) {
+ sb.append(gitDirString);
+ sb.append(File.pathSeparatorChar);
+ }
+
+ IEclipsePreferences prefs = new InstanceScope().getNode(Activator
+ .getPluginId());
+ prefs.put(PREFS_DIRECTORIES, sb.toString());
+ try {
+ prefs.flush();
+ } catch (BackingStoreException e) {
+ IStatus error = new Status(IStatus.ERROR, Activator.getPluginId(),
+ e.getMessage(), e);
+ Activator.getDefault().getLog().log(error);
+ }
+ }
+
+ /**
+ *
+ * @param root
+ * @param strings
+ * @param monitor
+ */
+ public static void recurseDir(File root, TreeSet<String> strings,
+ IProgressMonitor monitor) {
+
+ if (!root.exists() || !root.isDirectory()) {
+ return;
+ }
+ File[] children = root.listFiles();
+ for (File child : children) {
+ if (monitor.isCanceled()) {
+ return;
+ }
+
+ if (child.exists() && child.isDirectory()
+ && RepositoryCache.FileKey.isGitRepository(child)) {
+ strings.add(child.getAbsolutePath());
+ return;
+ }
+ if (child.isDirectory()) {
+ monitor.setTaskName(child.getPath());
+ recurseDir(child, strings, monitor);
+ }
+ }
+
+ }
+
+ private static String getRepositoryName(Repository repository) {
+ return repository.getDirectory().getParentFile().getName();
+ }
+
+ @Override
+ public void setFocus() {
+ // nothing special
+ }
+
+ @SuppressWarnings("boxing")
+ private boolean confirmProjectDeletion(List<IProject> projectsToDelete) {
+ boolean confirmed;
+ confirmed = MessageDialog
+ .openConfirm(
+ getSite().getShell(),
+ RepositoryViewUITexts.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
+ NLS
+ .bind(
+ RepositoryViewUITexts.RepositoriesView_ConfirmProjectDeletion_Question,
+ projectsToDelete.size()));
+ return confirmed;
+ }
+
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositorySearchDialog.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositorySearchDialog.java
new file mode 100644
index 0000000..2b18e53
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositorySearchDialog.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2010 SAP AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Mathias Kinzler (SAP AG) - initial implementation
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.repository;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.BaseLabelProvider;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.IColorProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Searches for Git directories under a path that can be selected by the user
+ * TODO String externalization
+ */
+public class RepositorySearchDialog extends Dialog {
+
+ private final Set<String> existingRepositoryDirs = new HashSet<String>();
+
+ private final String myInitialPath;
+
+ private Set<String> result;
+
+ CheckboxTableViewer tv;
+
+ private final class ContentProvider implements IStructuredContentProvider {
+
+ @SuppressWarnings("unchecked")
+ public Object[] getElements(Object inputElement) {
+ return ((Set<String>) inputElement).toArray();
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // nothing
+ }
+
+ public void dispose() {
+ // nothing
+ }
+
+ }
+
+ private final class LabelProvider extends BaseLabelProvider implements
+ ITableLabelProvider, IColorProvider {
+
+ public Image getColumnImage(Object element, int columnIndex) {
+ return null;
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+ return element.toString();
+ }
+
+ public Color getBackground(Object element) {
+ return null;
+ }
+
+ public Color getForeground(Object element) {
+ if (existingRepositoryDirs.contains(element))
+ return getShell().getDisplay().getSystemColor(SWT.COLOR_GRAY);
+
+ return null;
+ }
+
+ }
+
+ /**
+ * @param parentShell
+ * @param initialPath
+ * the initial path
+ * @param existingDirs
+ */
+ protected RepositorySearchDialog(Shell parentShell, String initialPath,
+ Collection<String> existingDirs) {
+ super(parentShell);
+ this.existingRepositoryDirs.addAll(existingDirs);
+ this.myInitialPath = initialPath;
+ setShellStyle(getShellStyle() | SWT.SHELL_TRIM);
+ }
+
+ /**
+ *
+ * @return the directories
+ */
+ public Set<String> getDirectories() {
+ return result;
+ }
+
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText("Search Git Repositories");
+ }
+
+ @Override
+ protected void okPressed() {
+ result = new HashSet<String>();
+ Object[] checked = tv.getCheckedElements();
+ for (Object o : checked) {
+ result.add((String) o);
+ }
+ super.okPressed();
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+
+ Composite main = new Composite(parent, SWT.NONE);
+ main.setLayout(new GridLayout(3, false));
+
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
+
+ Label dirLabel = new Label(main, SWT.NONE);
+ dirLabel.setText("Directory");
+ final Text dir = new Text(main, SWT.NONE);
+ if (myInitialPath != null)
+ dir.setText(myInitialPath);
+
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true,
+ false).applyTo(dir);
+
+ Button browse = new Button(main, SWT.PUSH);
+ browse.setText("Browse...");
+ browse.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ DirectoryDialog dd = new DirectoryDialog(getShell());
+ dd.setFilterPath(dir.getText());
+ String directory = dd.open();
+ if (directory != null) {
+ dir.setText(directory);
+ }
+ }
+
+ });
+
+ Button search = new Button(main, SWT.PUSH);
+ search.setText("Search");
+ GridDataFactory.fillDefaults().align(SWT.LEAD, SWT.CENTER).span(3, 1)
+ .applyTo(search);
+
+ tv = CheckboxTableViewer.newCheckList(main, SWT.NONE);
+ Table tab = tv.getTable();
+ GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(tab);
+
+ tv.addCheckStateListener(new ICheckStateListener() {
+
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ if (existingRepositoryDirs.contains(event.getElement()))
+ event.getCheckable().setChecked(event.getElement(), false);
+ getButton(IDialogConstants.OK_ID).setEnabled(
+ tv.getCheckedElements().length > 0);
+ }
+ });
+
+ tv.setContentProvider(new ContentProvider());
+ tv.setLabelProvider(new LabelProvider());
+
+ search.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ final TreeSet<String> directories = new TreeSet<String>();
+ final File file = new File(dir.getText());
+ if (file.exists()) {
+ IRunnableWithProgress action = new IRunnableWithProgress() {
+
+ public void run(IProgressMonitor monitor)
+ throws InvocationTargetException,
+ InterruptedException {
+ RepositoriesView.recurseDir(file, directories,
+ monitor);
+ }
+ };
+ try {
+ ProgressMonitorDialog pd = new ProgressMonitorDialog(
+ getShell());
+ pd.run(true, true, action);
+
+ } catch (InvocationTargetException e1) {
+ MessageDialog.openError(getShell(), "Error", e1
+ .getCause().getMessage());
+ } catch (InterruptedException e1) {
+ // ignore
+ }
+
+ tv.setInput(directories);
+ }
+ }
+
+ });
+
+ return main;
+ }
+
+ @Override
+ protected Control createButtonBar(Composite parent) {
+ // disable the OK button until the user selects something
+ Control bar = super.createButtonBar(parent);
+ getButton(IDialogConstants.OK_ID).setEnabled(false);
+ return bar;
+ }
+
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoryViewUITexts.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoryViewUITexts.java
new file mode 100644
index 0000000..0322cc7
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/RepositoryViewUITexts.java
@@ -0,0 +1,72 @@
+package org.eclipse.egit.ui.internal.repository;
+
+import org.eclipse.osgi.util.NLS;
+/**
+ * UI Texts for the Repositories View
+ *
+ */
+public class RepositoryViewUITexts extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.egit.ui.internal.repository.repositoryviewuitexts"; //$NON-NLS-1$
+
+ /** */
+ public static String RepositoriesView_ActionCanceled_Message;
+
+ /** */
+ public static String RepositoriesView_Add_Button;
+
+ /** */
+ public static String RepositoriesView_AddRepository_MenuItem;
+
+ /** */
+ public static String RepositoriesView_AddRepository_Tooltip;
+
+ /** */
+ public static String RepositoriesView_Branches_Nodetext;
+
+ /** */
+ public static String RepositoriesView_Checking_Message;
+
+ /** */
+ public static String RepositoriesView_CheckOut_MenuItem;
+
+ /** */
+ public static String RepositoriesView_Clone_Tooltip;
+
+ /** */
+ public static String RepositoriesView_ConfirmProjectDeletion_Question;
+
+ /** */
+ public static String RepositoriesView_ConfirmProjectDeletion_WindowTitle;
+
+ /** */
+ public static String RepositoriesView_Error_WindowTitle;
+
+ /** */
+ public static String RepositoriesView_ExistingProjects_Nodetext;
+
+ /** */
+ public static String RepositoriesView_Import_Button;
+
+ /** */
+ public static String RepositoriesView_ImportExistingProjects_MenuItem;
+
+ /** */
+ public static String RepositoriesView_ImportProject_MenuItem;
+
+ /** */
+ public static String RepositoriesView_ImportRepository_MenuItem;
+
+ /** */
+ public static String RepositoriesView_Refresh_Button;
+
+ /** */
+ public static String RepositoriesView_Remove_MenuItem;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, RepositoryViewUITexts.class);
+ }
+
+ private RepositoryViewUITexts() {
+ }
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/repositoryviewuitexts.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/repositoryviewuitexts.properties
new file mode 100644
index 0000000..e8e369c
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/repository/repositoryviewuitexts.properties
@@ -0,0 +1,18 @@
+RepositoriesView_ActionCanceled_Message=Action was canceled
+RepositoriesView_Add_Button=Add...
+RepositoriesView_AddRepository_MenuItem=Add Git Repository...
+RepositoriesView_AddRepository_Tooltip=Add an existing Git Repository
+RepositoriesView_Branches_Nodetext=Branches
+RepositoriesView_Checking_Message=Checking: {0}
+RepositoriesView_CheckOut_MenuItem=Check out
+RepositoriesView_Clone_Tooltip=Import (clone) a Git Repository
+RepositoriesView_ConfirmProjectDeletion_Question={0} projects must be deleted, continue?
+RepositoriesView_ConfirmProjectDeletion_WindowTitle=Confirm project deletion
+RepositoriesView_Error_WindowTitle=Error
+RepositoriesView_ExistingProjects_Nodetext=Existing Projects
+RepositoriesView_Import_Button=Import...
+RepositoriesView_ImportExistingProjects_MenuItem=Import Existing projects...
+RepositoriesView_ImportProject_MenuItem=Import
+RepositoriesView_ImportRepository_MenuItem=Import Git Repository...
+RepositoriesView_Refresh_Button=Refresh
+RepositoriesView_Remove_MenuItem=Remove