Update org.apache.commons:commons-compress to 1.25.0
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / clone / AbstractGitCloneWizard.java
blob97beb08c38e626bbdab000490fa62b22ac60a6d7
1 /*******************************************************************************
2 * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
5 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
7 * Copyright (C) 2010, Benjamin Muskalla <bmuskalla@eclipsesource.com>
8 * Copyright (C) 2012, Stefan Lay <stefan.lay@sap.com>
9 * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
11 * All rights reserved. This program and the accompanying materials
12 * are made available under the terms of the Eclipse Public License 2.0
13 * which accompanies this distribution, and is available at
14 * https://www.eclipse.org/legal/epl-2.0/
16 * SPDX-License-Identifier: EPL-2.0
17 *******************************************************************************/
19 package org.eclipse.egit.ui.internal.clone;
21 import java.io.File;
22 import java.lang.reflect.InvocationTargetException;
23 import java.net.URISyntaxException;
24 import java.text.MessageFormat;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Set;
32 import org.eclipse.core.resources.WorkspaceJob;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IProgressMonitor;
35 import org.eclipse.core.runtime.IStatus;
36 import org.eclipse.core.runtime.Status;
37 import org.eclipse.core.runtime.SubMonitor;
38 import org.eclipse.core.runtime.jobs.Job;
39 import org.eclipse.egit.core.RepositoryUtil;
40 import org.eclipse.egit.core.credentials.UserPasswordCredentials;
41 import org.eclipse.egit.core.internal.credentials.EGitCredentialsProvider;
42 import org.eclipse.egit.core.internal.util.ProjectUtil;
43 import org.eclipse.egit.core.op.CloneOperation;
44 import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
45 import org.eclipse.egit.core.op.ConfigureFetchAfterCloneTask;
46 import org.eclipse.egit.core.op.ConfigureGerritAfterCloneTask;
47 import org.eclipse.egit.core.op.ConfigurePushAfterCloneTask;
48 import org.eclipse.egit.core.op.SetRepositoryConfigPropertyTask;
49 import org.eclipse.egit.core.settings.GitSettings;
50 import org.eclipse.egit.ui.Activator;
51 import org.eclipse.egit.ui.JobFamilies;
52 import org.eclipse.egit.ui.internal.KnownHosts;
53 import org.eclipse.egit.ui.internal.SecureStoreUtils;
54 import org.eclipse.egit.ui.internal.UIText;
55 import org.eclipse.egit.ui.internal.clone.GitCloneSourceProviderExtension.CloneSourceProvider;
56 import org.eclipse.egit.ui.internal.components.RepositorySelection;
57 import org.eclipse.egit.ui.internal.groups.RepositoryGroup;
58 import org.eclipse.egit.ui.internal.groups.RepositoryGroups;
59 import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo;
60 import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo.PushInfo;
61 import org.eclipse.egit.ui.internal.provisional.wizards.GitRepositoryInfo.RepositoryConfigProperty;
62 import org.eclipse.egit.ui.internal.provisional.wizards.IRepositorySearchResult;
63 import org.eclipse.egit.ui.internal.provisional.wizards.NoRepositoryInfoException;
64 import org.eclipse.jface.dialogs.ErrorDialog;
65 import org.eclipse.jface.operation.IRunnableWithProgress;
66 import org.eclipse.jface.wizard.IWizardContainer;
67 import org.eclipse.jface.wizard.IWizardPage;
68 import org.eclipse.jface.wizard.Wizard;
69 import org.eclipse.jface.wizard.WizardPage;
70 import org.eclipse.jgit.annotations.Nullable;
71 import org.eclipse.jgit.lib.Ref;
72 import org.eclipse.jgit.lib.Repository;
73 import org.eclipse.jgit.transport.CredentialsProvider;
74 import org.eclipse.jgit.transport.URIish;
75 import org.eclipse.osgi.util.NLS;
76 import org.eclipse.ui.IWorkingSet;
77 import org.eclipse.ui.PlatformUI;
79 /**
80 * Implements the basic functionality of a clone wizard
82 public abstract class AbstractGitCloneWizard extends Wizard {
84 /**
85 * a page for branch selection
87 protected SourceBranchPage validSource;
89 /**
90 * a page for selection of the clone destination
92 protected CloneDestinationPage cloneDestination;
94 /**
95 * the path where a clone has been created in
97 protected String alreadyClonedInto;
99 /**
100 * whether the clone operation is done later on by the caller of the wizard
102 protected boolean callerRunsCloneOperation;
105 * the result which was found when the last search was done
107 protected IRepositorySearchResult currentSearchResult;
109 private CloneOperation cloneOperation;
111 private RepositoryGroup group;
114 * Construct the clone wizard based on given repository search result. If
115 * the search result is an instance of org.eclipse.jface.wizard.WizardPage,
116 * then the page is shown in the wizard before the repository info is read.
117 * The repository location page that allows the repository info to be
118 * provided by different search providers is not shown.
120 * @param searchResult
121 * the search result to initialize the clone wizard with.
123 public AbstractGitCloneWizard(IRepositorySearchResult searchResult) {
124 this();
125 this.currentSearchResult = searchResult;
129 * Construct the clone wizard with a repository location page that allows
130 * the repository info to be provided by different search providers.
132 public AbstractGitCloneWizard() {
133 setNeedsProgressMonitor(true);
134 validSource = new SourceBranchPage() {
136 @Override
137 public void setVisible(boolean visible) {
138 RepositorySelection selection = getRepositorySelection();
139 if (selection != null && visible) {
140 setSelection(selection);
141 setCredentials(getCredentials());
143 super.setVisible(visible);
146 cloneDestination = new CloneDestinationPage() {
147 @Override
148 public void setVisible(boolean visible) {
149 RepositorySelection selection = getRepositorySelection();
150 if (selection != null && visible) {
151 setSelection(selection,
152 validSource.getAvailableBranches(),
153 validSource.getSelectedBranches(),
154 validSource.getHEAD());
156 super.setVisible(visible);
162 * subclasses may add pages to the Wizard which will be shown before the clone step
164 protected abstract void addPreClonePages();
167 * subclasses may add pages to the Wizard which will be shown after the clone step
169 protected abstract void addPostClonePages();
171 @Override
172 final public void addPages() {
173 if (hasSearchResult())
174 addRepositorySearchPage();
175 else
176 addRepositoryLocationPage();
177 addPreClonePages();
178 addPage(validSource);
179 addPage(cloneDestination);
180 addPostClonePages();
184 * @return if the search result is set
186 protected boolean hasSearchResult() {
187 return currentSearchResult != null;
190 private void addRepositorySearchPage() {
191 if (currentSearchResult instanceof WizardPage)
192 addPage((WizardPage)currentSearchResult);
195 private void addRepositoryLocationPage() {
196 List<CloneSourceProvider> cloneSourceProviders = getCloneSourceProviders();
197 if (hasSingleCloneSourceProviderWithFixedLocation(cloneSourceProviders))
198 try {
199 addPage(cloneSourceProviders.get(0).getRepositorySearchPage());
200 } catch (CoreException e) {
201 Activator.logError(e.getLocalizedMessage(), e);
203 else
204 addPage(new RepositoryLocationPage(cloneSourceProviders));
207 private boolean hasSingleCloneSourceProviderWithFixedLocation(
208 List<CloneSourceProvider> cloneSourceProviders) {
209 return cloneSourceProviders.size() == 1 && cloneSourceProviders.get(0).hasFixLocation();
213 * @return a list of CloneSourceProviders, may be extended by a subclass
215 protected List<CloneSourceProvider> getCloneSourceProviders() {
216 return GitCloneSourceProviderExtension.getCloneSourceProvider();
220 * Do the clone using data which were collected on the pages
221 * {@code validSource} and {@code cloneDestination}
223 * @param gitRepositoryInfo
224 * @return if clone was successful
225 * @throws URISyntaxException
227 protected boolean performClone(GitRepositoryInfo gitRepositoryInfo) throws URISyntaxException {
228 URIish uri = new URIish(gitRepositoryInfo.getCloneUri());
229 UserPasswordCredentials credentials = gitRepositoryInfo.getCredentials();
230 setWindowTitle(NLS.bind(UIText.GitCloneWizard_jobName, uri.toString()));
231 final boolean allSelected;
232 final Collection<Ref> selectedBranches;
233 if (validSource.isSourceRepoEmpty()) {
234 // fetch all branches of empty repo
235 allSelected = true;
236 selectedBranches = Collections.emptyList();
237 } else {
238 allSelected = validSource.isAllSelected();
239 selectedBranches = validSource.getSelectedBranches();
241 final File workdir = cloneDestination.getDestinationFile();
242 final Ref ref = cloneDestination.getInitialBranch();
243 final String remoteName = cloneDestination.getRemote();
245 boolean created = workdir.exists();
246 if (!created)
247 created = workdir.mkdirs();
249 if (!created || !workdir.isDirectory()) {
250 final String errorMessage = NLS.bind(
251 UIText.GitCloneWizard_errorCannotCreate, workdir.getPath());
252 ErrorDialog.openError(getShell(), getWindowTitle(),
253 UIText.GitCloneWizard_failed, new Status(IStatus.ERROR,
254 Activator.PLUGIN_ID, errorMessage, null));
255 // let's give user a chance to fix this minor problem
256 return false;
259 int timeout = GitSettings.getRemoteConnectionTimeout();
260 final CloneOperation op = new CloneOperation(uri, allSelected,
261 selectedBranches, workdir, ref != null ? ref.getName() : null,
262 remoteName, timeout);
263 CredentialsProvider credentialsProvider = null;
264 if (credentials != null) {
265 credentialsProvider = new EGitCredentialsProvider(
266 credentials.getUser(), credentials.getPassword());
267 } else {
268 credentialsProvider = new EGitCredentialsProvider();
270 op.setCredentialsProvider(credentialsProvider);
271 op.setCloneSubmodules(cloneDestination.isCloneSubmodules());
272 op.setTagOption(validSource.getTagOption());
274 rememberHttpHost(op, uri);
275 configureFetchSpec(op, gitRepositoryInfo, remoteName);
276 configurePush(op, gitRepositoryInfo, remoteName);
277 configureRepositoryConfig(op, gitRepositoryInfo);
278 configureGerrit(op, gitRepositoryInfo, credentialsProvider, remoteName,
279 timeout);
281 if (cloneDestination.isImportProjects()) {
282 final IWorkingSet[] sets = cloneDestination.getWorkingSets();
283 op.addPostCloneTask(new PostCloneTask() {
284 @Override
285 public void execute(Repository repository,
286 IProgressMonitor monitor) throws CoreException {
287 importProjects(repository, sets);
292 alreadyClonedInto = workdir.getPath();
294 if (!callerRunsCloneOperation)
295 runAsJob(uri, op, gitRepositoryInfo);
296 else
297 cloneOperation = op;
298 return true;
301 @Override
302 public IWizardPage getNextPage(IWizardPage page) {
303 if (page instanceof IRepositorySearchResult) {
304 currentSearchResult = (IRepositorySearchResult)page;
305 return validSource;
307 return super.getNextPage(page);
311 * @return the repository selected by the user or {@code null} if an error
312 * occurred
314 @Nullable
315 protected RepositorySelection getRepositorySelection() {
316 try {
317 return (new RepositorySelection(new URIish(currentSearchResult.getGitRepositoryInfo().getCloneUri()), null));
318 } catch (URISyntaxException e) {
319 Activator.error(UIText.GitImportWizard_errorParsingURI, e);
320 return null;
321 } catch (NoRepositoryInfoException e) {
322 Activator.error(UIText.GitImportWizard_noRepositoryInfo, e);
323 return null;
324 } catch (Exception e) {
325 Activator.error(e.getMessage(), e);
326 return null;
331 * @return the credentials
333 protected UserPasswordCredentials getCredentials() {
334 try {
335 return currentSearchResult.getGitRepositoryInfo().getCredentials();
336 } catch (NoRepositoryInfoException e) {
337 Activator.error(UIText.GitImportWizard_noRepositoryInfo, e);
338 return null;
339 } catch (Exception e) {
340 Activator.error(e.getMessage(), e);
341 return null;
345 private void rememberHttpHost(CloneOperation op, URIish uri) {
346 String scheme = uri.getScheme();
347 if (scheme != null && scheme.toLowerCase().startsWith("http")) { //$NON-NLS-1$
348 String host = uri.getHost();
349 if (host != null) {
350 op.addPostCloneTask((repo, monitor) -> {
351 PlatformUI.getWorkbench().getDisplay()
352 .asyncExec(() -> KnownHosts.addKnownHost(host));
358 private void configureFetchSpec(CloneOperation op,
359 GitRepositoryInfo gitRepositoryInfo, String remoteName) {
360 for (String fetchRefSpec : gitRepositoryInfo.getFetchRefSpecs())
361 op.addPostCloneTask(new ConfigureFetchAfterCloneTask(remoteName, fetchRefSpec));
364 private void configurePush(CloneOperation op,
365 GitRepositoryInfo gitRepositoryInfo, String remoteName) {
366 for (PushInfo pushInfo : gitRepositoryInfo.getPushInfos())
367 try {
368 URIish uri = pushInfo.getPushUri() != null ? new URIish(
369 pushInfo.getPushUri()) : null;
370 ConfigurePushAfterCloneTask task = new ConfigurePushAfterCloneTask(
371 remoteName, pushInfo.getPushRefSpec(), uri);
372 op.addPostCloneTask(task);
373 } catch (URISyntaxException e) {
374 Activator.handleError(UIText.GitCloneWizard_failed, e, true);
378 private void configureRepositoryConfig(CloneOperation op, GitRepositoryInfo gitRepositoryInfo) {
379 for (RepositoryConfigProperty p : gitRepositoryInfo.getRepositoryConfigProperties()) {
380 SetRepositoryConfigPropertyTask task = new SetRepositoryConfigPropertyTask(
381 p.getSection(), p.getSubsection(), p.getName(),
382 p.getValue());
383 op.addPostCloneTask(task);
387 private void configureGerrit(CloneOperation op,
388 GitRepositoryInfo gitRepositoryInfo,
389 CredentialsProvider credentialsProvider, String remoteName,
390 int timeout) {
391 ConfigureGerritAfterCloneTask task = new ConfigureGerritAfterCloneTask(
392 gitRepositoryInfo.getCloneUri(), remoteName,
393 credentialsProvider, timeout);
394 op.addPostCloneTask(task);
397 private void importProjects(final Repository repository,
398 final IWorkingSet[] sets) {
399 String repoName = RepositoryUtil.INSTANCE.getRepositoryName(repository);
400 Job importJob = new WorkspaceJob(MessageFormat.format(
401 UIText.GitCloneWizard_jobImportProjects, repoName)) {
403 @Override
404 public IStatus runInWorkspace(IProgressMonitor monitor) {
405 SubMonitor progress = SubMonitor.convert(monitor, 2);
406 List<File> files = new ArrayList<>();
407 ProjectUtil.findProjectFiles(files, repository.getWorkTree(),
408 true, progress.newChild(1));
409 if (files.isEmpty()) {
410 return Status.OK_STATUS;
412 Set<ProjectRecord> records = new LinkedHashSet<>();
413 for (File file : files) {
414 ProjectRecord record = new ProjectRecord(file);
415 if (record.getProjectDescription() == null) {
416 // Ignore invalid .project files
417 continue;
419 records.add(record);
421 if (records.isEmpty()) {
422 return Status.OK_STATUS;
424 try {
425 ProjectUtils.createProjects(records, sets,
426 progress.newChild(1));
427 } catch (InvocationTargetException | InterruptedException e) {
428 Activator.logError(e.getLocalizedMessage(), e);
430 return Status.OK_STATUS;
433 importJob.schedule();
437 * @param container
438 * @param repositoryInfo
440 public void runCloneOperation(IWizardContainer container, final GitRepositoryInfo repositoryInfo) {
441 try {
442 container.run(true, true, new IRunnableWithProgress() {
443 @Override
444 public void run(IProgressMonitor monitor)
445 throws InvocationTargetException, InterruptedException {
446 executeCloneOperation(cloneOperation, repositoryInfo, monitor);
449 } catch (InvocationTargetException e) {
450 Activator.handleError(UIText.GitCloneWizard_failed, e.getCause(),
451 true);
452 } catch (InterruptedException e) {
453 // nothing to do
457 private void runAsJob(final URIish uri, final CloneOperation op,
458 final GitRepositoryInfo repositoryInfo) {
459 final Job job = new Job(NLS.bind(UIText.GitCloneWizard_jobName,
460 uri.toString())) {
461 @Override
462 protected IStatus run(final IProgressMonitor monitor) {
463 try {
464 return executeCloneOperation(op, repositoryInfo, monitor);
465 } catch (InterruptedException e) {
466 return Status.CANCEL_STATUS;
467 } catch (InvocationTargetException e) {
468 Throwable thr = e.getCause();
469 return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
470 thr.getMessage(), thr);
474 @Override
475 public boolean belongsTo(Object family) {
476 if (JobFamilies.CLONE.equals(family))
477 return true;
478 return super.belongsTo(family);
481 job.setUser(true);
482 job.schedule();
485 private IStatus executeCloneOperation(final CloneOperation op,
486 final GitRepositoryInfo repositoryInfo,
487 final IProgressMonitor monitor) throws InvocationTargetException,
488 InterruptedException {
490 op.run(monitor);
491 if (group != null) {
492 RepositoryGroups.INSTANCE.addRepositoriesToGroup(group,
493 Collections.singletonList(op.getGitDir()));
495 RepositoryUtil.INSTANCE.addConfiguredRepository(op.getGitDir());
496 try {
497 if (repositoryInfo.shouldSaveCredentialsInSecureStore())
498 SecureStoreUtils.storeCredentials(repositoryInfo.getCredentials(),
499 new URIish(repositoryInfo.getCloneUri()));
500 } catch (Exception e) {
501 Activator.error(e.getMessage(), e);
503 return Status.OK_STATUS;
507 * @param newValue
508 * if true the clone wizard just creates a clone operation. The
509 * caller has to run this operation using runCloneOperation. If
510 * false the clone operation is performed using a job.
512 public void setCallerRunsCloneOperation(boolean newValue) {
513 callerRunsCloneOperation = newValue;
517 * Sets a {@link RepositoryGroup} to which the new repository will be added.
519 * @param group
520 * to add the new repository to
522 public void setRepositoryGroup(RepositoryGroup group) {
523 this.group = group;