Always use a single WindowCache for the entire JVM
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob7e65fec4053ae15a9854233ce8c554828dbe4fc2
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 GitIndex index;
71 /**
72 * Construct a representation of a Git repository.
74 * @param d
75 * GIT_DIR (the location of the repository metadata).
76 * @throws IOException
77 * the repository appears to already exist but cannot be
78 * accessed.
80 public Repository(final File d) throws IOException {
81 gitDir = d.getAbsoluteFile();
82 try {
83 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
84 new ArrayList<File>()).toArray(new File[0]);
85 } catch (IOException e) {
86 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
87 ex.initCause(e);
88 throw ex;
90 refs = new RefDatabase(this);
91 packs = new PackFile[0];
92 config = new RepositoryConfig(this);
94 final boolean isExisting = objectsDirs[0].exists();
95 if (isExisting) {
96 getConfig().load();
97 final String repositoryFormatVersion = getConfig().getString(
98 "core", null, "repositoryFormatVersion");
99 if (!"0".equals(repositoryFormatVersion)) {
100 throw new IOException("Unknown repository format \""
101 + repositoryFormatVersion + "\"; expected \"0\".");
103 } else {
104 getConfig().create();
106 if (isExisting)
107 scanForPacks();
110 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
111 ret.add(objectsDir);
112 final File altFile = FS.resolve(objectsDir, "info/alternates");
113 if (altFile.exists()) {
114 BufferedReader ar = new BufferedReader(new FileReader(altFile));
115 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
116 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
118 ar.close();
120 return ret;
124 * Create a new Git repository initializing the necessary files and
125 * directories.
127 * @throws IOException
129 public void create() throws IOException {
130 if (gitDir.exists()) {
131 throw new IllegalStateException("Repository already exists: "
132 + gitDir);
135 gitDir.mkdirs();
136 refs.create();
138 objectsDirs[0].mkdirs();
139 new File(objectsDirs[0], "pack").mkdir();
140 new File(objectsDirs[0], "info").mkdir();
142 new File(gitDir, "branches").mkdir();
143 new File(gitDir, "remotes").mkdir();
144 final String master = Constants.HEADS_PREFIX + "/" + Constants.MASTER;
145 refs.link(Constants.HEAD, master);
147 getConfig().create();
148 getConfig().save();
152 * @return GIT_DIR
154 public File getDirectory() {
155 return gitDir;
159 * @return the directory containg the objects owned by this repository.
161 public File getObjectsDirectory() {
162 return objectsDirs[0];
166 * @return the configuration of this repository
168 public RepositoryConfig getConfig() {
169 return config;
173 * Construct a filename where the loose object having a specified SHA-1
174 * should be stored. If the object is stored in a shared repository the path
175 * to the alternative repo will be returned. If the object is not yet store
176 * a usable path in this repo will be returned. It is assumed that callers
177 * will look for objects in a pack first.
179 * @param objectId
180 * @return suggested file name
182 public File toFile(final AnyObjectId objectId) {
183 final String n = objectId.toString();
184 String d=n.substring(0, 2);
185 String f=n.substring(2);
186 for (int i=0; i<objectsDirs.length; ++i) {
187 File ret = new File(new File(objectsDirs[i], d), f);
188 if (ret.exists())
189 return ret;
191 return new File(new File(objectsDirs[0], d), f);
195 * @param objectId
196 * @return true if the specified object is stored in this repo or any of the
197 * known shared repositories.
199 public boolean hasObject(final AnyObjectId objectId) {
200 int k = packs.length;
201 if (k > 0) {
202 do {
203 if (packs[--k].hasObject(objectId))
204 return true;
205 } while (k > 0);
207 return toFile(objectId).isFile();
211 * @param id
212 * SHA-1 of an object.
214 * @return a {@link ObjectLoader} for accessing the data of the named
215 * object, or null if the object does not exist.
216 * @throws IOException
218 public ObjectLoader openObject(final AnyObjectId id)
219 throws IOException {
220 return openObject(new WindowCursor(),id);
224 * @param curs
225 * temporary working space associated with the calling thread.
226 * @param id
227 * SHA-1 of an object.
229 * @return a {@link ObjectLoader} for accessing the data of the named
230 * object, or null if the object does not exist.
231 * @throws IOException
233 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
234 throws IOException {
235 int k = packs.length;
236 if (k > 0) {
237 do {
238 try {
239 final ObjectLoader ol = packs[--k].get(curs, id);
240 if (ol != null)
241 return ol;
242 } catch (IOException ioe) {
243 // This shouldn't happen unless the pack was corrupted
244 // after we opened it or the VM runs out of memory. This is
245 // a know problem with memory mapped I/O in java and have
246 // been noticed with JDK < 1.6. Tell the gc that now is a good
247 // time to collect and try once more.
248 try {
249 curs.release();
250 System.gc();
251 final ObjectLoader ol = packs[k].get(curs, id);
252 if (ol != null)
253 return ol;
254 } catch (IOException ioe2) {
255 ioe2.printStackTrace();
256 ioe.printStackTrace();
257 // Still fails.. that's BAD, maybe the pack has
258 // been corrupted after all, or the gc didn't manage
259 // to release enough previously mmaped areas.
262 } while (k > 0);
264 try {
265 return new UnpackedObjectLoader(this, id.toObjectId());
266 } catch (FileNotFoundException fnfe) {
267 return null;
272 * @param id
273 * SHA'1 of a blob
274 * @return an {@link ObjectLoader} for accessing the data of a named blob
275 * @throws IOException
277 public ObjectLoader openBlob(final ObjectId id) throws IOException {
278 return openObject(id);
282 * @param id
283 * SHA'1 of a tree
284 * @return an {@link ObjectLoader} for accessing the data of a named tree
285 * @throws IOException
287 public ObjectLoader openTree(final ObjectId id) throws IOException {
288 return openObject(id);
292 * Access a Commit object using a symbolic reference. This reference may
293 * be a SHA-1 or ref in combination with a number of symbols translating
294 * from one ref or SHA1-1 to another, such as HEAD^ etc.
296 * @param revstr a reference to a git commit object
297 * @return a Commit named by the specified string
298 * @throws IOException for I/O error or unexpected object type.
300 * @see #resolve(String)
302 public Commit mapCommit(final String revstr) throws IOException {
303 final ObjectId id = resolve(revstr);
304 return id != null ? mapCommit(id) : null;
308 * Access any type of Git object by id and
310 * @param id
311 * SHA-1 of object to read
312 * @param refName optional, only relevant for simple tags
313 * @return The Git object if found or null
314 * @throws IOException
316 public Object mapObject(final ObjectId id, final String refName) throws IOException {
317 final ObjectLoader or = openObject(id);
318 final byte[] raw = or.getBytes();
319 if (or.getType() == Constants.OBJ_TREE)
320 return makeTree(id, raw);
321 if (or.getType() == Constants.OBJ_COMMIT)
322 return makeCommit(id, raw);
323 if (or.getType() == Constants.OBJ_TAG)
324 return makeTag(id, refName, raw);
325 if (or.getType() == Constants.OBJ_BLOB)
326 return raw;
327 return null;
331 * Access a Commit by SHA'1 id.
332 * @param id
333 * @return Commit or null
334 * @throws IOException for I/O error or unexpected object type.
336 public Commit mapCommit(final ObjectId id) throws IOException {
337 final ObjectLoader or = openObject(id);
338 if (or == null)
339 return null;
340 final byte[] raw = or.getBytes();
341 if (Constants.OBJ_COMMIT == or.getType())
342 return new Commit(this, id, raw);
343 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
346 private Commit makeCommit(final ObjectId id, final byte[] raw) {
347 Commit ret = new Commit(this, id, raw);
348 return ret;
352 * Access a Tree object using a symbolic reference. This reference may
353 * be a SHA-1 or ref in combination with a number of symbols translating
354 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
356 * @param revstr a reference to a git commit object
357 * @return a Tree named by the specified string
358 * @throws IOException
360 * @see #resolve(String)
362 public Tree mapTree(final String revstr) throws IOException {
363 final ObjectId id = resolve(revstr);
364 return id != null ? mapTree(id) : null;
368 * Access a Tree by SHA'1 id.
369 * @param id
370 * @return Tree or null
371 * @throws IOException for I/O error or unexpected object type.
373 public Tree mapTree(final ObjectId id) throws IOException {
374 final ObjectLoader or = openObject(id);
375 if (or == null)
376 return null;
377 final byte[] raw = or.getBytes();
378 if (Constants.OBJ_TREE == or.getType()) {
379 return new Tree(this, id, raw);
381 if (Constants.OBJ_COMMIT == or.getType())
382 return mapTree(ObjectId.fromString(raw, 5));
383 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
386 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
387 Tree ret = new Tree(this, id, raw);
388 return ret;
391 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
392 Tag ret = new Tag(this, id, refName, raw);
393 return ret;
397 * Access a tag by symbolic name.
399 * @param revstr
400 * @return a Tag or null
401 * @throws IOException on I/O error or unexpected type
403 public Tag mapTag(String revstr) throws IOException {
404 final ObjectId id = resolve(revstr);
405 return id != null ? mapTag(revstr, id) : null;
409 * Access a Tag by SHA'1 id
410 * @param refName
411 * @param id
412 * @return Commit or null
413 * @throws IOException for I/O error or unexpected object type.
415 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
416 final ObjectLoader or = openObject(id);
417 if (or == null)
418 return null;
419 final byte[] raw = or.getBytes();
420 if (Constants.OBJ_TAG == or.getType())
421 return new Tag(this, id, refName, raw);
422 return new Tag(this, id, refName, null);
426 * Create a command to update (or create) a ref in this repository.
428 * @param ref
429 * name of the ref the caller wants to modify.
430 * @return an update command. The caller must finish populating this command
431 * and then invoke one of the update methods to actually make a
432 * change.
433 * @throws IOException
434 * a symbolic ref was passed in and could not be resolved back
435 * to the base ref, as the symbolic ref could not be read.
437 public RefUpdate updateRef(final String ref) throws IOException {
438 return refs.newUpdate(ref);
442 * Parse a git revision string and return an object id.
444 * Currently supported is combinations of these.
445 * <ul>
446 * <li>SHA-1 - a SHA-1</li>
447 * <li>refs/... - a ref name</li>
448 * <li>ref^n - nth parent reference</li>
449 * <li>ref~n - distance via parent reference</li>
450 * <li>ref@{n} - nth version of ref</li>
451 * <li>ref^{tree} - tree references by ref</li>
452 * <li>ref^{commit} - commit references by ref</li>
453 * </ul>
455 * Not supported is
456 * <ul>
457 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
458 * <li>abbreviated SHA-1's</li>
459 * </ul>
461 * @param revstr A git object references expression
462 * @return an ObjectId
463 * @throws IOException on serious errors
465 public ObjectId resolve(final String revstr) throws IOException {
466 char[] rev = revstr.toCharArray();
467 Object ref = null;
468 ObjectId refId = null;
469 for (int i = 0; i < rev.length; ++i) {
470 switch (rev[i]) {
471 case '^':
472 if (refId == null) {
473 String refstr = new String(rev,0,i);
474 refId = resolveSimple(refstr);
475 if (refId == null)
476 return null;
478 if (i + 1 < rev.length) {
479 switch (rev[i + 1]) {
480 case '0':
481 case '1':
482 case '2':
483 case '3':
484 case '4':
485 case '5':
486 case '6':
487 case '7':
488 case '8':
489 case '9':
490 int j;
491 ref = mapObject(refId, null);
492 if (!(ref instanceof Commit))
493 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
494 for (j=i+1; j<rev.length; ++j) {
495 if (!Character.isDigit(rev[j]))
496 break;
498 String parentnum = new String(rev, i+1, j-i-1);
499 int pnum = Integer.parseInt(parentnum);
500 if (pnum != 0)
501 refId = ((Commit)ref).getParentIds()[pnum - 1];
502 i = j - 1;
503 break;
504 case '{':
505 int k;
506 String item = null;
507 for (k=i+2; k<rev.length; ++k) {
508 if (rev[k] == '}') {
509 item = new String(rev, i+2, k-i-2);
510 break;
513 i = k;
514 if (item != null)
515 if (item.equals("tree")) {
516 ref = mapObject(refId, null);
517 while (ref instanceof Tag) {
518 Tag t = (Tag)ref;
519 refId = t.getObjId();
520 ref = mapObject(refId, null);
522 if (ref instanceof Treeish)
523 refId = ((Treeish)ref).getTreeId();
524 else
525 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
527 else if (item.equals("commit")) {
528 ref = mapObject(refId, null);
529 while (ref instanceof Tag) {
530 Tag t = (Tag)ref;
531 refId = t.getObjId();
532 ref = mapObject(refId, null);
534 if (!(ref instanceof Commit))
535 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
537 else if (item.equals("blob")) {
538 ref = mapObject(refId, null);
539 while (ref instanceof Tag) {
540 Tag t = (Tag)ref;
541 refId = t.getObjId();
542 ref = mapObject(refId, null);
544 if (!(ref instanceof byte[]))
545 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
547 else if (item.equals("")) {
548 ref = mapObject(refId, null);
549 if (ref instanceof Tag)
550 refId = ((Tag)ref).getObjId();
551 else {
552 // self
555 else
556 throw new RevisionSyntaxException(revstr);
557 else
558 throw new RevisionSyntaxException(revstr);
559 break;
560 default:
561 ref = mapObject(refId, null);
562 if (ref instanceof Commit)
563 refId = ((Commit)ref).getParentIds()[0];
564 else
565 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
568 } else {
569 ref = mapObject(refId, null);
570 if (ref instanceof Commit)
571 refId = ((Commit)ref).getParentIds()[0];
572 else
573 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
575 break;
576 case '~':
577 if (ref == null) {
578 String refstr = new String(rev,0,i);
579 refId = resolveSimple(refstr);
580 ref = mapCommit(refId);
582 int l;
583 for (l = i + 1; l < rev.length; ++l) {
584 if (!Character.isDigit(rev[l]))
585 break;
587 String distnum = new String(rev, i+1, l-i-1);
588 int dist = Integer.parseInt(distnum);
589 while (dist >= 0) {
590 refId = ((Commit)ref).getParentIds()[0];
591 ref = mapCommit(refId);
592 --dist;
594 i = l - 1;
595 break;
596 case '@':
597 int m;
598 String time = null;
599 for (m=i+2; m<rev.length; ++m) {
600 if (rev[m] == '}') {
601 time = new String(rev, i+2, m-i-2);
602 break;
605 if (time != null)
606 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
607 i = m - 1;
608 break;
609 default:
610 if (refId != null)
611 throw new RevisionSyntaxException(revstr);
614 if (refId == null)
615 refId = resolveSimple(revstr);
616 return refId;
619 private ObjectId resolveSimple(final String revstr) throws IOException {
620 if (ObjectId.isId(revstr))
621 return ObjectId.fromString(revstr);
622 final Ref r = refs.readRef(revstr);
623 return r != null ? r.getObjectId() : null;
627 * Close all resources used by this repository
629 public void close() {
630 closePacks();
633 void closePacks() {
634 for (int k = packs.length - 1; k >= 0; k--) {
635 packs[k].close();
637 packs = new PackFile[0];
641 * Add a single existing pack to the list of available pack files.
643 * @param pack
644 * path of the pack file to open.
645 * @param idx
646 * path of the corresponding index file.
647 * @throws IOException
648 * index file could not be opened, read, or is not recognized as
649 * a Git pack file index.
651 public void openPack(final File pack, final File idx) throws IOException {
652 final String p = pack.getName();
653 final String i = idx.getName();
654 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
655 throw new IllegalArgumentException("Not a valid pack " + pack);
656 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
657 throw new IllegalArgumentException("Not a valid pack " + idx);
658 if (!p.substring(0,45).equals(i.substring(0,45)))
659 throw new IllegalArgumentException("Pack " + pack
660 + "does not match index " + idx);
662 final PackFile[] cur = packs;
663 final PackFile[] arr = new PackFile[cur.length + 1];
664 System.arraycopy(cur, 0, arr, 1, cur.length);
665 arr[0] = new PackFile(this, idx, pack);
666 packs = arr;
670 * Scan the object dirs, including alternates for packs
671 * to use.
673 public void scanForPacks() {
674 final ArrayList<PackFile> p = new ArrayList<PackFile>();
675 for (int i=0; i<objectsDirs.length; ++i)
676 scanForPacks(new File(objectsDirs[i], "pack"), p);
677 final PackFile[] arr = new PackFile[p.size()];
678 p.toArray(arr);
679 packs = arr;
682 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
683 final String[] idxList = packDir.list(new FilenameFilter() {
684 public boolean accept(final File baseDir, final String n) {
685 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
686 return n.length() == 49 && n.endsWith(".idx")
687 && n.startsWith("pack-");
690 if (idxList != null) {
691 for (final String indexName : idxList) {
692 final String n = indexName.substring(0, indexName.length() - 4);
693 final File idxFile = new File(packDir, n + ".idx");
694 final File packFile = new File(packDir, n + ".pack");
696 if (!packFile.isFile()) {
697 // Sometimes C Git's http fetch transport leaves a
698 // .idx file behind and does not download the .pack.
699 // We have to skip over such useless indexes.
701 continue;
704 try {
705 packList.add(new PackFile(this, idxFile, packFile));
706 } catch (IOException ioe) {
707 // Whoops. That's not a pack!
709 ioe.printStackTrace();
716 * Writes a symref (e.g. HEAD) to disk
718 * @param name symref name
719 * @param target pointed to ref
720 * @throws IOException
722 public void writeSymref(final String name, final String target)
723 throws IOException {
724 refs.link(name, target);
727 public String toString() {
728 return "Repository[" + getDirectory() + "]";
732 * @return name of topmost Stacked Git patch.
733 * @throws IOException
735 public String getPatch() throws IOException {
736 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
737 final BufferedReader br = new BufferedReader(new FileReader(ptr));
738 String last=null;
739 try {
740 String line;
741 while ((line=br.readLine())!=null) {
742 last = line;
744 } finally {
745 br.close();
747 return last;
751 * @return name of current branch
752 * @throws IOException
754 public String getFullBranch() throws IOException {
755 final File ptr = new File(getDirectory(),"HEAD");
756 final BufferedReader br = new BufferedReader(new FileReader(ptr));
757 String ref;
758 try {
759 ref = br.readLine();
760 } finally {
761 br.close();
763 if (ref.startsWith("ref: "))
764 ref = ref.substring(5);
765 return ref;
769 * @return name of current branch.
770 * @throws IOException
772 public String getBranch() throws IOException {
773 try {
774 final File ptr = new File(getDirectory(), Constants.HEAD);
775 final BufferedReader br = new BufferedReader(new FileReader(ptr));
776 String ref;
777 try {
778 ref = br.readLine();
779 } finally {
780 br.close();
782 if (ref.startsWith("ref: "))
783 ref = ref.substring(5);
784 if (ref.startsWith("refs/heads/"))
785 ref = ref.substring(11);
786 return ref;
787 } catch (FileNotFoundException e) {
788 final File ptr = new File(getDirectory(),"head-name");
789 final BufferedReader br = new BufferedReader(new FileReader(ptr));
790 String ref;
791 try {
792 ref = br.readLine();
793 } finally {
794 br.close();
796 return ref;
801 * @return all known refs (heads, tags, remotes).
803 public Map<String, Ref> getAllRefs() {
804 return refs.getAllRefs();
808 * @return all tags; key is short tag name ("v1.0") and value of the entry
809 * contains the ref with the full tag name ("refs/tags/v1.0").
811 public Map<String, Ref> getTags() {
812 return refs.getTags();
816 * @return true if HEAD points to a StGit patch.
818 public boolean isStGitMode() {
819 try {
820 File file = new File(getDirectory(), "HEAD");
821 BufferedReader reader = new BufferedReader(new FileReader(file));
822 String string = reader.readLine();
823 if (!string.startsWith("ref: refs/heads/"))
824 return false;
825 String branch = string.substring("ref: refs/heads/".length());
826 File currentPatches = new File(new File(new File(getDirectory(),
827 "patches"), branch), "applied");
828 if (!currentPatches.exists())
829 return false;
830 if (currentPatches.length() == 0)
831 return false;
832 return true;
834 } catch (IOException e) {
835 e.printStackTrace();
836 return false;
841 * @return applied patches in a map indexed on current commit id
842 * @throws IOException
844 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
845 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
846 if (isStGitMode()) {
847 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
848 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
849 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
850 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
851 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
852 String objectId = tfr.readLine();
853 ObjectId id = ObjectId.fromString(objectId);
854 ret.put(id, new StGitPatch(patchName, id));
855 tfr.close();
857 apr.close();
859 return ret;
862 /** Clean up stale caches */
863 public void refreshFromDisk() {
864 refs.clearCache();
868 * @return a representation of the index associated with this repo
869 * @throws IOException
871 public GitIndex getIndex() throws IOException {
872 if (index == null) {
873 index = new GitIndex(this);
874 index.read();
875 } else {
876 index.rereadIfNecessary();
878 return index;
881 static byte[] gitInternalSlash(byte[] bytes) {
882 if (File.separatorChar == '/')
883 return bytes;
884 for (int i=0; i<bytes.length; ++i)
885 if (bytes[i] == File.separatorChar)
886 bytes[i] = '/';
887 return bytes;
891 * @return an important state
893 public RepositoryState getRepositoryState() {
894 if (new File(getWorkDir(), ".dotest").exists())
895 return RepositoryState.REBASING;
896 if (new File(gitDir,".dotest-merge").exists())
897 return RepositoryState.REBASING_INTERACTIVE;
898 if (new File(gitDir,"MERGE_HEAD").exists())
899 return RepositoryState.MERGING;
900 if (new File(gitDir,"BISECT_LOG").exists())
901 return RepositoryState.BISECTING;
902 return RepositoryState.SAFE;
906 * Check validty of a ref name. It must not contain character that has
907 * a special meaning in a Git object reference expression. Some other
908 * dangerous characters are also excluded.
910 * @param refName
912 * @return true if refName is a valid ref name
914 public static boolean isValidRefName(final String refName) {
915 final int len = refName.length();
916 char p = '\0';
917 for (int i=0; i<len; ++i) {
918 char c = refName.charAt(i);
919 if (c <= ' ')
920 return false;
921 switch(c) {
922 case '.':
923 if (i == 0)
924 return false;
925 if (p == '/')
926 return false;
927 if (p == '.')
928 return false;
929 break;
930 case '/':
931 if (i == 0)
932 return false;
933 if (i == len -1)
934 return false;
935 break;
936 case '~': case '^': case ':':
937 case '?': case '[':
938 return false;
939 case '*':
940 return false;
942 p = c;
944 return true;
948 * String work dir and return normalized repository path
950 * @param wd Work dir
951 * @param f File whose path shall be stripp off it's workdir
952 * @return normalized repository relative path
954 public static String stripWorkDir(File wd, File f) {
955 String relName = f.getPath().substring(wd.getPath().length() + 1);
956 relName = relName.replace(File.separatorChar, '/');
957 return relName;
961 * @return the workdir file, i.e. where the files are checked out
963 public File getWorkDir() {
964 return getDirectory().getParentFile();