2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License, version 2.1, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.egit
.core
.project
;
20 import java
.io
.FileInputStream
;
21 import java
.io
.FileNotFoundException
;
22 import java
.io
.FileOutputStream
;
23 import java
.io
.IOException
;
24 import java
.lang
.ref
.Reference
;
25 import java
.lang
.ref
.WeakReference
;
26 import java
.util
.ArrayList
;
27 import java
.util
.Collection
;
28 import java
.util
.HashMap
;
29 import java
.util
.HashSet
;
30 import java
.util
.Iterator
;
32 import java
.util
.Properties
;
35 import org
.eclipse
.core
.resources
.IContainer
;
36 import org
.eclipse
.core
.resources
.IProject
;
37 import org
.eclipse
.core
.resources
.IResource
;
38 import org
.eclipse
.core
.resources
.IResourceChangeEvent
;
39 import org
.eclipse
.core
.resources
.IResourceChangeListener
;
40 import org
.eclipse
.core
.resources
.IResourceDelta
;
41 import org
.eclipse
.core
.resources
.IResourceDeltaVisitor
;
42 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
43 import org
.eclipse
.core
.runtime
.CoreException
;
44 import org
.eclipse
.osgi
.util
.NLS
;
45 import org
.eclipse
.team
.core
.RepositoryProvider
;
46 import org
.spearce
.egit
.core
.Activator
;
47 import org
.spearce
.egit
.core
.CoreText
;
48 import org
.spearce
.egit
.core
.GitProvider
;
49 import org
.spearce
.jgit
.lib
.FileTreeEntry
;
50 import org
.spearce
.jgit
.lib
.Repository
;
51 import org
.spearce
.jgit
.lib
.Tree
;
52 import org
.spearce
.jgit
.lib
.TreeEntry
;
54 public class GitProjectData
{
55 private static final Map projectDataCache
= new HashMap();
57 private static final Map repositoryCache
= new HashMap();
59 private static RepositoryChangeListener
[] repositoryChangeListeners
= {};
61 private static final IResourceChangeListener rcl
= new RCL();
63 private static class RCL
implements IResourceChangeListener
{
64 public void resourceChanged(final IResourceChangeEvent event
) {
65 switch (event
.getType()) {
66 case IResourceChangeEvent
.POST_CHANGE
:
67 projectsChanged(event
.getDelta().getAffectedChildren(
68 IResourceDelta
.CHANGED
));
70 case IResourceChangeEvent
.PRE_CLOSE
:
71 uncache((IProject
) event
.getResource());
73 case IResourceChangeEvent
.PRE_DELETE
:
74 delete((IProject
) event
.getResource());
82 public static void attachToWorkspace(final boolean includeChange
) {
83 trace("attachToWorkspace - addResourceChangeListener");
84 ResourcesPlugin
.getWorkspace().addResourceChangeListener(
86 (includeChange ? IResourceChangeEvent
.POST_CHANGE
: 0)
87 | IResourceChangeEvent
.PRE_CLOSE
88 | IResourceChangeEvent
.PRE_DELETE
);
91 public static void detachFromWorkspace() {
92 trace("detachFromWorkspace - removeResourceChangeListener");
93 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(rcl
);
97 * Register a new listener for repository modification events.
99 * This is a no-op if <code>objectThatCares</code> has already been
103 * @param objectThatCares
104 * the new listener to register. Must not be null.
106 public static synchronized void addRepositoryChangeListener(
107 final RepositoryChangeListener objectThatCares
) {
108 if (objectThatCares
== null)
109 throw new NullPointerException();
110 for (int k
= repositoryChangeListeners
.length
- 1; k
>= 0; k
--) {
111 if (repositoryChangeListeners
[k
] == objectThatCares
)
114 final int p
= repositoryChangeListeners
.length
;
115 final RepositoryChangeListener
[] n
;
116 n
= new RepositoryChangeListener
[p
+ 1];
117 System
.arraycopy(repositoryChangeListeners
, 0, n
, 0, p
);
118 n
[p
] = objectThatCares
;
119 repositoryChangeListeners
= n
;
123 * Notify registered {@link RepositoryChangeListener}s of a change.
126 * the repository which has had changes occur within it.
128 static void fireRepositoryChanged(final RepositoryMapping which
) {
129 final RepositoryChangeListener
[] e
= getRepositoryChangeListeners();
130 for (int k
= e
.length
- 1; k
>= 0; k
--)
131 e
[k
].repositoryChanged(which
);
134 private static synchronized RepositoryChangeListener
[] getRepositoryChangeListeners() {
135 return repositoryChangeListeners
;
138 public synchronized static GitProjectData
get(final IProject p
) {
140 GitProjectData d
= lookup(p
);
142 && RepositoryProvider
.getProvider(p
) instanceof GitProvider
) {
143 d
= new GitProjectData(p
).load();
147 } catch (IOException err
) {
148 Activator
.logError(CoreText
.GitProjectData_missing
, err
);
153 public static void delete(final IProject p
) {
154 trace("delete(" + p
.getName() + ")");
155 GitProjectData d
= lookup(p
);
158 d
= new GitProjectData(p
).load();
159 } catch (IOException ioe
) {
160 d
= new GitProjectData(p
);
166 public static synchronized void checkpointAllProjects() {
167 final Iterator i
= projectDataCache
.values().iterator();
168 while (i
.hasNext()) {
169 ((GitProjectData
) i
.next()).checkpointIfNecessary();
173 private static void trace(final String m
) {
174 Activator
.trace("(GitProjectData) " + m
);
177 private static void projectsChanged(final IResourceDelta
[] projDeltas
) {
178 for (int k
= 0; k
< projDeltas
.length
; k
++) {
179 final IResource r
= projDeltas
[k
].getResource();
180 final GitProjectData d
= get((IProject
) r
);
182 d
.notifyChanged(projDeltas
[k
]);
187 private synchronized static void cache(final IProject p
,
188 final GitProjectData d
) {
189 projectDataCache
.put(p
, d
);
192 private synchronized static void uncache(final IProject p
) {
193 if (projectDataCache
.remove(p
) != null) {
194 trace("uncacheDataFor(" + p
.getName() + ")");
198 private synchronized static GitProjectData
lookup(final IProject p
) {
199 return (GitProjectData
) projectDataCache
.get(p
);
202 private synchronized static Repository
lookupRepository(final File gitDir
)
204 final Iterator i
= repositoryCache
.entrySet().iterator();
205 while (i
.hasNext()) {
206 final Map
.Entry e
= (Map
.Entry
) i
.next();
207 if (((Reference
) e
.getValue()).get() == null) {
212 final Reference r
= (Reference
) repositoryCache
.get(gitDir
);
213 Repository d
= r
!= null ?
(Repository
) r
.get() : null;
215 d
= new Repository(gitDir
);
216 repositoryCache
.put(gitDir
, new WeakReference(d
));
221 private final IProject project
;
223 private final Collection mappings
;
225 private final Map c2mapping
;
227 private final Set protectedResources
;
229 public GitProjectData(final IProject p
) {
231 mappings
= new ArrayList();
232 c2mapping
= new HashMap();
233 protectedResources
= new HashSet();
236 public IProject
getProject() {
240 public void setRepositoryMappings(final Collection newMappings
) {
242 mappings
.addAll(newMappings
);
246 public void markTeamPrivateResources() throws CoreException
{
247 final Iterator i
= c2mapping
.entrySet().iterator();
248 while (i
.hasNext()) {
249 final Map
.Entry e
= (Map
.Entry
) i
.next();
250 final IContainer c
= (IContainer
) e
.getKey();
251 final IResource dotGit
= c
.findMember(".git");
252 if (dotGit
!= null) {
254 final Repository r
= ((RepositoryMapping
) e
.getValue())
256 final File dotGitDir
= dotGit
.getLocation().toFile()
258 if (dotGitDir
.equals(r
.getDirectory())) {
259 trace("teamPrivate " + dotGit
);
260 dotGit
.setTeamPrivateMember(true);
262 } catch (IOException err
) {
263 throw Activator
.error(CoreText
.Error_CanonicalFile
, err
);
269 public boolean isProtected(final IResource f
) {
270 return protectedResources
.contains(f
);
273 public RepositoryMapping
getRepositoryMapping(final IResource r
) {
274 return (RepositoryMapping
) c2mapping
.get(r
);
277 public TreeEntry
[] getActiveDiffTreeEntries(IResource res
)
278 throws CoreException
{
280 RepositoryMapping m
= null;
284 m
= getRepositoryMapping(r
);
290 s
= r
.getName() + "/" + s
;
298 if (s
!= null && m
!= null && m
.getActiveDiff() != null) {
300 if (res
.getType() == IResource
.FILE
)
301 return m
.getActiveDiff().findBlobMember(s
);
303 return m
.getActiveDiff().findTreeMember(s
);
304 } catch (IOException ioe
) {
305 throw Activator
.error(
306 CoreText
.GitProjectData_lazyResolveFailed
, ioe
);
313 public void checkpointIfNecessary() {
314 final Iterator i
= c2mapping
.values().iterator();
315 while (i
.hasNext()) {
316 ((RepositoryMapping
) i
.next()).checkpointIfNecessary();
320 public void fullUpdate() {
321 final Iterator i
= c2mapping
.values().iterator();
322 while (i
.hasNext()) {
324 ((RepositoryMapping
) i
.next()).fullUpdate();
325 } catch (IOException ioe
) {
326 Activator
.logError(CoreText
.GitProjectData_cannotReadHEAD
, ioe
);
332 public void delete() {
333 final File dir
= propertyFile().getParentFile();
334 final File
[] todel
= dir
.listFiles();
336 for (int k
= 0; k
< todel
.length
; k
++) {
337 if (todel
[k
].isFile()) {
343 trace("deleteDataFor(" + getProject().getName() + ")");
344 uncache(getProject());
347 public void store() throws CoreException
{
348 final File dat
= propertyFile();
353 trace("save " + dat
);
354 tmp
= File
.createTempFile("gpd_", ".prop", dat
.getParentFile());
355 final FileOutputStream o
= new FileOutputStream(tmp
);
357 final Properties p
= new Properties();
358 final Iterator i
= mappings
.iterator();
359 while (i
.hasNext()) {
360 ((RepositoryMapping
) i
.next()).store(p
);
362 p
.store(o
, "GitProjectData");
370 } catch (IOException ioe
) {
371 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
376 if (!tmp
.renameTo(dat
)) {
378 throw Activator
.error(NLS
.bind(CoreText
.GitProjectData_saveFailed
,
383 private void notifyChanged(final IResourceDelta projDelta
) {
384 final Set affectedMappings
= new HashSet();
386 projDelta
.accept(new IResourceDeltaVisitor() {
387 public boolean visit(final IResourceDelta d
)
388 throws CoreException
{
389 final int f
= d
.getFlags();
390 IResource res
= d
.getResource();
392 if ((f
& IResourceDelta
.CONTENT
) != 0
393 || (f
& IResourceDelta
.ENCODING
) != 0
394 || r
instanceof IContainer
) {
396 RepositoryMapping m
= null;
399 m
= getRepositoryMapping(r
);
405 s
= r
.getName() + "/" + s
;
415 } else if (s
== null) {
419 final Tree cacheTree
= m
.getCacheTree();
420 if (cacheTree
!= null) {
422 synchronized (cacheTree
) {
424 if (res
.getType() == IResource
.FILE
)
425 e
= cacheTree
.findBlobMember(s
);
427 e
= cacheTree
.findTreeMember(s
);
428 if (e
instanceof FileTreeEntry
) {
429 trace("modified " + r
+ " -> "
432 affectedMappings
.add(m
);
435 } catch (IOException ioe
) {
438 CoreText
.GitProjectData_lazyResolveFailed
,
447 } catch (CoreException ce
) {
448 // We are in deep trouble. This should NOT have happend. Detach
449 // our listeners and forget it ever did.
451 attachToWorkspace(false);
452 Activator
.logError(CoreText
.GitProjectData_notifyChangedFailed
, ce
);
456 final Iterator i
= affectedMappings
.iterator();
457 while (i
.hasNext()) {
458 ((RepositoryMapping
) i
.next()).recomputeMerge();
460 } catch (IOException ioe
) {
462 .logError(CoreText
.GitProjectData_notifyChangedFailed
, ioe
);
466 private File
propertyFile() {
467 return new File(getProject()
468 .getWorkingLocation(Activator
.getPluginId()).toFile(),
469 "GitProjectData.properties");
472 private GitProjectData
load() throws IOException
{
473 final File dat
= propertyFile();
474 trace("load " + dat
);
476 final FileInputStream o
= new FileInputStream(dat
);
478 final Properties p
= new Properties();
482 final Iterator keyItr
= p
.keySet().iterator();
483 while (keyItr
.hasNext()) {
484 final String key
= keyItr
.next().toString();
485 if (RepositoryMapping
.isInitialKey(key
)) {
486 mappings
.add(new RepositoryMapping(p
, key
));
497 private void remapAll() {
498 protectedResources
.clear();
499 final Iterator i
= mappings
.iterator();
500 while (i
.hasNext()) {
501 map((RepositoryMapping
) i
.next());
505 private void map(final RepositoryMapping m
) {
508 final IResource dotGit
;
512 r
= getProject().findMember(m
.getContainerPath());
513 if (r
instanceof IContainer
) {
516 c
= (IContainer
) r
.getAdapter(IContainer
.class);
520 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
521 new FileNotFoundException(m
.getContainerPath().toString()));
527 git
= c
.getLocation().append(m
.getGitDirPath()).toFile();
528 if (!git
.isDirectory() || !new File(git
, "config").isFile()) {
529 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
530 new FileNotFoundException(m
.getContainerPath().toString()));
536 m
.setRepository(lookupRepository(git
));
537 } catch (IOException ioe
) {
538 Activator
.logError(CoreText
.GitProjectData_mappedResourceGone
,
539 new FileNotFoundException(m
.getContainerPath().toString()));
546 } catch (IOException ioe
) {
547 Activator
.logError(CoreText
.GitProjectData_cannotReadHEAD
, ioe
);
552 trace("map " + c
+ " -> " + m
.getRepository());
555 dotGit
= c
.findMember(".git");
556 if (dotGit
!= null && dotGit
.getLocation().toFile().equals(git
)) {
561 private void protect(IResource c
) {
562 while (c
!= null && !c
.equals(getProject())) {
563 trace("protect " + c
);
564 protectedResources
.add(c
);