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
;
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
;
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
;
80 * Implements the basic functionality of a clone wizard
82 public abstract class AbstractGitCloneWizard
extends Wizard
{
85 * a page for branch selection
87 protected SourceBranchPage validSource
;
90 * a page for selection of the clone destination
92 protected CloneDestinationPage cloneDestination
;
95 * the path where a clone has been created in
97 protected String alreadyClonedInto
;
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
) {
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() {
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() {
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();
172 final public void addPages() {
173 if (hasSearchResult())
174 addRepositorySearchPage();
176 addRepositoryLocationPage();
178 addPage(validSource
);
179 addPage(cloneDestination
);
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
))
199 addPage(cloneSourceProviders
.get(0).getRepositorySearchPage());
200 } catch (CoreException e
) {
201 Activator
.logError(e
.getLocalizedMessage(), e
);
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
236 selectedBranches
= Collections
.emptyList();
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();
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
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());
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
,
281 if (cloneDestination
.isImportProjects()) {
282 final IWorkingSet
[] sets
= cloneDestination
.getWorkingSets();
283 op
.addPostCloneTask(new PostCloneTask() {
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
);
302 public IWizardPage
getNextPage(IWizardPage page
) {
303 if (page
instanceof IRepositorySearchResult
) {
304 currentSearchResult
= (IRepositorySearchResult
)page
;
307 return super.getNextPage(page
);
311 * @return the repository selected by the user or {@code null} if an error
315 protected RepositorySelection
getRepositorySelection() {
317 return (new RepositorySelection(new URIish(currentSearchResult
.getGitRepositoryInfo().getCloneUri()), null));
318 } catch (URISyntaxException e
) {
319 Activator
.error(UIText
.GitImportWizard_errorParsingURI
, e
);
321 } catch (NoRepositoryInfoException e
) {
322 Activator
.error(UIText
.GitImportWizard_noRepositoryInfo
, e
);
324 } catch (Exception e
) {
325 Activator
.error(e
.getMessage(), e
);
331 * @return the credentials
333 protected UserPasswordCredentials
getCredentials() {
335 return currentSearchResult
.getGitRepositoryInfo().getCredentials();
336 } catch (NoRepositoryInfoException e
) {
337 Activator
.error(UIText
.GitImportWizard_noRepositoryInfo
, e
);
339 } catch (Exception e
) {
340 Activator
.error(e
.getMessage(), e
);
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();
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())
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(),
383 op
.addPostCloneTask(task
);
387 private void configureGerrit(CloneOperation op
,
388 GitRepositoryInfo gitRepositoryInfo
,
389 CredentialsProvider credentialsProvider
, String remoteName
,
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
)) {
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
421 if (records
.isEmpty()) {
422 return Status
.OK_STATUS
;
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();
438 * @param repositoryInfo
440 public void runCloneOperation(IWizardContainer container
, final GitRepositoryInfo repositoryInfo
) {
442 container
.run(true, true, new IRunnableWithProgress() {
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(),
452 } catch (InterruptedException e
) {
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
,
462 protected IStatus
run(final IProgressMonitor monitor
) {
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
);
475 public boolean belongsTo(Object family
) {
476 if (JobFamilies
.CLONE
.equals(family
))
478 return super.belongsTo(family
);
485 private IStatus
executeCloneOperation(final CloneOperation op
,
486 final GitRepositoryInfo repositoryInfo
,
487 final IProgressMonitor monitor
) throws InvocationTargetException
,
488 InterruptedException
{
492 RepositoryGroups
.INSTANCE
.addRepositoriesToGroup(group
,
493 Collections
.singletonList(op
.getGitDir()));
495 RepositoryUtil
.INSTANCE
.addConfiguredRepository(op
.getGitDir());
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
;
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.
520 * to add the new repository to
522 public void setRepositoryGroup(RepositoryGroup group
) {