Replace System.out with proper tracing
[egit/spearce.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / project / GitProjectData.java
bloba2628b6c1fd4afe20d58c4321c59eb40315f4f1c
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;
13 import java.io.File;
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;
25 import java.util.Map;
26 import java.util.Properties;
27 import java.util.Set;
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;
50 /**
51 * This class keeps information about how a project is mapped to
52 * a Git repository.
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());
70 break;
71 case IResourceChangeEvent.PRE_DELETE:
72 delete((IProject) event.getResource());
73 break;
74 default:
75 break;
80 private static QualifiedName MAPPING_KEY = new QualifiedName(
81 GitProjectData.class.getName(), "RepositoryMapping"); //$NON-NLS-1$
83 /**
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(
91 rcl,
92 (includeChange ? IResourceChangeEvent.POST_CHANGE : 0)
93 | IResourceChangeEvent.PRE_CLOSE
94 | IResourceChangeEvent.PRE_DELETE);
97 /**
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.
107 * <p>
108 * This is a no-op if <code>objectThatCares</code> has already been
109 * registered.
110 * </p>
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.
136 * @param which
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
146 * <p>
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
154 .size()]);
158 * @param p
159 * @return {@link GitProjectData} for the specified project
161 public synchronized static GitProjectData get(final IProject p) {
162 try {
163 GitProjectData d = lookup(p);
164 if (d == null
165 && RepositoryProvider.getProvider(p) instanceof GitProvider) {
166 d = new GitProjectData(p).load();
167 cache(p, d);
169 return d;
170 } catch (IOException err) {
171 Activator.logError(CoreText.GitProjectData_missing, err);
172 return null;
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);
184 if (d == null) {
185 try {
186 d = new GitProjectData(p).load();
187 } catch (IOException ioe) {
188 d = new GitProjectData(p);
191 d.delete();
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)
219 throws IOException {
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) {
224 i.remove();
228 final Reference r = repositoryCache.get(gitDir);
229 Repository d = r != null ? (Repository) r.get() : null;
230 if (d == null) {
231 d = new Repository(gitDir);
232 repositoryCache.put(gitDir, new WeakReference<Repository>(d));
234 return 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
258 * of a project.
260 * @param p Eclipse project
262 public GitProjectData(final IProject p) {
263 project = p;
267 * @return the Eclipse project mapped through this resource.
269 public IProject getProject() {
270 return project;
274 * TODO: is this right?
276 * @param newMappings
278 public void setRepositoryMappings(final Collection<RepositoryMapping> newMappings) {
279 mappings.clear();
280 mappings.addAll(newMappings);
281 remapAll();
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();
293 if (c == null)
294 continue; // Not fully mapped yet?
296 final IResource dotGit = c.findMember(Constants.DOT_GIT);
297 if (dotGit != null) {
298 try {
299 final Repository r = rm.getRepository();
300 final File dotGitDir = dotGit.getLocation().toFile()
301 .getCanonicalFile();
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);
314 * @param f
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) {
326 try {
327 for (; r != null; r = r.getParent()) {
328 final RepositoryMapping m;
330 if (!r.isAccessible())
331 continue;
332 m = (RepositoryMapping) r.getSessionProperty(MAPPING_KEY);
333 if (m != null)
334 return m;
336 } catch (CoreException err) {
337 Activator.logError(
338 CoreText.GitProjectData_failedFindingRepoMapping, err);
340 return null;
343 private void delete() {
344 final File dir = propertyFile().getParentFile();
345 final File[] todel = dir.listFiles();
346 if (todel != null) {
347 for (int k = 0; k < todel.length; k++) {
348 if (todel[k].isFile()) {
349 todel[k].delete();
353 dir.delete();
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();
367 final File tmp;
368 boolean ok = false;
370 try {
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);
377 try {
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$
384 ok = true;
385 } finally {
386 o.close();
387 if (!ok) {
388 tmp.delete();
391 } catch (IOException ioe) {
392 throw Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed,
393 dat), ioe);
396 dat.delete();
397 if (!tmp.renameTo(dat)) {
398 tmp.delete();
399 throw Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed,
400 dat), null);
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);
415 try {
416 final Properties p = new Properties();
417 p.load(o);
419 mappings.clear();
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));
427 } finally {
428 o.close();
431 remapAll();
432 return this;
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) {
444 final IResource r;
445 final File git;
446 final IResource dotGit;
447 IContainer c = null;
449 m.clear();
450 r = getProject().findMember(m.getContainerPath());
451 if (r instanceof IContainer) {
452 c = (IContainer) r;
453 } else {
454 c = (IContainer) r.getAdapter(IContainer.class);
457 if (c == null) {
458 Activator.logError(CoreText.GitProjectData_mappedResourceGone,
459 new FileNotFoundException(m.getContainerPath().toString()));
460 m.clear();
461 return;
463 m.setContainer(c);
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()));
470 m.clear();
471 return;
474 try {
475 m.setRepository(lookupRepository(git));
476 } catch (IOException ioe) {
477 Activator.logError(CoreText.GitProjectData_mappedResourceGone,
478 new FileNotFoundException(m.getContainerPath().toString()));
479 m.clear();
480 return;
483 m.fireRepositoryChanged();
485 trace("map " //$NON-NLS-1$
487 + " -> " //$NON-NLS-1$
488 + m.getRepository());
489 try {
490 c.setSessionProperty(MAPPING_KEY, m);
491 } catch (CoreException err) {
492 Activator.logError(
493 CoreText.GitProjectData_failedToCacheRepoMapping, err);
496 dotGit = c.findMember(Constants.DOT_GIT);
497 if (dotGit != null && dotGit.getLocation().toFile().equals(git)) {
498 protect(dotGit);
502 private void protect(IResource c) {
503 while (c != null && !c.equals(getProject())) {
504 trace("protect " + c); //$NON-NLS-1$
505 protectedResources.add(c);
506 c = c.getParent();