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
.Collection
;
50 import java
.util
.Collections
;
51 import java
.util
.HashMap
;
52 import java
.util
.HashSet
;
53 import java
.util
.LinkedList
;
54 import java
.util
.List
;
57 import java
.util
.Vector
;
59 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
60 import org
.spearce
.jgit
.errors
.RevisionSyntaxException
;
61 import org
.spearce
.jgit
.stgit
.StGitPatch
;
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 implementation only handles a subtly undocumented subset of git features.
87 public class Repository
{
88 private final File gitDir
;
90 private final File
[] objectsDirs
;
92 private final RepositoryConfig config
;
94 private final RefDatabase refs
;
96 private PackFile
[] packs
;
98 private GitIndex index
;
100 private List
<RepositoryListener
> listeners
= new Vector
<RepositoryListener
>(); // thread safe
101 static private List
<RepositoryListener
> allListeners
= new Vector
<RepositoryListener
>(); // thread safe
104 * Construct a representation of a Git repository.
107 * GIT_DIR (the location of the repository metadata).
108 * @throws IOException
109 * the repository appears to already exist but cannot be
112 public Repository(final File d
) throws IOException
{
113 gitDir
= d
.getAbsoluteFile();
115 objectsDirs
= readObjectsDirs(FS
.resolve(gitDir
, "objects"),
116 new ArrayList
<File
>()).toArray(new File
[0]);
117 } catch (IOException e
) {
118 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
122 refs
= new RefDatabase(this);
123 packs
= new PackFile
[0];
124 config
= new RepositoryConfig(this);
126 final boolean isExisting
= objectsDirs
[0].exists();
129 final String repositoryFormatVersion
= getConfig().getString(
130 "core", null, "repositoryFormatVersion");
131 if (!"0".equals(repositoryFormatVersion
)) {
132 throw new IOException("Unknown repository format \""
133 + repositoryFormatVersion
+ "\"; expected \"0\".");
136 getConfig().create();
142 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
144 final File altFile
= FS
.resolve(objectsDir
, "info/alternates");
145 if (altFile
.exists()) {
146 BufferedReader ar
= new BufferedReader(new FileReader(altFile
));
147 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
148 readObjectsDirs(FS
.resolve(objectsDir
, alt
), ret
);
156 * Create a new Git repository initializing the necessary files and
159 * @throws IOException
161 public void create() throws IOException
{
162 if (gitDir
.exists()) {
163 throw new IllegalStateException("Repository already exists: "
170 objectsDirs
[0].mkdirs();
171 new File(objectsDirs
[0], "pack").mkdir();
172 new File(objectsDirs
[0], "info").mkdir();
174 new File(gitDir
, "branches").mkdir();
175 new File(gitDir
, "remotes").mkdir();
176 final String master
= Constants
.R_HEADS
+ Constants
.MASTER
;
177 refs
.link(Constants
.HEAD
, master
);
179 getConfig().create();
186 public File
getDirectory() {
191 * @return the directory containing the objects owned by this repository.
193 public File
getObjectsDirectory() {
194 return objectsDirs
[0];
198 * @return the configuration of this repository
200 public RepositoryConfig
getConfig() {
205 * Construct a filename where the loose object having a specified SHA-1
206 * should be stored. If the object is stored in a shared repository the path
207 * to the alternative repo will be returned. If the object is not yet store
208 * a usable path in this repo will be returned. It is assumed that callers
209 * will look for objects in a pack first.
212 * @return suggested file name
214 public File
toFile(final AnyObjectId objectId
) {
215 final String n
= objectId
.name();
216 String d
=n
.substring(0, 2);
217 String f
=n
.substring(2);
218 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
219 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
223 return new File(new File(objectsDirs
[0], d
), f
);
228 * @return true if the specified object is stored in this repo or any of the
229 * known shared repositories.
231 public boolean hasObject(final AnyObjectId objectId
) {
232 int k
= packs
.length
;
235 if (packs
[--k
].hasObject(objectId
))
239 return toFile(objectId
).isFile();
244 * SHA-1 of an object.
246 * @return a {@link ObjectLoader} for accessing the data of the named
247 * object, or null if the object does not exist.
248 * @throws IOException
250 public ObjectLoader
openObject(final AnyObjectId id
)
252 return openObject(new WindowCursor(),id
);
257 * temporary working space associated with the calling thread.
259 * SHA-1 of an object.
261 * @return a {@link ObjectLoader} for accessing the data of the named
262 * object, or null if the object does not exist.
263 * @throws IOException
265 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
267 int k
= packs
.length
;
271 final ObjectLoader ol
= packs
[--k
].get(curs
, id
);
274 } catch (IOException ioe
) {
275 // This shouldn't happen unless the pack was corrupted
276 // after we opened it or the VM runs out of memory. This is
277 // a know problem with memory mapped I/O in java and have
278 // been noticed with JDK < 1.6. Tell the gc that now is a good
279 // time to collect and try once more.
283 final ObjectLoader ol
= packs
[k
].get(curs
, id
);
286 } catch (IOException ioe2
) {
287 ioe2
.printStackTrace();
288 ioe
.printStackTrace();
289 // Still fails.. that's BAD, maybe the pack has
290 // been corrupted after all, or the gc didn't manage
291 // to release enough previously mmaped areas.
297 return new UnpackedObjectLoader(this, id
.toObjectId());
298 } catch (FileNotFoundException fnfe
) {
304 * Open object in all packs containing specified object.
307 * id of object to search for
309 * temporary working space associated with the calling thread.
310 * @return collection of loaders for this object, from all packs containing
312 * @throws IOException
314 public Collection
<PackedObjectLoader
> openObjectInAllPacks(
315 final AnyObjectId objectId
, final WindowCursor curs
)
317 Collection
<PackedObjectLoader
> result
= new LinkedList
<PackedObjectLoader
>();
318 openObjectInAllPacks(objectId
, result
, curs
);
323 * Open object in all packs containing specified object.
326 * id of object to search for
327 * @param resultLoaders
328 * result collection of loaders for this object, filled with
329 * loaders from all packs containing specified object
331 * temporary working space associated with the calling thread.
332 * @throws IOException
334 void openObjectInAllPacks(final AnyObjectId objectId
,
335 final Collection
<PackedObjectLoader
> resultLoaders
,
336 final WindowCursor curs
) throws IOException
{
337 for (PackFile pack
: packs
) {
338 final PackedObjectLoader loader
= pack
.get(curs
, objectId
);
340 resultLoaders
.add(loader
);
347 * @return an {@link ObjectLoader} for accessing the data of a named blob
348 * @throws IOException
350 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
351 return openObject(id
);
357 * @return an {@link ObjectLoader} for accessing the data of a named tree
358 * @throws IOException
360 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
361 return openObject(id
);
365 * Access a Commit object using a symbolic reference. This reference may
366 * be a SHA-1 or ref in combination with a number of symbols translating
367 * from one ref or SHA1-1 to another, such as HEAD^ etc.
369 * @param revstr a reference to a git commit object
370 * @return a Commit named by the specified string
371 * @throws IOException for I/O error or unexpected object type.
373 * @see #resolve(String)
375 public Commit
mapCommit(final String revstr
) throws IOException
{
376 final ObjectId id
= resolve(revstr
);
377 return id
!= null ?
mapCommit(id
) : null;
381 * Access any type of Git object by id and
384 * SHA-1 of object to read
385 * @param refName optional, only relevant for simple tags
386 * @return The Git object if found or null
387 * @throws IOException
389 public Object
mapObject(final ObjectId id
, final String refName
) throws IOException
{
390 final ObjectLoader or
= openObject(id
);
393 final byte[] raw
= or
.getBytes();
394 if (or
.getType() == Constants
.OBJ_TREE
)
395 return makeTree(id
, raw
);
396 if (or
.getType() == Constants
.OBJ_COMMIT
)
397 return makeCommit(id
, raw
);
398 if (or
.getType() == Constants
.OBJ_TAG
)
399 return makeTag(id
, refName
, raw
);
400 if (or
.getType() == Constants
.OBJ_BLOB
)
402 throw new IncorrectObjectTypeException(id
,
403 "COMMIT nor TREE nor BLOB nor TAG");
407 * Access a Commit by SHA'1 id.
409 * @return Commit or null
410 * @throws IOException for I/O error or unexpected object type.
412 public Commit
mapCommit(final ObjectId id
) throws IOException
{
413 final ObjectLoader or
= openObject(id
);
416 final byte[] raw
= or
.getBytes();
417 if (Constants
.OBJ_COMMIT
== or
.getType())
418 return new Commit(this, id
, raw
);
419 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
422 private Commit
makeCommit(final ObjectId id
, final byte[] raw
) {
423 Commit ret
= new Commit(this, id
, raw
);
428 * Access a Tree object using a symbolic reference. This reference may
429 * be a SHA-1 or ref in combination with a number of symbols translating
430 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
432 * @param revstr a reference to a git commit object
433 * @return a Tree named by the specified string
434 * @throws IOException
436 * @see #resolve(String)
438 public Tree
mapTree(final String revstr
) throws IOException
{
439 final ObjectId id
= resolve(revstr
);
440 return id
!= null ?
mapTree(id
) : null;
444 * Access a Tree by SHA'1 id.
446 * @return Tree or null
447 * @throws IOException for I/O error or unexpected object type.
449 public Tree
mapTree(final ObjectId id
) throws IOException
{
450 final ObjectLoader or
= openObject(id
);
453 final byte[] raw
= or
.getBytes();
454 if (Constants
.OBJ_TREE
== or
.getType()) {
455 return new Tree(this, id
, raw
);
457 if (Constants
.OBJ_COMMIT
== or
.getType())
458 return mapTree(ObjectId
.fromString(raw
, 5));
459 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
462 private Tree
makeTree(final ObjectId id
, final byte[] raw
) throws IOException
{
463 Tree ret
= new Tree(this, id
, raw
);
467 private Tag
makeTag(final ObjectId id
, final String refName
, final byte[] raw
) {
468 Tag ret
= new Tag(this, id
, refName
, raw
);
473 * Access a tag by symbolic name.
476 * @return a Tag or null
477 * @throws IOException on I/O error or unexpected type
479 public Tag
mapTag(String revstr
) throws IOException
{
480 final ObjectId id
= resolve(revstr
);
481 return id
!= null ?
mapTag(revstr
, id
) : null;
485 * Access a Tag by SHA'1 id
488 * @return Commit or null
489 * @throws IOException for I/O error or unexpected object type.
491 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
492 final ObjectLoader or
= openObject(id
);
495 final byte[] raw
= or
.getBytes();
496 if (Constants
.OBJ_TAG
== or
.getType())
497 return new Tag(this, id
, refName
, raw
);
498 return new Tag(this, id
, refName
, null);
502 * Create a command to update, create or delete a ref in this repository.
505 * name of the ref the caller wants to modify.
506 * @return an update command. The caller must finish populating this command
507 * and then invoke one of the update methods to actually make a
509 * @throws IOException
510 * a symbolic ref was passed in and could not be resolved back
511 * to the base ref, as the symbolic ref could not be read.
513 public RefUpdate
updateRef(final String ref
) throws IOException
{
514 return refs
.newUpdate(ref
);
518 * Parse a git revision string and return an object id.
520 * Currently supported is combinations of these.
522 * <li>SHA-1 - a SHA-1</li>
523 * <li>refs/... - a ref name</li>
524 * <li>ref^n - nth parent reference</li>
525 * <li>ref~n - distance via parent reference</li>
526 * <li>ref@{n} - nth version of ref</li>
527 * <li>ref^{tree} - tree references by ref</li>
528 * <li>ref^{commit} - commit references by ref</li>
533 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
534 * <li>abbreviated SHA-1's</li>
537 * @param revstr A git object references expression
538 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
539 * @throws IOException on serious errors
541 public ObjectId
resolve(final String revstr
) throws IOException
{
542 char[] rev
= revstr
.toCharArray();
544 ObjectId refId
= null;
545 for (int i
= 0; i
< rev
.length
; ++i
) {
549 String refstr
= new String(rev
,0,i
);
550 refId
= resolveSimple(refstr
);
554 if (i
+ 1 < rev
.length
) {
555 switch (rev
[i
+ 1]) {
567 ref
= mapObject(refId
, null);
568 while (ref
instanceof Tag
) {
570 refId
= tag
.getObjId();
571 ref
= mapObject(refId
, null);
573 if (!(ref
instanceof Commit
))
574 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
575 for (j
=i
+1; j
<rev
.length
; ++j
) {
576 if (!Character
.isDigit(rev
[j
]))
579 String parentnum
= new String(rev
, i
+1, j
-i
-1);
582 pnum
= Integer
.parseInt(parentnum
);
583 } catch (NumberFormatException e
) {
584 throw new RevisionSyntaxException(
585 "Invalid commit parent number",
589 final ObjectId parents
[] = ((Commit
) ref
)
591 if (pnum
> parents
.length
)
594 refId
= parents
[pnum
- 1];
601 for (k
=i
+2; k
<rev
.length
; ++k
) {
603 item
= new String(rev
, i
+2, k
-i
-2);
609 if (item
.equals("tree")) {
610 ref
= mapObject(refId
, null);
611 while (ref
instanceof Tag
) {
613 refId
= t
.getObjId();
614 ref
= mapObject(refId
, null);
616 if (ref
instanceof Treeish
)
617 refId
= ((Treeish
)ref
).getTreeId();
619 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_TREE
);
621 else if (item
.equals("commit")) {
622 ref
= mapObject(refId
, null);
623 while (ref
instanceof Tag
) {
625 refId
= t
.getObjId();
626 ref
= mapObject(refId
, null);
628 if (!(ref
instanceof Commit
))
629 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
631 else if (item
.equals("blob")) {
632 ref
= mapObject(refId
, null);
633 while (ref
instanceof Tag
) {
635 refId
= t
.getObjId();
636 ref
= mapObject(refId
, null);
638 if (!(ref
instanceof byte[]))
639 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_BLOB
);
641 else if (item
.equals("")) {
642 ref
= mapObject(refId
, null);
643 while (ref
instanceof Tag
) {
645 refId
= t
.getObjId();
646 ref
= mapObject(refId
, null);
650 throw new RevisionSyntaxException(revstr
);
652 throw new RevisionSyntaxException(revstr
);
655 ref
= mapObject(refId
, null);
656 if (ref
instanceof Commit
) {
657 final ObjectId parents
[] = ((Commit
) ref
)
659 if (parents
.length
== 0)
664 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
668 ref
= mapObject(refId
, null);
669 while (ref
instanceof Tag
) {
671 refId
= tag
.getObjId();
672 ref
= mapObject(refId
, null);
674 if (ref
instanceof Commit
) {
675 final ObjectId parents
[] = ((Commit
) ref
)
677 if (parents
.length
== 0)
682 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
687 String refstr
= new String(rev
,0,i
);
688 refId
= resolveSimple(refstr
);
691 ref
= mapObject(refId
, null);
693 while (ref
instanceof Tag
) {
695 refId
= tag
.getObjId();
696 ref
= mapObject(refId
, null);
698 if (!(ref
instanceof Commit
))
699 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
701 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
702 if (!Character
.isDigit(rev
[l
]))
705 String distnum
= new String(rev
, i
+1, l
-i
-1);
708 dist
= Integer
.parseInt(distnum
);
709 } catch (NumberFormatException e
) {
710 throw new RevisionSyntaxException(
711 "Invalid ancestry length", revstr
);
714 final ObjectId
[] parents
= ((Commit
) ref
).getParentIds();
715 if (parents
.length
== 0) {
720 ref
= mapCommit(refId
);
728 for (m
=i
+2; m
<rev
.length
; ++m
) {
730 time
= new String(rev
, i
+2, m
-i
-2);
735 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr
);
740 throw new RevisionSyntaxException(revstr
);
744 refId
= resolveSimple(revstr
);
748 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
749 if (ObjectId
.isId(revstr
))
750 return ObjectId
.fromString(revstr
);
751 final Ref r
= refs
.readRef(revstr
);
752 return r
!= null ? r
.getObjectId() : null;
756 * Close all resources used by this repository
758 public void close() {
763 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
766 packs
= new PackFile
[0];
770 * Add a single existing pack to the list of available pack files.
773 * path of the pack file to open.
775 * path of the corresponding index file.
776 * @throws IOException
777 * index file could not be opened, read, or is not recognized as
778 * a Git pack file index.
780 public void openPack(final File pack
, final File idx
) throws IOException
{
781 final String p
= pack
.getName();
782 final String i
= idx
.getName();
783 if (p
.length() != 50 || !p
.startsWith("pack-") || !p
.endsWith(".pack"))
784 throw new IllegalArgumentException("Not a valid pack " + pack
);
785 if (i
.length() != 49 || !i
.startsWith("pack-") || !i
.endsWith(".idx"))
786 throw new IllegalArgumentException("Not a valid pack " + idx
);
787 if (!p
.substring(0,45).equals(i
.substring(0,45)))
788 throw new IllegalArgumentException("Pack " + pack
789 + "does not match index " + idx
);
791 final PackFile
[] cur
= packs
;
792 final PackFile
[] arr
= new PackFile
[cur
.length
+ 1];
793 System
.arraycopy(cur
, 0, arr
, 1, cur
.length
);
794 arr
[0] = new PackFile(this, idx
, pack
);
799 * Scan the object dirs, including alternates for packs
802 public void scanForPacks() {
803 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
804 for (int i
=0; i
<objectsDirs
.length
; ++i
)
805 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
806 final PackFile
[] arr
= new PackFile
[p
.size()];
811 private void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
812 final String
[] idxList
= packDir
.list(new FilenameFilter() {
813 public boolean accept(final File baseDir
, final String n
) {
814 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
815 return n
.length() == 49 && n
.endsWith(".idx")
816 && n
.startsWith("pack-");
819 if (idxList
!= null) {
820 for (final String indexName
: idxList
) {
821 final String n
= indexName
.substring(0, indexName
.length() - 4);
822 final File idxFile
= new File(packDir
, n
+ ".idx");
823 final File packFile
= new File(packDir
, n
+ ".pack");
825 if (!packFile
.isFile()) {
826 // Sometimes C Git's http fetch transport leaves a
827 // .idx file behind and does not download the .pack.
828 // We have to skip over such useless indexes.
834 packList
.add(new PackFile(this, idxFile
, packFile
));
835 } catch (IOException ioe
) {
836 // Whoops. That's not a pack!
838 ioe
.printStackTrace();
845 * Writes a symref (e.g. HEAD) to disk
847 * @param name symref name
848 * @param target pointed to ref
849 * @throws IOException
851 public void writeSymref(final String name
, final String target
)
853 refs
.link(name
, target
);
856 public String
toString() {
857 return "Repository[" + getDirectory() + "]";
861 * @return name of topmost Stacked Git patch.
862 * @throws IOException
864 public String
getPatch() throws IOException
{
865 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
866 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
870 while ((line
=br
.readLine())!=null) {
880 * @return name of current branch
881 * @throws IOException
883 public String
getFullBranch() throws IOException
{
884 final File ptr
= new File(getDirectory(),"HEAD");
885 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
892 if (ref
.startsWith("ref: "))
893 ref
= ref
.substring(5);
898 * @return name of current branch.
899 * @throws IOException
901 public String
getBranch() throws IOException
{
903 final File ptr
= new File(getDirectory(), Constants
.HEAD
);
904 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
911 if (ref
.startsWith("ref: "))
912 ref
= ref
.substring(5);
913 if (ref
.startsWith("refs/heads/"))
914 ref
= ref
.substring(11);
916 } catch (FileNotFoundException e
) {
917 final File ptr
= new File(getDirectory(),"head-name");
918 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
930 * @return all known refs (heads, tags, remotes).
932 public Map
<String
, Ref
> getAllRefs() {
933 return refs
.getAllRefs();
937 * @return all tags; key is short tag name ("v1.0") and value of the entry
938 * contains the ref with the full tag name ("refs/tags/v1.0").
940 public Map
<String
, Ref
> getTags() {
941 return refs
.getTags();
945 * Peel a possibly unpeeled ref and updates it.
947 * If the ref cannot be peeled (as it does not refer to an annotated tag)
948 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
952 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
953 * new Ref object representing the same data as Ref, but isPeeled()
954 * will be true and getPeeledObjectId will contain the peeled object
957 public Ref
peel(final Ref ref
) {
958 return refs
.peel(ref
);
962 * @return a map with all objects referenced by a peeled ref.
964 public Map
<AnyObjectId
, Set
<Ref
>> getAllRefsByPeeledObjectId() {
965 Map
<String
, Ref
> allRefs
= getAllRefs();
966 Map
<AnyObjectId
, Set
<Ref
>> ret
= new HashMap
<AnyObjectId
, Set
<Ref
>>(allRefs
.size());
967 for (Ref ref
: allRefs
.values()) {
970 AnyObjectId target
= ref
.getPeeledObjectId();
972 target
= ref
.getObjectId();
973 // We assume most Sets here are singletons
974 Set
<Ref
> oset
= ret
.put(target
, Collections
.singleton(ref
));
976 // that was not the case (rare)
977 if (oset
.size() == 1) {
978 // Was a read-only singleton, we must copy to a new Set
979 oset
= new HashSet
<Ref
>(oset
);
981 ret
.put(target
, oset
);
989 * @return true if HEAD points to a StGit patch.
991 public boolean isStGitMode() {
992 File file
= new File(getDirectory(), "HEAD");
993 BufferedReader reader
= null;
995 reader
= new BufferedReader(new FileReader(file
));
996 String string
= reader
.readLine();
997 if (!string
.startsWith("ref: refs/heads/"))
999 String branch
= string
.substring("ref: refs/heads/".length());
1000 File currentPatches
= new File(new File(new File(getDirectory(),
1001 "patches"), branch
), "applied");
1002 if (!currentPatches
.exists())
1004 if (currentPatches
.length() == 0)
1008 } catch (IOException e
) {
1009 e
.printStackTrace();
1015 } catch (IOException e1
) {
1016 // nothing to do here
1022 * @return applied patches in a map indexed on current commit id
1023 * @throws IOException
1025 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
1026 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
1027 if (isStGitMode()) {
1028 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
1029 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
1030 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
1031 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
1032 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
1033 String objectId
= tfr
.readLine();
1034 ObjectId id
= ObjectId
.fromString(objectId
);
1035 ret
.put(id
, new StGitPatch(patchName
, id
));
1043 /** Clean up stale caches */
1044 public void refreshFromDisk() {
1049 * @return a representation of the index associated with this repo
1050 * @throws IOException
1052 public GitIndex
getIndex() throws IOException
{
1053 if (index
== null) {
1054 index
= new GitIndex(this);
1057 index
.rereadIfNecessary();
1062 static byte[] gitInternalSlash(byte[] bytes
) {
1063 if (File
.separatorChar
== '/')
1065 for (int i
=0; i
<bytes
.length
; ++i
)
1066 if (bytes
[i
] == File
.separatorChar
)
1072 * @return an important state
1074 public RepositoryState
getRepositoryState() {
1075 // Pre Git-1.6 logic
1076 if (new File(getWorkDir(), ".dotest").exists())
1077 return RepositoryState
.REBASING
;
1078 if (new File(gitDir
,".dotest-merge").exists())
1079 return RepositoryState
.REBASING_INTERACTIVE
;
1082 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1083 return RepositoryState
.REBASING_REBASING
;
1084 if (new File(getDirectory(),"rebase-apply/applying").exists())
1085 return RepositoryState
.APPLY
;
1086 if (new File(getDirectory(),"rebase-apply").exists())
1087 return RepositoryState
.REBASING
;
1089 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1090 return RepositoryState
.REBASING_INTERACTIVE
;
1091 if (new File(getDirectory(),"rebase-merge").exists())
1092 return RepositoryState
.REBASING_MERGE
;
1095 if (new File(gitDir
,"MERGE_HEAD").exists())
1096 return RepositoryState
.MERGING
;
1097 if (new File(gitDir
,"BISECT_LOG").exists())
1098 return RepositoryState
.BISECTING
;
1100 return RepositoryState
.SAFE
;
1104 * Check validity of a ref name. It must not contain character that has
1105 * a special meaning in a Git object reference expression. Some other
1106 * dangerous characters are also excluded.
1110 * @return true if refName is a valid ref name
1112 public static boolean isValidRefName(final String refName
) {
1113 final int len
= refName
.length();
1118 for (int i
=0; i
<len
; ++i
) {
1119 char c
= refName
.charAt(i
);
1137 case '~': case '^': case ':':
1149 * Strip work dir and return normalized repository path
1151 * @param wd Work dir
1152 * @param f File whose path shall be stripped of its workdir
1153 * @return normalized repository relative path
1155 public static String
stripWorkDir(File wd
, File f
) {
1156 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
1157 relName
= relName
.replace(File
.separatorChar
, '/');
1162 * @return the workdir file, i.e. where the files are checked out
1164 public File
getWorkDir() {
1165 return getDirectory().getParentFile();
1169 * Register a {@link RepositoryListener} which will be notified
1170 * when ref changes are detected.
1174 public void addRepositoryChangedListener(final RepositoryListener l
) {
1179 * Remove a registered {@link RepositoryListener}
1182 public void removeRepositoryChangedListener(final RepositoryListener l
) {
1183 listeners
.remove(l
);
1187 * Register a global {@link RepositoryListener} which will be notified
1188 * when a ref changes in any repository are detected.
1192 public static void addAnyRepositoryChangedListener(final RepositoryListener l
) {
1193 allListeners
.add(l
);
1197 * Remove a globally registered {@link RepositoryListener}
1200 public static void removeAnyRepositoryChangedListener(final RepositoryListener l
) {
1201 allListeners
.remove(l
);
1204 void fireRefsMaybeChanged() {
1205 if (refs
.lastRefModification
!= refs
.lastNotifiedRefModification
) {
1206 refs
.lastNotifiedRefModification
= refs
.lastRefModification
;
1207 final RefsChangedEvent event
= new RefsChangedEvent(this);
1208 List
<RepositoryListener
> all
;
1209 synchronized (listeners
) {
1210 all
= new ArrayList
<RepositoryListener
>(listeners
);
1212 synchronized (allListeners
) {
1213 all
.addAll(allListeners
);
1215 for (final RepositoryListener l
: all
) {
1216 l
.refsChanged(event
);
1221 void fireIndexChanged() {
1222 final IndexChangedEvent event
= new IndexChangedEvent(this);
1223 List
<RepositoryListener
> all
;
1224 synchronized (listeners
) {
1225 all
= new ArrayList
<RepositoryListener
>(listeners
);
1227 synchronized (allListeners
) {
1228 all
.addAll(allListeners
);
1230 for (final RepositoryListener l
: all
) {
1231 l
.indexChanged(event
);
1236 * Force a scan for changed refs.
1238 * @throws IOException
1240 public void scanForRepoChanges() throws IOException
{
1241 getAllRefs(); // This will look for changes to refs
1242 getIndex(); // This will detect changes in the index