Switch jgit library to the EDL (3-clause BSD)
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob3efe60b89d5a402f30a4f640e80002efe16780af
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.Map;
53 import org.spearce.jgit.errors.IncorrectObjectTypeException;
54 import org.spearce.jgit.errors.RevisionSyntaxException;
55 import org.spearce.jgit.stgit.StGitPatch;
56 import org.spearce.jgit.util.FS;
58 /**
59 * Represents a Git repository. A repository holds all objects and refs used for
60 * managing source code (could by any type of file, but source code is what
61 * SCM's are typically used for).
63 * In Git terms all data is stored in GIT_DIR, typically a directory called
64 * .git. A work tree is maintained unless the repository is a bare repository.
65 * Typically the .git directory is located at the root of the work dir.
67 * <ul>
68 * <li>GIT_DIR
69 * <ul>
70 * <li>objects/ - objects</li>
71 * <li>refs/ - tags and heads</li>
72 * <li>config - configuration</li>
73 * <li>info/ - more configurations</li>
74 * </ul>
75 * </li>
76 * </ul>
78 * This implementation only handles a subtly undocumented subset of git features.
81 public class Repository {
82 private final File gitDir;
84 private final File[] objectsDirs;
86 private final RepositoryConfig config;
88 private final RefDatabase refs;
90 private PackFile[] packs;
92 private GitIndex index;
94 /**
95 * Construct a representation of a Git repository.
97 * @param d
98 * GIT_DIR (the location of the repository metadata).
99 * @throws IOException
100 * the repository appears to already exist but cannot be
101 * accessed.
103 public Repository(final File d) throws IOException {
104 gitDir = d.getAbsoluteFile();
105 try {
106 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
107 new ArrayList<File>()).toArray(new File[0]);
108 } catch (IOException e) {
109 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
110 ex.initCause(e);
111 throw ex;
113 refs = new RefDatabase(this);
114 packs = new PackFile[0];
115 config = new RepositoryConfig(this);
117 final boolean isExisting = objectsDirs[0].exists();
118 if (isExisting) {
119 getConfig().load();
120 final String repositoryFormatVersion = getConfig().getString(
121 "core", null, "repositoryFormatVersion");
122 if (!"0".equals(repositoryFormatVersion)) {
123 throw new IOException("Unknown repository format \""
124 + repositoryFormatVersion + "\"; expected \"0\".");
126 } else {
127 getConfig().create();
129 if (isExisting)
130 scanForPacks();
133 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
134 ret.add(objectsDir);
135 final File altFile = FS.resolve(objectsDir, "info/alternates");
136 if (altFile.exists()) {
137 BufferedReader ar = new BufferedReader(new FileReader(altFile));
138 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
139 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
141 ar.close();
143 return ret;
147 * Create a new Git repository initializing the necessary files and
148 * directories.
150 * @throws IOException
152 public void create() throws IOException {
153 if (gitDir.exists()) {
154 throw new IllegalStateException("Repository already exists: "
155 + gitDir);
158 gitDir.mkdirs();
159 refs.create();
161 objectsDirs[0].mkdirs();
162 new File(objectsDirs[0], "pack").mkdir();
163 new File(objectsDirs[0], "info").mkdir();
165 new File(gitDir, "branches").mkdir();
166 new File(gitDir, "remotes").mkdir();
167 final String master = Constants.HEADS_PREFIX + "/" + Constants.MASTER;
168 refs.link(Constants.HEAD, master);
170 getConfig().create();
171 getConfig().save();
175 * @return GIT_DIR
177 public File getDirectory() {
178 return gitDir;
182 * @return the directory containg the objects owned by this repository.
184 public File getObjectsDirectory() {
185 return objectsDirs[0];
189 * @return the configuration of this repository
191 public RepositoryConfig getConfig() {
192 return config;
196 * Construct a filename where the loose object having a specified SHA-1
197 * should be stored. If the object is stored in a shared repository the path
198 * to the alternative repo will be returned. If the object is not yet store
199 * a usable path in this repo will be returned. It is assumed that callers
200 * will look for objects in a pack first.
202 * @param objectId
203 * @return suggested file name
205 public File toFile(final AnyObjectId objectId) {
206 final String n = objectId.toString();
207 String d=n.substring(0, 2);
208 String f=n.substring(2);
209 for (int i=0; i<objectsDirs.length; ++i) {
210 File ret = new File(new File(objectsDirs[i], d), f);
211 if (ret.exists())
212 return ret;
214 return new File(new File(objectsDirs[0], d), f);
218 * @param objectId
219 * @return true if the specified object is stored in this repo or any of the
220 * known shared repositories.
222 public boolean hasObject(final AnyObjectId objectId) {
223 int k = packs.length;
224 if (k > 0) {
225 do {
226 if (packs[--k].hasObject(objectId))
227 return true;
228 } while (k > 0);
230 return toFile(objectId).isFile();
234 * @param id
235 * SHA-1 of an object.
237 * @return a {@link ObjectLoader} for accessing the data of the named
238 * object, or null if the object does not exist.
239 * @throws IOException
241 public ObjectLoader openObject(final AnyObjectId id)
242 throws IOException {
243 return openObject(new WindowCursor(),id);
247 * @param curs
248 * temporary working space associated with the calling thread.
249 * @param id
250 * SHA-1 of an object.
252 * @return a {@link ObjectLoader} for accessing the data of the named
253 * object, or null if the object does not exist.
254 * @throws IOException
256 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
257 throws IOException {
258 int k = packs.length;
259 if (k > 0) {
260 do {
261 try {
262 final ObjectLoader ol = packs[--k].get(curs, id);
263 if (ol != null)
264 return ol;
265 } catch (IOException ioe) {
266 // This shouldn't happen unless the pack was corrupted
267 // after we opened it or the VM runs out of memory. This is
268 // a know problem with memory mapped I/O in java and have
269 // been noticed with JDK < 1.6. Tell the gc that now is a good
270 // time to collect and try once more.
271 try {
272 curs.release();
273 System.gc();
274 final ObjectLoader ol = packs[k].get(curs, id);
275 if (ol != null)
276 return ol;
277 } catch (IOException ioe2) {
278 ioe2.printStackTrace();
279 ioe.printStackTrace();
280 // Still fails.. that's BAD, maybe the pack has
281 // been corrupted after all, or the gc didn't manage
282 // to release enough previously mmaped areas.
285 } while (k > 0);
287 try {
288 return new UnpackedObjectLoader(this, id.toObjectId());
289 } catch (FileNotFoundException fnfe) {
290 return null;
295 * @param id
296 * SHA'1 of a blob
297 * @return an {@link ObjectLoader} for accessing the data of a named blob
298 * @throws IOException
300 public ObjectLoader openBlob(final ObjectId id) throws IOException {
301 return openObject(id);
305 * @param id
306 * SHA'1 of a tree
307 * @return an {@link ObjectLoader} for accessing the data of a named tree
308 * @throws IOException
310 public ObjectLoader openTree(final ObjectId id) throws IOException {
311 return openObject(id);
315 * Access a Commit object using a symbolic reference. This reference may
316 * be a SHA-1 or ref in combination with a number of symbols translating
317 * from one ref or SHA1-1 to another, such as HEAD^ etc.
319 * @param revstr a reference to a git commit object
320 * @return a Commit named by the specified string
321 * @throws IOException for I/O error or unexpected object type.
323 * @see #resolve(String)
325 public Commit mapCommit(final String revstr) throws IOException {
326 final ObjectId id = resolve(revstr);
327 return id != null ? mapCommit(id) : null;
331 * Access any type of Git object by id and
333 * @param id
334 * SHA-1 of object to read
335 * @param refName optional, only relevant for simple tags
336 * @return The Git object if found or null
337 * @throws IOException
339 public Object mapObject(final ObjectId id, final String refName) throws IOException {
340 final ObjectLoader or = openObject(id);
341 final byte[] raw = or.getBytes();
342 if (or.getType() == Constants.OBJ_TREE)
343 return makeTree(id, raw);
344 if (or.getType() == Constants.OBJ_COMMIT)
345 return makeCommit(id, raw);
346 if (or.getType() == Constants.OBJ_TAG)
347 return makeTag(id, refName, raw);
348 if (or.getType() == Constants.OBJ_BLOB)
349 return raw;
350 return null;
354 * Access a Commit by SHA'1 id.
355 * @param id
356 * @return Commit or null
357 * @throws IOException for I/O error or unexpected object type.
359 public Commit mapCommit(final ObjectId id) throws IOException {
360 final ObjectLoader or = openObject(id);
361 if (or == null)
362 return null;
363 final byte[] raw = or.getBytes();
364 if (Constants.OBJ_COMMIT == or.getType())
365 return new Commit(this, id, raw);
366 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
369 private Commit makeCommit(final ObjectId id, final byte[] raw) {
370 Commit ret = new Commit(this, id, raw);
371 return ret;
375 * Access a Tree object using a symbolic reference. This reference may
376 * be a SHA-1 or ref in combination with a number of symbols translating
377 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
379 * @param revstr a reference to a git commit object
380 * @return a Tree named by the specified string
381 * @throws IOException
383 * @see #resolve(String)
385 public Tree mapTree(final String revstr) throws IOException {
386 final ObjectId id = resolve(revstr);
387 return id != null ? mapTree(id) : null;
391 * Access a Tree by SHA'1 id.
392 * @param id
393 * @return Tree or null
394 * @throws IOException for I/O error or unexpected object type.
396 public Tree mapTree(final ObjectId id) throws IOException {
397 final ObjectLoader or = openObject(id);
398 if (or == null)
399 return null;
400 final byte[] raw = or.getBytes();
401 if (Constants.OBJ_TREE == or.getType()) {
402 return new Tree(this, id, raw);
404 if (Constants.OBJ_COMMIT == or.getType())
405 return mapTree(ObjectId.fromString(raw, 5));
406 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
409 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
410 Tree ret = new Tree(this, id, raw);
411 return ret;
414 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
415 Tag ret = new Tag(this, id, refName, raw);
416 return ret;
420 * Access a tag by symbolic name.
422 * @param revstr
423 * @return a Tag or null
424 * @throws IOException on I/O error or unexpected type
426 public Tag mapTag(String revstr) throws IOException {
427 final ObjectId id = resolve(revstr);
428 return id != null ? mapTag(revstr, id) : null;
432 * Access a Tag by SHA'1 id
433 * @param refName
434 * @param id
435 * @return Commit or null
436 * @throws IOException for I/O error or unexpected object type.
438 public Tag mapTag(final String refName, 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_TAG == or.getType())
444 return new Tag(this, id, refName, raw);
445 return new Tag(this, id, refName, null);
449 * Create a command to update (or create) a ref in this repository.
451 * @param ref
452 * name of the ref the caller wants to modify.
453 * @return an update command. The caller must finish populating this command
454 * and then invoke one of the update methods to actually make a
455 * change.
456 * @throws IOException
457 * a symbolic ref was passed in and could not be resolved back
458 * to the base ref, as the symbolic ref could not be read.
460 public RefUpdate updateRef(final String ref) throws IOException {
461 return refs.newUpdate(ref);
465 * Parse a git revision string and return an object id.
467 * Currently supported is combinations of these.
468 * <ul>
469 * <li>SHA-1 - a SHA-1</li>
470 * <li>refs/... - a ref name</li>
471 * <li>ref^n - nth parent reference</li>
472 * <li>ref~n - distance via parent reference</li>
473 * <li>ref@{n} - nth version of ref</li>
474 * <li>ref^{tree} - tree references by ref</li>
475 * <li>ref^{commit} - commit references by ref</li>
476 * </ul>
478 * Not supported is
479 * <ul>
480 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
481 * <li>abbreviated SHA-1's</li>
482 * </ul>
484 * @param revstr A git object references expression
485 * @return an ObjectId
486 * @throws IOException on serious errors
488 public ObjectId resolve(final String revstr) throws IOException {
489 char[] rev = revstr.toCharArray();
490 Object ref = null;
491 ObjectId refId = null;
492 for (int i = 0; i < rev.length; ++i) {
493 switch (rev[i]) {
494 case '^':
495 if (refId == null) {
496 String refstr = new String(rev,0,i);
497 refId = resolveSimple(refstr);
498 if (refId == null)
499 return null;
501 if (i + 1 < rev.length) {
502 switch (rev[i + 1]) {
503 case '0':
504 case '1':
505 case '2':
506 case '3':
507 case '4':
508 case '5':
509 case '6':
510 case '7':
511 case '8':
512 case '9':
513 int j;
514 ref = mapObject(refId, null);
515 if (!(ref instanceof Commit))
516 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
517 for (j=i+1; j<rev.length; ++j) {
518 if (!Character.isDigit(rev[j]))
519 break;
521 String parentnum = new String(rev, i+1, j-i-1);
522 int pnum = Integer.parseInt(parentnum);
523 if (pnum != 0)
524 refId = ((Commit)ref).getParentIds()[pnum - 1];
525 i = j - 1;
526 break;
527 case '{':
528 int k;
529 String item = null;
530 for (k=i+2; k<rev.length; ++k) {
531 if (rev[k] == '}') {
532 item = new String(rev, i+2, k-i-2);
533 break;
536 i = k;
537 if (item != null)
538 if (item.equals("tree")) {
539 ref = mapObject(refId, null);
540 while (ref instanceof Tag) {
541 Tag t = (Tag)ref;
542 refId = t.getObjId();
543 ref = mapObject(refId, null);
545 if (ref instanceof Treeish)
546 refId = ((Treeish)ref).getTreeId();
547 else
548 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
550 else if (item.equals("commit")) {
551 ref = mapObject(refId, null);
552 while (ref instanceof Tag) {
553 Tag t = (Tag)ref;
554 refId = t.getObjId();
555 ref = mapObject(refId, null);
557 if (!(ref instanceof Commit))
558 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
560 else if (item.equals("blob")) {
561 ref = mapObject(refId, null);
562 while (ref instanceof Tag) {
563 Tag t = (Tag)ref;
564 refId = t.getObjId();
565 ref = mapObject(refId, null);
567 if (!(ref instanceof byte[]))
568 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
570 else if (item.equals("")) {
571 ref = mapObject(refId, null);
572 if (ref instanceof Tag)
573 refId = ((Tag)ref).getObjId();
574 else {
575 // self
578 else
579 throw new RevisionSyntaxException(revstr);
580 else
581 throw new RevisionSyntaxException(revstr);
582 break;
583 default:
584 ref = mapObject(refId, null);
585 if (ref instanceof Commit)
586 refId = ((Commit)ref).getParentIds()[0];
587 else
588 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
591 } else {
592 ref = mapObject(refId, null);
593 if (ref instanceof Commit)
594 refId = ((Commit)ref).getParentIds()[0];
595 else
596 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
598 break;
599 case '~':
600 if (ref == null) {
601 String refstr = new String(rev,0,i);
602 refId = resolveSimple(refstr);
603 ref = mapCommit(refId);
605 int l;
606 for (l = i + 1; l < rev.length; ++l) {
607 if (!Character.isDigit(rev[l]))
608 break;
610 String distnum = new String(rev, i+1, l-i-1);
611 int dist = Integer.parseInt(distnum);
612 while (dist >= 0) {
613 refId = ((Commit)ref).getParentIds()[0];
614 ref = mapCommit(refId);
615 --dist;
617 i = l - 1;
618 break;
619 case '@':
620 int m;
621 String time = null;
622 for (m=i+2; m<rev.length; ++m) {
623 if (rev[m] == '}') {
624 time = new String(rev, i+2, m-i-2);
625 break;
628 if (time != null)
629 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
630 i = m - 1;
631 break;
632 default:
633 if (refId != null)
634 throw new RevisionSyntaxException(revstr);
637 if (refId == null)
638 refId = resolveSimple(revstr);
639 return refId;
642 private ObjectId resolveSimple(final String revstr) throws IOException {
643 if (ObjectId.isId(revstr))
644 return ObjectId.fromString(revstr);
645 final Ref r = refs.readRef(revstr);
646 return r != null ? r.getObjectId() : null;
650 * Close all resources used by this repository
652 public void close() {
653 closePacks();
656 void closePacks() {
657 for (int k = packs.length - 1; k >= 0; k--) {
658 packs[k].close();
660 packs = new PackFile[0];
664 * Add a single existing pack to the list of available pack files.
666 * @param pack
667 * path of the pack file to open.
668 * @param idx
669 * path of the corresponding index file.
670 * @throws IOException
671 * index file could not be opened, read, or is not recognized as
672 * a Git pack file index.
674 public void openPack(final File pack, final File idx) throws IOException {
675 final String p = pack.getName();
676 final String i = idx.getName();
677 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
678 throw new IllegalArgumentException("Not a valid pack " + pack);
679 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
680 throw new IllegalArgumentException("Not a valid pack " + idx);
681 if (!p.substring(0,45).equals(i.substring(0,45)))
682 throw new IllegalArgumentException("Pack " + pack
683 + "does not match index " + idx);
685 final PackFile[] cur = packs;
686 final PackFile[] arr = new PackFile[cur.length + 1];
687 System.arraycopy(cur, 0, arr, 1, cur.length);
688 arr[0] = new PackFile(this, idx, pack);
689 packs = arr;
693 * Scan the object dirs, including alternates for packs
694 * to use.
696 public void scanForPacks() {
697 final ArrayList<PackFile> p = new ArrayList<PackFile>();
698 for (int i=0; i<objectsDirs.length; ++i)
699 scanForPacks(new File(objectsDirs[i], "pack"), p);
700 final PackFile[] arr = new PackFile[p.size()];
701 p.toArray(arr);
702 packs = arr;
705 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
706 final String[] idxList = packDir.list(new FilenameFilter() {
707 public boolean accept(final File baseDir, final String n) {
708 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
709 return n.length() == 49 && n.endsWith(".idx")
710 && n.startsWith("pack-");
713 if (idxList != null) {
714 for (final String indexName : idxList) {
715 final String n = indexName.substring(0, indexName.length() - 4);
716 final File idxFile = new File(packDir, n + ".idx");
717 final File packFile = new File(packDir, n + ".pack");
719 if (!packFile.isFile()) {
720 // Sometimes C Git's http fetch transport leaves a
721 // .idx file behind and does not download the .pack.
722 // We have to skip over such useless indexes.
724 continue;
727 try {
728 packList.add(new PackFile(this, idxFile, packFile));
729 } catch (IOException ioe) {
730 // Whoops. That's not a pack!
732 ioe.printStackTrace();
739 * Writes a symref (e.g. HEAD) to disk
741 * @param name symref name
742 * @param target pointed to ref
743 * @throws IOException
745 public void writeSymref(final String name, final String target)
746 throws IOException {
747 refs.link(name, target);
750 public String toString() {
751 return "Repository[" + getDirectory() + "]";
755 * @return name of topmost Stacked Git patch.
756 * @throws IOException
758 public String getPatch() throws IOException {
759 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
760 final BufferedReader br = new BufferedReader(new FileReader(ptr));
761 String last=null;
762 try {
763 String line;
764 while ((line=br.readLine())!=null) {
765 last = line;
767 } finally {
768 br.close();
770 return last;
774 * @return name of current branch
775 * @throws IOException
777 public String getFullBranch() throws IOException {
778 final File ptr = new File(getDirectory(),"HEAD");
779 final BufferedReader br = new BufferedReader(new FileReader(ptr));
780 String ref;
781 try {
782 ref = br.readLine();
783 } finally {
784 br.close();
786 if (ref.startsWith("ref: "))
787 ref = ref.substring(5);
788 return ref;
792 * @return name of current branch.
793 * @throws IOException
795 public String getBranch() throws IOException {
796 try {
797 final File ptr = new File(getDirectory(), Constants.HEAD);
798 final BufferedReader br = new BufferedReader(new FileReader(ptr));
799 String ref;
800 try {
801 ref = br.readLine();
802 } finally {
803 br.close();
805 if (ref.startsWith("ref: "))
806 ref = ref.substring(5);
807 if (ref.startsWith("refs/heads/"))
808 ref = ref.substring(11);
809 return ref;
810 } catch (FileNotFoundException e) {
811 final File ptr = new File(getDirectory(),"head-name");
812 final BufferedReader br = new BufferedReader(new FileReader(ptr));
813 String ref;
814 try {
815 ref = br.readLine();
816 } finally {
817 br.close();
819 return ref;
824 * @return all known refs (heads, tags, remotes).
826 public Map<String, Ref> getAllRefs() {
827 return refs.getAllRefs();
831 * @return all tags; key is short tag name ("v1.0") and value of the entry
832 * contains the ref with the full tag name ("refs/tags/v1.0").
834 public Map<String, Ref> getTags() {
835 return refs.getTags();
839 * @return true if HEAD points to a StGit patch.
841 public boolean isStGitMode() {
842 try {
843 File file = new File(getDirectory(), "HEAD");
844 BufferedReader reader = new BufferedReader(new FileReader(file));
845 String string = reader.readLine();
846 if (!string.startsWith("ref: refs/heads/"))
847 return false;
848 String branch = string.substring("ref: refs/heads/".length());
849 File currentPatches = new File(new File(new File(getDirectory(),
850 "patches"), branch), "applied");
851 if (!currentPatches.exists())
852 return false;
853 if (currentPatches.length() == 0)
854 return false;
855 return true;
857 } catch (IOException e) {
858 e.printStackTrace();
859 return false;
864 * @return applied patches in a map indexed on current commit id
865 * @throws IOException
867 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
868 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
869 if (isStGitMode()) {
870 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
871 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
872 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
873 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
874 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
875 String objectId = tfr.readLine();
876 ObjectId id = ObjectId.fromString(objectId);
877 ret.put(id, new StGitPatch(patchName, id));
878 tfr.close();
880 apr.close();
882 return ret;
885 /** Clean up stale caches */
886 public void refreshFromDisk() {
887 refs.clearCache();
891 * @return a representation of the index associated with this repo
892 * @throws IOException
894 public GitIndex getIndex() throws IOException {
895 if (index == null) {
896 index = new GitIndex(this);
897 index.read();
898 } else {
899 index.rereadIfNecessary();
901 return index;
904 static byte[] gitInternalSlash(byte[] bytes) {
905 if (File.separatorChar == '/')
906 return bytes;
907 for (int i=0; i<bytes.length; ++i)
908 if (bytes[i] == File.separatorChar)
909 bytes[i] = '/';
910 return bytes;
914 * @return an important state
916 public RepositoryState getRepositoryState() {
917 if (new File(getWorkDir(), ".dotest").exists())
918 return RepositoryState.REBASING;
919 if (new File(gitDir,".dotest-merge").exists())
920 return RepositoryState.REBASING_INTERACTIVE;
921 if (new File(gitDir,"MERGE_HEAD").exists())
922 return RepositoryState.MERGING;
923 if (new File(gitDir,"BISECT_LOG").exists())
924 return RepositoryState.BISECTING;
925 return RepositoryState.SAFE;
929 * Check validty of a ref name. It must not contain character that has
930 * a special meaning in a Git object reference expression. Some other
931 * dangerous characters are also excluded.
933 * @param refName
935 * @return true if refName is a valid ref name
937 public static boolean isValidRefName(final String refName) {
938 final int len = refName.length();
939 char p = '\0';
940 for (int i=0; i<len; ++i) {
941 char c = refName.charAt(i);
942 if (c <= ' ')
943 return false;
944 switch(c) {
945 case '.':
946 if (i == 0)
947 return false;
948 if (p == '/')
949 return false;
950 if (p == '.')
951 return false;
952 break;
953 case '/':
954 if (i == 0)
955 return false;
956 if (i == len -1)
957 return false;
958 break;
959 case '~': case '^': case ':':
960 case '?': case '[':
961 return false;
962 case '*':
963 return false;
965 p = c;
967 return true;
971 * String work dir and return normalized repository path
973 * @param wd Work dir
974 * @param f File whose path shall be stripp off it's workdir
975 * @return normalized repository relative path
977 public static String stripWorkDir(File wd, File f) {
978 String relName = f.getPath().substring(wd.getPath().length() + 1);
979 relName = relName.replace(File.separatorChar, '/');
980 return relName;
984 * @return the workdir file, i.e. where the files are checked out
986 public File getWorkDir() {
987 return getDirectory().getParentFile();