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
;
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
;
31 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
32 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
33 import org
.spearce
.jgit
.errors
.RevisionSyntaxException
;
34 import org
.spearce
.jgit
.stgit
.StGitPatch
;
35 import org
.spearce
.jgit
.util
.FS
;
38 * Represents a Git repository. A repository holds all objects and refs used for
39 * managing source code (could by any type of file, but source code is what
40 * SCM's are typically used for).
42 * In Git terms all data is stored in GIT_DIR, typically a directory called
43 * .git. A work tree is maintained unless the repository is a bare repository.
44 * Typically the .git directory is located at the root of the work dir.
49 * <li>objects/ - objects</li>
50 * <li>refs/ - tags and heads</li>
51 * <li>config - configuration</li>
52 * <li>info/ - more configurations</li>
57 * This implementation only handles a subtly undocumented subset of git features.
60 public class Repository
{
61 private static final String
[] refSearchPaths
= { "", "refs/",
62 Constants
.TAGS_PREFIX
+ "/", Constants
.HEADS_PREFIX
+ "/",
63 Constants
.REMOTES_PREFIX
+ "/" };
65 private final File gitDir
;
67 private final File
[] objectsDirs
;
69 private final File refsDir
;
71 private final File packedRefsFile
;
73 private final RepositoryConfig config
;
75 private PackFile
[] packs
;
77 private final WindowCache windows
;
79 private GitIndex index
;
82 * Construct a representation of this git repo managing a Git repository.
88 public Repository(final File d
) throws IOException
{
93 * Construct a representation of a Git repository.
96 * cache this repository's data will be cached through during
97 * access. May be shared with another repository, or null to
98 * indicate this repository should allocate its own private
101 * GIT_DIR (the location of the repository metadata).
102 * @throws IOException
103 * the repository appears to already exist but cannot be
106 public Repository(final WindowCache wc
, final File d
) throws IOException
{
107 gitDir
= d
.getAbsoluteFile();
109 objectsDirs
= readObjectsDirs(FS
.resolve(gitDir
, "objects"),
110 new ArrayList
<File
>()).toArray(new File
[0]);
111 } catch (IOException e
) {
112 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
116 refsDir
= FS
.resolve(gitDir
, "refs");
117 packedRefsFile
= FS
.resolve(gitDir
, "packed-refs");
118 packs
= new PackFile
[0];
119 config
= new RepositoryConfig(this);
121 final boolean isExisting
= objectsDirs
[0].exists();
124 final String repositoryFormatVersion
= getConfig().getString(
125 "core", null, "repositoryFormatVersion");
126 if (!"0".equals(repositoryFormatVersion
)) {
127 throw new IOException("Unknown repository format \""
128 + repositoryFormatVersion
+ "\"; expected \"0\".");
131 getConfig().create();
133 windows
= wc
!= null ? wc
: new WindowCache(getConfig());
138 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
140 final File altFile
= FS
.resolve(objectsDir
, "info/alternates");
141 if (altFile
.exists()) {
142 BufferedReader ar
= new BufferedReader(new FileReader(altFile
));
143 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
144 readObjectsDirs(FS
.resolve(objectsDir
, alt
), ret
);
152 * Create a new Git repository initializing the necessary files and
155 * @throws IOException
157 public void create() throws IOException
{
158 if (gitDir
.exists()) {
159 throw new IllegalStateException("Repository already exists: "
165 objectsDirs
[0].mkdirs();
166 new File(objectsDirs
[0], "pack").mkdir();
167 new File(objectsDirs
[0], "info").mkdir();
170 new File(refsDir
, "heads").mkdir();
171 new File(refsDir
, "tags").mkdir();
173 new File(gitDir
, "branches").mkdir();
174 new File(gitDir
, "remotes").mkdir();
175 final String master
= Constants
.HEADS_PREFIX
+ "/" + Constants
.MASTER
;
176 writeSymref(Constants
.HEAD
, master
);
178 getConfig().create();
185 public File
getDirectory() {
190 * @return the directory containg the objects owned by this repository.
192 public File
getObjectsDirectory() {
193 return objectsDirs
[0];
197 * @return the configuration of this repository
199 public RepositoryConfig
getConfig() {
204 * @return the cache needed for accessing packed objects in this repository.
206 public WindowCache
getWindowCache() {
211 * Construct a filename where the loose object having a specified SHA-1
212 * should be stored. If the object is stored in a shared repository the path
213 * to the alternative repo will be returned. If the object is not yet store
214 * a usable path in this repo will be returned. It is assumed that callers
215 * will look for objects in a pack first.
218 * @return suggested file name
220 public File
toFile(final AnyObjectId objectId
) {
221 final String n
= objectId
.toString();
222 String d
=n
.substring(0, 2);
223 String f
=n
.substring(2);
224 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
225 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
229 return new File(new File(objectsDirs
[0], d
), f
);
234 * @return true if the specified object is stored in this repo or any of the
235 * known shared repositories.
237 public boolean hasObject(final AnyObjectId objectId
) {
238 int k
= packs
.length
;
241 if (packs
[--k
].hasObject(objectId
))
245 return toFile(objectId
).isFile();
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 AnyObjectId id
)
258 return openObject(new WindowCursor(),id
);
263 * temporary working space associated with the calling thread.
265 * SHA-1 of an object.
267 * @return a {@link ObjectLoader} for accessing the data of the named
268 * object, or null if the object does not exist.
269 * @throws IOException
271 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
273 int k
= packs
.length
;
277 final ObjectLoader ol
= packs
[--k
].get(curs
, id
);
280 } catch (IOException ioe
) {
281 // This shouldn't happen unless the pack was corrupted
282 // after we opened it or the VM runs out of memory. This is
283 // a know problem with memory mapped I/O in java and have
284 // been noticed with JDK < 1.6. Tell the gc that now is a good
285 // time to collect and try once more.
289 final ObjectLoader ol
= packs
[k
].get(curs
, id
);
292 } catch (IOException ioe2
) {
293 ioe2
.printStackTrace();
294 ioe
.printStackTrace();
295 // Still fails.. that's BAD, maybe the pack has
296 // been corrupted after all, or the gc didn't manage
297 // to release enough previously mmaped areas.
303 return new UnpackedObjectLoader(this, id
.toObjectId());
304 } catch (FileNotFoundException fnfe
) {
312 * @return an {@link ObjectLoader} for accessing the data of a named blob
313 * @throws IOException
315 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
316 return openObject(id
);
322 * @return an {@link ObjectLoader} for accessing the data of a named tree
323 * @throws IOException
325 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
326 return openObject(id
);
330 * Access a Commit object using a symbolic reference. This reference may
331 * be a SHA-1 or ref in combination with a number of symbols translating
332 * from one ref or SHA1-1 to another, such as HEAD^ etc.
334 * @param revstr a reference to a git commit object
335 * @return a Commit named by the specified string
336 * @throws IOException for I/O error or unexpected object type.
338 * @see #resolve(String)
340 public Commit
mapCommit(final String revstr
) throws IOException
{
341 final ObjectId id
= resolve(revstr
);
342 return id
!= null ?
mapCommit(id
) : null;
346 * Access any type of Git object by id and
349 * SHA-1 of object to read
350 * @param refName optional, only relevant for simple tags
351 * @return The Git object if found or null
352 * @throws IOException
354 public Object
mapObject(final ObjectId id
, final String refName
) throws IOException
{
355 final ObjectLoader or
= openObject(id
);
356 final byte[] raw
= or
.getBytes();
357 if (or
.getType() == Constants
.OBJ_TREE
)
358 return makeTree(id
, raw
);
359 if (or
.getType() == Constants
.OBJ_COMMIT
)
360 return makeCommit(id
, raw
);
361 if (or
.getType() == Constants
.OBJ_TAG
)
362 return makeTag(id
, refName
, raw
);
363 if (or
.getType() == Constants
.OBJ_BLOB
)
369 * Access a Commit by SHA'1 id.
371 * @return Commit or null
372 * @throws IOException for I/O error or unexpected object type.
374 public Commit
mapCommit(final ObjectId id
) throws IOException
{
375 final ObjectLoader or
= openObject(id
);
378 final byte[] raw
= or
.getBytes();
379 if (Constants
.OBJ_COMMIT
== or
.getType())
380 return new Commit(this, id
, raw
);
381 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
384 private Commit
makeCommit(final ObjectId id
, final byte[] raw
) {
385 Commit ret
= new Commit(this, id
, raw
);
390 * Access a Tree object using a symbolic reference. This reference may
391 * be a SHA-1 or ref in combination with a number of symbols translating
392 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
394 * @param revstr a reference to a git commit object
395 * @return a Tree named by the specified string
396 * @throws IOException
398 * @see #resolve(String)
400 public Tree
mapTree(final String revstr
) throws IOException
{
401 final ObjectId id
= resolve(revstr
);
402 return id
!= null ?
mapTree(id
) : null;
406 * Access a Tree by SHA'1 id.
408 * @return Tree or null
409 * @throws IOException for I/O error or unexpected object type.
411 public Tree
mapTree(final ObjectId id
) throws IOException
{
412 final ObjectLoader or
= openObject(id
);
415 final byte[] raw
= or
.getBytes();
416 if (Constants
.OBJ_TREE
== or
.getType()) {
417 return new Tree(this, id
, raw
);
419 if (Constants
.OBJ_COMMIT
== or
.getType())
420 return mapTree(ObjectId
.fromString(raw
, 5));
421 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
424 private Tree
makeTree(final ObjectId id
, final byte[] raw
) throws IOException
{
425 Tree ret
= new Tree(this, id
, raw
);
429 private Tag
makeTag(final ObjectId id
, final String refName
, final byte[] raw
) {
430 Tag ret
= new Tag(this, id
, refName
, raw
);
435 * Access a tag by symbolic name.
438 * @return a Tag or null
439 * @throws IOException on I/O error or unexpected type
441 public Tag
mapTag(String revstr
) throws IOException
{
442 final ObjectId id
= resolve(revstr
);
443 return id
!= null ?
mapTag(revstr
, id
) : null;
447 * Access a Tag by SHA'1 id
450 * @return Commit or null
451 * @throws IOException for I/O error or unexpected object type.
453 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
454 final ObjectLoader or
= openObject(id
);
457 final byte[] raw
= or
.getBytes();
458 if (Constants
.OBJ_TAG
== or
.getType())
459 return new Tag(this, id
, refName
, raw
);
460 return new Tag(this, id
, refName
, null);
464 * Get a locked handle to a ref suitable for updating or creating.
466 * @param ref name to lock
467 * @return a locked ref
468 * @throws IOException
470 public LockFile
lockRef(final String ref
) throws IOException
{
471 final Ref r
= readRef(ref
, true);
472 final LockFile l
= new LockFile(fileForRef(r
.getName()));
473 return l
.lock() ? l
: null;
477 * Create a command to update (or create) a ref in this repository.
480 * name of the ref the caller wants to modify.
481 * @return an update command. The caller must finish populating this command
482 * and then invoke one of the update methods to actually make a
484 * @throws IOException
485 * a symbolic ref was passed in and could not be resolved back
486 * to the base ref, as the symbolic ref could not be read.
488 public RefUpdate
updateRef(final String ref
) throws IOException
{
489 final Ref r
= readRef(ref
, true);
490 return new RefUpdate(this, r
, fileForRef(r
.getName()));
494 * Parse a git revision string and return an object id.
496 * Currently supported is combinations of these.
498 * <li>SHA-1 - a SHA-1</li>
499 * <li>refs/... - a ref name</li>
500 * <li>ref^n - nth parent reference</li>
501 * <li>ref~n - distance via parent reference</li>
502 * <li>ref@{n} - nth version of ref</li>
503 * <li>ref^{tree} - tree references by ref</li>
504 * <li>ref^{commit} - commit references by ref</li>
509 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
510 * <li>abbreviated SHA-1's</li>
513 * @param revstr A git object references expression
514 * @return an ObjectId
515 * @throws IOException on serious errors
517 public ObjectId
resolve(final String revstr
) throws IOException
{
518 char[] rev
= revstr
.toCharArray();
520 ObjectId refId
= null;
521 for (int i
= 0; i
< rev
.length
; ++i
) {
525 String refstr
= new String(rev
,0,i
);
526 refId
= resolveSimple(refstr
);
530 if (i
+ 1 < rev
.length
) {
531 switch (rev
[i
+ 1]) {
543 ref
= mapObject(refId
, null);
544 if (!(ref
instanceof Commit
))
545 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
546 for (j
=i
+1; j
<rev
.length
; ++j
) {
547 if (!Character
.isDigit(rev
[j
]))
550 String parentnum
= new String(rev
, i
+1, j
-i
-1);
551 int pnum
= Integer
.parseInt(parentnum
);
553 refId
= ((Commit
)ref
).getParentIds()[pnum
- 1];
559 for (k
=i
+2; k
<rev
.length
; ++k
) {
561 item
= new String(rev
, i
+2, k
-i
-2);
567 if (item
.equals("tree")) {
568 ref
= mapObject(refId
, null);
569 while (ref
instanceof Tag
) {
571 refId
= t
.getObjId();
572 ref
= mapObject(refId
, null);
574 if (ref
instanceof Treeish
)
575 refId
= ((Treeish
)ref
).getTreeId();
577 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_TREE
);
579 else if (item
.equals("commit")) {
580 ref
= mapObject(refId
, null);
581 while (ref
instanceof Tag
) {
583 refId
= t
.getObjId();
584 ref
= mapObject(refId
, null);
586 if (!(ref
instanceof Commit
))
587 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
589 else if (item
.equals("blob")) {
590 ref
= mapObject(refId
, null);
591 while (ref
instanceof Tag
) {
593 refId
= t
.getObjId();
594 ref
= mapObject(refId
, null);
596 if (!(ref
instanceof byte[]))
597 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
599 else if (item
.equals("")) {
600 ref
= mapObject(refId
, null);
601 if (ref
instanceof Tag
)
602 refId
= ((Tag
)ref
).getObjId();
608 throw new RevisionSyntaxException(revstr
);
610 throw new RevisionSyntaxException(revstr
);
613 ref
= mapObject(refId
, null);
614 if (ref
instanceof Commit
)
615 refId
= ((Commit
)ref
).getParentIds()[0];
617 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
621 ref
= mapObject(refId
, null);
622 if (ref
instanceof Commit
)
623 refId
= ((Commit
)ref
).getParentIds()[0];
625 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
630 String refstr
= new String(rev
,0,i
);
631 refId
= resolveSimple(refstr
);
632 ref
= mapCommit(refId
);
635 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
636 if (!Character
.isDigit(rev
[l
]))
639 String distnum
= new String(rev
, i
+1, l
-i
-1);
640 int dist
= Integer
.parseInt(distnum
);
642 refId
= ((Commit
)ref
).getParentIds()[0];
643 ref
= mapCommit(refId
);
651 for (m
=i
+2; m
<rev
.length
; ++m
) {
653 time
= new String(rev
, i
+2, m
-i
-2);
658 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr
);
663 throw new RevisionSyntaxException(revstr
);
667 refId
= resolveSimple(revstr
);
671 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
672 if (ObjectId
.isId(revstr
))
673 return ObjectId
.fromString(revstr
);
674 final Ref r
= readRef(revstr
, false);
676 return r
.getObjectId();
682 * Close all resources used by this repository
684 public void close() {
689 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
692 packs
= new PackFile
[0];
696 * Add a single existing pack to the list of available pack files.
699 * path of the pack file to open.
701 * path of the corresponding index file.
702 * @throws IOException
703 * index file could not be opened, read, or is not recognized as
704 * a Git pack file index.
706 public void openPack(final File pack
, final File idx
) throws IOException
{
707 final String p
= pack
.getName();
708 final String i
= idx
.getName();
709 if (p
.length() != 50 || !p
.startsWith("pack-") || !p
.endsWith(".pack"))
710 throw new IllegalArgumentException("Not a valid pack " + pack
);
711 if (i
.length() != 49 || !i
.startsWith("pack-") || !i
.endsWith(".idx"))
712 throw new IllegalArgumentException("Not a valid pack " + idx
);
713 if (!p
.substring(0,45).equals(i
.substring(0,45)))
714 throw new IllegalArgumentException("Pack " + pack
715 + "does not match index " + idx
);
717 final PackFile
[] cur
= packs
;
718 final PackFile
[] arr
= new PackFile
[cur
.length
+ 1];
719 System
.arraycopy(cur
, 0, arr
, 1, cur
.length
);
720 arr
[0] = new PackFile(this, idx
, pack
);
725 * Scan the object dirs, including alternates for packs
728 public void scanForPacks() {
729 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
730 for (int i
=0; i
<objectsDirs
.length
; ++i
)
731 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
732 final PackFile
[] arr
= new PackFile
[p
.size()];
737 private void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
738 final String
[] idxList
= packDir
.list(new FilenameFilter() {
739 public boolean accept(final File baseDir
, final String n
) {
740 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
741 return n
.length() == 49 && n
.endsWith(".idx")
742 && n
.startsWith("pack-");
745 if (idxList
!= null) {
746 for (final String indexName
: idxList
) {
747 final String n
= indexName
.substring(0, indexName
.length() - 4);
748 final File idxFile
= new File(packDir
, n
+ ".idx");
749 final File packFile
= new File(packDir
, n
+ ".pack");
751 packList
.add(new PackFile(this, idxFile
, packFile
));
752 } catch (IOException ioe
) {
753 // Whoops. That's not a pack!
755 ioe
.printStackTrace();
762 * Writes a symref (e.g. HEAD) to disk
764 * @param name symref name
765 * @param target pointed to ref
766 * @throws IOException
768 public void writeSymref(final String name
, final String target
)
770 final byte[] content
= ("ref: " + target
+ "\n").getBytes("UTF-8");
771 final LockFile lck
= new LockFile(fileForRef(name
));
773 throw new ObjectWritingException("Unable to lock " + name
);
776 } catch (IOException ioe
) {
777 throw new ObjectWritingException("Unable to write " + name
, ioe
);
780 throw new ObjectWritingException("Unable to write " + name
);
783 private Ref
readRef(final String revstr
, final boolean missingOk
)
785 refreshPackedRefsCache();
786 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
787 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
788 if (missingOk
|| r
.getObjectId() != null) {
795 private Ref
readRefBasic(String name
) throws IOException
{
798 // prefer unpacked ref to packed ref
799 final File f
= fileForRef(name
);
801 // look for packed ref, since this one doesn't exist
802 ObjectId id
= packedRefs
.get(name
);
804 return new Ref(name
, id
);
806 // no packed ref found, return blank one
807 return new Ref(name
, null);
810 final BufferedReader br
= new BufferedReader(new FileReader(f
));
812 final String line
= br
.readLine();
813 if (line
== null || line
.length() == 0)
814 return new Ref(name
, null);
815 else if (line
.startsWith("ref: ")) {
816 name
= line
.substring("ref: ".length());
817 continue REF_READING
;
818 } else if (ObjectId
.isId(line
))
819 return new Ref(name
, ObjectId
.fromString(line
));
820 throw new IOException("Not a ref: " + name
+ ": " + line
);
824 } while (depth
++ < 5);
825 throw new IOException("Exceed maximum ref depth. Circular reference?");
828 private File
fileForRef(final String name
) {
829 if (name
.startsWith("refs/"))
830 return new File(refsDir
, name
.substring("refs/".length()));
831 return new File(gitDir
, name
);
834 public String
toString() {
835 return "Repository[" + getDirectory() + "]";
839 * @return name of topmost Stacked Git patch.
840 * @throws IOException
842 public String
getPatch() throws IOException
{
843 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
844 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
848 while ((line
=br
.readLine())!=null) {
858 * @return name of current branch
859 * @throws IOException
861 public String
getFullBranch() throws IOException
{
862 final File ptr
= new File(getDirectory(),"HEAD");
863 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
870 if (ref
.startsWith("ref: "))
871 ref
= ref
.substring(5);
876 * @return name of current branch.
877 * @throws IOException
879 public String
getBranch() throws IOException
{
881 final File ptr
= new File(getDirectory(), Constants
.HEAD
);
882 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
889 if (ref
.startsWith("ref: "))
890 ref
= ref
.substring(5);
891 if (ref
.startsWith("refs/heads/"))
892 ref
= ref
.substring(11);
894 } catch (FileNotFoundException e
) {
895 final File ptr
= new File(getDirectory(),"head-name");
896 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
908 * @return the names of all refs (local and remotes branches, tags)
910 public Collection
<String
> getAllRefs() {
914 private Collection
<String
> listRefs(String refSubDir
) {
915 // add / to end, unless empty
916 if (refSubDir
.length() > 0 && refSubDir
.charAt(refSubDir
.length() -1 ) != '/')
919 Collection
<String
> branchesRaw
= listFilesRecursively(new File(refsDir
, refSubDir
), null);
920 ArrayList
<String
> branches
= new ArrayList
<String
>();
921 for (String b
: branchesRaw
) {
922 branches
.add("refs/" + refSubDir
+ b
);
925 refreshPackedRefsCache();
926 Set
<String
> keySet
= packedRefs
.keySet();
927 for (String s
: keySet
)
928 if (s
.startsWith("refs/" + refSubDir
) && !branches
.contains(s
))
934 * @return all git tags
936 public Collection
<String
> getTags() {
937 return listRefs("tags");
940 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
941 private long packedrefstime
= 0;
943 private void refreshPackedRefsCache() {
944 if (!packedRefsFile
.exists()) {
945 if (packedRefs
.size() > 0)
946 packedRefs
= new HashMap
<String
,ObjectId
>();
949 if (packedRefsFile
.lastModified() == packedrefstime
)
951 Map
<String
,ObjectId
> newPackedRefs
= new HashMap
<String
,ObjectId
>();
952 FileReader fileReader
= null;
954 fileReader
= new FileReader(packedRefsFile
);
955 BufferedReader b
=new BufferedReader(fileReader
);
957 while ((p
= b
.readLine()) != null) {
958 if (p
.charAt(0) == '#')
960 if (p
.charAt(0) == '^') {
963 int spos
= p
.indexOf(' ');
964 ObjectId id
= ObjectId
.fromString(p
.substring(0,spos
));
965 String name
= p
.substring(spos
+1);
966 newPackedRefs
.put(name
, id
);
968 } catch (IOException e
) {
969 throw new RuntimeException("Cannot read packed refs",e
);
971 if (fileReader
!= null) {
974 } catch (IOException e
) {
975 // Cannot do anything more here
980 packedRefs
= newPackedRefs
;
984 * @return true if HEAD points to a StGit patch.
986 public boolean isStGitMode() {
988 File file
= new File(getDirectory(), "HEAD");
989 BufferedReader reader
= new BufferedReader(new FileReader(file
));
990 String string
= reader
.readLine();
991 if (!string
.startsWith("ref: refs/heads/"))
993 String branch
= string
.substring("ref: refs/heads/".length());
994 File currentPatches
= new File(new File(new File(getDirectory(),
995 "patches"), branch
), "applied");
996 if (!currentPatches
.exists())
998 if (currentPatches
.length() == 0)
1002 } catch (IOException e
) {
1003 e
.printStackTrace();
1009 * @return applied patches in a map indexed on current commit id
1010 * @throws IOException
1012 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
1013 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
1014 if (isStGitMode()) {
1015 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
1016 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
1017 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
1018 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
1019 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
1020 String objectId
= tfr
.readLine();
1021 ObjectId id
= ObjectId
.fromString(objectId
);
1022 ret
.put(id
, new StGitPatch(patchName
, id
));
1030 private Collection
<String
> listFilesRecursively(File root
, File start
) {
1033 Collection
<String
> ret
= new ArrayList
<String
>();
1034 File
[] files
= start
.listFiles();
1035 for (int i
= 0; i
< files
.length
; ++i
) {
1036 if (files
[i
].isDirectory())
1037 ret
.addAll(listFilesRecursively(root
, files
[i
]));
1038 else if (files
[i
].length() == 41) {
1039 String name
= files
[i
].toString().substring(
1040 root
.toString().length() + 1);
1041 if (File
.separatorChar
!= '/')
1042 name
= name
.replace(File
.separatorChar
, '/');
1049 /** Clean up stale caches */
1050 public void refreshFromDisk() {
1055 * @return a representation of the index associated with this repo
1056 * @throws IOException
1058 public GitIndex
getIndex() throws IOException
{
1059 if (index
== null) {
1060 index
= new GitIndex(this);
1063 index
.rereadIfNecessary();
1068 static byte[] gitInternalSlash(byte[] bytes
) {
1069 if (File
.separatorChar
== '/')
1071 for (int i
=0; i
<bytes
.length
; ++i
)
1072 if (bytes
[i
] == File
.separatorChar
)
1078 * @return an important state
1080 public RepositoryState
getRepositoryState() {
1081 if (new File(getWorkDir(), ".dotest").exists())
1082 return RepositoryState
.REBASING
;
1083 if (new File(gitDir
,".dotest-merge").exists())
1084 return RepositoryState
.REBASING_INTERACTIVE
;
1085 if (new File(gitDir
,"MERGE_HEAD").exists())
1086 return RepositoryState
.MERGING
;
1087 if (new File(gitDir
,"BISECT_LOG").exists())
1088 return RepositoryState
.BISECTING
;
1089 return RepositoryState
.SAFE
;
1093 * Check validty of a ref name. It must not contain character that has
1094 * a special meaning in a Git object reference expression. Some other
1095 * dangerous characters are also excluded.
1099 * @return true if refName is a valid ref name
1101 public static boolean isValidRefName(final String refName
) {
1102 final int len
= refName
.length();
1104 for (int i
=0; i
<len
; ++i
) {
1105 char c
= refName
.charAt(i
);
1123 case '~': case '^': case ':':
1135 * String work dir and return normalized repository path
1137 * @param wd Work dir
1138 * @param f File whose path shall be stripp off it's workdir
1139 * @return normalized repository relative path
1141 public static String
stripWorkDir(File wd
, File f
) {
1142 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
1143 relName
= relName
.replace(File
.separatorChar
, '/');
1148 * @return the workdir file, i.e. where the files are checked out
1150 public File
getWorkDir() {
1151 return getDirectory().getParentFile();