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
.Constants
;
45 import org
.eclipse
.jgit
.lib
.Repository
;
46 import org
.eclipse
.jgit
.lib
.WindowCache
;
47 import org
.eclipse
.jgit
.lib
.WindowCacheConfig
;
50 * This class keeps information about how a project is mapped to
53 public class GitProjectData
{
54 private static final Map
<IProject
, GitProjectData
> projectDataCache
= new HashMap
<IProject
, GitProjectData
>();
56 private static final Map
<File
, WeakReference
> repositoryCache
= new HashMap
<File
, WeakReference
>();
58 private static Set
<RepositoryChangeListener
> repositoryChangeListeners
= new HashSet
<RepositoryChangeListener
>();
60 @SuppressWarnings("synthetic-access")
61 private static final IResourceChangeListener rcl
= new RCL();
63 private static class RCL
implements IResourceChangeListener
{
64 @SuppressWarnings("synthetic-access")
65 public void resourceChanged(final IResourceChangeEvent event
) {
66 switch (event
.getType()) {
67 case IResourceChangeEvent
.PRE_CLOSE
:
68 uncache((IProject
) event
.getResource());
70 case IResourceChangeEvent
.PRE_DELETE
:
71 delete((IProject
) event
.getResource());
79 private static QualifiedName MAPPING_KEY
= new QualifiedName(
80 GitProjectData
.class.getName(), "RepositoryMapping"); //$NON-NLS-1$
83 * Start listening for resource changes.
85 * @param includeChange true to listen to content changes
87 public static void attachToWorkspace(final boolean includeChange
) {
88 trace("attachToWorkspace - addResourceChangeListener"); //$NON-NLS-1$
89 ResourcesPlugin
.getWorkspace().addResourceChangeListener(
91 (includeChange ? IResourceChangeEvent
.POST_CHANGE
: 0)
92 | IResourceChangeEvent
.PRE_CLOSE
93 | IResourceChangeEvent
.PRE_DELETE
);
97 * Stop listening to resource changes
99 public static void detachFromWorkspace() {
100 trace("detachFromWorkspace - removeResourceChangeListener"); //$NON-NLS-1$
101 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(rcl
);
105 * Register a new listener for repository modification events.
107 * This is a no-op if <code>objectThatCares</code> has already been
111 * @param objectThatCares
112 * the new listener to register. Must not be null.
114 public static synchronized void addRepositoryChangeListener(
115 final RepositoryChangeListener objectThatCares
) {
116 if (objectThatCares
== null)
117 throw new NullPointerException();
118 repositoryChangeListeners
.add(objectThatCares
);
122 * Remove a registered {@link RepositoryChangeListener}
124 * @param objectThatCares
125 * The listener to remove
127 public static synchronized void removeRepositoryChangeListener(
128 final RepositoryChangeListener objectThatCares
) {
129 repositoryChangeListeners
.remove(objectThatCares
);
133 * Notify registered {@link RepositoryChangeListener}s of a change.
136 * the repository which has had changes occur within it.
138 static void fireRepositoryChanged(final RepositoryMapping which
) {
139 for (RepositoryChangeListener listener
: getRepositoryChangeListeners())
140 listener
.repositoryChanged(which
);
144 * Get a copy of the current set of repository change listeners
146 * The array has no references, so is safe for iteration and modification
148 * @return a copy of the current repository change listeners
150 private static synchronized RepositoryChangeListener
[] getRepositoryChangeListeners() {
151 return repositoryChangeListeners
152 .toArray(new RepositoryChangeListener
[repositoryChangeListeners
158 * @return {@link GitProjectData} for the specified project
160 public synchronized static GitProjectData
get(final IProject p
) {
162 GitProjectData d
= lookup(p
);
164 && RepositoryProvider
.getProvider(p
) instanceof GitProvider
) {
165 d
= new GitProjectData(p
).load();
169 } catch (IOException err
) {
170 Activator
.logError(CoreText
.GitProjectData_missing
, err
);
176 * Drop the Eclipse project from our association of projects/repositories
178 * @param p Eclipse project
180 public static void delete(final IProject p
) {
181 trace("delete(" + p
.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
182 GitProjectData d
= lookup(p
);
185 d
= new GitProjectData(p
).load();
186 } catch (IOException ioe
) {
187 d
= new GitProjectData(p
);
193 static void trace(final String m
) {
194 Activator
.trace("(GitProjectData) " + m
); //$NON-NLS-1$
197 private synchronized static void cache(final IProject p
,
198 final GitProjectData d
) {
199 projectDataCache
.put(p
, d
);
202 private synchronized static void uncache(final IProject p
) {
203 if (projectDataCache
.remove(p
) != null) {
204 trace("uncacheDataFor(" //$NON-NLS-1$
205 + p
.getName() + ")"); //$NON-NLS-1$
209 private synchronized static GitProjectData
lookup(final IProject p
) {
210 return projectDataCache
.get(p
);
213 private synchronized static Repository
lookupRepository(final File gitDir
)
215 final Iterator i
= repositoryCache
.entrySet().iterator();
216 while (i
.hasNext()) {
217 final Map
.Entry e
= (Map
.Entry
) i
.next();
218 if (((Reference
) e
.getValue()).get() == null) {
223 final Reference r
= repositoryCache
.get(gitDir
);
224 Repository d
= r
!= null ?
(Repository
) r
.get() : null;
226 d
= new Repository(gitDir
);
227 repositoryCache
.put(gitDir
, new WeakReference
<Repository
>(d
));
233 * Update the settings for the global window cache of the workspace.
235 public static void reconfigureWindowCache() {
236 final WindowCacheConfig c
= new WindowCacheConfig();
237 Preferences p
= Activator
.getDefault().getPluginPreferences();
238 c
.setPackedGitLimit(p
.getInt(GitCorePreferences
.core_packedGitLimit
));
239 c
.setPackedGitWindowSize(p
.getInt(GitCorePreferences
.core_packedGitWindowSize
));
240 c
.setPackedGitMMAP(p
.getBoolean(GitCorePreferences
.core_packedGitMMAP
));
241 c
.setDeltaBaseCacheLimit(p
.getInt(GitCorePreferences
.core_deltaBaseCacheLimit
));
242 WindowCache
.reconfigure(c
);
245 private final IProject project
;
247 private final Collection
<RepositoryMapping
> mappings
= new ArrayList
<RepositoryMapping
>();
249 private final Set
<IResource
> protectedResources
= new HashSet
<IResource
>();
252 * Construct a {@link GitProjectData} for the mapping
255 * @param p Eclipse project
257 public GitProjectData(final IProject p
) {
262 * @return the Eclipse project mapped through this resource.
264 public IProject
getProject() {
269 * TODO: is this right?
273 public void setRepositoryMappings(final Collection
<RepositoryMapping
> newMappings
) {
275 mappings
.addAll(newMappings
);
280 * Hide our private parts from the navigators other browsers.
282 * @throws CoreException
284 public void markTeamPrivateResources() throws CoreException
{
285 for (final Object rmObj
: mappings
) {
286 final RepositoryMapping rm
= (RepositoryMapping
)rmObj
;
287 final IContainer c
= rm
.getContainer();
289 continue; // Not fully mapped yet?
291 final IResource dotGit
= c
.findMember(Constants
.DOT_GIT
);
292 if (dotGit
!= null) {
294 final Repository r
= rm
.getRepository();
295 final File dotGitDir
= dotGit
.getLocation().toFile()
297 if (dotGitDir
.equals(r
.getDirectory())) {
298 trace("teamPrivate " + dotGit
); //$NON-NLS-1$
299 dotGit
.setTeamPrivateMember(true);
301 } catch (IOException err
) {
302 throw Activator
.error(CoreText
.Error_CanonicalFile
, err
);
310 * @return true if a resource is protected in this repository
312 public boolean isProtected(final IResource f
) {
313 return protectedResources
.contains(f
);
317 * @param r any workbench resource contained within this project.
318 * @return the mapping for the specified project
320 public RepositoryMapping
getRepositoryMapping(IResource r
) {
322 for (; r
!= null; r
= r
.getParent()) {
323 final RepositoryMapping m
;
325 if (!r
.isAccessible())
327 m
= (RepositoryMapping
) r
.getSessionProperty(MAPPING_KEY
);
331 } catch (CoreException err
) {
333 CoreText
.GitProjectData_failedFindingRepoMapping
, err
);
338 private void delete() {
339 final File dir
= propertyFile().getParentFile();
340 final File
[] todel
= dir
.listFiles();
342 for (int k
= 0; k
< todel
.length
; k
++) {
343 if (todel
[k
].isFile()) {
349 trace("deleteDataFor(" //$NON-NLS-1$
350 + getProject().getName()
351 + ")"); //$NON-NLS-1$
352 uncache(getProject());
356 * Store information about the repository connection in the workspace
358 * @throws CoreException
360 public void store() throws CoreException
{
361 final File dat
= propertyFile();
366 trace("save " + dat
); //$NON-NLS-1$
367 tmp
= File
.createTempFile(
368 "gpd_", //$NON-NLS-1$
369 ".prop", //$NON-NLS-1$
370 dat
.getParentFile());
371 final FileOutputStream o
= new FileOutputStream(tmp
);
373 final Properties p
= new Properties();
374 final Iterator i
= mappings
.iterator();
375 while (i
.hasNext()) {
376 ((RepositoryMapping
) i
.next()).store(p
);
378 p
.store(o
, "GitProjectData"); //$NON-NLS-1$
386 } catch (IOException ioe
) {
387 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
392 if (!tmp
.renameTo(dat
)) {
394 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
399 private File
propertyFile() {
400 return new File(getProject()
401 .getWorkingLocation(Activator
.getPluginId()).toFile(),
402 "GitProjectData.properties"); //$NON-NLS-1$
405 private GitProjectData
load() throws IOException
{
406 final File dat
= propertyFile();
407 trace("load " + dat
); //$NON-NLS-1$
409 final FileInputStream o
= new FileInputStream(dat
);
411 final Properties p
= new Properties();
415 final Iterator keyItr
= p
.keySet().iterator();
416 while (keyItr
.hasNext()) {
417 final String key
= keyItr
.next().toString();
418 if (RepositoryMapping
.isInitialKey(key
)) {
419 mappings
.add(new RepositoryMapping(p
, key
));
430 private void remapAll() {
431 protectedResources
.clear();
432 final Iterator i
= mappings
.iterator();
433 while (i
.hasNext()) {
434 map((RepositoryMapping
) i
.next());
438 private void map(final RepositoryMapping m
) {
441 final IResource dotGit
;
445 r
= getProject().findMember(m
.getContainerPath());
446 if (r
instanceof IContainer
) {
449 c
= (IContainer
) r
.getAdapter(IContainer
.class);
453 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
454 new FileNotFoundException(m
.getContainerPath().toString()));
460 git
= c
.getLocation().append(m
.getGitDirPath()).toFile();
461 if (!git
.isDirectory()
462 || !new File(git
, "config").isFile()) { //$NON-NLS-1$
463 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
464 new FileNotFoundException(m
.getContainerPath().toString()));
470 m
.setRepository(lookupRepository(git
));
471 } catch (IOException ioe
) {
472 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
473 new FileNotFoundException(m
.getContainerPath().toString()));
478 m
.fireRepositoryChanged();
480 trace("map " //$NON-NLS-1$
482 + " -> " //$NON-NLS-1$
483 + m
.getRepository());
485 c
.setSessionProperty(MAPPING_KEY
, m
);
486 } catch (CoreException err
) {
488 CoreText
.GitProjectData_failedToCacheRepoMapping
, err
);
491 dotGit
= c
.findMember(Constants
.DOT_GIT
);
492 if (dotGit
!= null && dotGit
.getLocation().toFile().equals(git
)) {
497 private void protect(IResource c
) {
498 while (c
!= null && !c
.equals(getProject())) {
499 trace("protect " + c
); //$NON-NLS-1$
500 protectedResources
.add(c
);