/*
* Copyright 2011-2017 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and
* limitations under the License.
*/
package com.amazonaws.eclipse.core.egit.ui;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.egit.ui.Activator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
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.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.StringUtils;
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.PlatformUI;
import com.amazonaws.eclipse.core.egit.RepositorySelection;
import com.amazonaws.eclipse.core.egit.UIText;
/**
* Wizard page that allows the user entering the location of a repository to be
* cloned.
*/
@SuppressWarnings("restriction")
public class CloneDestinationPage extends WizardPage {
private final List<Ref> availableRefs = new ArrayList<Ref>();
private RepositorySelection validatedRepoSelection;
private List<Ref> validatedSelectedBranches;
private Ref validatedHEAD;
private ComboViewer initialBranch;
private Text directoryText;
private Text remoteText;
private Button cloneSubmodulesButton;
private String helpContext = null;
private File clonedDestination;
private Ref clonedInitialBranch;
private String clonedRemote;
public CloneDestinationPage() {
super(CloneDestinationPage.class.getName());
setTitle(UIText.CloneDestinationPage_title);
}
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);
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();
}
public void setSelection(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(
RepositorySelection repositorySelection, List<Ref> branches,
Ref head) {
if (!repositorySelection.equals(validatedRepoSelection)
|| !branches.equals(validatedSelectedBranches)
|| !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() {
public void modifyText(final ModifyEvent e) {
checkPage();
}
});
final Button b = new Button(p, SWT.PUSH);
b.setText(UIText.CloneDestinationPage_browseButton);
b.addSelectionListener(new SelectionAdapter() {
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() {
public void modifyText(ModifyEvent e) {
checkPage();
}
});
}
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 clone submodules, false otherwise
*/
public boolean isCloneSubmodules() {
return cloneSubmodulesButton != null
&& cloneSubmodulesButton.getSelection();
}
/**
* @return location the user wants to store this repository.
*/
public File getDestinationFile() {
return 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();
if (dstpath.length() == 0) {
setErrorMessage(UIText.CloneDestinationPage_errorDirectoryRequired);
setPageComplete(false);
return;
}
final File absoluteFile = new File(dstpath).getAbsoluteFile();
if (!isEmptyDir(absoluteFile)) {
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);
}
public void saveSettingsForClonedRepo() {
clonedDestination = getDestinationFile();
clonedInitialBranch = getInitialBranch();
clonedRemote = getRemote();
}
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;
}
private static boolean isEmptyDir(final File dir) {
if (!dir.exists())
return true;
if (!dir.isDirectory())
return false;
return dir.listFiles().length == 0;
}
// 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(RepositorySelection repoSelection, List<Ref> branches, Ref head) {
if (repoSelection.equals(validatedRepoSelection)
&& branches.equals(validatedSelectedBranches)
&& 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 = getDefaultRepositoryDir();
File parentDir;
if (defaultRepoDir.length() > 0)
parentDir = new File(defaultRepoDir);
else
parentDir = ResourcesPlugin.getWorkspace().getRoot()
.getRawLocation().toFile();
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();
}
/**
* @return The default repository directory as configured in the preferences, with variables
* substituted. Root directory of current workspace if there was an error during substitution.
*/
public static String getDefaultRepositoryDir() {
// If the EGit version is prior to 4.1, return the setting from the legacy code path.
String dir = Activator.getDefault().getPreferenceStore().getString("default_repository_dir");
if (StringUtils.isEmptyOrNull(dir)) {
// If the EGit version is after 4.1, read the preference setting from Core plugin.
IEclipsePreferences p = InstanceScope.INSTANCE.getNode(org.eclipse.egit.core.Activator.getPluginId());
dir = p.get("core_defaultRepositoryDir", new File(FS.DETECTED.userHome(), "git").getPath());
}
IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
try {
return manager.performStringSubstitution(dir);
} catch (CoreException e) {
return ResourcesPlugin.getWorkspace().getRoot().getRawLocation().toOSString();
}
}
}