Refactor Repository's ref reading/writing for aggressive caching
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob8741b44b2dbbd798713597fedd957ccf12eccdc2
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.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Map;
30 import org.spearce.jgit.errors.IncorrectObjectTypeException;
31 import org.spearce.jgit.errors.RevisionSyntaxException;
32 import org.spearce.jgit.stgit.StGitPatch;
33 import org.spearce.jgit.util.FS;
35 /**
36 * Represents a Git repository. A repository holds all objects and refs used for
37 * managing source code (could by any type of file, but source code is what
38 * SCM's are typically used for).
40 * In Git terms all data is stored in GIT_DIR, typically a directory called
41 * .git. A work tree is maintained unless the repository is a bare repository.
42 * Typically the .git directory is located at the root of the work dir.
44 * <ul>
45 * <li>GIT_DIR
46 * <ul>
47 * <li>objects/ - objects</li>
48 * <li>refs/ - tags and heads</li>
49 * <li>config - configuration</li>
50 * <li>info/ - more configurations</li>
51 * </ul>
52 * </li>
53 * </ul>
55 * This implementation only handles a subtly undocumented subset of git features.
58 public class Repository {
59 private final File gitDir;
61 private final File[] objectsDirs;
63 private final RepositoryConfig config;
65 private final RefDatabase refs;
67 private PackFile[] packs;
69 private final WindowCache windows;
71 private GitIndex index;
73 /**
74 * Construct a representation of this git repo managing a Git repository.
76 * @param d
77 * GIT_DIR
78 * @throws IOException
80 public Repository(final File d) throws IOException {
81 this(null, d);
84 /**
85 * Construct a representation of a Git repository.
87 * @param wc
88 * cache this repository's data will be cached through during
89 * access. May be shared with another repository, or null to
90 * indicate this repository should allocate its own private
91 * cache.
92 * @param d
93 * GIT_DIR (the location of the repository metadata).
94 * @throws IOException
95 * the repository appears to already exist but cannot be
96 * accessed.
98 public Repository(final WindowCache wc, final File d) throws IOException {
99 gitDir = d.getAbsoluteFile();
100 try {
101 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
102 new ArrayList<File>()).toArray(new File[0]);
103 } catch (IOException e) {
104 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
105 ex.initCause(e);
106 throw ex;
108 refs = new RefDatabase(this);
109 packs = new PackFile[0];
110 config = new RepositoryConfig(this);
112 final boolean isExisting = objectsDirs[0].exists();
113 if (isExisting) {
114 getConfig().load();
115 final String repositoryFormatVersion = getConfig().getString(
116 "core", null, "repositoryFormatVersion");
117 if (!"0".equals(repositoryFormatVersion)) {
118 throw new IOException("Unknown repository format \""
119 + repositoryFormatVersion + "\"; expected \"0\".");
121 } else {
122 getConfig().create();
124 windows = wc != null ? wc : new WindowCache(getConfig());
125 if (isExisting)
126 scanForPacks();
129 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
130 ret.add(objectsDir);
131 final File altFile = FS.resolve(objectsDir, "info/alternates");
132 if (altFile.exists()) {
133 BufferedReader ar = new BufferedReader(new FileReader(altFile));
134 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
135 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
137 ar.close();
139 return ret;
143 * Create a new Git repository initializing the necessary files and
144 * directories.
146 * @throws IOException
148 public void create() throws IOException {
149 if (gitDir.exists()) {
150 throw new IllegalStateException("Repository already exists: "
151 + gitDir);
154 gitDir.mkdirs();
155 refs.create();
157 objectsDirs[0].mkdirs();
158 new File(objectsDirs[0], "pack").mkdir();
159 new File(objectsDirs[0], "info").mkdir();
161 new File(gitDir, "branches").mkdir();
162 new File(gitDir, "remotes").mkdir();
163 final String master = Constants.HEADS_PREFIX + "/" + Constants.MASTER;
164 refs.link(Constants.HEAD, master);
166 getConfig().create();
167 getConfig().save();
171 * @return GIT_DIR
173 public File getDirectory() {
174 return gitDir;
178 * @return the directory containg the objects owned by this repository.
180 public File getObjectsDirectory() {
181 return objectsDirs[0];
185 * @return the configuration of this repository
187 public RepositoryConfig getConfig() {
188 return config;
192 * @return the cache needed for accessing packed objects in this repository.
194 public WindowCache getWindowCache() {
195 return windows;
199 * Construct a filename where the loose object having a specified SHA-1
200 * should be stored. If the object is stored in a shared repository the path
201 * to the alternative repo will be returned. If the object is not yet store
202 * a usable path in this repo will be returned. It is assumed that callers
203 * will look for objects in a pack first.
205 * @param objectId
206 * @return suggested file name
208 public File toFile(final AnyObjectId objectId) {
209 final String n = objectId.toString();
210 String d=n.substring(0, 2);
211 String f=n.substring(2);
212 for (int i=0; i<objectsDirs.length; ++i) {
213 File ret = new File(new File(objectsDirs[i], d), f);
214 if (ret.exists())
215 return ret;
217 return new File(new File(objectsDirs[0], d), f);
221 * @param objectId
222 * @return true if the specified object is stored in this repo or any of the
223 * known shared repositories.
225 public boolean hasObject(final AnyObjectId objectId) {
226 int k = packs.length;
227 if (k > 0) {
228 do {
229 if (packs[--k].hasObject(objectId))
230 return true;
231 } while (k > 0);
233 return toFile(objectId).isFile();
237 * @param id
238 * SHA-1 of an object.
240 * @return a {@link ObjectLoader} for accessing the data of the named
241 * object, or null if the object does not exist.
242 * @throws IOException
244 public ObjectLoader openObject(final AnyObjectId id)
245 throws IOException {
246 return openObject(new WindowCursor(),id);
250 * @param curs
251 * temporary working space associated with the calling thread.
252 * @param id
253 * SHA-1 of an object.
255 * @return a {@link ObjectLoader} for accessing the data of the named
256 * object, or null if the object does not exist.
257 * @throws IOException
259 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
260 throws IOException {
261 int k = packs.length;
262 if (k > 0) {
263 do {
264 try {
265 final ObjectLoader ol = packs[--k].get(curs, id);
266 if (ol != null)
267 return ol;
268 } catch (IOException ioe) {
269 // This shouldn't happen unless the pack was corrupted
270 // after we opened it or the VM runs out of memory. This is
271 // a know problem with memory mapped I/O in java and have
272 // been noticed with JDK < 1.6. Tell the gc that now is a good
273 // time to collect and try once more.
274 try {
275 curs.release();
276 System.gc();
277 final ObjectLoader ol = packs[k].get(curs, id);
278 if (ol != null)
279 return ol;
280 } catch (IOException ioe2) {
281 ioe2.printStackTrace();
282 ioe.printStackTrace();
283 // Still fails.. that's BAD, maybe the pack has
284 // been corrupted after all, or the gc didn't manage
285 // to release enough previously mmaped areas.
288 } while (k > 0);
290 try {
291 return new UnpackedObjectLoader(this, id.toObjectId());
292 } catch (FileNotFoundException fnfe) {
293 return null;
298 * @param id
299 * SHA'1 of a blob
300 * @return an {@link ObjectLoader} for accessing the data of a named blob
301 * @throws IOException
303 public ObjectLoader openBlob(final ObjectId id) throws IOException {
304 return openObject(id);
308 * @param id
309 * SHA'1 of a tree
310 * @return an {@link ObjectLoader} for accessing the data of a named tree
311 * @throws IOException
313 public ObjectLoader openTree(final ObjectId id) throws IOException {
314 return openObject(id);
318 * Access a Commit object using a symbolic reference. This reference may
319 * be a SHA-1 or ref in combination with a number of symbols translating
320 * from one ref or SHA1-1 to another, such as HEAD^ etc.
322 * @param revstr a reference to a git commit object
323 * @return a Commit named by the specified string
324 * @throws IOException for I/O error or unexpected object type.
326 * @see #resolve(String)
328 public Commit mapCommit(final String revstr) throws IOException {
329 final ObjectId id = resolve(revstr);
330 return id != null ? mapCommit(id) : null;
334 * Access any type of Git object by id and
336 * @param id
337 * SHA-1 of object to read
338 * @param refName optional, only relevant for simple tags
339 * @return The Git object if found or null
340 * @throws IOException
342 public Object mapObject(final ObjectId id, final String refName) throws IOException {
343 final ObjectLoader or = openObject(id);
344 final byte[] raw = or.getBytes();
345 if (or.getType() == Constants.OBJ_TREE)
346 return makeTree(id, raw);
347 if (or.getType() == Constants.OBJ_COMMIT)
348 return makeCommit(id, raw);
349 if (or.getType() == Constants.OBJ_TAG)
350 return makeTag(id, refName, raw);
351 if (or.getType() == Constants.OBJ_BLOB)
352 return raw;
353 return null;
357 * Access a Commit by SHA'1 id.
358 * @param id
359 * @return Commit or null
360 * @throws IOException for I/O error or unexpected object type.
362 public Commit mapCommit(final ObjectId id) throws IOException {
363 final ObjectLoader or = openObject(id);
364 if (or == null)
365 return null;
366 final byte[] raw = or.getBytes();
367 if (Constants.OBJ_COMMIT == or.getType())
368 return new Commit(this, id, raw);
369 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
372 private Commit makeCommit(final ObjectId id, final byte[] raw) {
373 Commit ret = new Commit(this, id, raw);
374 return ret;
378 * Access a Tree object using a symbolic reference. This reference may
379 * be a SHA-1 or ref in combination with a number of symbols translating
380 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
382 * @param revstr a reference to a git commit object
383 * @return a Tree named by the specified string
384 * @throws IOException
386 * @see #resolve(String)
388 public Tree mapTree(final String revstr) throws IOException {
389 final ObjectId id = resolve(revstr);
390 return id != null ? mapTree(id) : null;
394 * Access a Tree by SHA'1 id.
395 * @param id
396 * @return Tree or null
397 * @throws IOException for I/O error or unexpected object type.
399 public Tree mapTree(final ObjectId id) throws IOException {
400 final ObjectLoader or = openObject(id);
401 if (or == null)
402 return null;
403 final byte[] raw = or.getBytes();
404 if (Constants.OBJ_TREE == or.getType()) {
405 return new Tree(this, id, raw);
407 if (Constants.OBJ_COMMIT == or.getType())
408 return mapTree(ObjectId.fromString(raw, 5));
409 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
412 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
413 Tree ret = new Tree(this, id, raw);
414 return ret;
417 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
418 Tag ret = new Tag(this, id, refName, raw);
419 return ret;
423 * Access a tag by symbolic name.
425 * @param revstr
426 * @return a Tag or null
427 * @throws IOException on I/O error or unexpected type
429 public Tag mapTag(String revstr) throws IOException {
430 final ObjectId id = resolve(revstr);
431 return id != null ? mapTag(revstr, id) : null;
435 * Access a Tag by SHA'1 id
436 * @param refName
437 * @param id
438 * @return Commit or null
439 * @throws IOException for I/O error or unexpected object type.
441 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
442 final ObjectLoader or = openObject(id);
443 if (or == null)
444 return null;
445 final byte[] raw = or.getBytes();
446 if (Constants.OBJ_TAG == or.getType())
447 return new Tag(this, id, refName, raw);
448 return new Tag(this, id, refName, null);
452 * Create a command to update (or create) a ref in this repository.
454 * @param ref
455 * name of the ref the caller wants to modify.
456 * @return an update command. The caller must finish populating this command
457 * and then invoke one of the update methods to actually make a
458 * change.
459 * @throws IOException
460 * a symbolic ref was passed in and could not be resolved back
461 * to the base ref, as the symbolic ref could not be read.
463 public RefUpdate updateRef(final String ref) throws IOException {
464 return refs.newUpdate(ref);
468 * Parse a git revision string and return an object id.
470 * Currently supported is combinations of these.
471 * <ul>
472 * <li>SHA-1 - a SHA-1</li>
473 * <li>refs/... - a ref name</li>
474 * <li>ref^n - nth parent reference</li>
475 * <li>ref~n - distance via parent reference</li>
476 * <li>ref@{n} - nth version of ref</li>
477 * <li>ref^{tree} - tree references by ref</li>
478 * <li>ref^{commit} - commit references by ref</li>
479 * </ul>
481 * Not supported is
482 * <ul>
483 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
484 * <li>abbreviated SHA-1's</li>
485 * </ul>
487 * @param revstr A git object references expression
488 * @return an ObjectId
489 * @throws IOException on serious errors
491 public ObjectId resolve(final String revstr) throws IOException {
492 char[] rev = revstr.toCharArray();
493 Object ref = null;
494 ObjectId refId = null;
495 for (int i = 0; i < rev.length; ++i) {
496 switch (rev[i]) {
497 case '^':
498 if (refId == null) {
499 String refstr = new String(rev,0,i);
500 refId = resolveSimple(refstr);
501 if (refId == null)
502 return null;
504 if (i + 1 < rev.length) {
505 switch (rev[i + 1]) {
506 case '0':
507 case '1':
508 case '2':
509 case '3':
510 case '4':
511 case '5':
512 case '6':
513 case '7':
514 case '8':
515 case '9':
516 int j;
517 ref = mapObject(refId, null);
518 if (!(ref instanceof Commit))
519 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
520 for (j=i+1; j<rev.length; ++j) {
521 if (!Character.isDigit(rev[j]))
522 break;
524 String parentnum = new String(rev, i+1, j-i-1);
525 int pnum = Integer.parseInt(parentnum);
526 if (pnum != 0)
527 refId = ((Commit)ref).getParentIds()[pnum - 1];
528 i = j - 1;
529 break;
530 case '{':
531 int k;
532 String item = null;
533 for (k=i+2; k<rev.length; ++k) {
534 if (rev[k] == '}') {
535 item = new String(rev, i+2, k-i-2);
536 break;
539 i = k;
540 if (item != null)
541 if (item.equals("tree")) {
542 ref = mapObject(refId, null);
543 while (ref instanceof Tag) {
544 Tag t = (Tag)ref;
545 refId = t.getObjId();
546 ref = mapObject(refId, null);
548 if (ref instanceof Treeish)
549 refId = ((Treeish)ref).getTreeId();
550 else
551 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
553 else if (item.equals("commit")) {
554 ref = mapObject(refId, null);
555 while (ref instanceof Tag) {
556 Tag t = (Tag)ref;
557 refId = t.getObjId();
558 ref = mapObject(refId, null);
560 if (!(ref instanceof Commit))
561 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
563 else if (item.equals("blob")) {
564 ref = mapObject(refId, null);
565 while (ref instanceof Tag) {
566 Tag t = (Tag)ref;
567 refId = t.getObjId();
568 ref = mapObject(refId, null);
570 if (!(ref instanceof byte[]))
571 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
573 else if (item.equals("")) {
574 ref = mapObject(refId, null);
575 if (ref instanceof Tag)
576 refId = ((Tag)ref).getObjId();
577 else {
578 // self
581 else
582 throw new RevisionSyntaxException(revstr);
583 else
584 throw new RevisionSyntaxException(revstr);
585 break;
586 default:
587 ref = mapObject(refId, null);
588 if (ref instanceof Commit)
589 refId = ((Commit)ref).getParentIds()[0];
590 else
591 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
594 } else {
595 ref = mapObject(refId, null);
596 if (ref instanceof Commit)
597 refId = ((Commit)ref).getParentIds()[0];
598 else
599 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
601 break;
602 case '~':
603 if (ref == null) {
604 String refstr = new String(rev,0,i);
605 refId = resolveSimple(refstr);
606 ref = mapCommit(refId);
608 int l;
609 for (l = i + 1; l < rev.length; ++l) {
610 if (!Character.isDigit(rev[l]))
611 break;
613 String distnum = new String(rev, i+1, l-i-1);
614 int dist = Integer.parseInt(distnum);
615 while (dist >= 0) {
616 refId = ((Commit)ref).getParentIds()[0];
617 ref = mapCommit(refId);
618 --dist;
620 i = l - 1;
621 break;
622 case '@':
623 int m;
624 String time = null;
625 for (m=i+2; m<rev.length; ++m) {
626 if (rev[m] == '}') {
627 time = new String(rev, i+2, m-i-2);
628 break;
631 if (time != null)
632 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
633 i = m - 1;
634 break;
635 default:
636 if (refId != null)
637 throw new RevisionSyntaxException(revstr);
640 if (refId == null)
641 refId = resolveSimple(revstr);
642 return refId;
645 private ObjectId resolveSimple(final String revstr) throws IOException {
646 if (ObjectId.isId(revstr))
647 return ObjectId.fromString(revstr);
648 final Ref r = refs.readRef(revstr);
649 return r != null ? r.getObjectId() : null;
653 * Close all resources used by this repository
655 public void close() {
656 closePacks();
659 void closePacks() {
660 for (int k = packs.length - 1; k >= 0; k--) {
661 packs[k].close();
663 packs = new PackFile[0];
667 * Add a single existing pack to the list of available pack files.
669 * @param pack
670 * path of the pack file to open.
671 * @param idx
672 * path of the corresponding index file.
673 * @throws IOException
674 * index file could not be opened, read, or is not recognized as
675 * a Git pack file index.
677 public void openPack(final File pack, final File idx) throws IOException {
678 final String p = pack.getName();
679 final String i = idx.getName();
680 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
681 throw new IllegalArgumentException("Not a valid pack " + pack);
682 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
683 throw new IllegalArgumentException("Not a valid pack " + idx);
684 if (!p.substring(0,45).equals(i.substring(0,45)))
685 throw new IllegalArgumentException("Pack " + pack
686 + "does not match index " + idx);
688 final PackFile[] cur = packs;
689 final PackFile[] arr = new PackFile[cur.length + 1];
690 System.arraycopy(cur, 0, arr, 1, cur.length);
691 arr[0] = new PackFile(this, idx, pack);
692 packs = arr;
696 * Scan the object dirs, including alternates for packs
697 * to use.
699 public void scanForPacks() {
700 final ArrayList<PackFile> p = new ArrayList<PackFile>();
701 for (int i=0; i<objectsDirs.length; ++i)
702 scanForPacks(new File(objectsDirs[i], "pack"), p);
703 final PackFile[] arr = new PackFile[p.size()];
704 p.toArray(arr);
705 packs = arr;
708 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
709 final String[] idxList = packDir.list(new FilenameFilter() {
710 public boolean accept(final File baseDir, final String n) {
711 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
712 return n.length() == 49 && n.endsWith(".idx")
713 && n.startsWith("pack-");
716 if (idxList != null) {
717 for (final String indexName : idxList) {
718 final String n = indexName.substring(0, indexName.length() - 4);
719 final File idxFile = new File(packDir, n + ".idx");
720 final File packFile = new File(packDir, n + ".pack");
721 try {
722 packList.add(new PackFile(this, idxFile, packFile));
723 } catch (IOException ioe) {
724 // Whoops. That's not a pack!
726 ioe.printStackTrace();
733 * Writes a symref (e.g. HEAD) to disk
735 * @param name symref name
736 * @param target pointed to ref
737 * @throws IOException
739 public void writeSymref(final String name, final String target)
740 throws IOException {
741 refs.link(name, target);
744 public String toString() {
745 return "Repository[" + getDirectory() + "]";
749 * @return name of topmost Stacked Git patch.
750 * @throws IOException
752 public String getPatch() throws IOException {
753 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
754 final BufferedReader br = new BufferedReader(new FileReader(ptr));
755 String last=null;
756 try {
757 String line;
758 while ((line=br.readLine())!=null) {
759 last = line;
761 } finally {
762 br.close();
764 return last;
768 * @return name of current branch
769 * @throws IOException
771 public String getFullBranch() throws IOException {
772 final File ptr = new File(getDirectory(),"HEAD");
773 final BufferedReader br = new BufferedReader(new FileReader(ptr));
774 String ref;
775 try {
776 ref = br.readLine();
777 } finally {
778 br.close();
780 if (ref.startsWith("ref: "))
781 ref = ref.substring(5);
782 return ref;
786 * @return name of current branch.
787 * @throws IOException
789 public String getBranch() throws IOException {
790 try {
791 final File ptr = new File(getDirectory(), Constants.HEAD);
792 final BufferedReader br = new BufferedReader(new FileReader(ptr));
793 String ref;
794 try {
795 ref = br.readLine();
796 } finally {
797 br.close();
799 if (ref.startsWith("ref: "))
800 ref = ref.substring(5);
801 if (ref.startsWith("refs/heads/"))
802 ref = ref.substring(11);
803 return ref;
804 } catch (FileNotFoundException e) {
805 final File ptr = new File(getDirectory(),"head-name");
806 final BufferedReader br = new BufferedReader(new FileReader(ptr));
807 String ref;
808 try {
809 ref = br.readLine();
810 } finally {
811 br.close();
813 return ref;
818 * @return all known refs (heads, tags, remotes).
820 public Map<String, Ref> getAllRefs() {
821 return refs.getAllRefs();
825 * @return all tags; key is short tag name ("v1.0") and value of the entry
826 * contains the ref with the full tag name ("refs/tags/v1.0").
828 public Map<String, Ref> getTags() {
829 return refs.getTags();
833 * @return true if HEAD points to a StGit patch.
835 public boolean isStGitMode() {
836 try {
837 File file = new File(getDirectory(), "HEAD");
838 BufferedReader reader = new BufferedReader(new FileReader(file));
839 String string = reader.readLine();
840 if (!string.startsWith("ref: refs/heads/"))
841 return false;
842 String branch = string.substring("ref: refs/heads/".length());
843 File currentPatches = new File(new File(new File(getDirectory(),
844 "patches"), branch), "applied");
845 if (!currentPatches.exists())
846 return false;
847 if (currentPatches.length() == 0)
848 return false;
849 return true;
851 } catch (IOException e) {
852 e.printStackTrace();
853 return false;
858 * @return applied patches in a map indexed on current commit id
859 * @throws IOException
861 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
862 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
863 if (isStGitMode()) {
864 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
865 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
866 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
867 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
868 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
869 String objectId = tfr.readLine();
870 ObjectId id = ObjectId.fromString(objectId);
871 ret.put(id, new StGitPatch(patchName, id));
872 tfr.close();
874 apr.close();
876 return ret;
879 /** Clean up stale caches */
880 public void refreshFromDisk() {
881 refs.clearCache();
885 * @return a representation of the index associated with this repo
886 * @throws IOException
888 public GitIndex getIndex() throws IOException {
889 if (index == null) {
890 index = new GitIndex(this);
891 index.read();
892 } else {
893 index.rereadIfNecessary();
895 return index;
898 static byte[] gitInternalSlash(byte[] bytes) {
899 if (File.separatorChar == '/')
900 return bytes;
901 for (int i=0; i<bytes.length; ++i)
902 if (bytes[i] == File.separatorChar)
903 bytes[i] = '/';
904 return bytes;
908 * @return an important state
910 public RepositoryState getRepositoryState() {
911 if (new File(getWorkDir(), ".dotest").exists())
912 return RepositoryState.REBASING;
913 if (new File(gitDir,".dotest-merge").exists())
914 return RepositoryState.REBASING_INTERACTIVE;
915 if (new File(gitDir,"MERGE_HEAD").exists())
916 return RepositoryState.MERGING;
917 if (new File(gitDir,"BISECT_LOG").exists())
918 return RepositoryState.BISECTING;
919 return RepositoryState.SAFE;
923 * Check validty of a ref name. It must not contain character that has
924 * a special meaning in a Git object reference expression. Some other
925 * dangerous characters are also excluded.
927 * @param refName
929 * @return true if refName is a valid ref name
931 public static boolean isValidRefName(final String refName) {
932 final int len = refName.length();
933 char p = '\0';
934 for (int i=0; i<len; ++i) {
935 char c = refName.charAt(i);
936 if (c <= ' ')
937 return false;
938 switch(c) {
939 case '.':
940 if (i == 0)
941 return false;
942 if (p == '/')
943 return false;
944 if (p == '.')
945 return false;
946 break;
947 case '/':
948 if (i == 0)
949 return false;
950 if (i == len -1)
951 return false;
952 break;
953 case '~': case '^': case ':':
954 case '?': case '[':
955 return false;
956 case '*':
957 return false;
959 p = c;
961 return true;
965 * String work dir and return normalized repository path
967 * @param wd Work dir
968 * @param f File whose path shall be stripp off it's workdir
969 * @return normalized repository relative path
971 public static String stripWorkDir(File wd, File f) {
972 String relName = f.getPath().substring(wd.getPath().length() + 1);
973 relName = relName.replace(File.separatorChar, '/');
974 return relName;
978 * @return the workdir file, i.e. where the files are checked out
980 public File getWorkDir() {
981 return getDirectory().getParentFile();