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 * See LICENSE for the full license text, also available.
9 *******************************************************************************/
10 package org
.spearce
.egit
.core
.project
;
13 import java
.io
.FileInputStream
;
14 import java
.io
.FileNotFoundException
;
15 import java
.io
.FileOutputStream
;
16 import java
.io
.IOException
;
17 import java
.lang
.ref
.Reference
;
18 import java
.lang
.ref
.WeakReference
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Collection
;
21 import java
.util
.HashMap
;
22 import java
.util
.HashSet
;
23 import java
.util
.Iterator
;
25 import java
.util
.Properties
;
28 import org
.eclipse
.core
.resources
.IContainer
;
29 import org
.eclipse
.core
.resources
.IProject
;
30 import org
.eclipse
.core
.resources
.IResource
;
31 import org
.eclipse
.core
.resources
.IResourceChangeEvent
;
32 import org
.eclipse
.core
.resources
.IResourceChangeListener
;
33 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
34 import org
.eclipse
.core
.runtime
.CoreException
;
35 import org
.eclipse
.core
.runtime
.Preferences
;
36 import org
.eclipse
.core
.runtime
.QualifiedName
;
37 import org
.eclipse
.osgi
.util
.NLS
;
38 import org
.eclipse
.team
.core
.RepositoryProvider
;
39 import org
.spearce
.egit
.core
.Activator
;
40 import org
.spearce
.egit
.core
.CoreText
;
41 import org
.spearce
.egit
.core
.GitCorePreferences
;
42 import org
.spearce
.egit
.core
.GitProvider
;
43 import org
.spearce
.jgit
.lib
.Repository
;
44 import org
.spearce
.jgit
.lib
.WindowCache
;
45 import org
.spearce
.jgit
.lib
.WindowCacheConfig
;
48 * This class keeps information about how a project is mapped to
51 public class GitProjectData
{
52 private static final Map
<IProject
, GitProjectData
> projectDataCache
= new HashMap
<IProject
, GitProjectData
>();
54 private static final Map
<File
, WeakReference
> repositoryCache
= new HashMap
<File
, WeakReference
>();
56 private static Set
<RepositoryChangeListener
> repositoryChangeListeners
= new HashSet
<RepositoryChangeListener
>();
58 @SuppressWarnings("synthetic-access")
59 private static final IResourceChangeListener rcl
= new RCL();
61 private static class RCL
implements IResourceChangeListener
{
62 @SuppressWarnings("synthetic-access")
63 public void resourceChanged(final IResourceChangeEvent event
) {
64 switch (event
.getType()) {
65 case IResourceChangeEvent
.PRE_CLOSE
:
66 uncache((IProject
) event
.getResource());
68 case IResourceChangeEvent
.PRE_DELETE
:
69 delete((IProject
) event
.getResource());
77 private static QualifiedName MAPPING_KEY
= new QualifiedName(
78 GitProjectData
.class.getName(), "RepositoryMapping");
81 * Start listening for resource changes.
83 * @param includeChange true to listen to content changes
85 public static void attachToWorkspace(final boolean includeChange
) {
86 trace("attachToWorkspace - addResourceChangeListener");
87 ResourcesPlugin
.getWorkspace().addResourceChangeListener(
89 (includeChange ? IResourceChangeEvent
.POST_CHANGE
: 0)
90 | IResourceChangeEvent
.PRE_CLOSE
91 | IResourceChangeEvent
.PRE_DELETE
);
95 * Stop listening to resource changes
97 public static void detachFromWorkspace() {
98 trace("detachFromWorkspace - removeResourceChangeListener");
99 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(rcl
);
103 * Register a new listener for repository modification events.
105 * This is a no-op if <code>objectThatCares</code> has already been
109 * @param objectThatCares
110 * the new listener to register. Must not be null.
112 public static synchronized void addRepositoryChangeListener(
113 final RepositoryChangeListener objectThatCares
) {
114 if (objectThatCares
== null)
115 throw new NullPointerException();
116 repositoryChangeListeners
.add(objectThatCares
);
120 * Remove a registered {@link RepositoryChangeListener}
122 * @param objectThatCares
123 * The listener to remove
125 public static synchronized void removeRepositoryChangeListener(
126 final RepositoryChangeListener objectThatCares
) {
127 repositoryChangeListeners
.remove(objectThatCares
);
131 * Notify registered {@link RepositoryChangeListener}s of a change.
134 * the repository which has had changes occur within it.
136 static void fireRepositoryChanged(final RepositoryMapping which
) {
137 for (RepositoryChangeListener listener
: getRepositoryChangeListeners())
138 listener
.repositoryChanged(which
);
142 * Get a copy of the current set of repository change listeners
144 * The array has no references, so is safe for iteration and modification
146 * @return a copy of the current repository change listeners
148 private static synchronized RepositoryChangeListener
[] getRepositoryChangeListeners() {
149 return repositoryChangeListeners
150 .toArray(new RepositoryChangeListener
[repositoryChangeListeners
156 * @return {@link GitProjectData} for the specified project
158 public synchronized static GitProjectData
get(final IProject p
) {
160 GitProjectData d
= lookup(p
);
162 && RepositoryProvider
.getProvider(p
) instanceof GitProvider
) {
163 d
= new GitProjectData(p
).load();
167 } catch (IOException err
) {
168 Activator
.logError(CoreText
.GitProjectData_missing
, err
);
174 * Drop the Eclipse project from our association of projects/repositories
176 * @param p Eclipse project
178 public static void delete(final IProject p
) {
179 trace("delete(" + p
.getName() + ")");
180 GitProjectData d
= lookup(p
);
183 d
= new GitProjectData(p
).load();
184 } catch (IOException ioe
) {
185 d
= new GitProjectData(p
);
191 static void trace(final String m
) {
192 Activator
.trace("(GitProjectData) " + m
);
195 private synchronized static void cache(final IProject p
,
196 final GitProjectData d
) {
197 projectDataCache
.put(p
, d
);
200 private synchronized static void uncache(final IProject p
) {
201 if (projectDataCache
.remove(p
) != null) {
202 trace("uncacheDataFor(" + p
.getName() + ")");
206 private synchronized static GitProjectData
lookup(final IProject p
) {
207 return projectDataCache
.get(p
);
210 private synchronized static Repository
lookupRepository(final File gitDir
)
212 final Iterator i
= repositoryCache
.entrySet().iterator();
213 while (i
.hasNext()) {
214 final Map
.Entry e
= (Map
.Entry
) i
.next();
215 if (((Reference
) e
.getValue()).get() == null) {
220 final Reference r
= repositoryCache
.get(gitDir
);
221 Repository d
= r
!= null ?
(Repository
) r
.get() : null;
223 d
= new Repository(gitDir
);
224 repositoryCache
.put(gitDir
, new WeakReference
<Repository
>(d
));
230 * Update the settings for the global window cache of the workspace.
232 public static void reconfigureWindowCache() {
233 final WindowCacheConfig c
= new WindowCacheConfig();
234 Preferences p
= Activator
.getDefault().getPluginPreferences();
235 c
.setPackedGitLimit(p
.getInt(GitCorePreferences
.core_packedGitLimit
));
236 c
.setPackedGitWindowSize(p
.getInt(GitCorePreferences
.core_packedGitWindowSize
));
237 c
.setPackedGitMMAP(p
.getBoolean(GitCorePreferences
.core_packedGitMMAP
));
238 c
.setDeltaBaseCacheLimit(p
.getInt(GitCorePreferences
.core_deltaBaseCacheLimit
));
239 WindowCache
.reconfigure(c
);
242 private final IProject project
;
244 private final Collection
<RepositoryMapping
> mappings
= new ArrayList
<RepositoryMapping
>();
246 private final Set
<IResource
> protectedResources
= new HashSet
<IResource
>();
249 * Construct a {@link GitProjectData} for the mapping
252 * @param p Eclipse project
254 public GitProjectData(final IProject p
) {
259 * @return the Eclipse project mapped through this resource.
261 public IProject
getProject() {
266 * TODO: is this right?
270 public void setRepositoryMappings(final Collection
<RepositoryMapping
> newMappings
) {
272 mappings
.addAll(newMappings
);
277 * Hide our private parts from the navigators other browsers.
279 * @throws CoreException
281 public void markTeamPrivateResources() throws CoreException
{
282 for (final Object rmObj
: mappings
) {
283 final RepositoryMapping rm
= (RepositoryMapping
)rmObj
;
284 final IContainer c
= rm
.getContainer();
286 continue; // Not fully mapped yet?
288 final IResource dotGit
= c
.findMember(".git");
289 if (dotGit
!= null) {
291 final Repository r
= rm
.getRepository();
292 final File dotGitDir
= dotGit
.getLocation().toFile()
294 if (dotGitDir
.equals(r
.getDirectory())) {
295 trace("teamPrivate " + dotGit
);
296 dotGit
.setTeamPrivateMember(true);
298 } catch (IOException err
) {
299 throw Activator
.error(CoreText
.Error_CanonicalFile
, err
);
307 * @return true if a resource is protected in this repository
309 public boolean isProtected(final IResource f
) {
310 return protectedResources
.contains(f
);
314 * @param r any workbench resource contained within this project.
315 * @return the mapping for the specified project
317 public RepositoryMapping
getRepositoryMapping(IResource r
) {
319 for (; r
!= null; r
= r
.getParent()) {
320 final RepositoryMapping m
;
322 if (!r
.isAccessible())
324 m
= (RepositoryMapping
) r
.getSessionProperty(MAPPING_KEY
);
328 } catch (CoreException err
) {
329 Activator
.logError("Failed finding RepositoryMapping", err
);
334 private void delete() {
335 final File dir
= propertyFile().getParentFile();
336 final File
[] todel
= dir
.listFiles();
338 for (int k
= 0; k
< todel
.length
; k
++) {
339 if (todel
[k
].isFile()) {
345 trace("deleteDataFor(" + getProject().getName() + ")");
346 uncache(getProject());
350 * Store information about the repository connection in the workspace
352 * @throws CoreException
354 public void store() throws CoreException
{
355 final File dat
= propertyFile();
360 trace("save " + dat
);
361 tmp
= File
.createTempFile("gpd_", ".prop", dat
.getParentFile());
362 final FileOutputStream o
= new FileOutputStream(tmp
);
364 final Properties p
= new Properties();
365 final Iterator i
= mappings
.iterator();
366 while (i
.hasNext()) {
367 ((RepositoryMapping
) i
.next()).store(p
);
369 p
.store(o
, "GitProjectData");
377 } catch (IOException ioe
) {
378 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
383 if (!tmp
.renameTo(dat
)) {
385 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
390 private File
propertyFile() {
391 return new File(getProject()
392 .getWorkingLocation(Activator
.getPluginId()).toFile(),
393 "GitProjectData.properties");
396 private GitProjectData
load() throws IOException
{
397 final File dat
= propertyFile();
398 trace("load " + dat
);
400 final FileInputStream o
= new FileInputStream(dat
);
402 final Properties p
= new Properties();
406 final Iterator keyItr
= p
.keySet().iterator();
407 while (keyItr
.hasNext()) {
408 final String key
= keyItr
.next().toString();
409 if (RepositoryMapping
.isInitialKey(key
)) {
410 mappings
.add(new RepositoryMapping(p
, key
));
421 private void remapAll() {
422 protectedResources
.clear();
423 final Iterator i
= mappings
.iterator();
424 while (i
.hasNext()) {
425 map((RepositoryMapping
) i
.next());
429 private void map(final RepositoryMapping m
) {
432 final IResource dotGit
;
436 r
= getProject().findMember(m
.getContainerPath());
437 if (r
instanceof IContainer
) {
440 c
= (IContainer
) r
.getAdapter(IContainer
.class);
444 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
445 new FileNotFoundException(m
.getContainerPath().toString()));
451 git
= c
.getLocation().append(m
.getGitDirPath()).toFile();
452 if (!git
.isDirectory() || !new File(git
, "config").isFile()) {
453 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
454 new FileNotFoundException(m
.getContainerPath().toString()));
460 m
.setRepository(lookupRepository(git
));
461 } catch (IOException ioe
) {
462 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
463 new FileNotFoundException(m
.getContainerPath().toString()));
468 m
.fireRepositoryChanged();
470 trace("map " + c
+ " -> " + m
.getRepository());
472 c
.setSessionProperty(MAPPING_KEY
, m
);
473 } catch (CoreException err
) {
474 Activator
.logError("Failed to cache RepositoryMapping", err
);
477 dotGit
= c
.findMember(".git");
478 if (dotGit
!= null && dotGit
.getLocation().toFile().equals(git
)) {
483 private void protect(IResource c
) {
484 while (c
!= null && !c
.equals(getProject())) {
485 trace("protect " + c
);
486 protectedResources
.add(c
);