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 *******************************************************************************/
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
;
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
;
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
{
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
;
93 File projectSystemFile
;
101 IProjectDescription description
;
104 * Create a record for a project based on the info in the file.
108 ProjectRecord(File file
) {
109 projectSystemFile
= file
;
115 * The parent folder of the .project file
117 * The number of levels deep in the provider the file is
119 ProjectRecord(Object parent
, int level
) {
120 this.parent
= parent
;
126 * Set the name of the project based on the projectFile.
128 private void setProjectName() {
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
);
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
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)
164 return path
.removeLastSegments(2).toFile().equals(
165 Platform
.getLocation().toFile());
169 * Get the name of the project
173 public String
getProjectName() {
178 * Gets the label to be used when rendering this project record in the
181 * @return String the label
184 public String
getProjectLabel() {
185 if (description
== null)
188 String path
= projectSystemFile
== null ? structureProvider
189 .getLabel(parent
) : projectSystemFile
.getParent();
191 return NLS
.bind(UIText
.WizardProjectsImportPage_projectLabel
,
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.
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.
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() {
293 public boolean isElementVisible(Viewer viewer
, Object element
) {
295 if (checkedItems
.contains(element
)) {
299 return super.isElementVisible(viewer
, element
);
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
308 Display
.getDefault().asyncExec(new Runnable() {
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
) {
332 public Object
[] getElements(Object inputElement
) {
333 return getValidProjects();
336 public boolean hasChildren(Object element
) {
340 public Object
getParent(Object element
) {
344 public void dispose() {
348 public void inputChanged(Viewer viewer
, Object oldInput
,
354 projectsList
.getTree().addMouseListener(new MouseAdapter() {
356 public void mouseUp(MouseEvent e
) {
357 if (e
.widget
instanceof Tree
) {
358 TreeItem item
= ((Tree
) e
.widget
).getItem(new Point(e
.x
,
361 if (item
.getChecked())
362 checkedItems
.add(item
.getData());
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);
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.
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
466 public void setGitDir(File gitDir
) {
467 this.gitRepositoryDir
= gitDir
;
471 * Update the list of projects based on path. This will not check any
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);
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
496 lastModified
= modified
;
499 getContainer().run(true, true, new IRunnableWithProgress() {
501 public void run(IProgressMonitor monitor
) {
504 UIText
.WizardProjectsImportPage_SearchingMessage
,
506 selectedProjects
= new ProjectRecord
[0];
507 Collection
<File
> files
= new ArrayList
<File
>();
509 if (directory
.isDirectory()) {
511 if (!collectProjectFilesFromDirectory(files
, directory
,
515 Iterator
<File
> filesIterator
= files
.iterator();
516 selectedProjects
= new ProjectRecord
[files
.size()];
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
]);
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
,
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);
556 selectAll
.setEnabled(false);
557 deselectAll
.setEnabled(false);
562 * Collect the list of .project files that are under directory into files.
566 * @param directoriesVisited
567 * Set of canonical paths of directories, used as recursion guard
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()) {
579 monitor
.subTask(NLS
.bind(
580 UIText
.WizardProjectsImportPage_CheckingMessage
, directory
582 File
[] contents
= directory
.listFiles();
583 if (contents
== null)
586 // Initialize recursion guard for recursive symbolic links
587 if (directoriesVisited
== null) {
588 directoriesVisited
= new HashSet
<String
>();
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
)) {
604 // don't search sub-directories since we can't have nested
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
)) {
614 String canonicalPath
= contents
[i
].getCanonicalPath();
615 if (!directoriesVisited
.add(canonicalPath
)) {
616 // already been here --> do not recurse
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
);
634 * Create the selected projects
636 * @return boolean <code>true</code> if all project creations were
639 boolean createProjects() {
640 final Object
[] selected
= checkedItems
.toArray();
641 WorkspaceModifyOperation op
= new WorkspaceModifyOperation() {
642 protected void execute(IProgressMonitor monitor
)
643 throws InvocationTargetException
, InterruptedException
{
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));
658 // run the new project creation operation
660 getContainer().run(true, true, op
);
661 } catch (InterruptedException e
) {
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
;
668 if (t
instanceof CoreException
) {
669 status
= ((CoreException
) t
).getStatus();
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
);
682 * Create the project described in record. If it is successful return true.
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) {
698 record
.description
= workspace
.newProjectDescription(projectName
);
699 IPath locationPath
= new Path(record
.projectSystemFile
702 // If it is under the root use the default location
703 if (Platform
.getLocation().isPrefixOf(locationPath
)) {
704 record
.description
.setLocation(null);
706 record
.description
.setLocation(locationPath
);
709 record
.description
.setName(projectName
);
714 UIText
.WizardProjectsImportPage_CreateProjectsTask
, 100);
715 project
.create(record
.description
, new SubProgressMonitor(monitor
,
717 int openTicks
= share ?
50 : 70;
718 project
.open(IResource
.BACKGROUND_REFRESH
, new SubProgressMonitor(
719 monitor
, openTicks
));
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
);
736 * Method used for test suite.
738 * @return CheckboxTreeViewer the viewer containing all the projects found
740 public TreeViewer
getProjectsList() {
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()
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
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.
783 * String the project name to check
784 * @return boolean true if the project with the given name is in this
787 private boolean isProjectInWorkspace(String projectName
) {
788 if (projectName
== null) {
791 IProject
[] workspaceProjects
= getProjectsInWorkspace();
792 for (int i
= 0; i
< workspaceProjects
.length
; i
++) {
793 if (projectName
.equals(workspaceProjects
[i
].getName())) {