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>
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
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
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
;
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
.Arrays
;
50 import java
.util
.Collection
;
51 import java
.util
.Collections
;
52 import java
.util
.HashMap
;
53 import java
.util
.HashSet
;
54 import java
.util
.LinkedList
;
55 import java
.util
.List
;
58 import java
.util
.Vector
;
60 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
61 import org
.spearce
.jgit
.errors
.RevisionSyntaxException
;
62 import org
.spearce
.jgit
.util
.FS
;
65 * Represents a Git repository. A repository holds all objects and refs used for
66 * managing source code (could by any type of file, but source code is what
67 * SCM's are typically used for).
69 * In Git terms all data is stored in GIT_DIR, typically a directory called
70 * .git. A work tree is maintained unless the repository is a bare repository.
71 * Typically the .git directory is located at the root of the work dir.
76 * <li>objects/ - objects</li>
77 * <li>refs/ - tags and heads</li>
78 * <li>config - configuration</li>
79 * <li>info/ - more configurations</li>
84 * This class is thread-safe.
86 * This implementation only handles a subtly undocumented subset of git features.
89 public class Repository
{
90 private final File gitDir
;
92 private final RepositoryConfig config
;
94 private final RefDatabase refs
;
96 private File
[] objectDirectoryList
;
98 private PackFile
[] packFileList
;
100 private GitIndex index
;
102 private List
<RepositoryListener
> listeners
= new Vector
<RepositoryListener
>(); // thread safe
103 static private List
<RepositoryListener
> allListeners
= new Vector
<RepositoryListener
>(); // thread safe
106 * Construct a representation of a Git repository.
109 * GIT_DIR (the location of the repository metadata).
110 * @throws IOException
111 * the repository appears to already exist but cannot be
114 public Repository(final File d
) throws IOException
{
115 gitDir
= d
.getAbsoluteFile();
117 objectDirectoryList
= readObjectsDirs(
118 FS
.resolve(gitDir
, "objects"), new ArrayList
<File
>())
119 .toArray(new File
[0]);
120 } catch (IOException e
) {
121 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
125 refs
= new RefDatabase(this);
126 packFileList
= new PackFile
[0];
127 config
= new RepositoryConfig(this);
129 final boolean isExisting
= objectDirectoryList
[0].exists();
132 final String repositoryFormatVersion
= getConfig().getString(
133 "core", null, "repositoryFormatVersion");
134 if (!"0".equals(repositoryFormatVersion
)) {
135 throw new IOException("Unknown repository format \""
136 + repositoryFormatVersion
+ "\"; expected \"0\".");
139 getConfig().create();
145 private static Collection
<File
> readObjectsDirs(File objectsDir
,
146 Collection
<File
> ret
) throws IOException
{
148 final File altFile
= FS
.resolve(objectsDir
, "info/alternates");
149 if (altFile
.exists()) {
150 BufferedReader ar
= new BufferedReader(new FileReader(altFile
));
152 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
153 readObjectsDirs(FS
.resolve(objectsDir
, alt
), ret
);
163 * Create a new Git repository initializing the necessary files and
166 * @throws IOException
168 public synchronized void create() throws IOException
{
169 if (gitDir
.exists()) {
170 throw new IllegalStateException("Repository already exists: "
177 objectDirectoryList
[0].mkdirs();
178 new File(objectDirectoryList
[0], "pack").mkdir();
179 new File(objectDirectoryList
[0], "info").mkdir();
181 new File(gitDir
, "branches").mkdir();
182 new File(gitDir
, "remotes").mkdir();
183 final String master
= Constants
.R_HEADS
+ Constants
.MASTER
;
184 refs
.link(Constants
.HEAD
, master
);
186 getConfig().create();
190 private synchronized File
[] objectsDirs(){
191 return objectDirectoryList
;
194 private synchronized PackFile
[] packs(){
201 public File
getDirectory() {
206 * @return the directory containing the objects owned by this repository.
208 public File
getObjectsDirectory() {
209 return objectsDirs()[0];
213 * @return the configuration of this repository
215 public RepositoryConfig
getConfig() {
220 * Construct a filename where the loose object having a specified SHA-1
221 * should be stored. If the object is stored in a shared repository the path
222 * to the alternative repo will be returned. If the object is not yet store
223 * a usable path in this repo will be returned. It is assumed that callers
224 * will look for objects in a pack first.
227 * @return suggested file name
229 public File
toFile(final AnyObjectId objectId
) {
230 final String n
= objectId
.name();
231 String d
=n
.substring(0, 2);
232 String f
=n
.substring(2);
233 final File
[] objectsDirs
= objectsDirs();
234 for (File objectsDir
: objectsDirs
) {
235 File ret
= new File(new File(objectsDir
, d
), f
);
239 return new File(new File(objectsDirs
[0], d
), f
);
244 * @return true if the specified object is stored in this repo or any of the
245 * known shared repositories.
247 public boolean hasObject(final AnyObjectId objectId
) {
248 final PackFile
[] packs
= packs();
249 int k
= packs
.length
;
252 if (packs
[--k
].hasObject(objectId
))
254 } catch (IOException e
) {
255 // Assume that means the pack is invalid, and such
256 // packs are treated as though they are empty.
261 return toFile(objectId
).isFile();
266 * SHA-1 of an object.
268 * @return a {@link ObjectLoader} for accessing the data of the named
269 * object, or null if the object does not exist.
270 * @throws IOException
272 public ObjectLoader
openObject(final AnyObjectId id
)
274 final WindowCursor wc
= new WindowCursor();
276 return openObject(wc
, id
);
284 * temporary working space associated with the calling thread.
286 * SHA-1 of an object.
288 * @return a {@link ObjectLoader} for accessing the data of the named
289 * object, or null if the object does not exist.
290 * @throws IOException
292 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
294 final PackFile
[] packs
= packs();
295 int k
= packs
.length
;
297 final ObjectLoader ol
= packs
[--k
].get(curs
, id
);
302 return new UnpackedObjectLoader(this, id
);
303 } catch (FileNotFoundException fnfe
) {
309 * Open object in all packs containing specified object.
312 * id of object to search for
314 * temporary working space associated with the calling thread.
315 * @return collection of loaders for this object, from all packs containing
317 * @throws IOException
319 public Collection
<PackedObjectLoader
> openObjectInAllPacks(
320 final AnyObjectId objectId
, final WindowCursor curs
)
322 Collection
<PackedObjectLoader
> result
= new LinkedList
<PackedObjectLoader
>();
323 openObjectInAllPacks(objectId
, result
, curs
);
328 * Open object in all packs containing specified object.
331 * id of object to search for
332 * @param resultLoaders
333 * result collection of loaders for this object, filled with
334 * loaders from all packs containing specified object
336 * temporary working space associated with the calling thread.
337 * @throws IOException
339 void openObjectInAllPacks(final AnyObjectId objectId
,
340 final Collection
<PackedObjectLoader
> resultLoaders
,
341 final WindowCursor curs
) throws IOException
{
342 for (PackFile pack
: packs()) {
343 final PackedObjectLoader loader
= pack
.get(curs
, objectId
);
345 resultLoaders
.add(loader
);
352 * @return an {@link ObjectLoader} for accessing the data of a named blob
353 * @throws IOException
355 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
356 return openObject(id
);
362 * @return an {@link ObjectLoader} for accessing the data of a named tree
363 * @throws IOException
365 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
366 return openObject(id
);
370 * Access a Commit object using a symbolic reference. This reference may
371 * be a SHA-1 or ref in combination with a number of symbols translating
372 * from one ref or SHA1-1 to another, such as HEAD^ etc.
374 * @param revstr a reference to a git commit object
375 * @return a Commit named by the specified string
376 * @throws IOException for I/O error or unexpected object type.
378 * @see #resolve(String)
380 public Commit
mapCommit(final String revstr
) throws IOException
{
381 final ObjectId id
= resolve(revstr
);
382 return id
!= null ?
mapCommit(id
) : null;
386 * Access any type of Git object by id and
389 * SHA-1 of object to read
390 * @param refName optional, only relevant for simple tags
391 * @return The Git object if found or null
392 * @throws IOException
394 public Object
mapObject(final ObjectId id
, final String refName
) throws IOException
{
395 final ObjectLoader or
= openObject(id
);
398 final byte[] raw
= or
.getBytes();
399 switch (or
.getType()) {
400 case Constants
.OBJ_TREE
:
401 return makeTree(id
, raw
);
403 case Constants
.OBJ_COMMIT
:
404 return makeCommit(id
, raw
);
406 case Constants
.OBJ_TAG
:
407 return makeTag(id
, refName
, raw
);
409 case Constants
.OBJ_BLOB
:
413 throw new IncorrectObjectTypeException(id
,
414 "COMMIT nor TREE nor BLOB nor TAG");
419 * Access a Commit by SHA'1 id.
421 * @return Commit or null
422 * @throws IOException for I/O error or unexpected object type.
424 public Commit
mapCommit(final ObjectId id
) throws IOException
{
425 final ObjectLoader or
= openObject(id
);
428 final byte[] raw
= or
.getBytes();
429 if (Constants
.OBJ_COMMIT
== or
.getType())
430 return new Commit(this, id
, raw
);
431 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
434 private Commit
makeCommit(final ObjectId id
, final byte[] raw
) {
435 Commit ret
= new Commit(this, id
, raw
);
440 * Access a Tree object using a symbolic reference. This reference may
441 * be a SHA-1 or ref in combination with a number of symbols translating
442 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
444 * @param revstr a reference to a git commit object
445 * @return a Tree named by the specified string
446 * @throws IOException
448 * @see #resolve(String)
450 public Tree
mapTree(final String revstr
) throws IOException
{
451 final ObjectId id
= resolve(revstr
);
452 return id
!= null ?
mapTree(id
) : null;
456 * Access a Tree by SHA'1 id.
458 * @return Tree or null
459 * @throws IOException for I/O error or unexpected object type.
461 public Tree
mapTree(final ObjectId id
) throws IOException
{
462 final ObjectLoader or
= openObject(id
);
465 final byte[] raw
= or
.getBytes();
466 switch (or
.getType()) {
467 case Constants
.OBJ_TREE
:
468 return new Tree(this, id
, raw
);
470 case Constants
.OBJ_COMMIT
:
471 return mapTree(ObjectId
.fromString(raw
, 5));
474 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
478 private Tree
makeTree(final ObjectId id
, final byte[] raw
) throws IOException
{
479 Tree ret
= new Tree(this, id
, raw
);
483 private Tag
makeTag(final ObjectId id
, final String refName
, final byte[] raw
) {
484 Tag ret
= new Tag(this, id
, refName
, raw
);
489 * Access a tag by symbolic name.
492 * @return a Tag or null
493 * @throws IOException on I/O error or unexpected type
495 public Tag
mapTag(String revstr
) throws IOException
{
496 final ObjectId id
= resolve(revstr
);
497 return id
!= null ?
mapTag(revstr
, id
) : null;
501 * Access a Tag by SHA'1 id
504 * @return Commit or null
505 * @throws IOException for I/O error or unexpected object type.
507 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
508 final ObjectLoader or
= openObject(id
);
511 final byte[] raw
= or
.getBytes();
512 if (Constants
.OBJ_TAG
== or
.getType())
513 return new Tag(this, id
, refName
, raw
);
514 return new Tag(this, id
, refName
, null);
518 * Create a command to update, create or delete a ref in this repository.
521 * name of the ref the caller wants to modify.
522 * @return an update command. The caller must finish populating this command
523 * and then invoke one of the update methods to actually make a
525 * @throws IOException
526 * a symbolic ref was passed in and could not be resolved back
527 * to the base ref, as the symbolic ref could not be read.
529 public RefUpdate
updateRef(final String ref
) throws IOException
{
530 return refs
.newUpdate(ref
);
534 * Parse a git revision string and return an object id.
536 * Currently supported is combinations of these.
538 * <li>SHA-1 - a SHA-1</li>
539 * <li>refs/... - a ref name</li>
540 * <li>ref^n - nth parent reference</li>
541 * <li>ref~n - distance via parent reference</li>
542 * <li>ref@{n} - nth version of ref</li>
543 * <li>ref^{tree} - tree references by ref</li>
544 * <li>ref^{commit} - commit references by ref</li>
549 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
550 * <li>abbreviated SHA-1's</li>
553 * @param revstr A git object references expression
554 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
555 * @throws IOException on serious errors
557 public ObjectId
resolve(final String revstr
) throws IOException
{
558 char[] rev
= revstr
.toCharArray();
560 ObjectId refId
= null;
561 for (int i
= 0; i
< rev
.length
; ++i
) {
565 String refstr
= new String(rev
,0,i
);
566 refId
= resolveSimple(refstr
);
570 if (i
+ 1 < rev
.length
) {
571 switch (rev
[i
+ 1]) {
583 ref
= mapObject(refId
, null);
584 while (ref
instanceof Tag
) {
586 refId
= tag
.getObjId();
587 ref
= mapObject(refId
, null);
589 if (!(ref
instanceof Commit
))
590 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
591 for (j
=i
+1; j
<rev
.length
; ++j
) {
592 if (!Character
.isDigit(rev
[j
]))
595 String parentnum
= new String(rev
, i
+1, j
-i
-1);
598 pnum
= Integer
.parseInt(parentnum
);
599 } catch (NumberFormatException e
) {
600 throw new RevisionSyntaxException(
601 "Invalid commit parent number",
605 final ObjectId parents
[] = ((Commit
) ref
)
607 if (pnum
> parents
.length
)
610 refId
= parents
[pnum
- 1];
617 for (k
=i
+2; k
<rev
.length
; ++k
) {
619 item
= new String(rev
, i
+2, k
-i
-2);
625 if (item
.equals("tree")) {
626 ref
= mapObject(refId
, null);
627 while (ref
instanceof Tag
) {
629 refId
= t
.getObjId();
630 ref
= mapObject(refId
, null);
632 if (ref
instanceof Treeish
)
633 refId
= ((Treeish
)ref
).getTreeId();
635 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_TREE
);
637 else if (item
.equals("commit")) {
638 ref
= mapObject(refId
, null);
639 while (ref
instanceof Tag
) {
641 refId
= t
.getObjId();
642 ref
= mapObject(refId
, null);
644 if (!(ref
instanceof Commit
))
645 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
647 else if (item
.equals("blob")) {
648 ref
= mapObject(refId
, null);
649 while (ref
instanceof Tag
) {
651 refId
= t
.getObjId();
652 ref
= mapObject(refId
, null);
654 if (!(ref
instanceof byte[]))
655 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_BLOB
);
657 else if (item
.equals("")) {
658 ref
= mapObject(refId
, null);
659 while (ref
instanceof Tag
) {
661 refId
= t
.getObjId();
662 ref
= mapObject(refId
, null);
666 throw new RevisionSyntaxException(revstr
);
668 throw new RevisionSyntaxException(revstr
);
671 ref
= mapObject(refId
, null);
672 if (ref
instanceof Commit
) {
673 final ObjectId parents
[] = ((Commit
) ref
)
675 if (parents
.length
== 0)
680 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
684 ref
= mapObject(refId
, null);
685 while (ref
instanceof Tag
) {
687 refId
= tag
.getObjId();
688 ref
= mapObject(refId
, null);
690 if (ref
instanceof Commit
) {
691 final ObjectId parents
[] = ((Commit
) ref
)
693 if (parents
.length
== 0)
698 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
703 String refstr
= new String(rev
,0,i
);
704 refId
= resolveSimple(refstr
);
707 ref
= mapObject(refId
, null);
709 while (ref
instanceof Tag
) {
711 refId
= tag
.getObjId();
712 ref
= mapObject(refId
, null);
714 if (!(ref
instanceof Commit
))
715 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
717 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
718 if (!Character
.isDigit(rev
[l
]))
721 String distnum
= new String(rev
, i
+1, l
-i
-1);
724 dist
= Integer
.parseInt(distnum
);
725 } catch (NumberFormatException e
) {
726 throw new RevisionSyntaxException(
727 "Invalid ancestry length", revstr
);
730 final ObjectId
[] parents
= ((Commit
) ref
).getParentIds();
731 if (parents
.length
== 0) {
736 ref
= mapCommit(refId
);
744 for (m
=i
+2; m
<rev
.length
; ++m
) {
746 time
= new String(rev
, i
+2, m
-i
-2);
751 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr
);
756 throw new RevisionSyntaxException(revstr
);
760 refId
= resolveSimple(revstr
);
764 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
765 if (ObjectId
.isId(revstr
))
766 return ObjectId
.fromString(revstr
);
767 final Ref r
= refs
.readRef(revstr
);
768 return r
!= null ? r
.getObjectId() : null;
772 * Close all resources used by this repository
774 public void close() {
778 synchronized void closePacks() {
779 for (int k
= packFileList
.length
- 1; k
>= 0; k
--)
780 packFileList
[k
].close();
781 packFileList
= new PackFile
[0];
785 * Add a single existing pack to the list of available pack files.
788 * path of the pack file to open.
790 * path of the corresponding index file.
791 * @throws IOException
792 * index file could not be opened, read, or is not recognized as
793 * a Git pack file index.
795 public void openPack(final File pack
, final File idx
) throws IOException
{
796 final String p
= pack
.getName();
797 final String i
= idx
.getName();
798 if (p
.length() != 50 || !p
.startsWith("pack-") || !p
.endsWith(".pack"))
799 throw new IllegalArgumentException("Not a valid pack " + pack
);
800 if (i
.length() != 49 || !i
.startsWith("pack-") || !i
.endsWith(".idx"))
801 throw new IllegalArgumentException("Not a valid pack " + idx
);
802 if (!p
.substring(0,45).equals(i
.substring(0,45)))
803 throw new IllegalArgumentException("Pack " + pack
804 + "does not match index " + idx
);
806 synchronized (this) {
807 final PackFile
[] cur
= packFileList
;
808 final PackFile
[] arr
= new PackFile
[cur
.length
+ 1];
809 System
.arraycopy(cur
, 0, arr
, 1, cur
.length
);
810 arr
[0] = new PackFile(idx
, pack
);
816 * Scan the object dirs, including alternates for packs
819 public void scanForPacks() {
820 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
821 p
.addAll(Arrays
.asList(packs()));
822 for (final File d
: objectsDirs())
823 scanForPacks(new File(d
, "pack"), p
);
824 final PackFile
[] arr
= new PackFile
[p
.size()];
826 Arrays
.sort(arr
, PackFile
.SORT
);
827 synchronized (this) {
832 private void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
833 final String
[] idxList
= packDir
.list(new FilenameFilter() {
834 public boolean accept(final File baseDir
, final String n
) {
835 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
836 return n
.length() == 49 && n
.endsWith(".idx")
837 && n
.startsWith("pack-");
840 if (idxList
!= null) {
841 SCAN
: for (final String indexName
: idxList
) {
842 final String n
= indexName
.substring(0, indexName
.length() - 4);
843 final File idxFile
= new File(packDir
, n
+ ".idx");
844 final File packFile
= new File(packDir
, n
+ ".pack");
846 if (!packFile
.isFile()) {
847 // Sometimes C Git's http fetch transport leaves a
848 // .idx file behind and does not download the .pack.
849 // We have to skip over such useless indexes.
854 for (final PackFile p
: packList
) {
855 if (packFile
.equals(p
.getPackFile()))
859 packList
.add(new PackFile(idxFile
, packFile
));
865 * Writes a symref (e.g. HEAD) to disk
867 * @param name symref name
868 * @param target pointed to ref
869 * @throws IOException
871 public void writeSymref(final String name
, final String target
)
873 refs
.link(name
, target
);
876 public String
toString() {
877 return "Repository[" + getDirectory() + "]";
881 * @return name of current branch
882 * @throws IOException
884 public String
getFullBranch() throws IOException
{
885 final File ptr
= new File(getDirectory(),Constants
.HEAD
);
886 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
893 if (ref
.startsWith("ref: "))
894 ref
= ref
.substring(5);
899 * @return name of current branch.
900 * @throws IOException
902 public String
getBranch() throws IOException
{
904 final File ptr
= new File(getDirectory(), Constants
.HEAD
);
905 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
912 if (ref
.startsWith("ref: "))
913 ref
= ref
.substring(5);
914 if (ref
.startsWith("refs/heads/"))
915 ref
= ref
.substring(11);
917 } catch (FileNotFoundException e
) {
918 final File ptr
= new File(getDirectory(),"head-name");
919 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
934 * the name of the ref to lookup. May be a short-hand form, e.g.
935 * "master" which is is automatically expanded to
936 * "refs/heads/master" if "refs/heads/master" already exists.
937 * @return the Ref with the given name, or null if it does not exist
938 * @throws IOException
940 public Ref
getRef(final String name
) throws IOException
{
941 return refs
.readRef(name
);
945 * @return all known refs (heads, tags, remotes).
947 public Map
<String
, Ref
> getAllRefs() {
948 return refs
.getAllRefs();
952 * @return all tags; key is short tag name ("v1.0") and value of the entry
953 * contains the ref with the full tag name ("refs/tags/v1.0").
955 public Map
<String
, Ref
> getTags() {
956 return refs
.getTags();
960 * Peel a possibly unpeeled ref and updates it.
962 * If the ref cannot be peeled (as it does not refer to an annotated tag)
963 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
967 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
968 * new Ref object representing the same data as Ref, but isPeeled()
969 * will be true and getPeeledObjectId will contain the peeled object
972 public Ref
peel(final Ref ref
) {
973 return refs
.peel(ref
);
977 * @return a map with all objects referenced by a peeled ref.
979 public Map
<AnyObjectId
, Set
<Ref
>> getAllRefsByPeeledObjectId() {
980 Map
<String
, Ref
> allRefs
= getAllRefs();
981 Map
<AnyObjectId
, Set
<Ref
>> ret
= new HashMap
<AnyObjectId
, Set
<Ref
>>(allRefs
.size());
982 for (Ref ref
: allRefs
.values()) {
985 AnyObjectId target
= ref
.getPeeledObjectId();
987 target
= ref
.getObjectId();
988 // We assume most Sets here are singletons
989 Set
<Ref
> oset
= ret
.put(target
, Collections
.singleton(ref
));
991 // that was not the case (rare)
992 if (oset
.size() == 1) {
993 // Was a read-only singleton, we must copy to a new Set
994 oset
= new HashSet
<Ref
>(oset
);
996 ret
.put(target
, oset
);
1003 /** Clean up stale caches */
1004 public void refreshFromDisk() {
1009 * @return a representation of the index associated with this repo
1010 * @throws IOException
1012 public GitIndex
getIndex() throws IOException
{
1013 if (index
== null) {
1014 index
= new GitIndex(this);
1017 index
.rereadIfNecessary();
1022 static byte[] gitInternalSlash(byte[] bytes
) {
1023 if (File
.separatorChar
== '/')
1025 for (int i
=0; i
<bytes
.length
; ++i
)
1026 if (bytes
[i
] == File
.separatorChar
)
1032 * @return an important state
1034 public RepositoryState
getRepositoryState() {
1035 // Pre Git-1.6 logic
1036 if (new File(getWorkDir(), ".dotest").exists())
1037 return RepositoryState
.REBASING
;
1038 if (new File(gitDir
,".dotest-merge").exists())
1039 return RepositoryState
.REBASING_INTERACTIVE
;
1042 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1043 return RepositoryState
.REBASING_REBASING
;
1044 if (new File(getDirectory(),"rebase-apply/applying").exists())
1045 return RepositoryState
.APPLY
;
1046 if (new File(getDirectory(),"rebase-apply").exists())
1047 return RepositoryState
.REBASING
;
1049 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1050 return RepositoryState
.REBASING_INTERACTIVE
;
1051 if (new File(getDirectory(),"rebase-merge").exists())
1052 return RepositoryState
.REBASING_MERGE
;
1055 if (new File(gitDir
,"MERGE_HEAD").exists())
1056 return RepositoryState
.MERGING
;
1057 if (new File(gitDir
,"BISECT_LOG").exists())
1058 return RepositoryState
.BISECTING
;
1060 return RepositoryState
.SAFE
;
1064 * Check validity of a ref name. It must not contain character that has
1065 * a special meaning in a Git object reference expression. Some other
1066 * dangerous characters are also excluded.
1070 * @return true if refName is a valid ref name
1072 public static boolean isValidRefName(final String refName
) {
1073 final int len
= refName
.length();
1078 for (int i
=0; i
<len
; ++i
) {
1079 char c
= refName
.charAt(i
);
1097 case '~': case '^': case ':':
1109 * Strip work dir and return normalized repository path
1111 * @param wd Work dir
1112 * @param f File whose path shall be stripped of its workdir
1113 * @return normalized repository relative path
1115 public static String
stripWorkDir(File wd
, File f
) {
1116 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
1117 relName
= relName
.replace(File
.separatorChar
, '/');
1122 * @return the workdir file, i.e. where the files are checked out
1124 public File
getWorkDir() {
1125 return getDirectory().getParentFile();
1129 * Register a {@link RepositoryListener} which will be notified
1130 * when ref changes are detected.
1134 public void addRepositoryChangedListener(final RepositoryListener l
) {
1139 * Remove a registered {@link RepositoryListener}
1142 public void removeRepositoryChangedListener(final RepositoryListener l
) {
1143 listeners
.remove(l
);
1147 * Register a global {@link RepositoryListener} which will be notified
1148 * when a ref changes in any repository are detected.
1152 public static void addAnyRepositoryChangedListener(final RepositoryListener l
) {
1153 allListeners
.add(l
);
1157 * Remove a globally registered {@link RepositoryListener}
1160 public static void removeAnyRepositoryChangedListener(final RepositoryListener l
) {
1161 allListeners
.remove(l
);
1164 void fireRefsMaybeChanged() {
1165 if (refs
.lastRefModification
!= refs
.lastNotifiedRefModification
) {
1166 refs
.lastNotifiedRefModification
= refs
.lastRefModification
;
1167 final RefsChangedEvent event
= new RefsChangedEvent(this);
1168 List
<RepositoryListener
> all
;
1169 synchronized (listeners
) {
1170 all
= new ArrayList
<RepositoryListener
>(listeners
);
1172 synchronized (allListeners
) {
1173 all
.addAll(allListeners
);
1175 for (final RepositoryListener l
: all
) {
1176 l
.refsChanged(event
);
1181 void fireIndexChanged() {
1182 final IndexChangedEvent event
= new IndexChangedEvent(this);
1183 List
<RepositoryListener
> all
;
1184 synchronized (listeners
) {
1185 all
= new ArrayList
<RepositoryListener
>(listeners
);
1187 synchronized (allListeners
) {
1188 all
.addAll(allListeners
);
1190 for (final RepositoryListener l
: all
) {
1191 l
.indexChanged(event
);
1196 * Force a scan for changed refs.
1198 * @throws IOException
1200 public void scanForRepoChanges() throws IOException
{
1201 getAllRefs(); // This will look for changes to refs
1202 getIndex(); // This will detect changes in the index