Reverse pack index implementation: PackReverseIndex
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob94a36a5e6d2f63f93d2626b13eb7a3cfbd5cdc5b
1 /*
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
10 * conditions are met:
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
23 * written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org.spearce.jgit.lib;
42 import java.io.BufferedReader;
43 import java.io.File;
44 import java.io.FileNotFoundException;
45 import java.io.FileReader;
46 import java.io.FilenameFilter;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.HashMap;
51 import java.util.LinkedList;
52 import java.util.Map;
54 import org.spearce.jgit.errors.IncorrectObjectTypeException;
55 import org.spearce.jgit.errors.RevisionSyntaxException;
56 import org.spearce.jgit.stgit.StGitPatch;
57 import org.spearce.jgit.util.FS;
59 /**
60 * Represents a Git repository. A repository holds all objects and refs used for
61 * managing source code (could by any type of file, but source code is what
62 * SCM's are typically used for).
64 * In Git terms all data is stored in GIT_DIR, typically a directory called
65 * .git. A work tree is maintained unless the repository is a bare repository.
66 * Typically the .git directory is located at the root of the work dir.
68 * <ul>
69 * <li>GIT_DIR
70 * <ul>
71 * <li>objects/ - objects</li>
72 * <li>refs/ - tags and heads</li>
73 * <li>config - configuration</li>
74 * <li>info/ - more configurations</li>
75 * </ul>
76 * </li>
77 * </ul>
79 * This implementation only handles a subtly undocumented subset of git features.
82 public class Repository {
83 private final File gitDir;
85 private final File[] objectsDirs;
87 private final RepositoryConfig config;
89 private final RefDatabase refs;
91 private PackFile[] packs;
93 private GitIndex index;
95 /**
96 * Construct a representation of a Git repository.
98 * @param d
99 * GIT_DIR (the location of the repository metadata).
100 * @throws IOException
101 * the repository appears to already exist but cannot be
102 * accessed.
104 public Repository(final File d) throws IOException {
105 gitDir = d.getAbsoluteFile();
106 try {
107 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
108 new ArrayList<File>()).toArray(new File[0]);
109 } catch (IOException e) {
110 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
111 ex.initCause(e);
112 throw ex;
114 refs = new RefDatabase(this);
115 packs = new PackFile[0];
116 config = new RepositoryConfig(this);
118 final boolean isExisting = objectsDirs[0].exists();
119 if (isExisting) {
120 getConfig().load();
121 final String repositoryFormatVersion = getConfig().getString(
122 "core", null, "repositoryFormatVersion");
123 if (!"0".equals(repositoryFormatVersion)) {
124 throw new IOException("Unknown repository format \""
125 + repositoryFormatVersion + "\"; expected \"0\".");
127 } else {
128 getConfig().create();
130 if (isExisting)
131 scanForPacks();
134 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
135 ret.add(objectsDir);
136 final File altFile = FS.resolve(objectsDir, "info/alternates");
137 if (altFile.exists()) {
138 BufferedReader ar = new BufferedReader(new FileReader(altFile));
139 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
140 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
142 ar.close();
144 return ret;
148 * Create a new Git repository initializing the necessary files and
149 * directories.
151 * @throws IOException
153 public void create() throws IOException {
154 if (gitDir.exists()) {
155 throw new IllegalStateException("Repository already exists: "
156 + gitDir);
159 gitDir.mkdirs();
160 refs.create();
162 objectsDirs[0].mkdirs();
163 new File(objectsDirs[0], "pack").mkdir();
164 new File(objectsDirs[0], "info").mkdir();
166 new File(gitDir, "branches").mkdir();
167 new File(gitDir, "remotes").mkdir();
168 final String master = Constants.HEADS_PREFIX + "/" + Constants.MASTER;
169 refs.link(Constants.HEAD, master);
171 getConfig().create();
172 getConfig().save();
176 * @return GIT_DIR
178 public File getDirectory() {
179 return gitDir;
183 * @return the directory containg the objects owned by this repository.
185 public File getObjectsDirectory() {
186 return objectsDirs[0];
190 * @return the configuration of this repository
192 public RepositoryConfig getConfig() {
193 return config;
197 * Construct a filename where the loose object having a specified SHA-1
198 * should be stored. If the object is stored in a shared repository the path
199 * to the alternative repo will be returned. If the object is not yet store
200 * a usable path in this repo will be returned. It is assumed that callers
201 * will look for objects in a pack first.
203 * @param objectId
204 * @return suggested file name
206 public File toFile(final AnyObjectId objectId) {
207 final String n = objectId.toString();
208 String d=n.substring(0, 2);
209 String f=n.substring(2);
210 for (int i=0; i<objectsDirs.length; ++i) {
211 File ret = new File(new File(objectsDirs[i], d), f);
212 if (ret.exists())
213 return ret;
215 return new File(new File(objectsDirs[0], d), f);
219 * @param objectId
220 * @return true if the specified object is stored in this repo or any of the
221 * known shared repositories.
223 public boolean hasObject(final AnyObjectId objectId) {
224 int k = packs.length;
225 if (k > 0) {
226 do {
227 if (packs[--k].hasObject(objectId))
228 return true;
229 } while (k > 0);
231 return toFile(objectId).isFile();
235 * @param id
236 * SHA-1 of an object.
238 * @return a {@link ObjectLoader} for accessing the data of the named
239 * object, or null if the object does not exist.
240 * @throws IOException
242 public ObjectLoader openObject(final AnyObjectId id)
243 throws IOException {
244 return openObject(new WindowCursor(),id);
248 * @param curs
249 * temporary working space associated with the calling thread.
250 * @param id
251 * SHA-1 of an object.
253 * @return a {@link ObjectLoader} for accessing the data of the named
254 * object, or null if the object does not exist.
255 * @throws IOException
257 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
258 throws IOException {
259 int k = packs.length;
260 if (k > 0) {
261 do {
262 try {
263 final ObjectLoader ol = packs[--k].get(curs, id);
264 if (ol != null)
265 return ol;
266 } catch (IOException ioe) {
267 // This shouldn't happen unless the pack was corrupted
268 // after we opened it or the VM runs out of memory. This is
269 // a know problem with memory mapped I/O in java and have
270 // been noticed with JDK < 1.6. Tell the gc that now is a good
271 // time to collect and try once more.
272 try {
273 curs.release();
274 System.gc();
275 final ObjectLoader ol = packs[k].get(curs, id);
276 if (ol != null)
277 return ol;
278 } catch (IOException ioe2) {
279 ioe2.printStackTrace();
280 ioe.printStackTrace();
281 // Still fails.. that's BAD, maybe the pack has
282 // been corrupted after all, or the gc didn't manage
283 // to release enough previously mmaped areas.
286 } while (k > 0);
288 try {
289 return new UnpackedObjectLoader(this, id.toObjectId());
290 } catch (FileNotFoundException fnfe) {
291 return null;
296 * Open object in all packs containing specified object.
298 * @param objectId
299 * id of object to search for
300 * @param curs
301 * temporary working space associated with the calling thread.
302 * @return collection of loaders for this object, from all packs containing
303 * this object
304 * @throws IOException
306 public Collection<PackedObjectLoader> openObjectInAllPacks(
307 final AnyObjectId objectId, final WindowCursor curs)
308 throws IOException {
309 Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
310 openObjectInAllPacks(objectId, result, curs);
311 return result;
315 * Open object in all packs containing specified object.
317 * @param objectId
318 * id of object to search for
319 * @param resultLoaders
320 * result collection of loaders for this object, filled with
321 * loaders from all packs containing specified object
322 * @param curs
323 * temporary working space associated with the calling thread.
324 * @throws IOException
326 void openObjectInAllPacks(final AnyObjectId objectId,
327 final Collection<PackedObjectLoader> resultLoaders,
328 final WindowCursor curs) throws IOException {
329 for (PackFile pack : packs) {
330 final PackedObjectLoader loader = pack.get(curs, objectId);
331 if (loader != null)
332 resultLoaders.add(loader);
337 * @param id
338 * SHA'1 of a blob
339 * @return an {@link ObjectLoader} for accessing the data of a named blob
340 * @throws IOException
342 public ObjectLoader openBlob(final ObjectId id) throws IOException {
343 return openObject(id);
347 * @param id
348 * SHA'1 of a tree
349 * @return an {@link ObjectLoader} for accessing the data of a named tree
350 * @throws IOException
352 public ObjectLoader openTree(final ObjectId id) throws IOException {
353 return openObject(id);
357 * Access a Commit object using a symbolic reference. This reference may
358 * be a SHA-1 or ref in combination with a number of symbols translating
359 * from one ref or SHA1-1 to another, such as HEAD^ etc.
361 * @param revstr a reference to a git commit object
362 * @return a Commit named by the specified string
363 * @throws IOException for I/O error or unexpected object type.
365 * @see #resolve(String)
367 public Commit mapCommit(final String revstr) throws IOException {
368 final ObjectId id = resolve(revstr);
369 return id != null ? mapCommit(id) : null;
373 * Access any type of Git object by id and
375 * @param id
376 * SHA-1 of object to read
377 * @param refName optional, only relevant for simple tags
378 * @return The Git object if found or null
379 * @throws IOException
381 public Object mapObject(final ObjectId id, final String refName) throws IOException {
382 final ObjectLoader or = openObject(id);
383 final byte[] raw = or.getBytes();
384 if (or.getType() == Constants.OBJ_TREE)
385 return makeTree(id, raw);
386 if (or.getType() == Constants.OBJ_COMMIT)
387 return makeCommit(id, raw);
388 if (or.getType() == Constants.OBJ_TAG)
389 return makeTag(id, refName, raw);
390 if (or.getType() == Constants.OBJ_BLOB)
391 return raw;
392 return null;
396 * Access a Commit by SHA'1 id.
397 * @param id
398 * @return Commit or null
399 * @throws IOException for I/O error or unexpected object type.
401 public Commit mapCommit(final ObjectId id) throws IOException {
402 final ObjectLoader or = openObject(id);
403 if (or == null)
404 return null;
405 final byte[] raw = or.getBytes();
406 if (Constants.OBJ_COMMIT == or.getType())
407 return new Commit(this, id, raw);
408 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
411 private Commit makeCommit(final ObjectId id, final byte[] raw) {
412 Commit ret = new Commit(this, id, raw);
413 return ret;
417 * Access a Tree object using a symbolic reference. This reference may
418 * be a SHA-1 or ref in combination with a number of symbols translating
419 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
421 * @param revstr a reference to a git commit object
422 * @return a Tree named by the specified string
423 * @throws IOException
425 * @see #resolve(String)
427 public Tree mapTree(final String revstr) throws IOException {
428 final ObjectId id = resolve(revstr);
429 return id != null ? mapTree(id) : null;
433 * Access a Tree by SHA'1 id.
434 * @param id
435 * @return Tree or null
436 * @throws IOException for I/O error or unexpected object type.
438 public Tree mapTree(final ObjectId id) throws IOException {
439 final ObjectLoader or = openObject(id);
440 if (or == null)
441 return null;
442 final byte[] raw = or.getBytes();
443 if (Constants.OBJ_TREE == or.getType()) {
444 return new Tree(this, id, raw);
446 if (Constants.OBJ_COMMIT == or.getType())
447 return mapTree(ObjectId.fromString(raw, 5));
448 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
451 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
452 Tree ret = new Tree(this, id, raw);
453 return ret;
456 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
457 Tag ret = new Tag(this, id, refName, raw);
458 return ret;
462 * Access a tag by symbolic name.
464 * @param revstr
465 * @return a Tag or null
466 * @throws IOException on I/O error or unexpected type
468 public Tag mapTag(String revstr) throws IOException {
469 final ObjectId id = resolve(revstr);
470 return id != null ? mapTag(revstr, id) : null;
474 * Access a Tag by SHA'1 id
475 * @param refName
476 * @param id
477 * @return Commit or null
478 * @throws IOException for I/O error or unexpected object type.
480 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
481 final ObjectLoader or = openObject(id);
482 if (or == null)
483 return null;
484 final byte[] raw = or.getBytes();
485 if (Constants.OBJ_TAG == or.getType())
486 return new Tag(this, id, refName, raw);
487 return new Tag(this, id, refName, null);
491 * Create a command to update (or create) a ref in this repository.
493 * @param ref
494 * name of the ref the caller wants to modify.
495 * @return an update command. The caller must finish populating this command
496 * and then invoke one of the update methods to actually make a
497 * change.
498 * @throws IOException
499 * a symbolic ref was passed in and could not be resolved back
500 * to the base ref, as the symbolic ref could not be read.
502 public RefUpdate updateRef(final String ref) throws IOException {
503 return refs.newUpdate(ref);
507 * Parse a git revision string and return an object id.
509 * Currently supported is combinations of these.
510 * <ul>
511 * <li>SHA-1 - a SHA-1</li>
512 * <li>refs/... - a ref name</li>
513 * <li>ref^n - nth parent reference</li>
514 * <li>ref~n - distance via parent reference</li>
515 * <li>ref@{n} - nth version of ref</li>
516 * <li>ref^{tree} - tree references by ref</li>
517 * <li>ref^{commit} - commit references by ref</li>
518 * </ul>
520 * Not supported is
521 * <ul>
522 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
523 * <li>abbreviated SHA-1's</li>
524 * </ul>
526 * @param revstr A git object references expression
527 * @return an ObjectId
528 * @throws IOException on serious errors
530 public ObjectId resolve(final String revstr) throws IOException {
531 char[] rev = revstr.toCharArray();
532 Object ref = null;
533 ObjectId refId = null;
534 for (int i = 0; i < rev.length; ++i) {
535 switch (rev[i]) {
536 case '^':
537 if (refId == null) {
538 String refstr = new String(rev,0,i);
539 refId = resolveSimple(refstr);
540 if (refId == null)
541 return null;
543 if (i + 1 < rev.length) {
544 switch (rev[i + 1]) {
545 case '0':
546 case '1':
547 case '2':
548 case '3':
549 case '4':
550 case '5':
551 case '6':
552 case '7':
553 case '8':
554 case '9':
555 int j;
556 ref = mapObject(refId, null);
557 if (!(ref instanceof Commit))
558 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
559 for (j=i+1; j<rev.length; ++j) {
560 if (!Character.isDigit(rev[j]))
561 break;
563 String parentnum = new String(rev, i+1, j-i-1);
564 int pnum = Integer.parseInt(parentnum);
565 if (pnum != 0)
566 refId = ((Commit)ref).getParentIds()[pnum - 1];
567 i = j - 1;
568 break;
569 case '{':
570 int k;
571 String item = null;
572 for (k=i+2; k<rev.length; ++k) {
573 if (rev[k] == '}') {
574 item = new String(rev, i+2, k-i-2);
575 break;
578 i = k;
579 if (item != null)
580 if (item.equals("tree")) {
581 ref = mapObject(refId, null);
582 while (ref instanceof Tag) {
583 Tag t = (Tag)ref;
584 refId = t.getObjId();
585 ref = mapObject(refId, null);
587 if (ref instanceof Treeish)
588 refId = ((Treeish)ref).getTreeId();
589 else
590 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
592 else if (item.equals("commit")) {
593 ref = mapObject(refId, null);
594 while (ref instanceof Tag) {
595 Tag t = (Tag)ref;
596 refId = t.getObjId();
597 ref = mapObject(refId, null);
599 if (!(ref instanceof Commit))
600 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
602 else if (item.equals("blob")) {
603 ref = mapObject(refId, null);
604 while (ref instanceof Tag) {
605 Tag t = (Tag)ref;
606 refId = t.getObjId();
607 ref = mapObject(refId, null);
609 if (!(ref instanceof byte[]))
610 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
612 else if (item.equals("")) {
613 ref = mapObject(refId, null);
614 if (ref instanceof Tag)
615 refId = ((Tag)ref).getObjId();
616 else {
617 // self
620 else
621 throw new RevisionSyntaxException(revstr);
622 else
623 throw new RevisionSyntaxException(revstr);
624 break;
625 default:
626 ref = mapObject(refId, null);
627 if (ref instanceof Commit)
628 refId = ((Commit)ref).getParentIds()[0];
629 else
630 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
633 } else {
634 ref = mapObject(refId, null);
635 if (ref instanceof Commit)
636 refId = ((Commit)ref).getParentIds()[0];
637 else
638 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
640 break;
641 case '~':
642 if (ref == null) {
643 String refstr = new String(rev,0,i);
644 refId = resolveSimple(refstr);
645 ref = mapCommit(refId);
647 int l;
648 for (l = i + 1; l < rev.length; ++l) {
649 if (!Character.isDigit(rev[l]))
650 break;
652 String distnum = new String(rev, i+1, l-i-1);
653 int dist = Integer.parseInt(distnum);
654 while (dist >= 0) {
655 refId = ((Commit)ref).getParentIds()[0];
656 ref = mapCommit(refId);
657 --dist;
659 i = l - 1;
660 break;
661 case '@':
662 int m;
663 String time = null;
664 for (m=i+2; m<rev.length; ++m) {
665 if (rev[m] == '}') {
666 time = new String(rev, i+2, m-i-2);
667 break;
670 if (time != null)
671 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
672 i = m - 1;
673 break;
674 default:
675 if (refId != null)
676 throw new RevisionSyntaxException(revstr);
679 if (refId == null)
680 refId = resolveSimple(revstr);
681 return refId;
684 private ObjectId resolveSimple(final String revstr) throws IOException {
685 if (ObjectId.isId(revstr))
686 return ObjectId.fromString(revstr);
687 final Ref r = refs.readRef(revstr);
688 return r != null ? r.getObjectId() : null;
692 * Close all resources used by this repository
694 public void close() {
695 closePacks();
698 void closePacks() {
699 for (int k = packs.length - 1; k >= 0; k--) {
700 packs[k].close();
702 packs = new PackFile[0];
706 * Add a single existing pack to the list of available pack files.
708 * @param pack
709 * path of the pack file to open.
710 * @param idx
711 * path of the corresponding index file.
712 * @throws IOException
713 * index file could not be opened, read, or is not recognized as
714 * a Git pack file index.
716 public void openPack(final File pack, final File idx) throws IOException {
717 final String p = pack.getName();
718 final String i = idx.getName();
719 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
720 throw new IllegalArgumentException("Not a valid pack " + pack);
721 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
722 throw new IllegalArgumentException("Not a valid pack " + idx);
723 if (!p.substring(0,45).equals(i.substring(0,45)))
724 throw new IllegalArgumentException("Pack " + pack
725 + "does not match index " + idx);
727 final PackFile[] cur = packs;
728 final PackFile[] arr = new PackFile[cur.length + 1];
729 System.arraycopy(cur, 0, arr, 1, cur.length);
730 arr[0] = new PackFile(this, idx, pack);
731 packs = arr;
735 * Scan the object dirs, including alternates for packs
736 * to use.
738 public void scanForPacks() {
739 final ArrayList<PackFile> p = new ArrayList<PackFile>();
740 for (int i=0; i<objectsDirs.length; ++i)
741 scanForPacks(new File(objectsDirs[i], "pack"), p);
742 final PackFile[] arr = new PackFile[p.size()];
743 p.toArray(arr);
744 packs = arr;
747 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
748 final String[] idxList = packDir.list(new FilenameFilter() {
749 public boolean accept(final File baseDir, final String n) {
750 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
751 return n.length() == 49 && n.endsWith(".idx")
752 && n.startsWith("pack-");
755 if (idxList != null) {
756 for (final String indexName : idxList) {
757 final String n = indexName.substring(0, indexName.length() - 4);
758 final File idxFile = new File(packDir, n + ".idx");
759 final File packFile = new File(packDir, n + ".pack");
761 if (!packFile.isFile()) {
762 // Sometimes C Git's http fetch transport leaves a
763 // .idx file behind and does not download the .pack.
764 // We have to skip over such useless indexes.
766 continue;
769 try {
770 packList.add(new PackFile(this, idxFile, packFile));
771 } catch (IOException ioe) {
772 // Whoops. That's not a pack!
774 ioe.printStackTrace();
781 * Writes a symref (e.g. HEAD) to disk
783 * @param name symref name
784 * @param target pointed to ref
785 * @throws IOException
787 public void writeSymref(final String name, final String target)
788 throws IOException {
789 refs.link(name, target);
792 public String toString() {
793 return "Repository[" + getDirectory() + "]";
797 * @return name of topmost Stacked Git patch.
798 * @throws IOException
800 public String getPatch() throws IOException {
801 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
802 final BufferedReader br = new BufferedReader(new FileReader(ptr));
803 String last=null;
804 try {
805 String line;
806 while ((line=br.readLine())!=null) {
807 last = line;
809 } finally {
810 br.close();
812 return last;
816 * @return name of current branch
817 * @throws IOException
819 public String getFullBranch() throws IOException {
820 final File ptr = new File(getDirectory(),"HEAD");
821 final BufferedReader br = new BufferedReader(new FileReader(ptr));
822 String ref;
823 try {
824 ref = br.readLine();
825 } finally {
826 br.close();
828 if (ref.startsWith("ref: "))
829 ref = ref.substring(5);
830 return ref;
834 * @return name of current branch.
835 * @throws IOException
837 public String getBranch() throws IOException {
838 try {
839 final File ptr = new File(getDirectory(), Constants.HEAD);
840 final BufferedReader br = new BufferedReader(new FileReader(ptr));
841 String ref;
842 try {
843 ref = br.readLine();
844 } finally {
845 br.close();
847 if (ref.startsWith("ref: "))
848 ref = ref.substring(5);
849 if (ref.startsWith("refs/heads/"))
850 ref = ref.substring(11);
851 return ref;
852 } catch (FileNotFoundException e) {
853 final File ptr = new File(getDirectory(),"head-name");
854 final BufferedReader br = new BufferedReader(new FileReader(ptr));
855 String ref;
856 try {
857 ref = br.readLine();
858 } finally {
859 br.close();
861 return ref;
866 * @return all known refs (heads, tags, remotes).
868 public Map<String, Ref> getAllRefs() {
869 return refs.getAllRefs();
873 * @return all tags; key is short tag name ("v1.0") and value of the entry
874 * contains the ref with the full tag name ("refs/tags/v1.0").
876 public Map<String, Ref> getTags() {
877 return refs.getTags();
881 * @return true if HEAD points to a StGit patch.
883 public boolean isStGitMode() {
884 try {
885 File file = new File(getDirectory(), "HEAD");
886 BufferedReader reader = new BufferedReader(new FileReader(file));
887 String string = reader.readLine();
888 if (!string.startsWith("ref: refs/heads/"))
889 return false;
890 String branch = string.substring("ref: refs/heads/".length());
891 File currentPatches = new File(new File(new File(getDirectory(),
892 "patches"), branch), "applied");
893 if (!currentPatches.exists())
894 return false;
895 if (currentPatches.length() == 0)
896 return false;
897 return true;
899 } catch (IOException e) {
900 e.printStackTrace();
901 return false;
906 * @return applied patches in a map indexed on current commit id
907 * @throws IOException
909 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
910 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
911 if (isStGitMode()) {
912 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
913 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
914 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
915 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
916 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
917 String objectId = tfr.readLine();
918 ObjectId id = ObjectId.fromString(objectId);
919 ret.put(id, new StGitPatch(patchName, id));
920 tfr.close();
922 apr.close();
924 return ret;
927 /** Clean up stale caches */
928 public void refreshFromDisk() {
929 refs.clearCache();
933 * @return a representation of the index associated with this repo
934 * @throws IOException
936 public GitIndex getIndex() throws IOException {
937 if (index == null) {
938 index = new GitIndex(this);
939 index.read();
940 } else {
941 index.rereadIfNecessary();
943 return index;
946 static byte[] gitInternalSlash(byte[] bytes) {
947 if (File.separatorChar == '/')
948 return bytes;
949 for (int i=0; i<bytes.length; ++i)
950 if (bytes[i] == File.separatorChar)
951 bytes[i] = '/';
952 return bytes;
956 * @return an important state
958 public RepositoryState getRepositoryState() {
959 if (new File(getWorkDir(), ".dotest").exists())
960 return RepositoryState.REBASING;
961 if (new File(gitDir,".dotest-merge").exists())
962 return RepositoryState.REBASING_INTERACTIVE;
963 if (new File(gitDir,"MERGE_HEAD").exists())
964 return RepositoryState.MERGING;
965 if (new File(gitDir,"BISECT_LOG").exists())
966 return RepositoryState.BISECTING;
967 return RepositoryState.SAFE;
971 * Check validty of a ref name. It must not contain character that has
972 * a special meaning in a Git object reference expression. Some other
973 * dangerous characters are also excluded.
975 * @param refName
977 * @return true if refName is a valid ref name
979 public static boolean isValidRefName(final String refName) {
980 final int len = refName.length();
981 char p = '\0';
982 for (int i=0; i<len; ++i) {
983 char c = refName.charAt(i);
984 if (c <= ' ')
985 return false;
986 switch(c) {
987 case '.':
988 if (i == 0)
989 return false;
990 if (p == '/')
991 return false;
992 if (p == '.')
993 return false;
994 break;
995 case '/':
996 if (i == 0)
997 return false;
998 if (i == len -1)
999 return false;
1000 break;
1001 case '~': case '^': case ':':
1002 case '?': case '[':
1003 return false;
1004 case '*':
1005 return false;
1007 p = c;
1009 return true;
1013 * String work dir and return normalized repository path
1015 * @param wd Work dir
1016 * @param f File whose path shall be stripp off it's workdir
1017 * @return normalized repository relative path
1019 public static String stripWorkDir(File wd, File f) {
1020 String relName = f.getPath().substring(wd.getPath().length() + 1);
1021 relName = relName.replace(File.separatorChar, '/');
1022 return relName;
1026 * @return the workdir file, i.e. where the files are checked out
1028 public File getWorkDir() {
1029 return getDirectory().getParentFile();