Show tags in history view
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob12beb8804ba5eb7ae990a887efc201e489e51874
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.HashMap;
31 import java.util.Map;
32 import java.util.WeakHashMap;
34 import org.spearce.jgit.errors.IncorrectObjectTypeException;
35 import org.spearce.jgit.errors.ObjectWritingException;
37 public class Repository {
38 private static final String[] refSearchPaths = { "", "refs/", "refs/tags/",
39 "refs/heads/", };
41 private final File gitDir;
43 private final File[] objectsDirs;
45 private final File refsDir;
47 private final RepositoryConfig config;
49 private PackFile[] packs;
51 private WindowCache windows;
53 private Map treeCache = new WeakHashMap(30000);
54 private Map commitCache = new WeakHashMap(30000);
56 public Repository(final File d) throws IOException {
57 gitDir = d.getAbsoluteFile();
58 try {
59 objectsDirs = (File[])readObjectsDirs(new File(gitDir, "objects"), new ArrayList()).toArray(new File[0]);
60 } catch (IOException e) {
61 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
62 ex.initCause(e);
63 throw ex;
65 refsDir = new File(gitDir, "refs");
66 packs = new PackFile[0];
67 config = new RepositoryConfig(this);
68 if (objectsDirs[0].exists()) {
69 getConfig().load();
70 final String repositoryFormatVersion = getConfig().getString(
71 "core", "repositoryFormatVersion");
72 if (!"0".equals(repositoryFormatVersion)) {
73 throw new IOException("Unknown repository format \""
74 + repositoryFormatVersion + "\"; expected \"0\".");
76 initializeWindowCache();
77 scanForPacks();
81 private Collection readObjectsDirs(File objectsDir, Collection ret) throws IOException {
82 ret.add(objectsDir);
83 File alternatesFile = new File(objectsDir,"info/alternates");
84 if (alternatesFile.exists()) {
85 BufferedReader ar = new BufferedReader(new FileReader(alternatesFile));
86 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
87 readObjectsDirs(new File(alt), ret);
89 ar.close();
91 return ret;
94 public void create() throws IOException {
95 if (gitDir.exists()) {
96 throw new IllegalStateException("Repository already exists: "
97 + gitDir);
100 gitDir.mkdirs();
102 objectsDirs[0].mkdirs();
103 new File(objectsDirs[0], "pack").mkdir();
104 new File(objectsDirs[0], "info").mkdir();
106 refsDir.mkdir();
107 new File(refsDir, "heads").mkdir();
108 new File(refsDir, "tags").mkdir();
110 new File(gitDir, "branches").mkdir();
111 new File(gitDir, "remotes").mkdir();
112 writeSymref("HEAD", "refs/heads/master");
114 getConfig().create();
115 getConfig().save();
116 initializeWindowCache();
119 private void initializeWindowCache() {
120 // FIXME these should be configurable...
121 windows = new WindowCache(256 * 1024 * 1024, 4);
124 public File getDirectory() {
125 return gitDir;
128 public File getObjectsDirectory() {
129 return objectsDirs[0];
132 public RepositoryConfig getConfig() {
133 return config;
136 public WindowCache getWindowCache() {
137 return windows;
140 public File toFile(final ObjectId objectId) {
141 final String n = objectId.toString();
142 String d=n.substring(0, 2);
143 String f=n.substring(2);
144 for (int i=0; i<objectsDirs.length; ++i) {
145 File ret = new File(new File(objectsDirs[i], d), f);
146 if (ret.exists())
147 return ret;
149 return new File(new File(objectsDirs[0], d), f);
152 public boolean hasObject(final ObjectId objectId) {
153 int k = packs.length;
154 if (k > 0) {
155 do {
156 if (packs[--k].hasObject(objectId))
157 return true;
158 } while (k > 0);
160 return toFile(objectId).isFile();
163 public ObjectLoader openObject(final ObjectId id) throws IOException {
164 int k = packs.length;
165 if (k > 0) {
166 do {
167 try {
168 final ObjectLoader ol = packs[--k].get(id);
169 if (ol != null)
170 return ol;
171 } catch (IOException ioe) {
172 // This shouldn't happen unless the pack was corrupted
173 // after we opened it or the VM runs out of memory. This is
174 // a know problem with memory mapped I/O in java and have
175 // been noticed with JDK < 1.6. Tell the gc that now is a good
176 // time to collect and try once more.
177 try {
178 System.gc();
179 final ObjectLoader ol = packs[k].get(id);
180 if (ol != null)
181 return ol;
182 } catch (IOException ioe2) {
183 ioe2.printStackTrace();
184 ioe.printStackTrace();
185 // Still fails.. that's BAD, maybe the pack has
186 // been corrupted after all, or the gc didn't manage
187 // to release enough previously mmaped areas.
190 } while (k > 0);
192 try {
193 return new UnpackedObjectLoader(this, id);
194 } catch (FileNotFoundException fnfe) {
195 return null;
199 public ObjectLoader openBlob(final ObjectId id) throws IOException {
200 return openObject(id);
203 public ObjectLoader openTree(final ObjectId id) throws IOException {
204 return openObject(id);
207 public Commit mapCommit(final String revstr) throws IOException {
208 final ObjectId id = resolve(revstr);
209 return id != null ? mapCommit(id) : null;
212 public Commit mapCommit(final ObjectId id) throws IOException {
213 // System.out.println("commitcache.size="+commitCache.size());
214 Reference retr = (Reference)commitCache.get(id);
215 if (retr != null) {
216 Commit ret = (Commit)retr.get();
217 if (ret != null)
218 return ret;
219 System.out.println("Found a null id, size was "+commitCache.size());
222 final ObjectLoader or = openObject(id);
223 if (or == null)
224 return null;
225 final byte[] raw = or.getBytes();
226 if (Constants.TYPE_COMMIT.equals(or.getType())) {
227 Commit ret = new Commit(this, id, raw);
228 // The key must not be the referenced strongly
229 // by the value in WeakHashMaps
230 commitCache.put(id, new SoftReference(ret));
231 return ret;
233 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
236 public Tree mapTree(final String revstr) throws IOException {
237 final ObjectId id = resolve(revstr);
238 return id != null ? mapTree(id) : null;
241 public Tree mapTree(final ObjectId id) throws IOException {
242 Reference wret = (Reference)treeCache.get(id);
243 if (wret != null) {
244 Tree ret = (Tree)wret.get();
245 if (ret != null)
246 return ret;
249 final ObjectLoader or = openObject(id);
250 if (or == null)
251 return null;
252 final byte[] raw = or.getBytes();
253 if (Constants.TYPE_TREE.equals(or.getType())) {
254 Tree ret = new Tree(this, id, raw);
255 treeCache.put(id, new SoftReference(ret));
256 return ret;
258 if (Constants.TYPE_COMMIT.equals(or.getType()))
259 return mapTree(ObjectId.fromString(raw, 5));
260 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
263 public Tag mapTag(String revstr) throws IOException {
264 final ObjectId id = resolve(revstr);
265 return id != null ? mapTag(revstr, id) : null;
268 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
269 final ObjectLoader or = openObject(id);
270 if (or == null)
271 return null;
272 final byte[] raw = or.getBytes();
273 if (Constants.TYPE_TAG.equals(or.getType()))
274 return new Tag(this, id, refName, raw);
275 return new Tag(this, id, refName, null);
278 public RefLock lockRef(final String ref) throws IOException {
279 final RefLock l = new RefLock(readRef(ref, true));
280 return l.lock() ? l : null;
283 public ObjectId resolve(final String revstr) throws IOException {
284 ObjectId id = null;
286 if (ObjectId.isId(revstr)) {
287 id = new ObjectId(revstr);
290 if (id == null) {
291 final Ref r = readRef(revstr, false);
292 if (r != null) {
293 id = r.getObjectId();
297 return id;
300 public void close() throws IOException {
301 closePacks();
304 public void closePacks() throws IOException {
305 for (int k = packs.length - 1; k >= 0; k--) {
306 packs[k].close();
308 packs = new PackFile[0];
311 public void scanForPacks() {
312 final ArrayList p = new ArrayList();
313 for (int i=0; i<objectsDirs.length; ++i)
314 scanForPacks(new File(objectsDirs[i], "pack"), p);
315 final PackFile[] arr = new PackFile[p.size()];
316 p.toArray(arr);
317 packs = arr;
320 public void scanForPacks(final File packDir, Collection packList) {
321 final File[] list = packDir.listFiles(new FileFilter() {
322 public boolean accept(final File f) {
323 final String n = f.getName();
324 if (!n.endsWith(".pack")) {
325 return false;
327 final String nBase = n.substring(0, n.lastIndexOf('.'));
328 final File idx = new File(packDir, nBase + ".idx");
329 return f.isFile() && f.canRead() && idx.isFile()
330 && idx.canRead();
333 for (int k = 0; k < list.length; k++) {
334 try {
335 packList.add(new PackFile(this, list[k]));
336 } catch (IOException ioe) {
337 // Whoops. That's not a pack!
343 public void writeRef(String name, ObjectId id) throws IOException {
344 File f = new File(gitDir, name);
345 File t = File.createTempFile("ref", null, gitDir);
346 FileWriter w = new FileWriter(t);
347 try {
348 w.write(id.toString());
349 w.write('\n');
350 w.close();
351 w = null;
352 if (!t.renameTo(f)) {
353 f.getParentFile().mkdirs();
354 if (!t.renameTo(f)) {
355 f.delete();
356 if (!t.renameTo(f)) {
357 t.delete();
358 throw new ObjectWritingException("Unable to"
359 + " write ref " + name + " to point to "
360 + id.toString());
364 } finally {
365 if (w != null) {
366 w.close();
367 t.delete();
372 public void writeSymref(final String name, final String target)
373 throws IOException {
374 final File s = new File(gitDir, name);
375 final File t = File.createTempFile("srf", null, gitDir);
376 FileWriter w = new FileWriter(t);
377 try {
378 w.write("ref: ");
379 w.write(target);
380 w.write('\n');
381 w.close();
382 w = null;
383 if (!t.renameTo(s)) {
384 s.getParentFile().mkdirs();
385 if (!t.renameTo(s)) {
386 t.delete();
387 throw new ObjectWritingException("Unable to"
388 + " write symref " + name + " to point to "
389 + target);
392 } finally {
393 if (w != null) {
394 w.close();
395 t.delete();
400 private Ref readRef(final String revstr, final boolean missingOk)
401 throws IOException {
402 refreshPackredRefsCache();
403 for (int k = 0; k < refSearchPaths.length; k++) {
404 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
405 if (missingOk || r.getObjectId() != null) {
406 return r;
409 return null;
412 private Ref readRefBasic(String name) throws IOException {
413 int depth = 0;
414 REF_READING: do {
415 ObjectId id = packedRefs.get(name);
416 if (id != null)
417 return new Ref(null, id);
419 final File f = new File(getDirectory(), name);
420 if (!f.isFile()) {
421 return new Ref(f, null);
424 final BufferedReader br = new BufferedReader(new FileReader(f));
425 try {
426 final String line = br.readLine();
427 if (line == null || line.length() == 0) {
428 return new Ref(f, null);
429 } else if (line.startsWith("ref: ")) {
430 name = line.substring("ref: ".length());
431 continue REF_READING;
432 } else if (ObjectId.isId(line)) {
433 return new Ref(f, new ObjectId(line));
435 throw new IOException("Not a ref: " + name + ": " + line);
436 } finally {
437 br.close();
439 } while (depth++ < 5);
440 throw new IOException("Exceed maximum ref depth. Circular reference?");
443 public String toString() {
444 return "Repository[" + getDirectory() + "]";
447 public String getPatch() throws IOException {
448 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/current");
449 final BufferedReader br = new BufferedReader(new FileReader(ptr));
450 try {
451 return br.readLine();
452 } finally {
453 br.close();
457 public String getBranch() throws IOException {
458 final File ptr = new File(getDirectory(),"HEAD");
459 final BufferedReader br = new BufferedReader(new FileReader(ptr));
460 String ref;
461 try {
462 ref = br.readLine();
463 } finally {
464 br.close();
466 if (ref.startsWith("ref: "))
467 ref = ref.substring(5);
468 if (ref.startsWith("refs/heads/"))
469 ref = ref.substring(11);
470 return ref;
473 public Collection<String> getBranches() {
474 return listFilesRecursively(new File(refsDir, "heads"), null);
477 public Collection<String> getTags() {
478 Collection<String> tags = listFilesRecursively(new File(refsDir, "tags"), null);
479 refreshPackredRefsCache();
480 tags.addAll(packedRefs.keySet());
481 return tags;
484 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
485 private long packedrefstime = 0;
487 private void refreshPackredRefsCache() {
488 File file = new File(gitDir, "packed-refs");
489 if (!file.exists()) {
490 if (packedRefs.size() > 0)
491 packedRefs = new HashMap();
492 return;
494 if (file.lastModified() == packedrefstime)
495 return;
496 Map newPackedRefs = new HashMap();
497 try {
498 BufferedReader b=new BufferedReader(new FileReader(file));
499 String p;
500 while ((p = b.readLine()) != null) {
501 if (p.charAt(0) == '#')
502 continue;
503 if (p.charAt(0) == '^') {
504 continue;
506 int spos = p.indexOf(' ');
507 ObjectId id = new ObjectId(p.substring(0,spos));
508 String name = p.substring(spos+1);
509 newPackedRefs.put(name, id);
511 } catch (IOException e) {
512 e.printStackTrace();
514 packedRefs = newPackedRefs;
518 * @return true if HEAD points to a StGit patch.
520 public boolean isStGitMode() {
521 try {
522 File file = new File(getDirectory(), "HEAD");
523 BufferedReader reader = new BufferedReader(new FileReader(file));
524 String string = reader.readLine();
525 if (!string.startsWith("ref: refs/heads/"))
526 return false;
527 String branch = string.substring("ref: refs/heads/".length());
528 File currentPatch = new File(new File(new File(getDirectory(),
529 "patches"), branch), "current");
530 if (!currentPatch.exists())
531 return false;
532 if (currentPatch.length() == 0)
533 return false;
534 return true;
536 } catch (IOException e) {
537 e.printStackTrace();
538 return false;
542 public static class StGitPatch {
543 public StGitPatch(String patchName, ObjectId id) {
544 name = patchName;
545 gitId = id;
547 public ObjectId getGitId() {
548 return gitId;
550 public String getName() {
551 return name;
553 private String name;
554 private ObjectId gitId;
558 * @return applied patches in a map indexed on current commit id
559 * @throws IOException
561 public Map getAppliedPatches() throws IOException {
562 Map ret = new HashMap();
563 if (isStGitMode()) {
564 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
565 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
566 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
567 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
568 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
569 String objectId = tfr.readLine();
570 ObjectId id = new ObjectId(objectId);
571 ret.put(id, new StGitPatch(patchName, id));
572 tfr.close();
574 apr.close();
576 return ret;
579 private Collection<String> listFilesRecursively(File root, File start) {
580 if (start == null)
581 start = root;
582 Collection<String> ret = new ArrayList();
583 File[] files = start.listFiles();
584 for (int i = 0; i < files.length; ++i) {
585 if (files[i].isDirectory())
586 ret.addAll(listFilesRecursively(root, files[i]));
587 else if (files[i].length() == 41) {
588 String name = files[i].toString().substring(
589 root.toString().length() + 1);
590 ret.add(name);
593 return ret;
596 /** Clean up stale caches */
597 public void refreshFromDisk() {
598 packedRefs = null;