Do not crash if the repo does not have a pack
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob6c3383b6318c8da9442fa4f2547c0f17fe39f4f6
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.IOException;
25 import java.lang.ref.Reference;
26 import java.lang.ref.SoftReference;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
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[] objectsDirs;
44 private final File refsDir;
46 private final RepositoryConfig config;
48 private PackFile[] packs;
50 private WindowCache windows;
52 private Map<ObjectId,Reference<Tree>> treeCache = new WeakHashMap<ObjectId,Reference<Tree>>(30000);
53 private Map<ObjectId,Reference<Commit>> commitCache = new WeakHashMap<ObjectId,Reference<Commit>>(30000);
55 private GitIndex index;
57 public Repository(final File d) throws IOException {
58 gitDir = d.getAbsoluteFile();
59 try {
60 objectsDirs = readObjectsDirs(new File(gitDir, "objects"), new ArrayList<File>()).toArray(new File[0]);
61 } catch (IOException e) {
62 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
63 ex.initCause(e);
64 throw ex;
66 refsDir = new File(gitDir, "refs");
67 packs = new PackFile[0];
68 config = new RepositoryConfig(this);
69 if (objectsDirs[0].exists()) {
70 getConfig().load();
71 final String repositoryFormatVersion = getConfig().getString(
72 "core", "repositoryFormatVersion");
73 if (!"0".equals(repositoryFormatVersion)) {
74 throw new IOException("Unknown repository format \""
75 + repositoryFormatVersion + "\"; expected \"0\".");
77 initializeWindowCache();
78 scanForPacks();
82 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
83 ret.add(objectsDir);
84 File alternatesFile = new File(objectsDir,"info/alternates");
85 if (alternatesFile.exists()) {
86 BufferedReader ar = new BufferedReader(new FileReader(alternatesFile));
87 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
88 readObjectsDirs(new File(alt), ret);
90 ar.close();
92 return ret;
95 public void create() throws IOException {
96 if (gitDir.exists()) {
97 throw new IllegalStateException("Repository already exists: "
98 + gitDir);
101 gitDir.mkdirs();
103 objectsDirs[0].mkdirs();
104 new File(objectsDirs[0], "pack").mkdir();
105 new File(objectsDirs[0], "info").mkdir();
107 refsDir.mkdir();
108 new File(refsDir, "heads").mkdir();
109 new File(refsDir, "tags").mkdir();
111 new File(gitDir, "branches").mkdir();
112 new File(gitDir, "remotes").mkdir();
113 writeSymref("HEAD", "refs/heads/master");
115 getConfig().create();
116 getConfig().save();
117 initializeWindowCache();
120 private void initializeWindowCache() {
121 // FIXME these should be configurable...
122 windows = new WindowCache(256 * 1024 * 1024, 4);
125 public File getDirectory() {
126 return gitDir;
129 public File getObjectsDirectory() {
130 return objectsDirs[0];
133 public RepositoryConfig getConfig() {
134 return config;
137 public WindowCache getWindowCache() {
138 return windows;
141 public File toFile(final ObjectId objectId) {
142 final String n = objectId.toString();
143 String d=n.substring(0, 2);
144 String f=n.substring(2);
145 for (int i=0; i<objectsDirs.length; ++i) {
146 File ret = new File(new File(objectsDirs[i], d), f);
147 if (ret.exists())
148 return ret;
150 return new File(new File(objectsDirs[0], d), f);
153 public boolean hasObject(final ObjectId objectId) {
154 int k = packs.length;
155 if (k > 0) {
156 do {
157 if (packs[--k].hasObject(objectId))
158 return true;
159 } while (k > 0);
161 return toFile(objectId).isFile();
164 public ObjectLoader openObject(final ObjectId id) throws IOException {
165 int k = packs.length;
166 if (k > 0) {
167 do {
168 try {
169 final ObjectLoader ol = packs[--k].get(id);
170 if (ol != null)
171 return ol;
172 } catch (IOException ioe) {
173 // This shouldn't happen unless the pack was corrupted
174 // after we opened it or the VM runs out of memory. This is
175 // a know problem with memory mapped I/O in java and have
176 // been noticed with JDK < 1.6. Tell the gc that now is a good
177 // time to collect and try once more.
178 try {
179 System.gc();
180 final ObjectLoader ol = packs[k].get(id);
181 if (ol != null)
182 return ol;
183 } catch (IOException ioe2) {
184 ioe2.printStackTrace();
185 ioe.printStackTrace();
186 // Still fails.. that's BAD, maybe the pack has
187 // been corrupted after all, or the gc didn't manage
188 // to release enough previously mmaped areas.
191 } while (k > 0);
193 try {
194 return new UnpackedObjectLoader(this, id);
195 } catch (FileNotFoundException fnfe) {
196 return null;
200 public ObjectLoader openBlob(final ObjectId id) throws IOException {
201 return openObject(id);
204 public ObjectLoader openTree(final ObjectId id) throws IOException {
205 return openObject(id);
208 public Commit mapCommit(final String revstr) throws IOException {
209 final ObjectId id = resolve(revstr);
210 return id != null ? mapCommit(id) : null;
213 public Commit mapCommit(final ObjectId id) throws IOException {
214 Reference<Commit> retr = commitCache.get(id);
215 if (retr != null) {
216 Commit ret = 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<Commit>(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<Tree> wret = treeCache.get(id);
243 if (wret != null) {
244 Tree ret = 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<Tree>(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 Ref r = readRef(ref, true);
280 final RefLock l = new RefLock(new File(gitDir, r.getName()));
281 return l.lock() ? l : null;
284 /** Parse a git revision string and return an object id.
286 * It is not fully implemented, so it only deals with
287 * commits and to some extent tags. Reflogs are not
288 * supported yet.
289 * The plan is to implement it fully.
290 * @param revstr A git object references expression
291 * @return an ObjectId
292 * @throws IOException on serious errors
294 public ObjectId parse(String revstr) throws IOException {
295 char[] rev = revstr.toCharArray();
296 ObjectId ret = null;
297 Commit ref = null;
298 for (int i = 0; i < rev.length; ++i) {
299 switch (rev[i]) {
300 case '^':
301 if (ref == null) {
302 String refstr = new String(rev,0,i);
303 ObjectId refId = resolveSimple(refstr);
304 ref = mapCommit(refId);
306 if (i + 1 < rev.length) {
307 switch (rev[i + 1]) {
308 case '0':
309 case '1':
310 case '2':
311 case '3':
312 case '4':
313 case '5':
314 case '6':
315 case '7':
316 case '8':
317 case '9':
318 int j;
319 for (j=i+1; j<rev.length; ++j) {
320 if (!Character.isDigit(rev[j]))
321 break;
323 String parentnum = new String(rev, i+1, j-i-1);
324 int pnum = Integer.parseInt(parentnum);
325 if (pnum != 0)
326 ref = mapCommit(ref.getParentIds()[pnum - 1]);
327 i = j - 1;
328 break;
329 case '{':
330 int k;
331 String item = null;
332 for (k=i+2; k<rev.length; ++k) {
333 if (rev[k] == '}') {
334 item = new String(rev, i+2, k-i-2);
335 break;
338 i = k;
339 if (item != null)
340 if (item.equals("tree"))
341 ret = ref.getTreeId();
342 else if (item.equals("commit"))
343 ; // just reference self
344 else
345 return null; // invalid
346 else
347 return null; // invalid
348 break;
349 default:
350 ref = mapCommit(ref.getParentIds()[0]);
352 } else {
353 ref = mapCommit(ref.getParentIds()[0]);
355 break;
356 case '~':
357 if (ref == null) {
358 String refstr = new String(rev,0,i);
359 ObjectId refId = resolveSimple(refstr);
360 ref = mapCommit(refId);
362 int l;
363 for (l = i + 1; l < rev.length; ++l) {
364 if (!Character.isDigit(rev[l]))
365 break;
367 String distnum = new String(rev, i+1, l-i-1);
368 int dist = Integer.parseInt(distnum);
369 while (dist >= 0) {
370 ref = mapCommit(ref.getParentIds()[0]);
371 --dist;
373 i = l - 1;
374 break;
375 case '@':
376 int m;
377 String time = null;
378 for (m=i+2; m<rev.length; ++m) {
379 if (rev[m] == '}') {
380 time = new String(rev, i+2, m-i-2);
381 break;
384 if (time != null)
385 throw new IllegalArgumentException("reflogs not yet supprted");
386 i = m - 1;
387 break;
388 default:
389 if (ref != null)
390 return null; // cannot parse, return null
393 if (ret == null)
394 if (ref != null)
395 ret = ref.getCommitId();
396 else
397 ret = resolveSimple(revstr);
398 return ret;
401 public ObjectId resolve(final String revstr) throws IOException {
402 return parse(revstr);
405 public ObjectId resolveSimple(final String revstr) throws IOException {
406 ObjectId id = null;
408 if (ObjectId.isId(revstr)) {
409 id = new ObjectId(revstr);
412 if (id == null) {
413 final Ref r = readRef(revstr, false);
414 if (r != null) {
415 id = r.getObjectId();
419 return id;
422 public void close() throws IOException {
423 closePacks();
426 public void closePacks() throws IOException {
427 for (int k = packs.length - 1; k >= 0; k--) {
428 packs[k].close();
430 packs = new PackFile[0];
433 public void scanForPacks() {
434 final ArrayList<PackFile> p = new ArrayList<PackFile>();
435 for (int i=0; i<objectsDirs.length; ++i)
436 scanForPacks(new File(objectsDirs[i], "pack"), p);
437 final PackFile[] arr = new PackFile[p.size()];
438 p.toArray(arr);
439 packs = arr;
442 public void scanForPacks(final File packDir, Collection<PackFile> packList) {
443 final File[] list = packDir.listFiles(new FileFilter() {
444 public boolean accept(final File f) {
445 final String n = f.getName();
446 if (!n.endsWith(".pack")) {
447 return false;
449 final String nBase = n.substring(0, n.lastIndexOf('.'));
450 final File idx = new File(packDir, nBase + ".idx");
451 return f.isFile() && f.canRead() && idx.isFile()
452 && idx.canRead();
455 if (list != null) {
456 for (int k = 0; k < list.length; k++) {
457 try {
458 packList.add(new PackFile(this, list[k]));
459 } catch (IOException ioe) {
460 // Whoops. That's not a pack!
467 public void writeSymref(final String name, final String target)
468 throws IOException {
469 final byte[] content = ("ref: " + target + "\n").getBytes("UTF-8");
470 final RefLock lck = new RefLock(new File(gitDir, name));
471 if (!lck.lock())
472 throw new ObjectWritingException("Unable to lock " + name);
473 try {
474 lck.write(content);
475 } catch (IOException ioe) {
476 throw new ObjectWritingException("Unable to write " + name, ioe);
478 if (!lck.commit())
479 throw new ObjectWritingException("Unable to write " + name);
482 private Ref readRef(final String revstr, final boolean missingOk)
483 throws IOException {
484 refreshPackredRefsCache();
485 for (int k = 0; k < refSearchPaths.length; k++) {
486 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
487 if (missingOk || r.getObjectId() != null) {
488 return r;
491 return null;
494 private Ref readRefBasic(String name) throws IOException {
495 int depth = 0;
496 REF_READING: do {
497 ObjectId id = packedRefs.get(name);
498 if (id != null)
499 return new Ref(null, id);
501 final File f = new File(getDirectory(), name);
502 if (!f.isFile())
503 return new Ref(name, null);
505 final BufferedReader br = new BufferedReader(new FileReader(f));
506 try {
507 final String line = br.readLine();
508 if (line == null || line.length() == 0)
509 return new Ref(name, null);
510 else if (line.startsWith("ref: ")) {
511 name = line.substring("ref: ".length());
512 continue REF_READING;
513 } else if (ObjectId.isId(line))
514 return new Ref(name, new ObjectId(line));
515 throw new IOException("Not a ref: " + name + ": " + line);
516 } finally {
517 br.close();
519 } while (depth++ < 5);
520 throw new IOException("Exceed maximum ref depth. Circular reference?");
523 public String toString() {
524 return "Repository[" + getDirectory() + "]";
527 public String getPatch() throws IOException {
528 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
529 final BufferedReader br = new BufferedReader(new FileReader(ptr));
530 String last=null;
531 try {
532 String line;
533 while ((line=br.readLine())!=null) {
534 last = line;
536 } finally {
537 br.close();
539 return last;
542 public String getBranch() throws IOException {
543 final File ptr = new File(getDirectory(),"HEAD");
544 final BufferedReader br = new BufferedReader(new FileReader(ptr));
545 String ref;
546 try {
547 ref = br.readLine();
548 } finally {
549 br.close();
551 if (ref.startsWith("ref: "))
552 ref = ref.substring(5);
553 if (ref.startsWith("refs/heads/"))
554 ref = ref.substring(11);
555 return ref;
558 public Collection<String> getBranches() {
559 return listFilesRecursively(new File(refsDir, "heads"), null);
562 public Collection<String> getTags() {
563 Collection<String> tags = listFilesRecursively(new File(refsDir, "tags"), null);
564 refreshPackredRefsCache();
565 tags.addAll(packedRefs.keySet());
566 return tags;
569 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
570 private long packedrefstime = 0;
572 private void refreshPackredRefsCache() {
573 File file = new File(gitDir, "packed-refs");
574 if (!file.exists()) {
575 if (packedRefs.size() > 0)
576 packedRefs = new HashMap<String,ObjectId>();
577 return;
579 if (file.lastModified() == packedrefstime)
580 return;
581 Map<String,ObjectId> newPackedRefs = new HashMap<String,ObjectId>();
582 try {
583 BufferedReader b=new BufferedReader(new FileReader(file));
584 String p;
585 while ((p = b.readLine()) != null) {
586 if (p.charAt(0) == '#')
587 continue;
588 if (p.charAt(0) == '^') {
589 continue;
591 int spos = p.indexOf(' ');
592 ObjectId id = new ObjectId(p.substring(0,spos));
593 String name = p.substring(spos+1);
594 newPackedRefs.put(name, id);
596 } catch (IOException e) {
597 e.printStackTrace();
599 packedRefs = newPackedRefs;
603 * @return true if HEAD points to a StGit patch.
605 public boolean isStGitMode() {
606 try {
607 File file = new File(getDirectory(), "HEAD");
608 BufferedReader reader = new BufferedReader(new FileReader(file));
609 String string = reader.readLine();
610 if (!string.startsWith("ref: refs/heads/"))
611 return false;
612 String branch = string.substring("ref: refs/heads/".length());
613 File currentPatches = new File(new File(new File(getDirectory(),
614 "patches"), branch), "applied");
615 if (!currentPatches.exists())
616 return false;
617 if (currentPatches.length() == 0)
618 return false;
619 return true;
621 } catch (IOException e) {
622 e.printStackTrace();
623 return false;
627 public static class StGitPatch {
628 public StGitPatch(String patchName, ObjectId id) {
629 name = patchName;
630 gitId = id;
632 public ObjectId getGitId() {
633 return gitId;
635 public String getName() {
636 return name;
638 private String name;
639 private ObjectId gitId;
643 * @return applied patches in a map indexed on current commit id
644 * @throws IOException
646 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
647 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
648 if (isStGitMode()) {
649 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
650 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
651 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
652 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
653 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
654 String objectId = tfr.readLine();
655 ObjectId id = new ObjectId(objectId);
656 ret.put(id, new StGitPatch(patchName, id));
657 tfr.close();
659 apr.close();
661 return ret;
664 private Collection<String> listFilesRecursively(File root, File start) {
665 if (start == null)
666 start = root;
667 Collection<String> ret = new ArrayList<String>();
668 File[] files = start.listFiles();
669 for (int i = 0; i < files.length; ++i) {
670 if (files[i].isDirectory())
671 ret.addAll(listFilesRecursively(root, files[i]));
672 else if (files[i].length() == 41) {
673 String name = files[i].toString().substring(
674 root.toString().length() + 1);
675 ret.add(name);
678 return ret;
681 /** Clean up stale caches */
682 public void refreshFromDisk() {
683 packedRefs = null;
686 public GitIndex getIndex() throws IOException {
687 if (index == null) {
688 index = new GitIndex(this);
689 index.read();
690 } else {
691 index.rereadIfNecessary();
693 return index;
696 public static byte[] gitInternalSlash(byte[] bytes) {
697 if (File.separatorChar == '/')
698 return bytes;
699 for (int i=0; i<bytes.length; ++i)
700 if (bytes[i] == File.separatorChar)
701 bytes[i] = '/';
702 return bytes;