Externalize strings / add NON-NLS comments for technical strings
[egit/spearce.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / project / GitProjectData.java
blob821e879e5bc97909c5301110b766467bef7196db
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.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;
49 /**
50 * This class keeps information about how a project is mapped to
51 * a Git repository.
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());
69 break;
70 case IResourceChangeEvent.PRE_DELETE:
71 delete((IProject) event.getResource());
72 break;
73 default:
74 break;
79 private static QualifiedName MAPPING_KEY = new QualifiedName(
80 GitProjectData.class.getName(), "RepositoryMapping"); //$NON-NLS-1$
82 /**
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(
90 rcl,
91 (includeChange ? IResourceChangeEvent.POST_CHANGE : 0)
92 | IResourceChangeEvent.PRE_CLOSE
93 | IResourceChangeEvent.PRE_DELETE);
96 /**
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.
106 * <p>
107 * This is a no-op if <code>objectThatCares</code> has already been
108 * registered.
109 * </p>
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.
135 * @param which
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
145 * <p>
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
153 .size()]);
157 * @param p
158 * @return {@link GitProjectData} for the specified project
160 public synchronized static GitProjectData get(final IProject p) {
161 try {
162 GitProjectData d = lookup(p);
163 if (d == null
164 && RepositoryProvider.getProvider(p) instanceof GitProvider) {
165 d = new GitProjectData(p).load();
166 cache(p, d);
168 return d;
169 } catch (IOException err) {
170 Activator.logError(CoreText.GitProjectData_missing, err);
171 return null;
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);
183 if (d == null) {
184 try {
185 d = new GitProjectData(p).load();
186 } catch (IOException ioe) {
187 d = new GitProjectData(p);
190 d.delete();
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)
214 throws IOException {
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) {
219 i.remove();
223 final Reference r = repositoryCache.get(gitDir);
224 Repository d = r != null ? (Repository) r.get() : null;
225 if (d == null) {
226 d = new Repository(gitDir);
227 repositoryCache.put(gitDir, new WeakReference<Repository>(d));
229 return 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
253 * of a project.
255 * @param p Eclipse project
257 public GitProjectData(final IProject p) {
258 project = p;
262 * @return the Eclipse project mapped through this resource.
264 public IProject getProject() {
265 return project;
269 * TODO: is this right?
271 * @param newMappings
273 public void setRepositoryMappings(final Collection<RepositoryMapping> newMappings) {
274 mappings.clear();
275 mappings.addAll(newMappings);
276 remapAll();
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();
288 if (c == null)
289 continue; // Not fully mapped yet?
291 final IResource dotGit = c.findMember(Constants.DOT_GIT);
292 if (dotGit != null) {
293 try {
294 final Repository r = rm.getRepository();
295 final File dotGitDir = dotGit.getLocation().toFile()
296 .getCanonicalFile();
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);
309 * @param f
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) {
321 try {
322 for (; r != null; r = r.getParent()) {
323 final RepositoryMapping m;
325 if (!r.isAccessible())
326 continue;
327 m = (RepositoryMapping) r.getSessionProperty(MAPPING_KEY);
328 if (m != null)
329 return m;
331 } catch (CoreException err) {
332 Activator.logError(
333 CoreText.GitProjectData_failedFindingRepoMapping, err);
335 return null;
338 private void delete() {
339 final File dir = propertyFile().getParentFile();
340 final File[] todel = dir.listFiles();
341 if (todel != null) {
342 for (int k = 0; k < todel.length; k++) {
343 if (todel[k].isFile()) {
344 todel[k].delete();
348 dir.delete();
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();
362 final File tmp;
363 boolean ok = false;
365 try {
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);
372 try {
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$
379 ok = true;
380 } finally {
381 o.close();
382 if (!ok) {
383 tmp.delete();
386 } catch (IOException ioe) {
387 throw Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed,
388 dat), ioe);
391 dat.delete();
392 if (!tmp.renameTo(dat)) {
393 tmp.delete();
394 throw Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed,
395 dat), null);
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);
410 try {
411 final Properties p = new Properties();
412 p.load(o);
414 mappings.clear();
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));
422 } finally {
423 o.close();
426 remapAll();
427 return this;
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) {
439 final IResource r;
440 final File git;
441 final IResource dotGit;
442 IContainer c = null;
444 m.clear();
445 r = getProject().findMember(m.getContainerPath());
446 if (r instanceof IContainer) {
447 c = (IContainer) r;
448 } else {
449 c = (IContainer) r.getAdapter(IContainer.class);
452 if (c == null) {
453 Activator.logError(CoreText.GitProjectData_mappedResourceGone,
454 new FileNotFoundException(m.getContainerPath().toString()));
455 m.clear();
456 return;
458 m.setContainer(c);
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()));
465 m.clear();
466 return;
469 try {
470 m.setRepository(lookupRepository(git));
471 } catch (IOException ioe) {
472 Activator.logError(CoreText.GitProjectData_mappedResourceGone,
473 new FileNotFoundException(m.getContainerPath().toString()));
474 m.clear();
475 return;
478 m.fireRepositoryChanged();
480 trace("map " //$NON-NLS-1$
482 + " -> " //$NON-NLS-1$
483 + m.getRepository());
484 try {
485 c.setSessionProperty(MAPPING_KEY, m);
486 } catch (CoreException err) {
487 Activator.logError(
488 CoreText.GitProjectData_failedToCacheRepoMapping, err);
491 dotGit = c.findMember(Constants.DOT_GIT);
492 if (dotGit != null && dotGit.getLocation().toFile().equals(git)) {
493 protect(dotGit);
497 private void protect(IResource c) {
498 while (c != null && !c.equals(getProject())) {
499 trace("protect " + c); //$NON-NLS-1$
500 protectedResources.add(c);
501 c = c.getParent();