1 /*******************************************************************************
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3 * Copyright (C) 2008, Google Inc.
4 * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua>
5 * Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com>
6 * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
8 * All rights reserved. This program and the accompanying materials
9 * are made available under the terms of the Eclipse Public License 2.0
10 * which accompanies this distribution, and is available at
11 * https://www.eclipse.org/legal/epl-2.0/
13 * SPDX-License-Identifier: EPL-2.0
14 *******************************************************************************/
15 package org
.eclipse
.egit
.core
.op
;
18 import java
.io
.IOException
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Arrays
;
21 import java
.util
.Collection
;
22 import java
.util
.HashSet
;
23 import java
.util
.LinkedHashMap
;
24 import java
.util
.List
;
26 import java
.util
.Map
.Entry
;
29 import org
.eclipse
.core
.resources
.IContainer
;
30 import org
.eclipse
.core
.resources
.IProject
;
31 import org
.eclipse
.core
.resources
.IResource
;
32 import org
.eclipse
.core
.resources
.IResourceProxy
;
33 import org
.eclipse
.core
.resources
.IResourceProxyVisitor
;
34 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
35 import org
.eclipse
.core
.runtime
.CoreException
;
36 import org
.eclipse
.core
.runtime
.IPath
;
37 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
38 import org
.eclipse
.core
.runtime
.MultiStatus
;
39 import org
.eclipse
.core
.runtime
.Path
;
40 import org
.eclipse
.core
.runtime
.Platform
;
41 import org
.eclipse
.core
.runtime
.SubMonitor
;
42 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
43 import org
.eclipse
.core
.runtime
.jobs
.MultiRule
;
44 import org
.eclipse
.egit
.core
.Activator
;
45 import org
.eclipse
.egit
.core
.GitCorePreferences
;
46 import org
.eclipse
.egit
.core
.GitProvider
;
47 import org
.eclipse
.egit
.core
.JobFamilies
;
48 import org
.eclipse
.egit
.core
.RepositoryCache
;
49 import org
.eclipse
.egit
.core
.RepositoryUtil
;
50 import org
.eclipse
.egit
.core
.internal
.CoreText
;
51 import org
.eclipse
.egit
.core
.internal
.indexdiff
.IndexDiffCache
;
52 import org
.eclipse
.egit
.core
.internal
.indexdiff
.IndexDiffCacheEntry
;
53 import org
.eclipse
.egit
.core
.internal
.job
.JobUtil
;
54 import org
.eclipse
.egit
.core
.internal
.trace
.GitTraceLocation
;
55 import org
.eclipse
.egit
.core
.project
.GitProjectData
;
56 import org
.eclipse
.egit
.core
.project
.RepositoryFinder
;
57 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
58 import org
.eclipse
.jgit
.annotations
.Nullable
;
59 import org
.eclipse
.jgit
.lib
.Constants
;
60 import org
.eclipse
.jgit
.lib
.Repository
;
61 import org
.eclipse
.jgit
.storage
.file
.FileRepositoryBuilder
;
62 import org
.eclipse
.osgi
.util
.NLS
;
63 import org
.eclipse
.team
.core
.RepositoryProvider
;
66 * Connects Eclipse to an existing Git repository
68 public class ConnectProviderOperation
implements IEGitOperation
{
69 private final Map
<IProject
, File
> projects
= new LinkedHashMap
<>();
71 private boolean refreshResources
= true;
74 * Create a new connection operation to execute within the workspace.
76 * Uses <code>.git</code> as a default relative path to repository.
77 * @see #ConnectProviderOperation(IProject, File)
80 * the project to connect to the Git team provider.
82 public ConnectProviderOperation(final IProject proj
) {
83 this(proj
, proj
.getLocation().append(Constants
.DOT_GIT
).toFile());
87 * Create a new connection operation to execute within the workspace.
90 * the project to connect to the Git team provider.
92 * absolute path to the repository
94 public ConnectProviderOperation(final IProject proj
, File pathToRepo
) {
95 this.projects
.put(proj
, pathToRepo
);
99 * Create a new connection operation to execute within the workspace.
102 * the projects to connect to the Git team provider.
104 public ConnectProviderOperation(final Map
<IProject
, File
> projects
) {
105 this.projects
.putAll(projects
);
109 public void execute(IProgressMonitor m
) throws CoreException
{
110 SubMonitor progress
= SubMonitor
.convert(m
,
111 CoreText
.ConnectProviderOperation_connecting
, projects
.size());
112 MultiStatus ms
= new MultiStatus(Activator
.PLUGIN_ID
, 0,
113 CoreText
.ConnectProviderOperation_ConnectErrors
, null);
114 for (Entry
<IProject
, File
> entry
: projects
.entrySet()) {
115 connectProject(entry
, ms
, progress
.newChild(1));
118 throw new CoreException(ms
);
122 private void connectProject(Entry
<IProject
, File
> entry
, MultiStatus ms
,
123 IProgressMonitor monitor
) throws CoreException
{
124 IProject project
= entry
.getKey();
126 String taskName
= NLS
.bind(
127 CoreText
.ConnectProviderOperation_ConnectingProject
,
129 SubMonitor subMon
= SubMonitor
.convert(monitor
, taskName
, 100);
131 if (GitTraceLocation
.CORE
.isActive()) {
132 GitTraceLocation
.getTrace()
133 .trace(GitTraceLocation
.CORE
.getLocation(), taskName
);
136 RepositoryFinder finder
= new RepositoryFinder(project
);
137 finder
.setFindInChildren(false);
138 Collection
<RepositoryMapping
> repos
= finder
.find(subMon
.newChild(50));
139 if (repos
.isEmpty()) {
140 ms
.add(Activator
.error(NLS
.bind(
141 CoreText
.ConnectProviderOperation_NoRepositoriesError
,
142 project
.getName()), null));
145 RepositoryMapping actualMapping
= findActualRepository(repos
,
147 if (actualMapping
== null) {
148 ms
.add(Activator
.error(NLS
.bind(
149 CoreText
.ConnectProviderOperation_UnexpectedRepositoryError
,
150 new Object
[] { project
.getName(),
151 entry
.getValue().toString(), repos
.toString() }),
155 GitProjectData projectData
= new GitProjectData(project
);
157 projectData
.setRepositoryMappings(Arrays
.asList(actualMapping
));
159 GitProjectData
.add(project
, projectData
);
160 } catch (CoreException ce
) {
161 ms
.add(ce
.getStatus());
162 deleteGitProvider(ms
, project
);
164 } catch (RuntimeException ce
) {
165 ms
.add(Activator
.error(ce
.getMessage(), ce
));
166 deleteGitProvider(ms
, project
);
169 RepositoryProvider
.map(project
, GitProvider
.ID
);
171 IPath gitPath
= actualMapping
.getGitDirAbsolutePath();
172 checkForSubmodules(project
, subMon
.newChild(10));
173 if (refreshResources
) {
174 project
.refreshLocal(IResource
.DEPTH_INFINITE
, subMon
.newChild(30));
175 if (gitPath
!= null) {
177 Repository repository
= RepositoryCache
.INSTANCE
178 .lookupRepository(gitPath
.toFile());
179 IndexDiffCacheEntry cacheEntry
= IndexDiffCache
.INSTANCE
180 .getIndexDiffCacheEntry(repository
);
181 if (cacheEntry
!= null) {
182 cacheEntry
.refresh();
184 } catch (IOException e
) {
185 Activator
.logError(e
.getMessage(), e
);
192 autoIgnoreDerivedResources(project
, subMon
.newChild(10));
193 if (gitPath
!= null) {
194 autoIgnoreWorkspaceMetaData(gitPath
.toFile());
199 * Scan all descendants for files or folders named ".git" and pass them to
200 * GitProjectData to pick up possible submodules or nested repositories.
205 * for progress reporting and cancellation, may be {@code null}
206 * if neither is desired
208 private void checkForSubmodules(IProject project
,
209 IProgressMonitor monitor
) {
210 SubMonitor progress
= SubMonitor
.convert(monitor
, 2);
211 SubMonitor seekProgress
= progress
.newChild(1);
212 List
<IResource
> gitDirs
= new ArrayList
<>();
214 project
.accept(new IResourceProxyVisitor() {
216 public boolean visit(IResourceProxy resource
)
217 throws CoreException
{
218 int type
= resource
.getType();
219 if ((type
== IResource
.FILE
|| type
== IResource
.FOLDER
)
220 && Constants
.DOT_GIT
.equals(resource
.getName())) {
221 seekProgress
.setWorkRemaining(2);
222 gitDirs
.add(resource
.requestResource());
223 seekProgress
.worked(1);
229 } catch (CoreException e
) {
230 Activator
.logError(e
.getMessage(), e
);
232 GitProjectData
.update(gitDirs
, progress
.newChild(1));
235 private void deleteGitProvider(MultiStatus ms
, IProject project
) {
237 GitProjectData
.delete(project
);
238 } catch (IOException e
) {
239 ms
.add(Activator
.error(e
.getMessage(), e
));
243 private void autoIgnoreDerivedResources(IProject project
,
244 IProgressMonitor monitor
) throws CoreException
{
245 if (!Platform
.getPreferencesService().getBoolean(Activator
.PLUGIN_ID
,
246 GitCorePreferences
.core_autoIgnoreDerivedResources
, true,
250 List
<IPath
> paths
= findDerivedResources(project
);
251 if (paths
.size() > 0) {
252 IgnoreOperation ignoreOp
= new IgnoreOperation(paths
);
253 ignoreOp
.execute(monitor
);
258 * Auto-ignore the .metadata and .recommenders folders located in the
259 * workspace root if the workspace is in the working tree of a git
260 * repository (which is not recommended)
263 * the .git directory containing the repository metadata
265 private static void autoIgnoreWorkspaceMetaData(File gitDir
) {
266 java
.nio
.file
.Path workspaceRoot
= ResourcesPlugin
.getWorkspace()
267 .getRoot().getLocation().toFile().toPath();
268 try (Repository r
= FileRepositoryBuilder
.create(gitDir
)) {
270 && workspaceRoot
.startsWith(r
.getWorkTree().toPath())) {
271 Collection
<IPath
> ignoredPaths
= buildIgnoredPathsList(
272 workspaceRoot
, ".metadata", //$NON-NLS-1$
273 ".recommenders"); //$NON-NLS-1$
274 JobUtil
.scheduleUserJob(new IgnoreOperation(ignoredPaths
),
275 CoreText
.ConnectProviderOperation_autoIgnoreMetaData
,
276 JobFamilies
.AUTO_IGNORE
);
278 } catch (IOException e
) {
279 Activator
.logError(e
.getMessage(), e
);
283 private static Collection
<IPath
> buildIgnoredPathsList(
284 java
.nio
.file
.Path workspaceRoot
,
285 String
... metaDataDirectoryNames
) {
286 Collection
<IPath
> ignoredPaths
= new HashSet
<>();
287 for (String m
: metaDataDirectoryNames
) {
288 Path metaData
= new Path(
289 workspaceRoot
.resolve(m
).toAbsolutePath().toString());
291 if (RepositoryUtil
.canBeAutoIgnored(metaData
)) {
292 ignoredPaths
.add(metaData
);
294 } catch (IOException e
) {
295 Activator
.logError(e
.getMessage(), e
);
301 private List
<IPath
> findDerivedResources(IContainer c
)
302 throws CoreException
{
303 List
<IPath
> derived
= new ArrayList
<>();
304 IResource
[] members
= c
.members(IContainer
.INCLUDE_HIDDEN
);
305 for (IResource r
: members
) {
310 derived
.add(r
.getLocation());
311 } else if (r
instanceof IContainer
) {
312 derived
.addAll(findDerivedResources((IContainer
) r
));
319 public ISchedulingRule
getSchedulingRule() {
320 Set
<IProject
> projectSet
= projects
.keySet();
321 return new MultiRule(projectSet
.toArray(new IProject
[0]));
326 * available repositories
327 * @param suggestedRepo
328 * relative path to git repository
329 * @return a repository mapping which corresponds to a suggested repository
330 * location, <code>null</code> otherwise
333 private RepositoryMapping
findActualRepository(
334 Collection
<RepositoryMapping
> repos
, File suggestedRepo
) {
335 File path
= Path
.fromOSString(suggestedRepo
.getPath()).toFile();
336 for (RepositoryMapping rm
: repos
) {
337 IPath other
= rm
.getGitDirAbsolutePath();
341 if (path
.equals(other
.toFile())) {
350 * true to refresh resources after connect operation (default)
352 public void setRefreshResources(boolean refresh
) {
353 this.refreshResources
= refresh
;