RepositoriesView: use the GitImportProject wizard
[egit/spearce.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / clone / GitProjectsImportPage.java
blob43ec84c6d708e9fbc7c42f81c090ba42dcc0800b
1 package org.eclipse.egit.ui.internal.clone;
3 /*******************************************************************************
4 * Copyright (c) 2004, 2008 IBM Corporation and others.
5 * Copyright (C) 2007, Martin Oberhuber (martin.oberhuber@windriver.com)
6 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
7 * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua>
8 * Copyright (C) 2010, Wim Jongman <wim.jongman@remainsoftware.com>
10 * All rights reserved. This program and the accompanying materials
11 * are made available under the terms of the Eclipse Public License v1.0
12 * which accompanies this distribution, and is available at
13 * http://www.eclipse.org/legal/epl-v10.html
14 *******************************************************************************/
16 import java.io.File;
17 import java.io.IOException;
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Set;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IProjectDescription;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.resources.IWorkspace;
30 import org.eclipse.core.resources.ResourcesPlugin;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IProgressMonitor;
34 import org.eclipse.core.runtime.IStatus;
35 import org.eclipse.core.runtime.OperationCanceledException;
36 import org.eclipse.core.runtime.Path;
37 import org.eclipse.core.runtime.Platform;
38 import org.eclipse.core.runtime.Status;
39 import org.eclipse.core.runtime.SubProgressMonitor;
40 import org.eclipse.egit.core.op.ConnectProviderOperation;
41 import org.eclipse.egit.ui.Activator;
42 import org.eclipse.egit.ui.UIText;
43 import org.eclipse.jface.dialogs.Dialog;
44 import org.eclipse.jface.dialogs.ErrorDialog;
45 import org.eclipse.jface.operation.IRunnableWithProgress;
46 import org.eclipse.jface.viewers.ITreeContentProvider;
47 import org.eclipse.jface.viewers.LabelProvider;
48 import org.eclipse.jface.viewers.TreeViewer;
49 import org.eclipse.jface.viewers.Viewer;
50 import org.eclipse.jface.viewers.ViewerComparator;
51 import org.eclipse.jface.wizard.WizardPage;
52 import org.eclipse.osgi.util.NLS;
53 import org.eclipse.swt.SWT;
54 import org.eclipse.swt.events.MouseAdapter;
55 import org.eclipse.swt.events.MouseEvent;
56 import org.eclipse.swt.events.SelectionAdapter;
57 import org.eclipse.swt.events.SelectionEvent;
58 import org.eclipse.swt.graphics.Point;
59 import org.eclipse.swt.layout.GridData;
60 import org.eclipse.swt.layout.GridLayout;
61 import org.eclipse.swt.widgets.Button;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Display;
64 import org.eclipse.swt.widgets.Label;
65 import org.eclipse.swt.widgets.Tree;
66 import org.eclipse.swt.widgets.TreeItem;
67 import org.eclipse.ui.actions.WorkspaceModifyOperation;
68 import org.eclipse.ui.dialogs.FilteredTree;
69 import org.eclipse.ui.dialogs.PatternFilter;
70 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
71 import org.eclipse.ui.internal.ide.StatusUtil;
72 import org.eclipse.ui.internal.wizards.datatransfer.WizardProjectsImportPage;
73 import org.eclipse.ui.statushandlers.StatusManager;
74 import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
76 /**
77 * The GitWizardProjectsImportPage is the page that allows the user to import
78 * projects from a particular location. This is a modified copy of
79 * {@link WizardProjectsImportPage}
81 public class GitProjectsImportPage extends WizardPage {
83 /**
84 * The name of the folder containing metadata information for the workspace.
86 public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
88 private IImportStructureProvider structureProvider;
90 private File gitRepositoryDir;
92 class ProjectRecord {
93 File projectSystemFile;
95 String projectName;
97 Object parent;
99 int level;
101 IProjectDescription description;
104 * Create a record for a project based on the info in the file.
106 * @param file
108 ProjectRecord(File file) {
109 projectSystemFile = file;
110 setProjectName();
114 * @param parent
115 * The parent folder of the .project file
116 * @param level
117 * The number of levels deep in the provider the file is
119 ProjectRecord(Object parent, int level) {
120 this.parent = parent;
121 this.level = level;
122 setProjectName();
126 * Set the name of the project based on the projectFile.
128 private void setProjectName() {
129 try {
130 // If we don't have the project name try again
131 if (projectName == null) {
132 IPath path = new Path(projectSystemFile.getPath());
133 // if the file is in the default location, use the directory
134 // name as the project name
135 if (isDefaultLocation(path)) {
136 projectName = path.segment(path.segmentCount() - 2);
137 description = ResourcesPlugin.getWorkspace()
138 .newProjectDescription(projectName);
139 } else {
140 description = ResourcesPlugin.getWorkspace()
141 .loadProjectDescription(path);
142 projectName = description.getName();
146 } catch (CoreException e) {
147 // no good couldn't get the name
152 * Returns whether the given project description file path is in the
153 * default location for a project
155 * @param path
156 * The path to examine
157 * @return Whether the given path is the default location for a project
159 private boolean isDefaultLocation(IPath path) {
160 // The project description file must at least be within the project,
161 // which is within the workspace location
162 if (path.segmentCount() < 2)
163 return false;
164 return path.removeLastSegments(2).toFile().equals(
165 Platform.getLocation().toFile());
169 * Get the name of the project
171 * @return String
173 public String getProjectName() {
174 return projectName;
178 * Gets the label to be used when rendering this project record in the
179 * UI.
181 * @return String the label
182 * @since 3.4
184 public String getProjectLabel() {
185 if (description == null)
186 return projectName;
188 String path = projectSystemFile == null ? structureProvider
189 .getLabel(parent) : projectSystemFile.getParent();
191 return NLS.bind(UIText.WizardProjectsImportPage_projectLabel,
192 projectName, path);
196 private TreeViewer projectsList;
198 private ProjectRecord[] selectedProjects = new ProjectRecord[0];
200 final private HashSet<Object> checkedItems = new HashSet<Object>();
202 private IProject[] wsProjects;
204 // The last selected path to minimize searches
205 private String lastPath;
207 // The last time that the file or folder at the selected path was modified
208 // to minimize searches
209 private long lastModified;
211 private Button shareCheckBox;
213 private Button selectAll;
215 private Button deselectAll;
217 private boolean share;
220 * Creates a new project creation wizard page.
222 public GitProjectsImportPage() {
223 super("gitWizardExternalProjectsPage"); //$NON-NLS-1$
224 setPageComplete(false);
225 setTitle(UIText.WizardProjectsImportPage_ImportProjectsTitle);
226 setDescription(UIText.WizardProjectsImportPage_ImportProjectsDescription);
229 public void createControl(Composite parent) {
231 initializeDialogUnits(parent);
233 Composite workArea = new Composite(parent, SWT.NONE);
234 setControl(workArea);
236 workArea.setLayout(new GridLayout());
237 workArea.setLayoutData(new GridData(GridData.FILL_BOTH
238 | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL));
240 createProjectsRoot(workArea);
241 createProjectsList(workArea);
242 createOptionsArea(workArea);
243 Dialog.applyDialogFont(workArea);
248 * Create the area with the extra options.
250 * @param workArea
252 private void createOptionsArea(Composite workArea) {
253 Composite optionsGroup = new Composite(workArea, SWT.NONE);
254 optionsGroup.setLayout(new GridLayout());
255 optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
257 shareCheckBox = new Button(optionsGroup, SWT.CHECK);
258 shareCheckBox.setText(UIText.WizardProjectsImportPage_enableGit);
259 shareCheckBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
260 shareCheckBox.setSelection(share = true);
261 shareCheckBox.addSelectionListener(new SelectionAdapter() {
262 public void widgetSelected(SelectionEvent e) {
263 share = shareCheckBox.getSelection();
269 * Create the checkbox list for the found projects.
271 * @param workArea
273 private void createProjectsList(Composite workArea) {
275 checkedItems.clear();
277 Label title = new Label(workArea, SWT.NONE);
278 title.setText(UIText.WizardProjectsImportPage_ProjectsListTitle);
280 Composite listComposite = new Composite(workArea, SWT.NONE);
281 GridLayout layout = new GridLayout();
282 layout.numColumns = 2;
283 layout.marginWidth = 0;
284 layout.makeColumnsEqualWidth = false;
285 listComposite.setLayout(layout);
287 listComposite.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
288 | GridData.GRAB_VERTICAL | GridData.FILL_BOTH));
290 PatternFilter filter = new PatternFilter() {
292 @Override
293 public boolean isElementVisible(Viewer viewer, Object element) {
295 if (checkedItems.contains(element)) {
296 return true;
299 return super.isElementVisible(viewer, element);
302 @Override
303 public void setPattern(String patternString) {
304 super.setPattern(patternString);
305 // TODO: is there a better way to react on changes in the tree?
306 // disable select all button when tree becomes empty due to
307 // filtering
308 Display.getDefault().asyncExec(new Runnable() {
309 public void run() {
310 enableSelectAllButtons();
318 FilteredTree filteredTree = new FilteredTree(listComposite, SWT.CHECK
319 | SWT.BORDER, filter);
320 filteredTree.setInitialText(UIText.WizardProjectsImportPage_filterText);
321 projectsList = filteredTree.getViewer();
322 GridData listData = new GridData(GridData.GRAB_HORIZONTAL
323 | GridData.GRAB_VERTICAL | GridData.FILL_BOTH);
324 projectsList.getControl().setLayoutData(listData);
326 projectsList.setContentProvider(new ITreeContentProvider() {
328 public Object[] getChildren(Object parentElement) {
329 return null;
332 public Object[] getElements(Object inputElement) {
333 return getValidProjects();
336 public boolean hasChildren(Object element) {
337 return false;
340 public Object getParent(Object element) {
341 return null;
344 public void dispose() {
348 public void inputChanged(Viewer viewer, Object oldInput,
349 Object newInput) {
354 projectsList.getTree().addMouseListener(new MouseAdapter() {
355 @Override
356 public void mouseUp(MouseEvent e) {
357 if (e.widget instanceof Tree) {
358 TreeItem item = ((Tree) e.widget).getItem(new Point(e.x,
359 e.y));
360 if (item != null) {
361 if (item.getChecked())
362 checkedItems.add(item.getData());
363 else
364 checkedItems.remove(item.getData());
365 setPageComplete(!checkedItems.isEmpty());
371 projectsList.setLabelProvider(new LabelProvider() {
372 public String getText(Object element) {
373 // Need to set the checked item state. FIXME This is clumsy.
374 for (final TreeItem item : projectsList.getTree().getItems()) {
375 if (checkedItems.contains(item.getData()))
376 item.setChecked(true);
377 else
378 item.setChecked(false);
380 return ((ProjectRecord) element).getProjectLabel();
384 projectsList.setInput(this);
385 projectsList.setComparator(new ViewerComparator());
386 createSelectionButtons(listComposite);
390 * Create the selection buttons in the listComposite.
392 * @param listComposite
394 private void createSelectionButtons(Composite listComposite) {
395 Composite buttonsComposite = new Composite(listComposite, SWT.NONE);
396 GridLayout layout = new GridLayout();
397 layout.marginWidth = 0;
398 layout.marginHeight = 0;
399 buttonsComposite.setLayout(layout);
401 buttonsComposite.setLayoutData(new GridData(
402 GridData.VERTICAL_ALIGN_BEGINNING));
404 selectAll = new Button(buttonsComposite, SWT.PUSH);
405 selectAll.setText(UIText.WizardProjectsImportPage_selectAll);
406 selectAll.addSelectionListener(new SelectionAdapter() {
407 public void widgetSelected(SelectionEvent e) {
408 checkedItems.clear();
409 // only the root has children
410 for (final TreeItem item : projectsList.getTree().getItems()) {
411 item.setChecked(true);
412 checkedItems.add(item.getData());
414 setPageComplete(true);
417 Dialog.applyDialogFont(selectAll);
418 setButtonLayoutData(selectAll);
420 deselectAll = new Button(buttonsComposite, SWT.PUSH);
421 deselectAll.setText(UIText.WizardProjectsImportPage_deselectAll);
422 deselectAll.addSelectionListener(new SelectionAdapter() {
423 public void widgetSelected(SelectionEvent e) {
424 checkedItems.clear();
425 // only the root has children
426 for (final TreeItem item : projectsList.getTree().getItems()) {
427 item.setChecked(false);
429 projectsList.setInput(this); // filter away selected projects
430 setPageComplete(false);
433 Dialog.applyDialogFont(deselectAll);
434 setButtonLayoutData(deselectAll);
439 * Create the area where you select the root directory for the projects.
441 * @param workArea
442 * Composite
444 private void createProjectsRoot(Composite workArea) {
446 // project specification group
447 Composite projectGroup = new Composite(workArea, SWT.NONE);
448 GridLayout layout = new GridLayout();
449 layout.numColumns = 3;
450 layout.makeColumnsEqualWidth = false;
451 layout.marginWidth = 0;
452 projectGroup.setLayout(layout);
453 projectGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
457 public void setVisible(boolean visible) {
458 super.setVisible(visible);
462 * Set the git directory which will contain the repository
464 * @param gitDir
466 public void setGitDir(File gitDir) {
467 this.gitRepositoryDir = gitDir;
471 * Update the list of projects based on path. This will not check any
472 * projects.
474 * @param path
476 void setProjectsList(final String path) {
477 // on an empty path empty selectedProjects
478 if (path == null || path.length() == 0) {
479 setMessage(UIText.WizardProjectsImportPage_ImportProjectsDescription);
480 selectedProjects = new ProjectRecord[0];
481 projectsList.refresh(true);
482 setPageComplete(checkedItems.size() > 0);
483 lastPath = path;
484 return;
487 final File directory = new File(path);
488 long modified = directory.lastModified();
489 if (path.equals(lastPath) && lastModified == modified) {
490 // since the file/folder was not modified and the path did not
491 // change, no refreshing is required
492 return;
495 lastPath = path;
496 lastModified = modified;
498 try {
499 getContainer().run(true, true, new IRunnableWithProgress() {
501 public void run(IProgressMonitor monitor) {
503 monitor.beginTask(
504 UIText.WizardProjectsImportPage_SearchingMessage,
505 100);
506 selectedProjects = new ProjectRecord[0];
507 Collection<File> files = new ArrayList<File>();
508 monitor.worked(10);
509 if (directory.isDirectory()) {
511 if (!collectProjectFilesFromDirectory(files, directory,
512 null, monitor)) {
513 return;
515 Iterator<File> filesIterator = files.iterator();
516 selectedProjects = new ProjectRecord[files.size()];
517 int index = 0;
518 monitor.worked(50);
519 monitor
520 .subTask(UIText.WizardProjectsImportPage_ProcessingMessage);
521 while (filesIterator.hasNext()) {
522 File file = filesIterator.next();
523 selectedProjects[index] = new ProjectRecord(file);
524 checkedItems.add(selectedProjects[index]);
525 index++;
527 } else {
528 monitor.worked(60);
530 monitor.done();
534 } catch (InvocationTargetException e) {
535 IDEWorkbenchPlugin.log(e.getMessage(), e);
536 } catch (InterruptedException e) {
537 // Nothing to do if the user interrupts.
540 projectsList.refresh(true);
541 if (getValidProjects().length < selectedProjects.length) {
542 setMessage(UIText.WizardProjectsImportPage_projectsInWorkspace,
543 WARNING);
544 } else {
545 setMessage(UIText.WizardProjectsImportPage_ImportProjectsDescription);
547 enableSelectAllButtons();
548 setPageComplete(checkedItems.size() > 0);
551 private void enableSelectAllButtons() {
552 if (projectsList.getTree().getItemCount()>0){
553 selectAll.setEnabled(true);
554 deselectAll.setEnabled(true);
555 } else {
556 selectAll.setEnabled(false);
557 deselectAll.setEnabled(false);
562 * Collect the list of .project files that are under directory into files.
564 * @param files
565 * @param directory
566 * @param directoriesVisited
567 * Set of canonical paths of directories, used as recursion guard
568 * @param monitor
569 * The monitor to report to
570 * @return boolean <code>true</code> if the operation was completed.
572 private boolean collectProjectFilesFromDirectory(Collection<File> files,
573 File directory, Set<String> directoriesVisited,
574 IProgressMonitor monitor) {
576 if (monitor.isCanceled()) {
577 return false;
579 monitor.subTask(NLS.bind(
580 UIText.WizardProjectsImportPage_CheckingMessage, directory
581 .getPath()));
582 File[] contents = directory.listFiles();
583 if (contents == null)
584 return false;
586 // Initialize recursion guard for recursive symbolic links
587 if (directoriesVisited == null) {
588 directoriesVisited = new HashSet<String>();
589 try {
590 directoriesVisited.add(directory.getCanonicalPath());
591 } catch (IOException exception) {
592 StatusManager.getManager().handle(
593 StatusUtil.newStatus(IStatus.ERROR, exception
594 .getLocalizedMessage(), exception));
598 // first look for project description files
599 final String dotProject = IProjectDescription.DESCRIPTION_FILE_NAME;
600 for (int i = 0; i < contents.length; i++) {
601 File file = contents[i];
602 if (file.isFile() && file.getName().equals(dotProject)) {
603 files.add(file);
604 // don't search sub-directories since we can't have nested
605 // projects
606 return true;
609 // no project description found, so recurse into sub-directories
610 for (int i = 0; i < contents.length; i++) {
611 if (contents[i].isDirectory()) {
612 if (!contents[i].getName().equals(METADATA_FOLDER)) {
613 try {
614 String canonicalPath = contents[i].getCanonicalPath();
615 if (!directoriesVisited.add(canonicalPath)) {
616 // already been here --> do not recurse
617 continue;
619 } catch (IOException exception) {
620 StatusManager.getManager().handle(
621 StatusUtil.newStatus(IStatus.ERROR, exception
622 .getLocalizedMessage(), exception));
625 collectProjectFilesFromDirectory(files, contents[i],
626 directoriesVisited, monitor);
630 return true;
634 * Create the selected projects
636 * @return boolean <code>true</code> if all project creations were
637 * successful.
639 boolean createProjects() {
640 final Object[] selected = checkedItems.toArray();
641 WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
642 protected void execute(IProgressMonitor monitor)
643 throws InvocationTargetException, InterruptedException {
644 try {
645 monitor.beginTask("", selected.length); //$NON-NLS-1$
646 if (monitor.isCanceled()) {
647 throw new OperationCanceledException();
649 for (int i = 0; i < selected.length; i++) {
650 createExistingProject((ProjectRecord) selected[i],
651 new SubProgressMonitor(monitor, 1));
653 } finally {
654 monitor.done();
658 // run the new project creation operation
659 try {
660 getContainer().run(true, true, op);
661 } catch (InterruptedException e) {
662 return false;
663 } catch (InvocationTargetException e) {
664 // one of the steps resulted in a core exception
665 Throwable t = e.getTargetException();
666 String message = UIText.WizardProjectImportPage_errorMessage;
667 IStatus status;
668 if (t instanceof CoreException) {
669 status = ((CoreException) t).getStatus();
670 } else {
671 status = new Status(IStatus.ERROR,
672 IDEWorkbenchPlugin.IDE_WORKBENCH, 1, message, t);
674 Activator.logError(message, t);
675 ErrorDialog.openError(getShell(), message, null, status);
676 return false;
678 return true;
682 * Create the project described in record. If it is successful return true.
684 * @param record
685 * @param monitor
686 * @return boolean <code>true</code> if successful
687 * @throws InvocationTargetException
688 * @throws InterruptedException
690 private boolean createExistingProject(final ProjectRecord record,
691 IProgressMonitor monitor) throws InvocationTargetException,
692 InterruptedException {
693 String projectName = record.getProjectName();
694 final IWorkspace workspace = ResourcesPlugin.getWorkspace();
695 final IProject project = workspace.getRoot().getProject(projectName);
696 if (record.description == null) {
697 // error case
698 record.description = workspace.newProjectDescription(projectName);
699 IPath locationPath = new Path(record.projectSystemFile
700 .getAbsolutePath());
702 // If it is under the root use the default location
703 if (Platform.getLocation().isPrefixOf(locationPath)) {
704 record.description.setLocation(null);
705 } else {
706 record.description.setLocation(locationPath);
708 } else {
709 record.description.setName(projectName);
712 try {
713 monitor.beginTask(
714 UIText.WizardProjectsImportPage_CreateProjectsTask, 100);
715 project.create(record.description, new SubProgressMonitor(monitor,
716 30));
717 int openTicks = share ? 50 : 70;
718 project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
719 monitor, openTicks));
720 if (share) {
721 ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
722 project, gitRepositoryDir);
723 connectProviderOperation
724 .run(new SubProgressMonitor(monitor, 20));
726 } catch (CoreException e) {
727 throw new InvocationTargetException(e);
728 } finally {
729 monitor.done();
732 return true;
736 * Method used for test suite.
738 * @return CheckboxTreeViewer the viewer containing all the projects found
740 public TreeViewer getProjectsList() {
741 return projectsList;
745 * Retrieve all the projects in the current workspace.
747 * @return IProject[] array of IProject in the current workspace
749 private IProject[] getProjectsInWorkspace() {
750 if (wsProjects == null) {
751 wsProjects = IDEWorkbenchPlugin.getPluginWorkspace().getRoot()
752 .getProjects();
754 return wsProjects;
758 * Get the array of valid project records that can be imported from the
759 * source workspace or archive, selected by the user. If a project with the
760 * same name exists in both the source workspace and the current workspace,
761 * it will not appear in the list of projects to import and thus cannot be
762 * selected for import.
764 * Method declared public for test suite.
766 * @return ProjectRecord[] array of projects that can be imported into the
767 * workspace
769 public ProjectRecord[] getValidProjects() {
770 List<ProjectRecord> validProjects = new ArrayList<ProjectRecord>();
771 for (int i = 0; i < selectedProjects.length; i++) {
772 if (!isProjectInWorkspace(selectedProjects[i].getProjectName())) {
773 validProjects.add(selectedProjects[i]);
776 return validProjects.toArray(new ProjectRecord[validProjects.size()]);
780 * Determine if the project with the given name is in the current workspace.
782 * @param projectName
783 * String the project name to check
784 * @return boolean true if the project with the given name is in this
785 * workspace
787 private boolean isProjectInWorkspace(String projectName) {
788 if (projectName == null) {
789 return false;
791 IProject[] workspaceProjects = getProjectsInWorkspace();
792 for (int i = 0; i < workspaceProjects.length; i++) {
793 if (projectName.equals(workspaceProjects[i].getName())) {
794 return true;
797 return false;