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
.IOException
;
47 import java
.util
.ArrayList
;
48 import java
.util
.Collection
;
49 import java
.util
.Collections
;
50 import java
.util
.HashMap
;
51 import java
.util
.HashSet
;
52 import java
.util
.LinkedList
;
53 import java
.util
.List
;
56 import java
.util
.Vector
;
58 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
59 import org
.spearce
.jgit
.errors
.RevisionSyntaxException
;
60 import org
.spearce
.jgit
.util
.FS
;
63 * Represents a Git repository. A repository holds all objects and refs used for
64 * managing source code (could by any type of file, but source code is what
65 * SCM's are typically used for).
67 * In Git terms all data is stored in GIT_DIR, typically a directory called
68 * .git. A work tree is maintained unless the repository is a bare repository.
69 * Typically the .git directory is located at the root of the work dir.
74 * <li>objects/ - objects</li>
75 * <li>refs/ - tags and heads</li>
76 * <li>config - configuration</li>
77 * <li>info/ - more configurations</li>
82 * This class is thread-safe.
84 * This implementation only handles a subtly undocumented subset of git features.
87 public class Repository
{
88 private final File gitDir
;
90 private final RepositoryConfig config
;
92 private final RefDatabase refs
;
94 private final ObjectDirectory objectDatabase
;
96 private GitIndex index
;
98 private List
<RepositoryListener
> listeners
= new Vector
<RepositoryListener
>(); // thread safe
99 static private List
<RepositoryListener
> allListeners
= new Vector
<RepositoryListener
>(); // thread safe
102 * Construct a representation of a Git repository.
105 * GIT_DIR (the location of the repository metadata).
106 * @throws IOException
107 * the repository appears to already exist but cannot be
110 public Repository(final File d
) throws IOException
{
111 gitDir
= d
.getAbsoluteFile();
112 refs
= new RefDatabase(this);
113 objectDatabase
= new ObjectDirectory(FS
.resolve(gitDir
, "objects"));
114 config
= new RepositoryConfig(this);
116 if (objectDatabase
.exists()) {
118 final String repositoryFormatVersion
= getConfig().getString(
119 "core", null, "repositoryFormatVersion");
120 if (!"0".equals(repositoryFormatVersion
)) {
121 throw new IOException("Unknown repository format \""
122 + repositoryFormatVersion
+ "\"; expected \"0\".");
125 getConfig().create();
130 * Create a new Git repository initializing the necessary files and
131 * directories. Repository with working tree is created using this method.
133 * @throws IOException
134 * @see #create(boolean)
136 public synchronized void create() throws IOException
{
141 * Create a new Git repository initializing the necessary files and
145 * if true, a bare repository is created.
147 * @throws IOException
148 * in case of IO problem
150 public void create(boolean bare
) throws IOException
{
151 if ((bare ?
new File(gitDir
, "config") : gitDir
).exists()) {
152 throw new IllegalStateException("Repository already exists: "
157 objectDatabase
.create();
159 new File(gitDir
, "branches").mkdir();
160 new File(gitDir
, "remotes").mkdir();
161 final String master
= Constants
.R_HEADS
+ Constants
.MASTER
;
162 refs
.link(Constants
.HEAD
, master
);
164 RepositoryConfig cfg
= getConfig();
167 cfg
.setString("core", null, "bare", "true");
175 public File
getDirectory() {
180 * @return the directory containing the objects owned by this repository.
182 public File
getObjectsDirectory() {
183 return objectDatabase
.getDirectory();
187 * @return the configuration of this repository
189 public RepositoryConfig
getConfig() {
194 * Construct a filename where the loose object having a specified SHA-1
195 * should be stored. If the object is stored in a shared repository the path
196 * to the alternative repo will be returned. If the object is not yet store
197 * a usable path in this repo will be returned. It is assumed that callers
198 * will look for objects in a pack first.
201 * @return suggested file name
203 public File
toFile(final AnyObjectId objectId
) {
204 return objectDatabase
.fileFor(objectId
);
209 * @return true if the specified object is stored in this repo or any of the
210 * known shared repositories.
212 public boolean hasObject(final AnyObjectId objectId
) {
213 return objectDatabase
.hasObject(objectId
);
218 * SHA-1 of an object.
220 * @return a {@link ObjectLoader} for accessing the data of the named
221 * object, or null if the object does not exist.
222 * @throws IOException
224 public ObjectLoader
openObject(final AnyObjectId id
)
226 final WindowCursor wc
= new WindowCursor();
228 return openObject(wc
, id
);
236 * temporary working space associated with the calling thread.
238 * SHA-1 of an object.
240 * @return a {@link ObjectLoader} for accessing the data of the named
241 * object, or null if the object does not exist.
242 * @throws IOException
244 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
246 return objectDatabase
.openObject(curs
, id
);
250 * Open object in all packs containing specified object.
253 * id of object to search for
255 * temporary working space associated with the calling thread.
256 * @return collection of loaders for this object, from all packs containing
258 * @throws IOException
260 public Collection
<PackedObjectLoader
> openObjectInAllPacks(
261 final AnyObjectId objectId
, final WindowCursor curs
)
263 Collection
<PackedObjectLoader
> result
= new LinkedList
<PackedObjectLoader
>();
264 openObjectInAllPacks(objectId
, result
, curs
);
269 * Open object in all packs containing specified object.
272 * id of object to search for
273 * @param resultLoaders
274 * result collection of loaders for this object, filled with
275 * loaders from all packs containing specified object
277 * temporary working space associated with the calling thread.
278 * @throws IOException
280 void openObjectInAllPacks(final AnyObjectId objectId
,
281 final Collection
<PackedObjectLoader
> resultLoaders
,
282 final WindowCursor curs
) throws IOException
{
283 objectDatabase
.openObjectInAllPacks(resultLoaders
, curs
, objectId
);
289 * @return an {@link ObjectLoader} for accessing the data of a named blob
290 * @throws IOException
292 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
293 return openObject(id
);
299 * @return an {@link ObjectLoader} for accessing the data of a named tree
300 * @throws IOException
302 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
303 return openObject(id
);
307 * Access a Commit object using a symbolic reference. This reference may
308 * be a SHA-1 or ref in combination with a number of symbols translating
309 * from one ref or SHA1-1 to another, such as HEAD^ etc.
311 * @param revstr a reference to a git commit object
312 * @return a Commit named by the specified string
313 * @throws IOException for I/O error or unexpected object type.
315 * @see #resolve(String)
317 public Commit
mapCommit(final String revstr
) throws IOException
{
318 final ObjectId id
= resolve(revstr
);
319 return id
!= null ?
mapCommit(id
) : null;
323 * Access any type of Git object by id and
326 * SHA-1 of object to read
327 * @param refName optional, only relevant for simple tags
328 * @return The Git object if found or null
329 * @throws IOException
331 public Object
mapObject(final ObjectId id
, final String refName
) throws IOException
{
332 final ObjectLoader or
= openObject(id
);
335 final byte[] raw
= or
.getBytes();
336 switch (or
.getType()) {
337 case Constants
.OBJ_TREE
:
338 return makeTree(id
, raw
);
340 case Constants
.OBJ_COMMIT
:
341 return makeCommit(id
, raw
);
343 case Constants
.OBJ_TAG
:
344 return makeTag(id
, refName
, raw
);
346 case Constants
.OBJ_BLOB
:
350 throw new IncorrectObjectTypeException(id
,
351 "COMMIT nor TREE nor BLOB nor TAG");
356 * Access a Commit by SHA'1 id.
358 * @return Commit or null
359 * @throws IOException for I/O error or unexpected object type.
361 public Commit
mapCommit(final ObjectId id
) throws IOException
{
362 final ObjectLoader or
= openObject(id
);
365 final byte[] raw
= or
.getBytes();
366 if (Constants
.OBJ_COMMIT
== or
.getType())
367 return new Commit(this, id
, raw
);
368 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
371 private Commit
makeCommit(final ObjectId id
, final byte[] raw
) {
372 Commit ret
= new Commit(this, id
, raw
);
377 * Access a Tree object using a symbolic reference. This reference may
378 * be a SHA-1 or ref in combination with a number of symbols translating
379 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
381 * @param revstr a reference to a git commit object
382 * @return a Tree named by the specified string
383 * @throws IOException
385 * @see #resolve(String)
387 public Tree
mapTree(final String revstr
) throws IOException
{
388 final ObjectId id
= resolve(revstr
);
389 return id
!= null ?
mapTree(id
) : null;
393 * Access a Tree by SHA'1 id.
395 * @return Tree or null
396 * @throws IOException for I/O error or unexpected object type.
398 public Tree
mapTree(final ObjectId id
) throws IOException
{
399 final ObjectLoader or
= openObject(id
);
402 final byte[] raw
= or
.getBytes();
403 switch (or
.getType()) {
404 case Constants
.OBJ_TREE
:
405 return new Tree(this, id
, raw
);
407 case Constants
.OBJ_COMMIT
:
408 return mapTree(ObjectId
.fromString(raw
, 5));
411 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
415 private Tree
makeTree(final ObjectId id
, final byte[] raw
) throws IOException
{
416 Tree ret
= new Tree(this, id
, raw
);
420 private Tag
makeTag(final ObjectId id
, final String refName
, final byte[] raw
) {
421 Tag ret
= new Tag(this, id
, refName
, raw
);
426 * Access a tag by symbolic name.
429 * @return a Tag or null
430 * @throws IOException on I/O error or unexpected type
432 public Tag
mapTag(String revstr
) throws IOException
{
433 final ObjectId id
= resolve(revstr
);
434 return id
!= null ?
mapTag(revstr
, id
) : null;
438 * Access a Tag by SHA'1 id
441 * @return Commit or null
442 * @throws IOException for I/O error or unexpected object type.
444 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
445 final ObjectLoader or
= openObject(id
);
448 final byte[] raw
= or
.getBytes();
449 if (Constants
.OBJ_TAG
== or
.getType())
450 return new Tag(this, id
, refName
, raw
);
451 return new Tag(this, id
, refName
, null);
455 * Create a command to update, create or delete a ref in this repository.
458 * name of the ref the caller wants to modify.
459 * @return an update command. The caller must finish populating this command
460 * and then invoke one of the update methods to actually make a
462 * @throws IOException
463 * a symbolic ref was passed in and could not be resolved back
464 * to the base ref, as the symbolic ref could not be read.
466 public RefUpdate
updateRef(final String ref
) throws IOException
{
467 return refs
.newUpdate(ref
);
471 * Parse a git revision string and return an object id.
473 * Currently supported is combinations of these.
475 * <li>SHA-1 - a SHA-1</li>
476 * <li>refs/... - a ref name</li>
477 * <li>ref^n - nth parent reference</li>
478 * <li>ref~n - distance via parent reference</li>
479 * <li>ref@{n} - nth version of ref</li>
480 * <li>ref^{tree} - tree references by ref</li>
481 * <li>ref^{commit} - commit references by ref</li>
486 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
487 * <li>abbreviated SHA-1's</li>
490 * @param revstr A git object references expression
491 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
492 * @throws IOException on serious errors
494 public ObjectId
resolve(final String revstr
) throws IOException
{
495 char[] rev
= revstr
.toCharArray();
497 ObjectId refId
= null;
498 for (int i
= 0; i
< rev
.length
; ++i
) {
502 String refstr
= new String(rev
,0,i
);
503 refId
= resolveSimple(refstr
);
507 if (i
+ 1 < rev
.length
) {
508 switch (rev
[i
+ 1]) {
520 ref
= mapObject(refId
, null);
521 while (ref
instanceof Tag
) {
523 refId
= tag
.getObjId();
524 ref
= mapObject(refId
, null);
526 if (!(ref
instanceof Commit
))
527 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
528 for (j
=i
+1; j
<rev
.length
; ++j
) {
529 if (!Character
.isDigit(rev
[j
]))
532 String parentnum
= new String(rev
, i
+1, j
-i
-1);
535 pnum
= Integer
.parseInt(parentnum
);
536 } catch (NumberFormatException e
) {
537 throw new RevisionSyntaxException(
538 "Invalid commit parent number",
542 final ObjectId parents
[] = ((Commit
) ref
)
544 if (pnum
> parents
.length
)
547 refId
= parents
[pnum
- 1];
554 for (k
=i
+2; k
<rev
.length
; ++k
) {
556 item
= new String(rev
, i
+2, k
-i
-2);
562 if (item
.equals("tree")) {
563 ref
= mapObject(refId
, null);
564 while (ref
instanceof Tag
) {
566 refId
= t
.getObjId();
567 ref
= mapObject(refId
, null);
569 if (ref
instanceof Treeish
)
570 refId
= ((Treeish
)ref
).getTreeId();
572 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_TREE
);
574 else if (item
.equals("commit")) {
575 ref
= mapObject(refId
, null);
576 while (ref
instanceof Tag
) {
578 refId
= t
.getObjId();
579 ref
= mapObject(refId
, null);
581 if (!(ref
instanceof Commit
))
582 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
584 else if (item
.equals("blob")) {
585 ref
= mapObject(refId
, null);
586 while (ref
instanceof Tag
) {
588 refId
= t
.getObjId();
589 ref
= mapObject(refId
, null);
591 if (!(ref
instanceof byte[]))
592 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_BLOB
);
594 else if (item
.equals("")) {
595 ref
= mapObject(refId
, null);
596 while (ref
instanceof Tag
) {
598 refId
= t
.getObjId();
599 ref
= mapObject(refId
, null);
603 throw new RevisionSyntaxException(revstr
);
605 throw new RevisionSyntaxException(revstr
);
608 ref
= mapObject(refId
, null);
609 if (ref
instanceof Commit
) {
610 final ObjectId parents
[] = ((Commit
) ref
)
612 if (parents
.length
== 0)
617 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
621 ref
= mapObject(refId
, null);
622 while (ref
instanceof Tag
) {
624 refId
= tag
.getObjId();
625 ref
= mapObject(refId
, null);
627 if (ref
instanceof Commit
) {
628 final ObjectId parents
[] = ((Commit
) ref
)
630 if (parents
.length
== 0)
635 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
640 String refstr
= new String(rev
,0,i
);
641 refId
= resolveSimple(refstr
);
644 ref
= mapObject(refId
, null);
646 while (ref
instanceof Tag
) {
648 refId
= tag
.getObjId();
649 ref
= mapObject(refId
, null);
651 if (!(ref
instanceof Commit
))
652 throw new IncorrectObjectTypeException(refId
, Constants
.TYPE_COMMIT
);
654 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
655 if (!Character
.isDigit(rev
[l
]))
658 String distnum
= new String(rev
, i
+1, l
-i
-1);
661 dist
= Integer
.parseInt(distnum
);
662 } catch (NumberFormatException e
) {
663 throw new RevisionSyntaxException(
664 "Invalid ancestry length", revstr
);
667 final ObjectId
[] parents
= ((Commit
) ref
).getParentIds();
668 if (parents
.length
== 0) {
673 ref
= mapCommit(refId
);
681 for (m
=i
+2; m
<rev
.length
; ++m
) {
683 time
= new String(rev
, i
+2, m
-i
-2);
688 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr
);
693 throw new RevisionSyntaxException(revstr
);
697 refId
= resolveSimple(revstr
);
701 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
702 if (ObjectId
.isId(revstr
))
703 return ObjectId
.fromString(revstr
);
704 final Ref r
= refs
.readRef(revstr
);
705 return r
!= null ? r
.getObjectId() : null;
709 * Close all resources used by this repository
711 public void close() {
712 objectDatabase
.close();
716 * Add a single existing pack to the list of available pack files.
719 * path of the pack file to open.
721 * path of the corresponding index file.
722 * @throws IOException
723 * index file could not be opened, read, or is not recognized as
724 * a Git pack file index.
726 public void openPack(final File pack
, final File idx
) throws IOException
{
727 objectDatabase
.openPack(pack
, idx
);
731 * Writes a symref (e.g. HEAD) to disk
733 * @param name symref name
734 * @param target pointed to ref
735 * @throws IOException
737 public void writeSymref(final String name
, final String target
)
739 refs
.link(name
, target
);
742 public String
toString() {
743 return "Repository[" + getDirectory() + "]";
747 * @return name of current branch
748 * @throws IOException
750 public String
getFullBranch() throws IOException
{
751 final File ptr
= new File(getDirectory(),Constants
.HEAD
);
752 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
759 if (ref
.startsWith("ref: "))
760 ref
= ref
.substring(5);
765 * @return name of current branch.
766 * @throws IOException
768 public String
getBranch() throws IOException
{
770 final File ptr
= new File(getDirectory(), Constants
.HEAD
);
771 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
778 if (ref
.startsWith("ref: "))
779 ref
= ref
.substring(5);
780 if (ref
.startsWith("refs/heads/"))
781 ref
= ref
.substring(11);
783 } catch (FileNotFoundException e
) {
784 final File ptr
= new File(getDirectory(),"head-name");
785 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
800 * the name of the ref to lookup. May be a short-hand form, e.g.
801 * "master" which is is automatically expanded to
802 * "refs/heads/master" if "refs/heads/master" already exists.
803 * @return the Ref with the given name, or null if it does not exist
804 * @throws IOException
806 public Ref
getRef(final String name
) throws IOException
{
807 return refs
.readRef(name
);
811 * @return all known refs (heads, tags, remotes).
813 public Map
<String
, Ref
> getAllRefs() {
814 return refs
.getAllRefs();
818 * @return all tags; key is short tag name ("v1.0") and value of the entry
819 * contains the ref with the full tag name ("refs/tags/v1.0").
821 public Map
<String
, Ref
> getTags() {
822 return refs
.getTags();
826 * Peel a possibly unpeeled ref and updates it.
828 * If the ref cannot be peeled (as it does not refer to an annotated tag)
829 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
833 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
834 * new Ref object representing the same data as Ref, but isPeeled()
835 * will be true and getPeeledObjectId will contain the peeled object
838 public Ref
peel(final Ref ref
) {
839 return refs
.peel(ref
);
843 * @return a map with all objects referenced by a peeled ref.
845 public Map
<AnyObjectId
, Set
<Ref
>> getAllRefsByPeeledObjectId() {
846 Map
<String
, Ref
> allRefs
= getAllRefs();
847 Map
<AnyObjectId
, Set
<Ref
>> ret
= new HashMap
<AnyObjectId
, Set
<Ref
>>(allRefs
.size());
848 for (Ref ref
: allRefs
.values()) {
851 AnyObjectId target
= ref
.getPeeledObjectId();
853 target
= ref
.getObjectId();
854 // We assume most Sets here are singletons
855 Set
<Ref
> oset
= ret
.put(target
, Collections
.singleton(ref
));
857 // that was not the case (rare)
858 if (oset
.size() == 1) {
859 // Was a read-only singleton, we must copy to a new Set
860 oset
= new HashSet
<Ref
>(oset
);
862 ret
.put(target
, oset
);
869 /** Clean up stale caches */
870 public void refreshFromDisk() {
875 * @return a representation of the index associated with this repo
876 * @throws IOException
878 public GitIndex
getIndex() throws IOException
{
880 index
= new GitIndex(this);
883 index
.rereadIfNecessary();
888 static byte[] gitInternalSlash(byte[] bytes
) {
889 if (File
.separatorChar
== '/')
891 for (int i
=0; i
<bytes
.length
; ++i
)
892 if (bytes
[i
] == File
.separatorChar
)
898 * @return an important state
900 public RepositoryState
getRepositoryState() {
902 if (new File(getWorkDir(), ".dotest").exists())
903 return RepositoryState
.REBASING
;
904 if (new File(gitDir
,".dotest-merge").exists())
905 return RepositoryState
.REBASING_INTERACTIVE
;
908 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
909 return RepositoryState
.REBASING_REBASING
;
910 if (new File(getDirectory(),"rebase-apply/applying").exists())
911 return RepositoryState
.APPLY
;
912 if (new File(getDirectory(),"rebase-apply").exists())
913 return RepositoryState
.REBASING
;
915 if (new File(getDirectory(),"rebase-merge/interactive").exists())
916 return RepositoryState
.REBASING_INTERACTIVE
;
917 if (new File(getDirectory(),"rebase-merge").exists())
918 return RepositoryState
.REBASING_MERGE
;
921 if (new File(gitDir
,"MERGE_HEAD").exists())
922 return RepositoryState
.MERGING
;
923 if (new File(gitDir
,"BISECT_LOG").exists())
924 return RepositoryState
.BISECTING
;
926 return RepositoryState
.SAFE
;
930 * Check validity of a ref name. It must not contain character that has
931 * a special meaning in a Git object reference expression. Some other
932 * dangerous characters are also excluded.
934 * For portability reasons '\' is excluded
938 * @return true if refName is a valid ref name
940 public static boolean isValidRefName(final String refName
) {
941 final int len
= refName
.length();
944 if (refName
.endsWith(".lock"))
949 for (int i
= 0; i
< len
; i
++) {
950 final char c
= refName
.charAt(i
);
956 case '\0': case '/': case '.':
963 if (i
== 0 || i
== len
- 1)
971 case '~': case '^': case ':':
972 case '?': case '[': case '*':
978 return components
> 1;
982 * Strip work dir and return normalized repository path
985 * @param f File whose path shall be stripped of its workdir
986 * @return normalized repository relative path
988 public static String
stripWorkDir(File wd
, File f
) {
989 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
990 relName
= relName
.replace(File
.separatorChar
, '/');
995 * @return the workdir file, i.e. where the files are checked out
997 public File
getWorkDir() {
998 return getDirectory().getParentFile();
1002 * Register a {@link RepositoryListener} which will be notified
1003 * when ref changes are detected.
1007 public void addRepositoryChangedListener(final RepositoryListener l
) {
1012 * Remove a registered {@link RepositoryListener}
1015 public void removeRepositoryChangedListener(final RepositoryListener l
) {
1016 listeners
.remove(l
);
1020 * Register a global {@link RepositoryListener} which will be notified
1021 * when a ref changes in any repository are detected.
1025 public static void addAnyRepositoryChangedListener(final RepositoryListener l
) {
1026 allListeners
.add(l
);
1030 * Remove a globally registered {@link RepositoryListener}
1033 public static void removeAnyRepositoryChangedListener(final RepositoryListener l
) {
1034 allListeners
.remove(l
);
1037 void fireRefsMaybeChanged() {
1038 if (refs
.lastRefModification
!= refs
.lastNotifiedRefModification
) {
1039 refs
.lastNotifiedRefModification
= refs
.lastRefModification
;
1040 final RefsChangedEvent event
= new RefsChangedEvent(this);
1041 List
<RepositoryListener
> all
;
1042 synchronized (listeners
) {
1043 all
= new ArrayList
<RepositoryListener
>(listeners
);
1045 synchronized (allListeners
) {
1046 all
.addAll(allListeners
);
1048 for (final RepositoryListener l
: all
) {
1049 l
.refsChanged(event
);
1054 void fireIndexChanged() {
1055 final IndexChangedEvent event
= new IndexChangedEvent(this);
1056 List
<RepositoryListener
> all
;
1057 synchronized (listeners
) {
1058 all
= new ArrayList
<RepositoryListener
>(listeners
);
1060 synchronized (allListeners
) {
1061 all
.addAll(allListeners
);
1063 for (final RepositoryListener l
: all
) {
1064 l
.indexChanged(event
);
1069 * Force a scan for changed refs.
1071 * @throws IOException
1073 public void scanForRepoChanges() throws IOException
{
1074 getAllRefs(); // This will look for changes to refs
1075 getIndex(); // This will detect changes in the index
1081 * @return a more user friendly ref name
1083 public String
shortenRefName(String refName
) {
1084 if (refName
.startsWith(Constants
.R_HEADS
))
1085 return refName
.substring(Constants
.R_HEADS
.length());
1086 if (refName
.startsWith(Constants
.R_TAGS
))
1087 return refName
.substring(Constants
.R_TAGS
.length());
1088 if (refName
.startsWith(Constants
.R_REMOTES
))
1089 return refName
.substring(Constants
.R_REMOTES
.length());