| /******************************************************************************* |
| * 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; |
| } |
| |
| } |