1 /*******************************************************************************
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2008, Google Inc.
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *******************************************************************************/
11 package org
.eclipse
.egit
.core
.project
;
14 import java
.io
.FileInputStream
;
15 import java
.io
.FileNotFoundException
;
16 import java
.io
.FileOutputStream
;
17 import java
.io
.IOException
;
18 import java
.lang
.ref
.Reference
;
19 import java
.lang
.ref
.WeakReference
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Collection
;
22 import java
.util
.HashMap
;
23 import java
.util
.HashSet
;
24 import java
.util
.Iterator
;
26 import java
.util
.Properties
;
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
.IResourceChangeEvent
;
33 import org
.eclipse
.core
.resources
.IResourceChangeListener
;
34 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
35 import org
.eclipse
.core
.runtime
.CoreException
;
36 import org
.eclipse
.core
.runtime
.Preferences
;
37 import org
.eclipse
.core
.runtime
.QualifiedName
;
38 import org
.eclipse
.egit
.core
.Activator
;
39 import org
.eclipse
.egit
.core
.CoreText
;
40 import org
.eclipse
.egit
.core
.GitCorePreferences
;
41 import org
.eclipse
.egit
.core
.GitProvider
;
42 import org
.eclipse
.egit
.core
.internal
.trace
.GitTraceLocation
;
43 import org
.eclipse
.osgi
.util
.NLS
;
44 import org
.eclipse
.team
.core
.RepositoryProvider
;
45 import org
.eclipse
.jgit
.lib
.Constants
;
46 import org
.eclipse
.jgit
.lib
.Repository
;
47 import org
.eclipse
.jgit
.lib
.WindowCache
;
48 import org
.eclipse
.jgit
.lib
.WindowCacheConfig
;
51 * This class keeps information about how a project is mapped to
54 public class GitProjectData
{
55 private static final Map
<IProject
, GitProjectData
> projectDataCache
= new HashMap
<IProject
, GitProjectData
>();
57 private static final Map
<File
, WeakReference
> repositoryCache
= new HashMap
<File
, WeakReference
>();
59 private static Set
<RepositoryChangeListener
> repositoryChangeListeners
= new HashSet
<RepositoryChangeListener
>();
61 @SuppressWarnings("synthetic-access")
62 private static final IResourceChangeListener rcl
= new RCL();
64 private static class RCL
implements IResourceChangeListener
{
65 @SuppressWarnings("synthetic-access")
66 public void resourceChanged(final IResourceChangeEvent event
) {
67 switch (event
.getType()) {
68 case IResourceChangeEvent
.PRE_CLOSE
:
69 uncache((IProject
) event
.getResource());
71 case IResourceChangeEvent
.PRE_DELETE
:
72 delete((IProject
) event
.getResource());
80 private static QualifiedName MAPPING_KEY
= new QualifiedName(
81 GitProjectData
.class.getName(), "RepositoryMapping"); //$NON-NLS-1$
84 * Start listening for resource changes.
86 * @param includeChange true to listen to content changes
88 public static void attachToWorkspace(final boolean includeChange
) {
89 trace("attachToWorkspace - addResourceChangeListener"); //$NON-NLS-1$
90 ResourcesPlugin
.getWorkspace().addResourceChangeListener(
92 (includeChange ? IResourceChangeEvent
.POST_CHANGE
: 0)
93 | IResourceChangeEvent
.PRE_CLOSE
94 | IResourceChangeEvent
.PRE_DELETE
);
98 * Stop listening to resource changes
100 public static void detachFromWorkspace() {
101 trace("detachFromWorkspace - removeResourceChangeListener"); //$NON-NLS-1$
102 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(rcl
);
106 * Register a new listener for repository modification events.
108 * This is a no-op if <code>objectThatCares</code> has already been
112 * @param objectThatCares
113 * the new listener to register. Must not be null.
115 public static synchronized void addRepositoryChangeListener(
116 final RepositoryChangeListener objectThatCares
) {
117 if (objectThatCares
== null)
118 throw new NullPointerException();
119 repositoryChangeListeners
.add(objectThatCares
);
123 * Remove a registered {@link RepositoryChangeListener}
125 * @param objectThatCares
126 * The listener to remove
128 public static synchronized void removeRepositoryChangeListener(
129 final RepositoryChangeListener objectThatCares
) {
130 repositoryChangeListeners
.remove(objectThatCares
);
134 * Notify registered {@link RepositoryChangeListener}s of a change.
137 * the repository which has had changes occur within it.
139 static void fireRepositoryChanged(final RepositoryMapping which
) {
140 for (RepositoryChangeListener listener
: getRepositoryChangeListeners())
141 listener
.repositoryChanged(which
);
145 * Get a copy of the current set of repository change listeners
147 * The array has no references, so is safe for iteration and modification
149 * @return a copy of the current repository change listeners
151 private static synchronized RepositoryChangeListener
[] getRepositoryChangeListeners() {
152 return repositoryChangeListeners
153 .toArray(new RepositoryChangeListener
[repositoryChangeListeners
159 * @return {@link GitProjectData} for the specified project
161 public synchronized static GitProjectData
get(final IProject p
) {
163 GitProjectData d
= lookup(p
);
165 && RepositoryProvider
.getProvider(p
) instanceof GitProvider
) {
166 d
= new GitProjectData(p
).load();
170 } catch (IOException err
) {
171 Activator
.logError(CoreText
.GitProjectData_missing
, err
);
177 * Drop the Eclipse project from our association of projects/repositories
179 * @param p Eclipse project
181 public static void delete(final IProject p
) {
182 trace("delete(" + p
.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
183 GitProjectData d
= lookup(p
);
186 d
= new GitProjectData(p
).load();
187 } catch (IOException ioe
) {
188 d
= new GitProjectData(p
);
194 static void trace(final String m
) {
195 // TODO is this the right location?
196 if (GitTraceLocation
.CORE
.isActive())
197 GitTraceLocation
.getTrace().trace(
198 GitTraceLocation
.CORE
.getLocation(),
199 "(GitProjectData) " + m
); //$NON-NLS-1$
202 private synchronized static void cache(final IProject p
,
203 final GitProjectData d
) {
204 projectDataCache
.put(p
, d
);
207 private synchronized static void uncache(final IProject p
) {
208 if (projectDataCache
.remove(p
) != null) {
209 trace("uncacheDataFor(" //$NON-NLS-1$
210 + p
.getName() + ")"); //$NON-NLS-1$
214 private synchronized static GitProjectData
lookup(final IProject p
) {
215 return projectDataCache
.get(p
);
218 private synchronized static Repository
lookupRepository(final File gitDir
)
220 final Iterator i
= repositoryCache
.entrySet().iterator();
221 while (i
.hasNext()) {
222 final Map
.Entry e
= (Map
.Entry
) i
.next();
223 if (((Reference
) e
.getValue()).get() == null) {
228 final Reference r
= repositoryCache
.get(gitDir
);
229 Repository d
= r
!= null ?
(Repository
) r
.get() : null;
231 d
= new Repository(gitDir
);
232 repositoryCache
.put(gitDir
, new WeakReference
<Repository
>(d
));
238 * Update the settings for the global window cache of the workspace.
240 public static void reconfigureWindowCache() {
241 final WindowCacheConfig c
= new WindowCacheConfig();
242 Preferences p
= Activator
.getDefault().getPluginPreferences();
243 c
.setPackedGitLimit(p
.getInt(GitCorePreferences
.core_packedGitLimit
));
244 c
.setPackedGitWindowSize(p
.getInt(GitCorePreferences
.core_packedGitWindowSize
));
245 c
.setPackedGitMMAP(p
.getBoolean(GitCorePreferences
.core_packedGitMMAP
));
246 c
.setDeltaBaseCacheLimit(p
.getInt(GitCorePreferences
.core_deltaBaseCacheLimit
));
247 WindowCache
.reconfigure(c
);
250 private final IProject project
;
252 private final Collection
<RepositoryMapping
> mappings
= new ArrayList
<RepositoryMapping
>();
254 private final Set
<IResource
> protectedResources
= new HashSet
<IResource
>();
257 * Construct a {@link GitProjectData} for the mapping
260 * @param p Eclipse project
262 public GitProjectData(final IProject p
) {
267 * @return the Eclipse project mapped through this resource.
269 public IProject
getProject() {
274 * TODO: is this right?
278 public void setRepositoryMappings(final Collection
<RepositoryMapping
> newMappings
) {
280 mappings
.addAll(newMappings
);
285 * Hide our private parts from the navigators other browsers.
287 * @throws CoreException
289 public void markTeamPrivateResources() throws CoreException
{
290 for (final Object rmObj
: mappings
) {
291 final RepositoryMapping rm
= (RepositoryMapping
)rmObj
;
292 final IContainer c
= rm
.getContainer();
294 continue; // Not fully mapped yet?
296 final IResource dotGit
= c
.findMember(Constants
.DOT_GIT
);
297 if (dotGit
!= null) {
299 final Repository r
= rm
.getRepository();
300 final File dotGitDir
= dotGit
.getLocation().toFile()
302 if (dotGitDir
.equals(r
.getDirectory())) {
303 trace("teamPrivate " + dotGit
); //$NON-NLS-1$
304 dotGit
.setTeamPrivateMember(true);
306 } catch (IOException err
) {
307 throw Activator
.error(CoreText
.Error_CanonicalFile
, err
);
315 * @return true if a resource is protected in this repository
317 public boolean isProtected(final IResource f
) {
318 return protectedResources
.contains(f
);
322 * @param r any workbench resource contained within this project.
323 * @return the mapping for the specified project
325 public RepositoryMapping
getRepositoryMapping(IResource r
) {
327 for (; r
!= null; r
= r
.getParent()) {
328 final RepositoryMapping m
;
330 if (!r
.isAccessible())
332 m
= (RepositoryMapping
) r
.getSessionProperty(MAPPING_KEY
);
336 } catch (CoreException err
) {
338 CoreText
.GitProjectData_failedFindingRepoMapping
, err
);
343 private void delete() {
344 final File dir
= propertyFile().getParentFile();
345 final File
[] todel
= dir
.listFiles();
347 for (int k
= 0; k
< todel
.length
; k
++) {
348 if (todel
[k
].isFile()) {
354 trace("deleteDataFor(" //$NON-NLS-1$
355 + getProject().getName()
356 + ")"); //$NON-NLS-1$
357 uncache(getProject());
361 * Store information about the repository connection in the workspace
363 * @throws CoreException
365 public void store() throws CoreException
{
366 final File dat
= propertyFile();
371 trace("save " + dat
); //$NON-NLS-1$
372 tmp
= File
.createTempFile(
373 "gpd_", //$NON-NLS-1$
374 ".prop", //$NON-NLS-1$
375 dat
.getParentFile());
376 final FileOutputStream o
= new FileOutputStream(tmp
);
378 final Properties p
= new Properties();
379 final Iterator i
= mappings
.iterator();
380 while (i
.hasNext()) {
381 ((RepositoryMapping
) i
.next()).store(p
);
383 p
.store(o
, "GitProjectData"); //$NON-NLS-1$
391 } catch (IOException ioe
) {
392 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
397 if (!tmp
.renameTo(dat
)) {
399 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
404 private File
propertyFile() {
405 return new File(getProject()
406 .getWorkingLocation(Activator
.getPluginId()).toFile(),
407 "GitProjectData.properties"); //$NON-NLS-1$
410 private GitProjectData
load() throws IOException
{
411 final File dat
= propertyFile();
412 trace("load " + dat
); //$NON-NLS-1$
414 final FileInputStream o
= new FileInputStream(dat
);
416 final Properties p
= new Properties();
420 final Iterator keyItr
= p
.keySet().iterator();
421 while (keyItr
.hasNext()) {
422 final String key
= keyItr
.next().toString();
423 if (RepositoryMapping
.isInitialKey(key
)) {
424 mappings
.add(new RepositoryMapping(p
, key
));
435 private void remapAll() {
436 protectedResources
.clear();
437 final Iterator i
= mappings
.iterator();
438 while (i
.hasNext()) {
439 map((RepositoryMapping
) i
.next());
443 private void map(final RepositoryMapping m
) {
446 final IResource dotGit
;
450 r
= getProject().findMember(m
.getContainerPath());
451 if (r
instanceof IContainer
) {
454 c
= (IContainer
) r
.getAdapter(IContainer
.class);
458 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
459 new FileNotFoundException(m
.getContainerPath().toString()));
465 git
= c
.getLocation().append(m
.getGitDirPath()).toFile();
466 if (!git
.isDirectory()
467 || !new File(git
, "config").isFile()) { //$NON-NLS-1$
468 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
469 new FileNotFoundException(m
.getContainerPath().toString()));
475 m
.setRepository(lookupRepository(git
));
476 } catch (IOException ioe
) {
477 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
478 new FileNotFoundException(m
.getContainerPath().toString()));
483 m
.fireRepositoryChanged();
485 trace("map " //$NON-NLS-1$
487 + " -> " //$NON-NLS-1$
488 + m
.getRepository());
490 c
.setSessionProperty(MAPPING_KEY
, m
);
491 } catch (CoreException err
) {
493 CoreText
.GitProjectData_failedToCacheRepoMapping
, err
);
496 dotGit
= c
.findMember(Constants
.DOT_GIT
);
497 if (dotGit
!= null && dotGit
.getLocation().toFile().equals(git
)) {
502 private void protect(IResource c
) {
503 while (c
!= null && !c
.equals(getProject())) {
504 trace("protect " + c
); //$NON-NLS-1$
505 protectedResources
.add(c
);