/******************************************************************************* * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> * Copyright (C) 2013, Robin Stocker <robin@nibor.org> * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch> * * 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 *******************************************************************************/ package org.eclipse.egit.ui.internal.clone; import java.io.File; import java.util.ArrayList; import java.util.List; import org.eclipse.egit.core.RepositoryUtil; import org.eclipse.egit.core.internal.util.RepositoryPathChecker; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.UIPreferences; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.components.RepositorySelection; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FileUtils; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.WorkingSetGroup; /** * Wizard page that allows the user entering the location of a repository to be * cloned. */ public class CloneDestinationPage extends WizardPage { private final List<Ref> availableRefs = new ArrayList<>(); private RepositorySelection validatedRepoSelection; private List<Ref> validatedSelectedBranches; private Ref validatedHEAD; private boolean showProjectImport; private ComboViewer initialBranch; private Text directoryText; private Text remoteText; private Button importProjectsButton; private Button cloneSubmodulesButton; private WorkingSetGroup workingSetGroup; private String helpContext = null; private File clonedDestination; private Ref clonedInitialBranch; private String clonedRemote; CloneDestinationPage() { super(CloneDestinationPage.class.getName()); setTitle(UIText.CloneDestinationPage_title); } @Override public void createControl(final Composite parent) { final Composite panel = new Composite(parent, SWT.NULL); final GridLayout layout = new GridLayout(); layout.numColumns = 1; panel.setLayout(layout); createDestinationGroup(panel); createConfigGroup(panel); if (showProjectImport) createProjectGroup(panel); Dialog.applyDialogFont(panel); setControl(panel); checkPage(); } @Override public void setVisible(final boolean visible) { if (visible) if (this.availableRefs.isEmpty()) initialBranch.getCombo().setEnabled(false); super.setVisible(visible); if (visible) directoryText.setFocus(); } /** * @param repositorySelection * selection of remote repository made by user * @param availableRefs * all available refs * @param branches * branches selected to be cloned * @param head * HEAD in source repository */ public void setSelection(@NonNull RepositorySelection repositorySelection, List<Ref> availableRefs, List<Ref> branches, Ref head) { this.availableRefs.clear(); this.availableRefs.addAll(availableRefs); checkPreviousPagesSelections(repositorySelection, branches, head); revalidate(repositorySelection, branches, head); } private void checkPreviousPagesSelections( @NonNull RepositorySelection repositorySelection, List<Ref> branches, Ref head) { if (!repositorySelection.equals(validatedRepoSelection) || !branches.equals(validatedSelectedBranches) || (head != null && !head.equals(validatedHEAD))) { setPageComplete(false); } else { checkPage(); } } private void createDestinationGroup(final Composite parent) { final Group g = createGroup(parent, UIText.CloneDestinationPage_groupDestination); Label dirLabel = new Label(g, SWT.NONE); dirLabel.setText(UIText.CloneDestinationPage_promptDirectory + ":"); //$NON-NLS-1$ dirLabel .setToolTipText(UIText.CloneDestinationPage_DefaultRepoFolderTooltip); final Composite p = new Composite(g, SWT.NONE); final GridLayout grid = new GridLayout(); grid.numColumns = 2; p.setLayout(grid); p.setLayoutData(createFieldGridData()); directoryText = new Text(p, SWT.BORDER); directoryText.setLayoutData(createFieldGridData()); directoryText.addModifyListener(new ModifyListener() { @Override public void modifyText(final ModifyEvent e) { checkPage(); } }); final Button b = new Button(p, SWT.PUSH); b.setText(UIText.CloneDestinationPage_browseButton); b.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final FileDialog d; d = new FileDialog(getShell(), SWT.APPLICATION_MODAL | SWT.SAVE); if (directoryText.getText().length() > 0) { final File file = new File(directoryText.getText()) .getAbsoluteFile(); d.setFilterPath(file.getParent()); d.setFileName(file.getName()); } final String r = d.open(); if (r != null) directoryText.setText(r); } }); newLabel(g, UIText.CloneDestinationPage_promptInitialBranch + ":"); //$NON-NLS-1$ initialBranch = new ComboViewer(g, SWT.DROP_DOWN | SWT.READ_ONLY); initialBranch.getCombo().setLayoutData(createFieldGridData()); initialBranch.getCombo().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { checkPage(); } }); initialBranch.setContentProvider(ArrayContentProvider.getInstance()); initialBranch.setLabelProvider(new LabelProvider(){ @Override public String getText(Object element) { if (((Ref)element).getName().startsWith(Constants.R_HEADS)) return ((Ref)element).getName().substring(Constants.R_HEADS.length()); return ((Ref)element).getName(); } }); cloneSubmodulesButton = new Button(g, SWT.CHECK); cloneSubmodulesButton .setText(UIText.CloneDestinationPage_cloneSubmodulesButton); GridDataFactory.swtDefaults().span(2, 1).applyTo(cloneSubmodulesButton); } private void createConfigGroup(final Composite parent) { final Group g = createGroup(parent, UIText.CloneDestinationPage_groupConfiguration); newLabel(g, UIText.CloneDestinationPage_promptRemoteName + ":"); //$NON-NLS-1$ remoteText = new Text(g, SWT.BORDER); remoteText.setText(Constants.DEFAULT_REMOTE_NAME); remoteText.setLayoutData(createFieldGridData()); remoteText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { checkPage(); } }); } private void createProjectGroup(final Composite parent) { final Group group = createGroup(parent, UIText.CloneDestinationPage_groupProjects); GridLayoutFactory.swtDefaults().applyTo(group); importProjectsButton = new Button(group, SWT.CHECK); importProjectsButton.setText(UIText.CloneDestinationPage_importButton); importProjectsButton.setSelection(Activator.getDefault() .getPreferenceStore() .getBoolean(UIPreferences.CLONE_WIZARD_IMPORT_PROJECTS)); importProjectsButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Activator .getDefault() .getPreferenceStore() .setValue(UIPreferences.CLONE_WIZARD_IMPORT_PROJECTS, importProjectsButton.getSelection()); } }); // TODO: replace hardcoded ids once bug 245106 is fixed String[] workingSetTypes = new String[] { "org.eclipse.ui.resourceWorkingSetPage", //$NON-NLS-1$ "org.eclipse.jdt.ui.JavaWorkingSetPage" //$NON-NLS-1$ }; workingSetGroup = new WorkingSetGroup(group, null, workingSetTypes); } private static Group createGroup(final Composite parent, final String text) { final Group g = new Group(parent, SWT.NONE); final GridLayout layout = new GridLayout(); layout.numColumns = 2; g.setLayout(layout); g.setText(text); final GridData gd = new GridData(); gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = SWT.FILL; g.setLayoutData(gd); return g; } private static void newLabel(final Group g, final String text) { new Label(g, SWT.NULL).setText(text); } private static GridData createFieldGridData() { return new GridData(SWT.FILL, SWT.DEFAULT, true, false); } /** * @return true to import projects, false otherwise */ public boolean isImportProjects() { return importProjectsButton != null && importProjectsButton.getSelection(); } /** * @return true to clone submodules, false otherwise */ public boolean isCloneSubmodules() { return cloneSubmodulesButton != null && cloneSubmodulesButton.getSelection(); } /** * @return selected working sets */ public IWorkingSet[] getWorkingSets() { if (workingSetGroup == null) return new IWorkingSet[0]; return workingSetGroup.getSelectedWorkingSets(); } /** * @return location the user wants to store this repository. */ public File getDestinationFile() { return FileUtils.canonicalize(new File(directoryText.getText())); } /** * @return initial branch selected (includes refs/heads prefix). */ public Ref getInitialBranch() { IStructuredSelection selection = (IStructuredSelection)initialBranch.getSelection(); return (Ref)selection.getFirstElement(); } /** * @return remote name */ public String getRemote() { return remoteText.getText().trim(); } /** * Set the ID for context sensitive help * * @param id * help context */ public void setHelpContext(String id) { helpContext = id; } @Override public void performHelp() { PlatformUI.getWorkbench().getHelpSystem().displayHelp(helpContext); } /** * Check internal state for page completion status. */ private void checkPage() { if (!cloneSettingsChanged()) { setErrorMessage(null); setPageComplete(true); return; } final String dstpath = directoryText.getText(); RepositoryPathChecker checker = new RepositoryPathChecker(); if (!checker.check(dstpath)) { setErrorMessage(checker.getErrorMessage()); setPageComplete(false); return; } final File absoluteFile = new File(dstpath).getAbsoluteFile(); if (checker.hasContent()) { setErrorMessage(NLS.bind( UIText.CloneDestinationPage_errorNotEmptyDir, absoluteFile .getPath())); setPageComplete(false); return; } if (!canCreateSubdir(absoluteFile.getParentFile())) { setErrorMessage(NLS.bind(UIText.GitCloneWizard_errorCannotCreate, absoluteFile.getPath())); setPageComplete(false); return; } if (!availableRefs.isEmpty() && initialBranch.getCombo().getSelectionIndex() < 0) { setErrorMessage(UIText.CloneDestinationPage_errorInitialBranchRequired); setPageComplete(false); return; } String remoteName = getRemote(); if (remoteName.length() == 0) { setErrorMessage(UIText.CloneDestinationPage_errorRemoteNameRequired); setPageComplete(false); return; } if (!Repository.isValidRefName(Constants.R_REMOTES + remoteName)) { setErrorMessage(NLS.bind( UIText.CloneDestinationPage_errorInvalidRemoteName, remoteName)); setPageComplete(false); return; } setErrorMessage(null); setPageComplete(true); } void saveSettingsForClonedRepo() { clonedDestination = getDestinationFile(); clonedInitialBranch = getInitialBranch(); clonedRemote = getRemote(); } /** * @return whether user updated clone settings * @since 4.0.0 */ public boolean cloneSettingsChanged() { boolean cloneSettingsChanged = false; if (clonedDestination == null || !clonedDestination.equals(getDestinationFile()) || clonedInitialBranch == null || !clonedInitialBranch.equals(getInitialBranch()) || clonedRemote == null || !clonedRemote.equals(getRemote())) cloneSettingsChanged = true; return cloneSettingsChanged; } // this is actually just an optimistic heuristic - should be named // isThereHopeThatCanCreateSubdir() as probably there is no 100% reliable // way to check that in Java for Windows private static boolean canCreateSubdir(final File parent) { if (parent == null) return true; if (parent.exists()) return parent.isDirectory() && parent.canWrite(); return canCreateSubdir(parent.getParentFile()); } private void revalidate(@NonNull RepositorySelection repoSelection, List<Ref> branches, Ref head) { if (repoSelection.equals(validatedRepoSelection) && branches.equals(validatedSelectedBranches) && head != null && head.equals(validatedHEAD)) { checkPage(); return; } if (!repoSelection.equals(validatedRepoSelection)) { validatedRepoSelection = repoSelection; // update repo-related selection only if it changed final String n = validatedRepoSelection.getURI().getHumanishName(); setDescription(NLS.bind(UIText.CloneDestinationPage_description, n)); String defaultRepoDir = RepositoryUtil.getDefaultRepositoryDir(); File parentDir = new File(defaultRepoDir); directoryText.setText(new File(parentDir, n).getAbsolutePath()); } validatedSelectedBranches = branches; validatedHEAD = head; initialBranch.setInput(branches); if (head != null && branches.contains(head)) initialBranch.setSelection(new StructuredSelection(head)); else if (branches.size() > 0) initialBranch .setSelection(new StructuredSelection(branches.get(0))); checkPage(); } /** * Set whether to show project import options * * @param show * @return this wizard page */ public CloneDestinationPage setShowProjectImport(boolean show) { showProjectImport = show; return this; } }