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
.SubMonitor
;
41 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
42 import org
.eclipse
.core
.runtime
.jobs
.MultiRule
;
43 import org
.eclipse
.egit
.core
.Activator
;
44 import org
.eclipse
.egit
.core
.GitProvider
;
45 import org
.eclipse
.egit
.core
.JobFamilies
;
46 import org
.eclipse
.egit
.core
.RepositoryUtil
;
47 import org
.eclipse
.egit
.core
.internal
.CoreText
;
48 import org
.eclipse
.egit
.core
.internal
.indexdiff
.IndexDiffCacheEntry
;
49 import org
.eclipse
.egit
.core
.internal
.job
.JobUtil
;
50 import org
.eclipse
.egit
.core
.internal
.trace
.GitTraceLocation
;
51 import org
.eclipse
.egit
.core
.project
.GitProjectData
;
52 import org
.eclipse
.egit
.core
.project
.RepositoryFinder
;
53 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
54 import org
.eclipse
.jgit
.annotations
.Nullable
;
55 import org
.eclipse
.jgit
.lib
.Constants
;
56 import org
.eclipse
.jgit
.lib
.Repository
;
57 import org
.eclipse
.jgit
.storage
.file
.FileRepositoryBuilder
;
58 import org
.eclipse
.osgi
.util
.NLS
;
59 import org
.eclipse
.team
.core
.RepositoryProvider
;
62 * Connects Eclipse to an existing Git repository
64 public class ConnectProviderOperation
implements IEGitOperation
{
65 private final Map
<IProject
, File
> projects
= new LinkedHashMap
<>();
67 private boolean refreshResources
= true;
70 * Create a new connection operation to execute within the workspace.
72 * Uses <code>.git</code> as a default relative path to repository.
73 * @see #ConnectProviderOperation(IProject, File)
76 * the project to connect to the Git team provider.
78 public ConnectProviderOperation(final IProject proj
) {
79 this(proj
, proj
.getLocation().append(Constants
.DOT_GIT
).toFile());
83 * Create a new connection operation to execute within the workspace.
86 * the project to connect to the Git team provider.
88 * absolute path to the repository
90 public ConnectProviderOperation(final IProject proj
, File pathToRepo
) {
91 this.projects
.put(proj
, pathToRepo
);
95 * Create a new connection operation to execute within the workspace.
98 * the projects to connect to the Git team provider.
100 public ConnectProviderOperation(final Map
<IProject
, File
> projects
) {
101 this.projects
.putAll(projects
);
105 public void execute(IProgressMonitor m
) throws CoreException
{
106 SubMonitor progress
= SubMonitor
.convert(m
,
107 CoreText
.ConnectProviderOperation_connecting
, projects
.size());
108 MultiStatus ms
= new MultiStatus(Activator
.getPluginId(), 0,
109 CoreText
.ConnectProviderOperation_ConnectErrors
, null);
110 for (Entry
<IProject
, File
> entry
: projects
.entrySet()) {
111 connectProject(entry
, ms
, progress
.newChild(1));
114 throw new CoreException(ms
);
118 private void connectProject(Entry
<IProject
, File
> entry
, MultiStatus ms
,
119 IProgressMonitor monitor
) throws CoreException
{
120 IProject project
= entry
.getKey();
122 String taskName
= NLS
.bind(
123 CoreText
.ConnectProviderOperation_ConnectingProject
,
125 SubMonitor subMon
= SubMonitor
.convert(monitor
, taskName
, 100);
127 if (GitTraceLocation
.CORE
.isActive()) {
128 GitTraceLocation
.getTrace()
129 .trace(GitTraceLocation
.CORE
.getLocation(), taskName
);
132 RepositoryFinder finder
= new RepositoryFinder(project
);
133 finder
.setFindInChildren(false);
134 Collection
<RepositoryMapping
> repos
= finder
.find(subMon
.newChild(50));
135 if (repos
.isEmpty()) {
136 ms
.add(Activator
.error(NLS
.bind(
137 CoreText
.ConnectProviderOperation_NoRepositoriesError
,
138 project
.getName()), null));
141 RepositoryMapping actualMapping
= findActualRepository(repos
,
143 if (actualMapping
== null) {
144 ms
.add(Activator
.error(NLS
.bind(
145 CoreText
.ConnectProviderOperation_UnexpectedRepositoryError
,
146 new Object
[] { project
.getName(),
147 entry
.getValue().toString(), repos
.toString() }),
151 GitProjectData projectData
= new GitProjectData(project
);
153 projectData
.setRepositoryMappings(Arrays
.asList(actualMapping
));
155 GitProjectData
.add(project
, projectData
);
156 } catch (CoreException ce
) {
157 ms
.add(ce
.getStatus());
158 deleteGitProvider(ms
, project
);
160 } catch (RuntimeException ce
) {
161 ms
.add(Activator
.error(ce
.getMessage(), ce
));
162 deleteGitProvider(ms
, project
);
165 RepositoryProvider
.map(project
, GitProvider
.ID
);
167 IPath gitPath
= actualMapping
.getGitDirAbsolutePath();
168 if (refreshResources
) {
169 touchGitResources(project
, subMon
.newChild(10));
170 project
.refreshLocal(IResource
.DEPTH_INFINITE
, subMon
.newChild(30));
171 if (gitPath
!= null) {
173 Repository repository
= org
.eclipse
.egit
.core
.Activator
174 .getDefault().getRepositoryCache()
175 .lookupRepository(gitPath
.toFile());
176 IndexDiffCacheEntry cacheEntry
= org
.eclipse
.egit
.core
.Activator
177 .getDefault().getIndexDiffCache()
178 .getIndexDiffCacheEntry(repository
);
179 if (cacheEntry
!= null) {
180 cacheEntry
.refresh();
182 } catch (IOException e
) {
183 Activator
.logError(e
.getMessage(), e
);
190 autoIgnoreDerivedResources(project
, subMon
.newChild(10));
191 if (gitPath
!= null) {
192 autoIgnoreWorkspaceMetaData(gitPath
.toFile());
197 * Touches all descendants named ".git" so that they'll be included in a
198 * subsequent resource delta.
203 * for progress reporting and cancellation, may be {@code null}
204 * if neither is desired
206 private void touchGitResources(IProject project
, IProgressMonitor monitor
) {
207 final SubMonitor progress
= SubMonitor
.convert(monitor
, 1);
209 project
.accept(new IResourceProxyVisitor() {
211 public boolean visit(IResourceProxy resource
)
212 throws CoreException
{
213 int type
= resource
.getType();
214 if ((type
== IResource
.FILE
|| type
== IResource
.FOLDER
)
215 && Constants
.DOT_GIT
.equals(resource
.getName())) {
216 progress
.setWorkRemaining(2);
217 resource
.requestResource().touch(progress
.newChild(1));
223 } catch (CoreException e
) {
224 Activator
.logError(e
.getMessage(), e
);
228 private void deleteGitProvider(MultiStatus ms
, IProject project
) {
230 GitProjectData
.delete(project
);
231 } catch (IOException e
) {
232 ms
.add(Activator
.error(e
.getMessage(), e
));
236 private void autoIgnoreDerivedResources(IProject project
,
237 IProgressMonitor monitor
) throws CoreException
{
238 if (!Activator
.autoIgnoreDerived()) {
241 List
<IPath
> paths
= findDerivedResources(project
);
242 if (paths
.size() > 0) {
243 IgnoreOperation ignoreOp
= new IgnoreOperation(paths
);
244 ignoreOp
.execute(monitor
);
249 * Auto-ignore the .metadata and .recommenders folders located in the
250 * workspace root if the workspace is in the working tree of a git
251 * repository (which is not recommended)
254 * the .git directory containing the repository metadata
256 private static void autoIgnoreWorkspaceMetaData(File gitDir
) {
257 java
.nio
.file
.Path workspaceRoot
= ResourcesPlugin
.getWorkspace()
258 .getRoot().getLocation().toFile().toPath();
259 try (Repository r
= FileRepositoryBuilder
.create(gitDir
)) {
261 && workspaceRoot
.startsWith(r
.getWorkTree().toPath())) {
262 Collection
<IPath
> ignoredPaths
= buildIgnoredPathsList(
263 workspaceRoot
, ".metadata", //$NON-NLS-1$
264 ".recommenders"); //$NON-NLS-1$
265 JobUtil
.scheduleUserJob(new IgnoreOperation(ignoredPaths
),
266 CoreText
.ConnectProviderOperation_autoIgnoreMetaData
,
267 JobFamilies
.AUTO_IGNORE
);
269 } catch (IOException e
) {
270 Activator
.logError(e
.getMessage(), e
);
274 private static Collection
<IPath
> buildIgnoredPathsList(
275 java
.nio
.file
.Path workspaceRoot
,
276 String
... metaDataDirectoryNames
) {
277 Collection
<IPath
> ignoredPaths
= new HashSet
<>();
278 for (String m
: metaDataDirectoryNames
) {
279 Path metaData
= new Path(
280 workspaceRoot
.resolve(m
).toAbsolutePath().toString());
282 if (RepositoryUtil
.canBeAutoIgnored(metaData
)) {
283 ignoredPaths
.add(metaData
);
285 } catch (IOException e
) {
286 Activator
.logError(e
.getMessage(), e
);
292 private List
<IPath
> findDerivedResources(IContainer c
)
293 throws CoreException
{
294 List
<IPath
> derived
= new ArrayList
<>();
295 IResource
[] members
= c
.members(IContainer
.INCLUDE_HIDDEN
);
296 for (IResource r
: members
) {
298 derived
.add(r
.getLocation());
299 else if (r
instanceof IContainer
)
300 derived
.addAll(findDerivedResources((IContainer
) r
));
306 public ISchedulingRule
getSchedulingRule() {
307 Set
<IProject
> projectSet
= projects
.keySet();
308 return new MultiRule(projectSet
.toArray(new IProject
[0]));
313 * available repositories
314 * @param suggestedRepo
315 * relative path to git repository
316 * @return a repository mapping which corresponds to a suggested repository
317 * location, <code>null</code> otherwise
320 private RepositoryMapping
findActualRepository(
321 Collection
<RepositoryMapping
> repos
, File suggestedRepo
) {
322 File path
= Path
.fromOSString(suggestedRepo
.getPath()).toFile();
323 for (RepositoryMapping rm
: repos
) {
324 IPath other
= rm
.getGitDirAbsolutePath();
328 if (path
.equals(other
.toFile())) {
337 * true to refresh resources after connect operation (default)
339 public void setRefreshResources(boolean refresh
) {
340 this.refreshResources
= refresh
;