Allow mutable object ids, improving commit parsing performance
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob01c25b77972295dfa3ce63922019b061563e3b02
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.FileNotFoundException;
22 import java.io.FileReader;
23 import java.io.FilenameFilter;
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.Set;
32 import java.util.WeakHashMap;
34 import org.spearce.jgit.errors.IncorrectObjectTypeException;
35 import org.spearce.jgit.errors.ObjectWritingException;
36 import org.spearce.jgit.stgit.StGitPatch;
38 /**
39 * Represents a Git repository. A repository holds all objects and refs used for
40 * managing source code (could by any type of file, but source code is what
41 * SCM's are typically used for).
43 * In Git terms all data is stored in GIT_DIR, typically a directory called
44 * .git. A work tree is maintained unless the repository is a bare repository.
45 * Typically the .git directory is located at the root of the work dir.
47 * <ul>
48 * <li>GIT_DIR
49 * <ul>
50 * <li>objects/ - objects</li>
51 * <li>refs/ - tags and heads</li>
52 * <li>config - configuration</li>
53 * <li>info/ - more configurations</li>
54 * </ul>
55 * </li>
56 * </ul>
58 * This implementation only handles a subtly undocumented subset of git features.
61 public class Repository {
62 private static final String[] refSearchPaths = { "", "refs/", "refs/tags/",
63 "refs/heads/", "refs/remotes/" };
65 private final File gitDir;
67 private final File[] objectsDirs;
69 private final File refsDir;
71 private final RepositoryConfig config;
73 private PackFile[] packs;
75 private final WindowCache windows;
77 private Map<ObjectId,Reference<Tree>> treeCache = new WeakHashMap<ObjectId,Reference<Tree>>(30000);
78 private Map<ObjectId,Reference<Commit>> commitCache = new WeakHashMap<ObjectId,Reference<Commit>>(30000);
80 private GitIndex index;
82 /**
83 * Construct a representation of this git repo managing a Git repository.
85 * @param d
86 * GIT_DIR
87 * @throws IOException
89 public Repository(final File d) throws IOException {
90 this(null, d);
93 /**
94 * Construct a representation of a Git repository.
96 * @param wc
97 * cache this repository's data will be cached through during
98 * access. May be shared with another repository, or null to
99 * indicate this repository should allocate its own private
100 * cache.
101 * @param d
102 * GIT_DIR (the location of the repository metadata).
103 * @throws IOException
104 * the repository appears to already exist but cannot be
105 * accessed.
107 public Repository(final WindowCache wc, final File d) throws IOException {
108 gitDir = d.getAbsoluteFile();
109 try {
110 objectsDirs = readObjectsDirs(new File(gitDir, "objects"), new ArrayList<File>()).toArray(new File[0]);
111 } catch (IOException e) {
112 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
113 ex.initCause(e);
114 throw ex;
116 refsDir = new File(gitDir, "refs");
117 packs = new PackFile[0];
118 config = new RepositoryConfig(this);
120 final boolean isExisting = objectsDirs[0].exists();
121 if (isExisting) {
122 getConfig().load();
123 final String repositoryFormatVersion = getConfig().getString(
124 "core", null, "repositoryFormatVersion");
125 if (!"0".equals(repositoryFormatVersion)) {
126 throw new IOException("Unknown repository format \""
127 + repositoryFormatVersion + "\"; expected \"0\".");
129 } else {
130 getConfig().create();
132 windows = wc != null ? wc : new WindowCache(getConfig());
133 if (isExisting)
134 scanForPacks();
137 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
138 ret.add(objectsDir);
139 File alternatesFile = new File(objectsDir,"info/alternates");
140 if (alternatesFile.exists()) {
141 BufferedReader ar = new BufferedReader(new FileReader(alternatesFile));
142 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
143 readObjectsDirs(new File(alt), ret);
145 ar.close();
147 return ret;
151 * Create a new Git repository initializing the necessary files and
152 * directories.
154 * @throws IOException
156 public void create() throws IOException {
157 if (gitDir.exists()) {
158 throw new IllegalStateException("Repository already exists: "
159 + gitDir);
162 gitDir.mkdirs();
164 objectsDirs[0].mkdirs();
165 new File(objectsDirs[0], "pack").mkdir();
166 new File(objectsDirs[0], "info").mkdir();
168 refsDir.mkdir();
169 new File(refsDir, "heads").mkdir();
170 new File(refsDir, "tags").mkdir();
172 new File(gitDir, "branches").mkdir();
173 new File(gitDir, "remotes").mkdir();
174 writeSymref("HEAD", "refs/heads/master");
176 getConfig().create();
177 getConfig().save();
181 * @return GIT_DIR
183 public File getDirectory() {
184 return gitDir;
188 * @return the directory containg the objects owned by this repository.
190 public File getObjectsDirectory() {
191 return objectsDirs[0];
195 * @return the configuration of this repository
197 public RepositoryConfig getConfig() {
198 return config;
202 * @return the cache needed for accessing packed objects in this repository.
204 public WindowCache getWindowCache() {
205 return windows;
209 * Construct a filename where the loose object having a specified SHA-1
210 * should be stored. If the object is stored in a shared repository the path
211 * to the alternative repo will be returned. If the object is not yet store
212 * a usable path in this repo will be returned. It is assumed that callers
213 * will look for objects in a pack first.
215 * @param objectId
216 * @return suggested file name
218 public File toFile(final AnyObjectId objectId) {
219 final String n = objectId.toString();
220 String d=n.substring(0, 2);
221 String f=n.substring(2);
222 for (int i=0; i<objectsDirs.length; ++i) {
223 File ret = new File(new File(objectsDirs[i], d), f);
224 if (ret.exists())
225 return ret;
227 return new File(new File(objectsDirs[0], d), f);
231 * @param objectId
232 * @return true if the specified object is stored in this repo or any of the
233 * known shared repositories.
235 public boolean hasObject(final AnyObjectId objectId) {
236 int k = packs.length;
237 if (k > 0) {
238 do {
239 if (packs[--k].hasObject(objectId))
240 return true;
241 } while (k > 0);
243 return toFile(objectId).isFile();
247 * @param id
248 * SHA-1 of an object.
250 * @return a {@link ObjectLoader} for accessing the data of the named
251 * object, or null if the object does not exist.
252 * @throws IOException
254 public ObjectLoader openObject(final AnyObjectId id)
255 throws IOException {
256 return openObject(new WindowCursor(),id);
260 * @param curs
261 * temporary working space associated with the calling thread.
262 * @param id
263 * SHA-1 of an object.
265 * @return a {@link ObjectLoader} for accessing the data of the named
266 * object, or null if the object does not exist.
267 * @throws IOException
269 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
270 throws IOException {
271 int k = packs.length;
272 if (k > 0) {
273 do {
274 try {
275 final ObjectLoader ol = packs[--k].get(curs, id);
276 if (ol != null)
277 return ol;
278 } catch (IOException ioe) {
279 // This shouldn't happen unless the pack was corrupted
280 // after we opened it or the VM runs out of memory. This is
281 // a know problem with memory mapped I/O in java and have
282 // been noticed with JDK < 1.6. Tell the gc that now is a good
283 // time to collect and try once more.
284 try {
285 curs.release();
286 System.gc();
287 final ObjectLoader ol = packs[k].get(curs, id);
288 if (ol != null)
289 return ol;
290 } catch (IOException ioe2) {
291 ioe2.printStackTrace();
292 ioe.printStackTrace();
293 // Still fails.. that's BAD, maybe the pack has
294 // been corrupted after all, or the gc didn't manage
295 // to release enough previously mmaped areas.
298 } while (k > 0);
300 try {
301 return new UnpackedObjectLoader(this, id.toObjectId());
302 } catch (FileNotFoundException fnfe) {
303 return null;
308 * @param id
309 * SHA'1 of a blob
310 * @return an {@link ObjectLoader} for accessing the data of a named blob
311 * @throws IOException
313 public ObjectLoader openBlob(final ObjectId id) throws IOException {
314 return openObject(id);
318 * @param id
319 * SHA'1 of a tree
320 * @return an {@link ObjectLoader} for accessing the data of a named tree
321 * @throws IOException
323 public ObjectLoader openTree(final ObjectId id) throws IOException {
324 return openObject(id);
328 * Access a Commit object using a symbolic reference. This reference may
329 * be a SHA-1 or ref in combination with a number of symbols translating
330 * from one ref or SHA1-1 to another, such as HEAD^ etc.
332 * @param revstr a reference to a git commit object
333 * @return a Commit named by the specified string
334 * @throws IOException for I/O error or unexpected object type.
336 * @see #resolve(String)
338 public Commit mapCommit(final String revstr) throws IOException {
339 final ObjectId id = resolve(revstr);
340 return id != null ? mapCommit(id) : null;
344 * Access a Commit by SHA'1 id.
345 * @param id
346 * @return Commit or null
347 * @throws IOException for I/O error or unexpected object type.
349 public Commit mapCommit(final ObjectId id) throws IOException {
350 Reference<Commit> retr = commitCache.get(id);
351 if (retr != null) {
352 Commit ret = retr.get();
353 if (ret != null)
354 return ret;
355 // System.out.println("Found a null id, size was "+commitCache.size());
358 final ObjectLoader or = openObject(id);
359 if (or == null)
360 return null;
361 final byte[] raw = or.getBytes();
362 if (Constants.OBJ_COMMIT == or.getType()) {
363 Commit ret = new Commit(this, id, raw);
364 // The key must not be the referenced strongly
365 // by the value in WeakHashMaps
366 commitCache.put(id, new SoftReference<Commit>(ret));
367 return ret;
369 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
373 * Access a Tree object using a symbolic reference. This reference may
374 * be a SHA-1 or ref in combination with a number of symbols translating
375 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
377 * @param revstr a reference to a git commit object
378 * @return a Tree named by the specified string
379 * @throws IOException
381 * @see #resolve(String)
383 public Tree mapTree(final String revstr) throws IOException {
384 final ObjectId id = resolve(revstr);
385 return id != null ? mapTree(id) : null;
389 * Access a Tree by SHA'1 id.
390 * @param id
391 * @return Tree or null
392 * @throws IOException for I/O error or unexpected object type.
394 public Tree mapTree(final ObjectId id) throws IOException {
395 Reference<Tree> wret = treeCache.get(id);
396 if (wret != null) {
397 Tree ret = wret.get();
398 if (ret != null)
399 return ret;
402 final ObjectLoader or = openObject(id);
403 if (or == null)
404 return null;
405 final byte[] raw = or.getBytes();
406 if (Constants.OBJ_TREE == or.getType()) {
407 Tree ret = new Tree(this, id, raw);
408 treeCache.put(id, new SoftReference<Tree>(ret));
409 return ret;
411 if (Constants.OBJ_COMMIT == or.getType())
412 return mapTree(ObjectId.fromString(raw, 5));
413 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
417 * Access a tag by symbolic name.
419 * @param revstr
420 * @return a Tag or null
421 * @throws IOException on I/O error or unexpected type
423 public Tag mapTag(String revstr) throws IOException {
424 final ObjectId id = resolve(revstr);
425 return id != null ? mapTag(revstr, id) : null;
429 * Access a Tag by SHA'1 id
430 * @param refName
431 * @param id
432 * @return Commit or null
433 * @throws IOException for I/O error or unexpected object type.
435 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
436 final ObjectLoader or = openObject(id);
437 if (or == null)
438 return null;
439 final byte[] raw = or.getBytes();
440 if (Constants.OBJ_TAG == or.getType())
441 return new Tag(this, id, refName, raw);
442 return new Tag(this, id, refName, null);
446 * Get a locked handle to a ref suitable for updating or creating.
448 * @param ref name to lock
449 * @return a locked ref
450 * @throws IOException
452 public RefLock lockRef(final String ref) throws IOException {
453 final Ref r = readRef(ref, true);
454 final RefLock l = new RefLock(new File(gitDir, r.getName()));
455 return l.lock() ? l : null;
459 * Parse a git revision string and return an object id.
461 * Currently supported is combinations of these.
462 * <ul>
463 * <li>SHA-1 - a SHA-1</li>
464 * <li>refs/... - a ref name</li>
465 * <li>ref^n - nth parent reference</li>
466 * <li>ref~n - distance via parent reference</li>
467 * <li>ref@{n} - nth version of ref</li>
468 * <li>ref^{tree} - tree references by ref</li>
469 * <li>ref^{commit} - commit references by ref</li>
470 * </ul>
472 * Not supported is
473 * <ul>
474 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
475 * <li>abbreviated SHA-1's</li>
476 * </ul>
478 * @param revstr A git object references expression
479 * @return an ObjectId
480 * @throws IOException on serious errors
482 public ObjectId resolve(final String revstr) throws IOException {
483 char[] rev = revstr.toCharArray();
484 ObjectId ret = null;
485 Commit ref = null;
486 for (int i = 0; i < rev.length; ++i) {
487 switch (rev[i]) {
488 case '^':
489 if (ref == null) {
490 String refstr = new String(rev,0,i);
491 ObjectId refId = resolveSimple(refstr);
492 ref = mapCommit(refId);
494 if (i + 1 < rev.length) {
495 switch (rev[i + 1]) {
496 case '0':
497 case '1':
498 case '2':
499 case '3':
500 case '4':
501 case '5':
502 case '6':
503 case '7':
504 case '8':
505 case '9':
506 int j;
507 for (j=i+1; j<rev.length; ++j) {
508 if (!Character.isDigit(rev[j]))
509 break;
511 String parentnum = new String(rev, i+1, j-i-1);
512 int pnum = Integer.parseInt(parentnum);
513 if (pnum != 0)
514 ref = mapCommit(ref.getParentIds()[pnum - 1]);
515 i = j - 1;
516 break;
517 case '{':
518 int k;
519 String item = null;
520 for (k=i+2; k<rev.length; ++k) {
521 if (rev[k] == '}') {
522 item = new String(rev, i+2, k-i-2);
523 break;
526 i = k;
527 if (item != null)
528 if (item.equals("tree"))
529 ret = ref.getTreeId();
530 else if (item.equals("commit"))
531 ; // just reference self
532 else
533 return null; // invalid
534 else
535 return null; // invalid
536 break;
537 default:
538 ref = mapCommit(ref.getParentIds()[0]);
540 } else {
541 ref = mapCommit(ref.getParentIds()[0]);
543 break;
544 case '~':
545 if (ref == null) {
546 String refstr = new String(rev,0,i);
547 ObjectId refId = resolveSimple(refstr);
548 ref = mapCommit(refId);
550 int l;
551 for (l = i + 1; l < rev.length; ++l) {
552 if (!Character.isDigit(rev[l]))
553 break;
555 String distnum = new String(rev, i+1, l-i-1);
556 int dist = Integer.parseInt(distnum);
557 while (dist >= 0) {
558 ref = mapCommit(ref.getParentIds()[0]);
559 --dist;
561 i = l - 1;
562 break;
563 case '@':
564 int m;
565 String time = null;
566 for (m=i+2; m<rev.length; ++m) {
567 if (rev[m] == '}') {
568 time = new String(rev, i+2, m-i-2);
569 break;
572 if (time != null)
573 throw new IllegalArgumentException("reflogs not yet supprted");
574 i = m - 1;
575 break;
576 default:
577 if (ref != null)
578 return null; // cannot parse, return null
581 if (ret == null)
582 if (ref != null)
583 ret = ref.getCommitId();
584 else
585 ret = resolveSimple(revstr);
586 return ret;
589 private ObjectId resolveSimple(final String revstr) throws IOException {
590 if (ObjectId.isId(revstr))
591 return ObjectId.fromString(revstr);
592 final Ref r = readRef(revstr, false);
593 if (r != null) {
594 return r.getObjectId();
596 return null;
600 * Close all resources used by this repository
602 public void close() {
603 closePacks();
606 void closePacks() {
607 for (int k = packs.length - 1; k >= 0; k--) {
608 packs[k].close();
610 packs = new PackFile[0];
614 * Scan the object dirs, including alternates for packs
615 * to use.
617 public void scanForPacks() {
618 final ArrayList<PackFile> p = new ArrayList<PackFile>();
619 for (int i=0; i<objectsDirs.length; ++i)
620 scanForPacks(new File(objectsDirs[i], "pack"), p);
621 final PackFile[] arr = new PackFile[p.size()];
622 p.toArray(arr);
623 packs = arr;
626 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
627 final String[] idxList = packDir.list(new FilenameFilter() {
628 public boolean accept(final File baseDir, final String n) {
629 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
630 return n.length() == 49 && n.endsWith(".idx")
631 && n.startsWith("pack-");
634 if (idxList != null) {
635 for (final String indexName : idxList) {
636 final String n = indexName.substring(0, indexName.length() - 4);
637 final File idxFile = new File(packDir, n + ".idx");
638 final File packFile = new File(packDir, n + ".pack");
639 try {
640 packList.add(new PackFile(this, idxFile, packFile));
641 } catch (IOException ioe) {
642 // Whoops. That's not a pack!
644 ioe.printStackTrace();
651 * Writes a symref (e.g. HEAD) to disk
653 * @param name symref name
654 * @param target pointed to ref
655 * @throws IOException
657 public void writeSymref(final String name, final String target)
658 throws IOException {
659 final byte[] content = ("ref: " + target + "\n").getBytes("UTF-8");
660 final RefLock lck = new RefLock(new File(gitDir, name));
661 if (!lck.lock())
662 throw new ObjectWritingException("Unable to lock " + name);
663 try {
664 lck.write(content);
665 } catch (IOException ioe) {
666 throw new ObjectWritingException("Unable to write " + name, ioe);
668 if (!lck.commit())
669 throw new ObjectWritingException("Unable to write " + name);
672 private Ref readRef(final String revstr, final boolean missingOk)
673 throws IOException {
674 refreshPackedRefsCache();
675 for (int k = 0; k < refSearchPaths.length; k++) {
676 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
677 if (missingOk || r.getObjectId() != null) {
678 return r;
681 return null;
684 private Ref readRefBasic(String name) throws IOException {
685 int depth = 0;
686 REF_READING: do {
687 // prefer unpacked ref to packed ref
688 final File f = new File(getDirectory(), name);
689 if (!f.isFile()) {
690 // look for packed ref, since this one doesn't exist
691 ObjectId id = packedRefs.get(name);
692 if (id != null)
693 return new Ref(name, id);
695 // no packed ref found, return blank one
696 return new Ref(name, null);
699 final BufferedReader br = new BufferedReader(new FileReader(f));
700 try {
701 final String line = br.readLine();
702 if (line == null || line.length() == 0)
703 return new Ref(name, null);
704 else if (line.startsWith("ref: ")) {
705 name = line.substring("ref: ".length());
706 continue REF_READING;
707 } else if (ObjectId.isId(line))
708 return new Ref(name, ObjectId.fromString(line));
709 throw new IOException("Not a ref: " + name + ": " + line);
710 } finally {
711 br.close();
713 } while (depth++ < 5);
714 throw new IOException("Exceed maximum ref depth. Circular reference?");
717 public String toString() {
718 return "Repository[" + getDirectory() + "]";
722 * @return name of topmost Stacked Git patch.
723 * @throws IOException
725 public String getPatch() throws IOException {
726 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
727 final BufferedReader br = new BufferedReader(new FileReader(ptr));
728 String last=null;
729 try {
730 String line;
731 while ((line=br.readLine())!=null) {
732 last = line;
734 } finally {
735 br.close();
737 return last;
741 * @return name of current branch
742 * @throws IOException
744 public String getFullBranch() throws IOException {
745 final File ptr = new File(getDirectory(),"HEAD");
746 final BufferedReader br = new BufferedReader(new FileReader(ptr));
747 String ref;
748 try {
749 ref = br.readLine();
750 } finally {
751 br.close();
753 if (ref.startsWith("ref: "))
754 ref = ref.substring(5);
755 return ref;
759 * @return name of current branch.
760 * @throws IOException
762 public String getBranch() throws IOException {
763 try {
764 final File ptr = new File(getDirectory(),"HEAD");
765 final BufferedReader br = new BufferedReader(new FileReader(ptr));
766 String ref;
767 try {
768 ref = br.readLine();
769 } finally {
770 br.close();
772 if (ref.startsWith("ref: "))
773 ref = ref.substring(5);
774 if (ref.startsWith("refs/heads/"))
775 ref = ref.substring(11);
776 return ref;
777 } catch (FileNotFoundException e) {
778 final File ptr = new File(getDirectory(),"head-name");
779 final BufferedReader br = new BufferedReader(new FileReader(ptr));
780 String ref;
781 try {
782 ref = br.readLine();
783 } finally {
784 br.close();
786 return ref;
791 * @return names of all local branches
793 public Collection<String> getBranches() {
794 return listRefs("heads");
798 * @return the names of all refs (local and remotes branches, tags)
800 public Collection<String> getAllRefs() {
801 return listRefs("");
804 private Collection<String> listRefs(String refSubDir) {
805 // add / to end, unless empty
806 if (refSubDir.length() > 0 && refSubDir.charAt(refSubDir.length() -1 ) != '/')
807 refSubDir += "/";
809 Collection<String> branchesRaw = listFilesRecursively(new File(refsDir, refSubDir), null);
810 ArrayList<String> branches = new ArrayList<String>();
811 for (String b : branchesRaw) {
812 branches.add("refs/" + refSubDir + b);
815 refreshPackedRefsCache();
816 Set<String> keySet = packedRefs.keySet();
817 for (String s : keySet)
818 if (s.startsWith("refs/" + refSubDir) && !branches.contains(s))
819 branches.add(s);
820 return branches;
824 * @return all git tags
826 public Collection<String> getTags() {
827 return listRefs("tags");
830 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
831 private long packedrefstime = 0;
833 private void refreshPackedRefsCache() {
834 File file = new File(gitDir, "packed-refs");
835 if (!file.exists()) {
836 if (packedRefs.size() > 0)
837 packedRefs = new HashMap<String,ObjectId>();
838 return;
840 if (file.lastModified() == packedrefstime)
841 return;
842 Map<String,ObjectId> newPackedRefs = new HashMap<String,ObjectId>();
843 FileReader fileReader = null;
844 try {
845 fileReader = new FileReader(file);
846 BufferedReader b=new BufferedReader(fileReader);
847 String p;
848 while ((p = b.readLine()) != null) {
849 if (p.charAt(0) == '#')
850 continue;
851 if (p.charAt(0) == '^') {
852 continue;
854 int spos = p.indexOf(' ');
855 ObjectId id = ObjectId.fromString(p.substring(0,spos));
856 String name = p.substring(spos+1);
857 newPackedRefs.put(name, id);
859 } catch (IOException e) {
860 throw new Error("Cannot read packed refs",e);
861 } finally {
862 if (fileReader != null) {
863 try {
864 fileReader.close();
865 } catch (IOException e) {
866 // Cannot do anything more here
867 e.printStackTrace();
871 packedRefs = newPackedRefs;
875 * @return true if HEAD points to a StGit patch.
877 public boolean isStGitMode() {
878 try {
879 File file = new File(getDirectory(), "HEAD");
880 BufferedReader reader = new BufferedReader(new FileReader(file));
881 String string = reader.readLine();
882 if (!string.startsWith("ref: refs/heads/"))
883 return false;
884 String branch = string.substring("ref: refs/heads/".length());
885 File currentPatches = new File(new File(new File(getDirectory(),
886 "patches"), branch), "applied");
887 if (!currentPatches.exists())
888 return false;
889 if (currentPatches.length() == 0)
890 return false;
891 return true;
893 } catch (IOException e) {
894 e.printStackTrace();
895 return false;
900 * @return applied patches in a map indexed on current commit id
901 * @throws IOException
903 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
904 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
905 if (isStGitMode()) {
906 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
907 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
908 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
909 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
910 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
911 String objectId = tfr.readLine();
912 ObjectId id = ObjectId.fromString(objectId);
913 ret.put(id, new StGitPatch(patchName, id));
914 tfr.close();
916 apr.close();
918 return ret;
921 private Collection<String> listFilesRecursively(File root, File start) {
922 if (start == null)
923 start = root;
924 Collection<String> ret = new ArrayList<String>();
925 File[] files = start.listFiles();
926 for (int i = 0; i < files.length; ++i) {
927 if (files[i].isDirectory())
928 ret.addAll(listFilesRecursively(root, files[i]));
929 else if (files[i].length() == 41) {
930 String name = files[i].toString().substring(
931 root.toString().length() + 1);
932 if (File.separatorChar != '/')
933 name = name.replace(File.separatorChar, '/');
934 ret.add(name);
937 return ret;
940 /** Clean up stale caches */
941 public void refreshFromDisk() {
942 packedRefs = null;
946 * @return a representation of the index associated with this repo
947 * @throws IOException
949 public GitIndex getIndex() throws IOException {
950 if (index == null) {
951 index = new GitIndex(this);
952 index.read();
953 } else {
954 index.rereadIfNecessary();
956 return index;
959 static byte[] gitInternalSlash(byte[] bytes) {
960 if (File.separatorChar == '/')
961 return bytes;
962 for (int i=0; i<bytes.length; ++i)
963 if (bytes[i] == File.separatorChar)
964 bytes[i] = '/';
965 return bytes;
969 * @return an important state
971 public RepositoryState getRepositoryState() {
972 if (new File(gitDir.getParentFile(), ".dotest").exists())
973 return RepositoryState.REBASING;
974 if (new File(gitDir,".dotest-merge").exists())
975 return RepositoryState.REBASING_INTERACTIVE;
976 if (new File(gitDir,"MERGE_HEAD").exists())
977 return RepositoryState.MERGING;
978 if (new File(gitDir,"BISECT_LOG").exists())
979 return RepositoryState.BISECTING;
980 return RepositoryState.SAFE;
984 * String work dir and return normalized repository path
986 * @param wd Work dir
987 * @param f File whose path shall be stripp off it's workdir
988 * @return normalized repository relative path
990 public static String stripWorkDir(File wd, File f) {
991 String relName = f.getPath().substring(wd.getPath().length() + 1);
992 relName = relName.replace(File.separatorChar, '/');
993 return relName;