Rework tree/commit cache
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob8cc8857a229fd860c290f1da36d5a6b9dbaf4450
1 /*
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 General Public
6 * License, version 2, 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 * General Public License for more details.
13 * You should have received a copy of the GNU 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.jgit.lib;
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileFilter;
22 import java.io.FileNotFoundException;
23 import java.io.FileReader;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.lang.ref.Reference;
27 import java.lang.ref.SoftReference;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Map;
31 import java.util.WeakHashMap;
33 import org.spearce.jgit.errors.IncorrectObjectTypeException;
34 import org.spearce.jgit.errors.ObjectWritingException;
36 public class Repository {
37 private static final String[] refSearchPaths = { "", "refs/", "refs/tags/",
38 "refs/heads/", };
40 private final File gitDir;
42 private final File objectsDir;
44 private final File refsDir;
46 private final RepositoryConfig config;
48 private PackFile[] packs;
50 private WindowCache windows;
52 private Map treeCache = new WeakHashMap(30000);
53 private Map commitCache = new WeakHashMap(30000);
55 public Repository(final File d) throws IOException {
56 gitDir = d.getAbsoluteFile();
57 objectsDir = new File(gitDir, "objects");
58 refsDir = new File(gitDir, "refs");
59 packs = new PackFile[0];
60 config = new RepositoryConfig(this);
61 if (objectsDir.exists()) {
62 getConfig().load();
63 final String repositoryFormatVersion = getConfig().getString(
64 "core", "repositoryFormatVersion");
65 if (!"0".equals(repositoryFormatVersion)) {
66 throw new IOException("Unknown repository format \""
67 + repositoryFormatVersion + "\"; expected \"0\".");
69 initializeWindowCache();
70 scanForPacks();
74 public void create() throws IOException {
75 if (gitDir.exists()) {
76 throw new IllegalStateException("Repository already exists: "
77 + gitDir);
80 gitDir.mkdirs();
82 objectsDir.mkdirs();
83 new File(objectsDir, "pack").mkdir();
84 new File(objectsDir, "info").mkdir();
86 refsDir.mkdir();
87 new File(refsDir, "heads").mkdir();
88 new File(refsDir, "tags").mkdir();
90 new File(gitDir, "branches").mkdir();
91 new File(gitDir, "remotes").mkdir();
92 writeSymref("HEAD", "refs/heads/master");
94 getConfig().create();
95 getConfig().save();
96 initializeWindowCache();
99 private void initializeWindowCache() {
100 // FIXME these should be configurable...
101 windows = new WindowCache(256 * 1024 * 1024, 4);
104 public File getDirectory() {
105 return gitDir;
108 public File getObjectsDirectory() {
109 return objectsDir;
112 public RepositoryConfig getConfig() {
113 return config;
116 public WindowCache getWindowCache() {
117 return windows;
120 public File toFile(final ObjectId objectId) {
121 final String n = objectId.toString();
122 return new File(new File(objectsDir, n.substring(0, 2)), n.substring(2));
125 public boolean hasObject(final ObjectId objectId) {
126 int k = packs.length;
127 if (k > 0) {
128 final byte[] tmp = new byte[Constants.OBJECT_ID_LENGTH];
129 do {
130 try {
131 if (packs[--k].hasObject(objectId, tmp))
132 return true;
133 } catch (IOException ioe) {
134 // This shouldn't happen unless the pack was corrupted
135 // after we opened it. We'll ignore the error as though
136 // the object does not exist in this pack.
139 } while (k > 0);
141 return toFile(objectId).isFile();
144 public ObjectLoader openObject(final ObjectId id) throws IOException {
145 int k = packs.length;
146 if (k > 0) {
147 final byte[] tmp = new byte[Constants.OBJECT_ID_LENGTH];
148 do {
149 try {
150 final ObjectLoader ol = packs[--k].get(id, tmp);
151 if (ol != null)
152 return ol;
153 } catch (IOException ioe) {
154 // This shouldn't happen unless the pack was corrupted
155 // after we opened it or the VM runs out of memory. This is
156 // a know problem with memory mapped I/O in java and have
157 // been noticed with JDK < 1.6. Tell the gc that now is a good
158 // time to collect and try once more.
159 try {
160 System.gc();
161 final ObjectLoader ol = packs[k].get(id, tmp);
162 if (ol != null)
163 return ol;
164 } catch (IOException ioe2) {
165 ioe2.printStackTrace();
166 ioe.printStackTrace();
167 // Still fails.. that's BAD, maybe the pack has
168 // been corrupted after all, or the gc didn't manage
169 // to release enough previously mmaped areas.
172 } while (k > 0);
174 try {
175 return new UnpackedObjectLoader(this, id);
176 } catch (FileNotFoundException fnfe) {
177 return null;
181 public ObjectLoader openBlob(final ObjectId id) throws IOException {
182 return openObject(id);
185 public ObjectLoader openTree(final ObjectId id) throws IOException {
186 return openObject(id);
189 public Commit mapCommit(final String revstr) throws IOException {
190 final ObjectId id = resolve(revstr);
191 return id != null ? mapCommit(id) : null;
194 public Commit mapCommit(final ObjectId id) throws IOException {
195 // System.out.println("commitcache.size="+commitCache.size());
196 Reference retr = (Reference)commitCache.get(id);
197 if (retr != null) {
198 Commit ret = (Commit)retr.get();
199 if (ret != null)
200 return ret;
201 System.out.println("Found a null id, size was "+commitCache.size());
204 final ObjectLoader or = openObject(id);
205 if (or == null)
206 return null;
207 final byte[] raw = or.getBytes();
208 if (Constants.TYPE_COMMIT.equals(or.getType())) {
209 Commit ret = new Commit(this, id, raw);
210 // The key must not be the referenced strongly
211 // by the value in WeakHashMaps
212 commitCache.put(id, new SoftReference(ret));
213 return ret;
215 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
218 public Tree mapTree(final String revstr) throws IOException {
219 final ObjectId id = resolve(revstr);
220 return id != null ? mapTree(id) : null;
223 public Tree mapTree(final ObjectId id) throws IOException {
224 Reference wret = (Reference)treeCache.get(id);
225 if (wret != null) {
226 Tree ret = (Tree)wret.get();
227 if (ret != null)
228 return ret;
231 final ObjectLoader or = openObject(id);
232 if (or == null)
233 return null;
234 final byte[] raw = or.getBytes();
235 if (Constants.TYPE_TREE.equals(or.getType())) {
236 Tree ret = new Tree(this, id, raw);
237 treeCache.put(id, new SoftReference(ret));
238 return ret;
240 if (Constants.TYPE_COMMIT.equals(or.getType()))
241 return mapTree(ObjectId.fromString(raw, 5));
242 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
245 public Tag mapTag(String revstr) throws IOException {
246 final ObjectId id = resolve(revstr);
247 return id != null ? mapTag(id) : null;
250 public Tag mapTag(final ObjectId id) throws IOException {
251 final ObjectLoader or = openObject(id);
252 if (or == null)
253 return null;
254 final byte[] raw = or.getBytes();
255 if (Constants.TYPE_TAG.equals(or.getType()))
256 return new Tag(this, id, raw);
257 throw new IncorrectObjectTypeException(id, Constants.TYPE_TAG);
260 public RefLock lockRef(final String ref) throws IOException {
261 final RefLock l = new RefLock(readRef(ref, true));
262 return l.lock() ? l : null;
265 public ObjectId resolve(final String revstr) throws IOException {
266 ObjectId id = null;
268 if (ObjectId.isId(revstr)) {
269 id = new ObjectId(revstr);
272 if (id == null) {
273 final Ref r = readRef(revstr, false);
274 if (r != null) {
275 id = r.getObjectId();
279 return id;
282 public void close() throws IOException {
283 closePacks();
286 public void closePacks() throws IOException {
287 for (int k = packs.length - 1; k >= 0; k--) {
288 packs[k].close();
290 packs = new PackFile[0];
293 public void scanForPacks() {
294 final File packDir = new File(objectsDir, "pack");
295 final File[] list = packDir.listFiles(new FileFilter() {
296 public boolean accept(final File f) {
297 final String n = f.getName();
298 if (!n.endsWith(".pack")) {
299 return false;
301 final String nBase = n.substring(0, n.lastIndexOf('.'));
302 final File idx = new File(packDir, nBase + ".idx");
303 return f.isFile() && f.canRead() && idx.isFile()
304 && idx.canRead();
307 final ArrayList p = new ArrayList(list.length);
308 for (int k = 0; k < list.length; k++) {
309 try {
310 p.add(new PackFile(this, list[k]));
311 } catch (IOException ioe) {
312 // Whoops. That's not a pack!
316 final PackFile[] arr = new PackFile[p.size()];
317 p.toArray(arr);
318 packs = arr;
321 private void writeSymref(final String name, final String target)
322 throws IOException {
323 final File s = new File(gitDir, name);
324 final File t = File.createTempFile("srf", null, gitDir);
325 FileWriter w = new FileWriter(t);
326 try {
327 w.write("ref: ");
328 w.write(target);
329 w.write('\n');
330 w.close();
331 w = null;
332 if (!t.renameTo(s)) {
333 s.getParentFile().mkdirs();
334 if (!t.renameTo(s)) {
335 t.delete();
336 throw new ObjectWritingException("Unable to"
337 + " write symref " + name + " to point to "
338 + target);
341 } finally {
342 if (w != null) {
343 w.close();
344 t.delete();
349 private Ref readRef(final String revstr, final boolean missingOk)
350 throws IOException {
351 for (int k = 0; k < refSearchPaths.length; k++) {
352 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
353 if (missingOk || r.getObjectId() != null) {
354 return r;
357 return null;
360 private Ref readRefBasic(String name) throws IOException {
361 int depth = 0;
362 REF_READING: do {
363 final File f = new File(getDirectory(), name);
364 if (!f.isFile()) {
365 return new Ref(f, null);
368 final BufferedReader br = new BufferedReader(new FileReader(f));
369 try {
370 final String line = br.readLine();
371 if (line == null || line.length() == 0) {
372 return new Ref(f, null);
373 } else if (line.startsWith("ref: ")) {
374 name = line.substring("ref: ".length());
375 continue REF_READING;
376 } else if (ObjectId.isId(line)) {
377 return new Ref(f, new ObjectId(line));
379 throw new IOException("Not a ref: " + name + ": " + line);
380 } finally {
381 br.close();
383 } while (depth++ < 5);
384 throw new IOException("Exceed maximum ref depth. Circular reference?");
387 public String toString() {
388 return "Repository[" + getDirectory() + "]";
391 public String getPatch() throws IOException {
392 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/current");
393 final BufferedReader br = new BufferedReader(new FileReader(ptr));
394 try {
395 return br.readLine();
396 } finally {
397 br.close();
401 public String getBranch() throws IOException {
402 final File ptr = new File(getDirectory(),"HEAD");
403 final BufferedReader br = new BufferedReader(new FileReader(ptr));
404 String ref;
405 try {
406 ref = br.readLine();
407 } finally {
408 br.close();
410 if (ref.startsWith("ref: "))
411 ref = ref.substring(5);
412 if (ref.startsWith("refs/heads/"))
413 ref = ref.substring(11);
414 return ref;
417 public Collection getBranches() {
418 return listFilesRecursively(new File(refsDir, "heads"), null);
421 public Collection getTags() {
422 return listFilesRecursively(new File(refsDir, "tags"), null);
426 * @return true if HEAD points to a StGit patch.
428 public boolean isStGitMode() {
429 try {
430 File file = new File(getDirectory(), "HEAD");
431 BufferedReader reader = new BufferedReader(new FileReader(file));
432 String string = reader.readLine();
433 if (!string.startsWith("ref: refs/heads/"))
434 return false;
435 String branch = string.substring("ref: refs/heads/".length());
436 File currentPatch = new File(new File(new File(getDirectory(),
437 "patches"), branch), "current");
438 if (!currentPatch.exists())
439 return false;
440 if (currentPatch.length() == 0)
441 return false;
442 return true;
444 } catch (IOException e) {
445 e.printStackTrace();
446 return false;
450 private Collection listFilesRecursively(File root, File start) {
451 if (start == null)
452 start = root;
453 Collection ret = new ArrayList();
454 File[] files = start.listFiles();
455 for (int i = 0; i < files.length; ++i) {
456 if (files[i].isDirectory())
457 ret.addAll(listFilesRecursively(root, files[i]));
458 else if (files[i].length() == 41) {
459 String name = files[i].toString().substring(
460 root.toString().length() + 1);
461 ret.add(name);
464 return ret;