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
.osgi
.util
.NLS
;
43 import org
.eclipse
.team
.core
.RepositoryProvider
;
44 import org
.eclipse
.jgit
.lib
.Repository
;
45 import org
.eclipse
.jgit
.lib
.WindowCache
;
46 import org
.eclipse
.jgit
.lib
.WindowCacheConfig
;
49 * This class keeps information about how a project is mapped to
52 public class GitProjectData
{
53 private static final Map
<IProject
, GitProjectData
> projectDataCache
= new HashMap
<IProject
, GitProjectData
>();
55 private static final Map
<File
, WeakReference
> repositoryCache
= new HashMap
<File
, WeakReference
>();
57 private static Set
<RepositoryChangeListener
> repositoryChangeListeners
= new HashSet
<RepositoryChangeListener
>();
59 @SuppressWarnings("synthetic-access")
60 private static final IResourceChangeListener rcl
= new RCL();
62 private static class RCL
implements IResourceChangeListener
{
63 @SuppressWarnings("synthetic-access")
64 public void resourceChanged(final IResourceChangeEvent event
) {
65 switch (event
.getType()) {
66 case IResourceChangeEvent
.PRE_CLOSE
:
67 uncache((IProject
) event
.getResource());
69 case IResourceChangeEvent
.PRE_DELETE
:
70 delete((IProject
) event
.getResource());
78 private static QualifiedName MAPPING_KEY
= new QualifiedName(
79 GitProjectData
.class.getName(), "RepositoryMapping");
82 * Start listening for resource changes.
84 * @param includeChange true to listen to content changes
86 public static void attachToWorkspace(final boolean includeChange
) {
87 trace("attachToWorkspace - addResourceChangeListener");
88 ResourcesPlugin
.getWorkspace().addResourceChangeListener(
90 (includeChange ? IResourceChangeEvent
.POST_CHANGE
: 0)
91 | IResourceChangeEvent
.PRE_CLOSE
92 | IResourceChangeEvent
.PRE_DELETE
);
96 * Stop listening to resource changes
98 public static void detachFromWorkspace() {
99 trace("detachFromWorkspace - removeResourceChangeListener");
100 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(rcl
);
104 * Register a new listener for repository modification events.
106 * This is a no-op if <code>objectThatCares</code> has already been
110 * @param objectThatCares
111 * the new listener to register. Must not be null.
113 public static synchronized void addRepositoryChangeListener(
114 final RepositoryChangeListener objectThatCares
) {
115 if (objectThatCares
== null)
116 throw new NullPointerException();
117 repositoryChangeListeners
.add(objectThatCares
);
121 * Remove a registered {@link RepositoryChangeListener}
123 * @param objectThatCares
124 * The listener to remove
126 public static synchronized void removeRepositoryChangeListener(
127 final RepositoryChangeListener objectThatCares
) {
128 repositoryChangeListeners
.remove(objectThatCares
);
132 * Notify registered {@link RepositoryChangeListener}s of a change.
135 * the repository which has had changes occur within it.
137 static void fireRepositoryChanged(final RepositoryMapping which
) {
138 for (RepositoryChangeListener listener
: getRepositoryChangeListeners())
139 listener
.repositoryChanged(which
);
143 * Get a copy of the current set of repository change listeners
145 * The array has no references, so is safe for iteration and modification
147 * @return a copy of the current repository change listeners
149 private static synchronized RepositoryChangeListener
[] getRepositoryChangeListeners() {
150 return repositoryChangeListeners
151 .toArray(new RepositoryChangeListener
[repositoryChangeListeners
157 * @return {@link GitProjectData} for the specified project
159 public synchronized static GitProjectData
get(final IProject p
) {
161 GitProjectData d
= lookup(p
);
163 && RepositoryProvider
.getProvider(p
) instanceof GitProvider
) {
164 d
= new GitProjectData(p
).load();
168 } catch (IOException err
) {
169 Activator
.logError(CoreText
.GitProjectData_missing
, err
);
175 * Drop the Eclipse project from our association of projects/repositories
177 * @param p Eclipse project
179 public static void delete(final IProject p
) {
180 trace("delete(" + p
.getName() + ")");
181 GitProjectData d
= lookup(p
);
184 d
= new GitProjectData(p
).load();
185 } catch (IOException ioe
) {
186 d
= new GitProjectData(p
);
192 static void trace(final String m
) {
193 Activator
.trace("(GitProjectData) " + m
);
196 private synchronized static void cache(final IProject p
,
197 final GitProjectData d
) {
198 projectDataCache
.put(p
, d
);
201 private synchronized static void uncache(final IProject p
) {
202 if (projectDataCache
.remove(p
) != null) {
203 trace("uncacheDataFor(" + p
.getName() + ")");
207 private synchronized static GitProjectData
lookup(final IProject p
) {
208 return projectDataCache
.get(p
);
211 private synchronized static Repository
lookupRepository(final File gitDir
)
213 final Iterator i
= repositoryCache
.entrySet().iterator();
214 while (i
.hasNext()) {
215 final Map
.Entry e
= (Map
.Entry
) i
.next();
216 if (((Reference
) e
.getValue()).get() == null) {
221 final Reference r
= repositoryCache
.get(gitDir
);
222 Repository d
= r
!= null ?
(Repository
) r
.get() : null;
224 d
= new Repository(gitDir
);
225 repositoryCache
.put(gitDir
, new WeakReference
<Repository
>(d
));
231 * Update the settings for the global window cache of the workspace.
233 public static void reconfigureWindowCache() {
234 final WindowCacheConfig c
= new WindowCacheConfig();
235 Preferences p
= Activator
.getDefault().getPluginPreferences();
236 c
.setPackedGitLimit(p
.getInt(GitCorePreferences
.core_packedGitLimit
));
237 c
.setPackedGitWindowSize(p
.getInt(GitCorePreferences
.core_packedGitWindowSize
));
238 c
.setPackedGitMMAP(p
.getBoolean(GitCorePreferences
.core_packedGitMMAP
));
239 c
.setDeltaBaseCacheLimit(p
.getInt(GitCorePreferences
.core_deltaBaseCacheLimit
));
240 WindowCache
.reconfigure(c
);
243 private final IProject project
;
245 private final Collection
<RepositoryMapping
> mappings
= new ArrayList
<RepositoryMapping
>();
247 private final Set
<IResource
> protectedResources
= new HashSet
<IResource
>();
250 * Construct a {@link GitProjectData} for the mapping
253 * @param p Eclipse project
255 public GitProjectData(final IProject p
) {
260 * @return the Eclipse project mapped through this resource.
262 public IProject
getProject() {
267 * TODO: is this right?
271 public void setRepositoryMappings(final Collection
<RepositoryMapping
> newMappings
) {
273 mappings
.addAll(newMappings
);
278 * Hide our private parts from the navigators other browsers.
280 * @throws CoreException
282 public void markTeamPrivateResources() throws CoreException
{
283 for (final Object rmObj
: mappings
) {
284 final RepositoryMapping rm
= (RepositoryMapping
)rmObj
;
285 final IContainer c
= rm
.getContainer();
287 continue; // Not fully mapped yet?
289 final IResource dotGit
= c
.findMember(".git");
290 if (dotGit
!= null) {
292 final Repository r
= rm
.getRepository();
293 final File dotGitDir
= dotGit
.getLocation().toFile()
295 if (dotGitDir
.equals(r
.getDirectory())) {
296 trace("teamPrivate " + dotGit
);
297 dotGit
.setTeamPrivateMember(true);
299 } catch (IOException err
) {
300 throw Activator
.error(CoreText
.Error_CanonicalFile
, err
);
308 * @return true if a resource is protected in this repository
310 public boolean isProtected(final IResource f
) {
311 return protectedResources
.contains(f
);
315 * @param r any workbench resource contained within this project.
316 * @return the mapping for the specified project
318 public RepositoryMapping
getRepositoryMapping(IResource r
) {
320 for (; r
!= null; r
= r
.getParent()) {
321 final RepositoryMapping m
;
323 if (!r
.isAccessible())
325 m
= (RepositoryMapping
) r
.getSessionProperty(MAPPING_KEY
);
329 } catch (CoreException err
) {
330 Activator
.logError("Failed finding RepositoryMapping", err
);
335 private void delete() {
336 final File dir
= propertyFile().getParentFile();
337 final File
[] todel
= dir
.listFiles();
339 for (int k
= 0; k
< todel
.length
; k
++) {
340 if (todel
[k
].isFile()) {
346 trace("deleteDataFor(" + getProject().getName() + ")");
347 uncache(getProject());
351 * Store information about the repository connection in the workspace
353 * @throws CoreException
355 public void store() throws CoreException
{
356 final File dat
= propertyFile();
361 trace("save " + dat
);
362 tmp
= File
.createTempFile("gpd_", ".prop", dat
.getParentFile());
363 final FileOutputStream o
= new FileOutputStream(tmp
);
365 final Properties p
= new Properties();
366 final Iterator i
= mappings
.iterator();
367 while (i
.hasNext()) {
368 ((RepositoryMapping
) i
.next()).store(p
);
370 p
.store(o
, "GitProjectData");
378 } catch (IOException ioe
) {
379 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
384 if (!tmp
.renameTo(dat
)) {
386 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
391 private File
propertyFile() {
392 return new File(getProject()
393 .getWorkingLocation(Activator
.getPluginId()).toFile(),
394 "GitProjectData.properties");
397 private GitProjectData
load() throws IOException
{
398 final File dat
= propertyFile();
399 trace("load " + dat
);
401 final FileInputStream o
= new FileInputStream(dat
);
403 final Properties p
= new Properties();
407 final Iterator keyItr
= p
.keySet().iterator();
408 while (keyItr
.hasNext()) {
409 final String key
= keyItr
.next().toString();
410 if (RepositoryMapping
.isInitialKey(key
)) {
411 mappings
.add(new RepositoryMapping(p
, key
));
422 private void remapAll() {
423 protectedResources
.clear();
424 final Iterator i
= mappings
.iterator();
425 while (i
.hasNext()) {
426 map((RepositoryMapping
) i
.next());
430 private void map(final RepositoryMapping m
) {
433 final IResource dotGit
;
437 r
= getProject().findMember(m
.getContainerPath());
438 if (r
instanceof IContainer
) {
441 c
= (IContainer
) r
.getAdapter(IContainer
.class);
445 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
446 new FileNotFoundException(m
.getContainerPath().toString()));
452 git
= c
.getLocation().append(m
.getGitDirPath()).toFile();
453 if (!git
.isDirectory() || !new File(git
, "config").isFile()) {
454 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
455 new FileNotFoundException(m
.getContainerPath().toString()));
461 m
.setRepository(lookupRepository(git
));
462 } catch (IOException ioe
) {
463 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
464 new FileNotFoundException(m
.getContainerPath().toString()));
469 m
.fireRepositoryChanged();
471 trace("map " + c
+ " -> " + m
.getRepository());
473 c
.setSessionProperty(MAPPING_KEY
, m
);
474 } catch (CoreException err
) {
475 Activator
.logError("Failed to cache RepositoryMapping", err
);
478 dotGit
= c
.findMember(".git");
479 if (dotGit
!= null && dotGit
.getLocation().toFile().equals(git
)) {
484 private void protect(IResource c
) {
485 while (c
!= null && !c
.equals(getProject())) {
486 trace("protect " + c
);
487 protectedResources
.add(c
);