blob: f57f830f1fc4ae7aed1367e1219bcdab26c46297 [file] [log] [blame]
/*******************************************************************************
* 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) 2010, Benjamin Muskalla <bmuskalla@eclipsesource.com>
* Copyright (C) 2012, Stefan Lay <stefan.lay@sap.com>
* Copyright (C) 2016, 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.egit.ui.internal.clone;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.util.ProjectUtil;
import org.eclipse.egit.core.op.CloneOperation;
import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
import org.eclipse.egit.core.op.ConfigureFetchAfterCloneTask;
import org.eclipse.egit.core.op.ConfigureGerritAfterCloneTask;
import org.eclipse.egit.core.op.ConfigurePushAfterCloneTask;
import org.eclipse.egit.core.op.SetRepositoryConfigPropertyTask;
import org.eclipse.egit.core.securestorage.UserPasswordCredentials;
import org.eclipse.egit.core.settings.GitSettings;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.internal.SecureStoreUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.clone.GitCloneSourceProviderExtension.CloneSourceProvider;
import org.eclipse.egit.ui.internal.components.RepositorySelection;
import org.eclipse.egit.ui.internal.credentials.EGitCredentialsProvider;
import org.eclipse.egit.ui.internal.groups.RepositoryGroup;
import org.eclipse.egit.ui.internal.groups.RepositoryGroups;
import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo;
import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo.PushInfo;
import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo.RepositoryConfigProperty;
import org.eclipse.egit.ui.internal.provisional.wizards.IRepositorySearchResult;
import org.eclipse.egit.ui.internal.provisional.wizards.NoRepositoryInfoException;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IWorkingSet;
/**
* Implements the basic functionality of a clone wizard
*/
public abstract class AbstractGitCloneWizard extends Wizard {
/**
* a page for branch selection
*/
protected SourceBranchPage validSource;
/**
* a page for selection of the clone destination
*/
protected CloneDestinationPage cloneDestination;
/**
* the path where a clone has been created in
*/
protected String alreadyClonedInto;
/**
* whether the clone operation is done later on by the caller of the wizard
*/
protected boolean callerRunsCloneOperation;
/**
* the result which was found when the last search was done
*/
protected IRepositorySearchResult currentSearchResult;
private CloneOperation cloneOperation;
private RepositoryGroup group;
/**
* Construct the clone wizard based on given repository search result. If
* the search result is an instance of org.eclipse.jface.wizard.WizardPage,
* then the page is shown in the wizard before the repository info is read.
* The repository location page that allows the repository info to be
* provided by different search providers is not shown.
*
* @param searchResult
* the search result to initialize the clone wizard with.
*/
public AbstractGitCloneWizard(IRepositorySearchResult searchResult) {
this();
this.currentSearchResult = searchResult;
}
/**
* Construct the clone wizard with a repository location page that allows
* the repository info to be provided by different search providers.
*/
public AbstractGitCloneWizard() {
setNeedsProgressMonitor(true);
validSource = new SourceBranchPage() {
@Override
public void setVisible(boolean visible) {
RepositorySelection selection = getRepositorySelection();
if (selection != null && visible) {
setSelection(selection);
setCredentials(getCredentials());
}
super.setVisible(visible);
}
};
cloneDestination = new CloneDestinationPage() {
@Override
public void setVisible(boolean visible) {
RepositorySelection selection = getRepositorySelection();
if (selection != null && visible) {
setSelection(selection,
validSource.getAvailableBranches(),
validSource.getSelectedBranches(),
validSource.getHEAD());
}
super.setVisible(visible);
}
};
}
/**
* subclasses may add pages to the Wizard which will be shown before the clone step
*/
protected abstract void addPreClonePages();
/**
* subclasses may add pages to the Wizard which will be shown after the clone step
*/
protected abstract void addPostClonePages();
@Override
final public void addPages() {
if (hasSearchResult())
addRepositorySearchPage();
else
addRepositoryLocationPage();
addPreClonePages();
addPage(validSource);
addPage(cloneDestination);
addPostClonePages();
}
/**
* @return if the search result is set
*/
protected boolean hasSearchResult() {
return currentSearchResult != null;
}
private void addRepositorySearchPage() {
if (currentSearchResult instanceof WizardPage)
addPage((WizardPage)currentSearchResult);
}
private void addRepositoryLocationPage() {
List<CloneSourceProvider> cloneSourceProviders = getCloneSourceProviders();
if (hasSingleCloneSourceProviderWithFixedLocation(cloneSourceProviders))
try {
addPage(cloneSourceProviders.get(0).getRepositorySearchPage());
} catch (CoreException e) {
Activator.logError(e.getLocalizedMessage(), e);
}
else
addPage(new RepositoryLocationPage(cloneSourceProviders));
}
private boolean hasSingleCloneSourceProviderWithFixedLocation(
List<CloneSourceProvider> cloneSourceProviders) {
return cloneSourceProviders.size() == 1 && cloneSourceProviders.get(0).hasFixLocation();
}
/**
* @return a list of CloneSourceProviders, may be extended by a subclass
*/
protected List<CloneSourceProvider> getCloneSourceProviders() {
return GitCloneSourceProviderExtension.getCloneSourceProvider();
}
/**
* Do the clone using data which were collected on the pages
* {@code validSource} and {@code cloneDestination}
*
* @param gitRepositoryInfo
* @return if clone was successful
* @throws URISyntaxException
*/
protected boolean performClone(GitRepositoryInfo gitRepositoryInfo) throws URISyntaxException {
URIish uri = new URIish(gitRepositoryInfo.getCloneUri());
UserPasswordCredentials credentials = gitRepositoryInfo.getCredentials();
setWindowTitle(NLS.bind(UIText.GitCloneWizard_jobName, uri.toString()));
final boolean allSelected;
final Collection<Ref> selectedBranches;
if (validSource.isSourceRepoEmpty()) {
// fetch all branches of empty repo
allSelected = true;
selectedBranches = Collections.emptyList();
} else {
allSelected = validSource.isAllSelected();
selectedBranches = validSource.getSelectedBranches();
}
final File workdir = cloneDestination.getDestinationFile();
final Ref ref = cloneDestination.getInitialBranch();
final String remoteName = cloneDestination.getRemote();
boolean created = workdir.exists();
if (!created)
created = workdir.mkdirs();
if (!created || !workdir.isDirectory()) {
final String errorMessage = NLS.bind(
UIText.GitCloneWizard_errorCannotCreate, workdir.getPath());
ErrorDialog.openError(getShell(), getWindowTitle(),
UIText.GitCloneWizard_failed, new Status(IStatus.ERROR,
Activator.getPluginId(), 0, errorMessage, null));
// let's give user a chance to fix this minor problem
return false;
}
int timeout = GitSettings.getRemoteConnectionTimeout();
final CloneOperation op = new CloneOperation(uri, allSelected,
selectedBranches, workdir, ref != null ? ref.getName() : null,
remoteName, timeout);
CredentialsProvider credentialsProvider = null;
if (credentials != null) {
credentialsProvider = new EGitCredentialsProvider(
credentials.getUser(), credentials.getPassword());
} else {
credentialsProvider = new EGitCredentialsProvider();
}
op.setCredentialsProvider(credentialsProvider);
op.setCloneSubmodules(cloneDestination.isCloneSubmodules());
op.setTagOption(validSource.getTagOption());
rememberHttpHost(op, uri);
configureFetchSpec(op, gitRepositoryInfo, remoteName);
configurePush(op, gitRepositoryInfo, remoteName);
configureRepositoryConfig(op, gitRepositoryInfo);
configureGerrit(op, gitRepositoryInfo, credentialsProvider, remoteName,
timeout);
if (cloneDestination.isImportProjects()) {
final IWorkingSet[] sets = cloneDestination.getWorkingSets();
op.addPostCloneTask(new PostCloneTask() {
@Override
public void execute(Repository repository,
IProgressMonitor monitor) throws CoreException {
importProjects(repository, sets);
}
});
}
alreadyClonedInto = workdir.getPath();
if (!callerRunsCloneOperation)
runAsJob(uri, op, gitRepositoryInfo);
else
cloneOperation = op;
return true;
}
@Override
public IWizardPage getNextPage(IWizardPage page) {
if (page instanceof IRepositorySearchResult) {
currentSearchResult = (IRepositorySearchResult)page;
return validSource;
}
return super.getNextPage(page);
}
/**
* @return the repository selected by the user or {@code null} if an error
* occurred
*/
@Nullable
protected RepositorySelection getRepositorySelection() {
try {
return (new RepositorySelection(new URIish(currentSearchResult.getGitRepositoryInfo().getCloneUri()), null));
} catch (URISyntaxException e) {
Activator.error(UIText.GitImportWizard_errorParsingURI, e);
return null;
} catch (NoRepositoryInfoException e) {
Activator.error(UIText.GitImportWizard_noRepositoryInfo, e);
return null;
} catch (Exception e) {
Activator.error(e.getMessage(), e);
return null;
}
}
/**
* @return the credentials
*/
protected UserPasswordCredentials getCredentials() {
try {
return currentSearchResult.getGitRepositoryInfo().getCredentials();
} catch (NoRepositoryInfoException e) {
Activator.error(UIText.GitImportWizard_noRepositoryInfo, e);
return null;
} catch (Exception e) {
Activator.error(e.getMessage(), e);
return null;
}
}
private void rememberHttpHost(CloneOperation op, URIish uri) {
String scheme = uri.getScheme();
if (scheme != null && scheme.toLowerCase().startsWith("http")) { //$NON-NLS-1$
String host = uri.getHost();
if (host != null) {
op.addPostCloneTask(new RememberHostTask(host));
}
}
}
private void configureFetchSpec(CloneOperation op,
GitRepositoryInfo gitRepositoryInfo, String remoteName) {
for (String fetchRefSpec : gitRepositoryInfo.getFetchRefSpecs())
op.addPostCloneTask(new ConfigureFetchAfterCloneTask(remoteName, fetchRefSpec));
}
private void configurePush(CloneOperation op,
GitRepositoryInfo gitRepositoryInfo, String remoteName) {
for (PushInfo pushInfo : gitRepositoryInfo.getPushInfos())
try {
URIish uri = pushInfo.getPushUri() != null ? new URIish(
pushInfo.getPushUri()) : null;
ConfigurePushAfterCloneTask task = new ConfigurePushAfterCloneTask(
remoteName, pushInfo.getPushRefSpec(), uri);
op.addPostCloneTask(task);
} catch (URISyntaxException e) {
Activator.handleError(UIText.GitCloneWizard_failed, e, true);
}
}
private void configureRepositoryConfig(CloneOperation op, GitRepositoryInfo gitRepositoryInfo) {
for (RepositoryConfigProperty p : gitRepositoryInfo.getRepositoryConfigProperties()) {
SetRepositoryConfigPropertyTask task = new SetRepositoryConfigPropertyTask(
p.getSection(), p.getSubsection(), p.getName(),
p.getValue());
op.addPostCloneTask(task);
}
}
private void configureGerrit(CloneOperation op,
GitRepositoryInfo gitRepositoryInfo,
CredentialsProvider credentialsProvider, String remoteName,
int timeout) {
ConfigureGerritAfterCloneTask task = new ConfigureGerritAfterCloneTask(
gitRepositoryInfo.getCloneUri(), remoteName,
credentialsProvider, timeout);
op.addPostCloneTask(task);
}
private void importProjects(final Repository repository,
final IWorkingSet[] sets) {
String repoName = Activator.getDefault().getRepositoryUtil()
.getRepositoryName(repository);
Job importJob = new WorkspaceJob(MessageFormat.format(
UIText.GitCloneWizard_jobImportProjects, repoName)) {
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) {
SubMonitor progress = SubMonitor.convert(monitor, 2);
List<File> files = new ArrayList<>();
ProjectUtil.findProjectFiles(files, repository.getWorkTree(),
true, progress.newChild(1));
if (files.isEmpty()) {
return Status.OK_STATUS;
}
Set<ProjectRecord> records = new LinkedHashSet<>();
for (File file : files) {
ProjectRecord record = new ProjectRecord(file);
if (record.getProjectDescription() == null) {
// Ignore invalid .project files
continue;
}
records.add(record);
}
if (records.isEmpty()) {
return Status.OK_STATUS;
}
try {
ProjectUtils.createProjects(records, sets,
progress.newChild(1));
} catch (InvocationTargetException | InterruptedException e) {
Activator.logError(e.getLocalizedMessage(), e);
}
return Status.OK_STATUS;
}
};
importJob.schedule();
}
/**
* @param container
* @param repositoryInfo
*/
public void runCloneOperation(IWizardContainer container, final GitRepositoryInfo repositoryInfo) {
try {
container.run(true, true, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
executeCloneOperation(cloneOperation, repositoryInfo, monitor);
}
});
} catch (InvocationTargetException e) {
Activator.handleError(UIText.GitCloneWizard_failed, e.getCause(),
true);
} catch (InterruptedException e) {
// nothing to do
}
}
private void runAsJob(final URIish uri, final CloneOperation op,
final GitRepositoryInfo repositoryInfo) {
final Job job = new Job(NLS.bind(UIText.GitCloneWizard_jobName,
uri.toString())) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
try {
return executeCloneOperation(op, repositoryInfo, monitor);
} catch (InterruptedException e) {
return Status.CANCEL_STATUS;
} catch (InvocationTargetException e) {
Throwable thr = e.getCause();
return new Status(IStatus.ERROR, Activator.getPluginId(),
0, thr.getMessage(), thr);
}
}
@Override
public boolean belongsTo(Object family) {
if (JobFamilies.CLONE.equals(family))
return true;
return super.belongsTo(family);
}
};
job.setUser(true);
job.schedule();
}
private IStatus executeCloneOperation(final CloneOperation op,
final GitRepositoryInfo repositoryInfo,
final IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
final RepositoryUtil util = Activator.getDefault().getRepositoryUtil();
op.run(monitor);
if (group != null) {
RepositoryGroups.getInstance().addRepositoriesToGroup(group,
Collections.singletonList(op.getGitDir()));
}
util.addConfiguredRepository(op.getGitDir());
try {
if (repositoryInfo.shouldSaveCredentialsInSecureStore())
SecureStoreUtils.storeCredentials(repositoryInfo.getCredentials(),
new URIish(repositoryInfo.getCloneUri()));
} catch (Exception e) {
Activator.error(e.getMessage(), e);
}
return Status.OK_STATUS;
}
/**
* @param newValue
* if true the clone wizard just creates a clone operation. The
* caller has to run this operation using runCloneOperation. If
* false the clone operation is performed using a job.
*/
public void setCallerRunsCloneOperation(boolean newValue) {
callerRunsCloneOperation = newValue;
}
/**
* Sets a {@link RepositoryGroup} to which the new repository will be added.
*
* @param group
* to add the new repository to
*/
public void setRepositoryGroup(RepositoryGroup group) {
this.group = group;
}
}