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
.lang
.ref
.Reference
;
26 import java
.lang
.ref
.SoftReference
;
27 import java
.util
.ArrayList
;
28 import java
.util
.Collection
;
29 import java
.util
.HashMap
;
32 import java
.util
.WeakHashMap
;
34 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
35 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
36 import org
.spearce
.jgit
.stgit
.StGitPatch
;
39 * Represents a Git repository. A repository holds all objects and refs used for
40 * managing source code (could by any type of file, but source code is what
41 * SCM's are typically used for).
43 * In Git terms all data is stored in GIT_DIR, typically a directory called
44 * .git. A work tree is maintained unless the repository is a bare repository.
45 * Typically the .git directory is located at the root of the work dir.
50 * <li>objects/ - objects</li>
51 * <li>refs/ - tags and heads</li>
52 * <li>config - configuration</li>
53 * <li>info/ - more configurations</li>
58 * This implementation only handles a subtly undocumented subset of git features.
61 public class Repository
{
62 private static final String
[] refSearchPaths
= { "", "refs/", "refs/tags/",
63 "refs/heads/", "refs/remotes/" };
65 private final File gitDir
;
67 private final File
[] objectsDirs
;
69 private final File refsDir
;
71 private final RepositoryConfig config
;
73 private PackFile
[] packs
;
75 private final WindowCache windows
;
77 private Map
<ObjectId
,Reference
<Tree
>> treeCache
= new WeakHashMap
<ObjectId
,Reference
<Tree
>>(30000);
78 private Map
<ObjectId
,Reference
<Commit
>> commitCache
= new WeakHashMap
<ObjectId
,Reference
<Commit
>>(30000);
80 private GitIndex index
;
83 * Construct a representation of this git repo managing a Git repository.
89 public Repository(final File d
) throws IOException
{
94 * Construct a representation of a Git repository.
97 * cache this repository's data will be cached through during
98 * access. May be shared with another repository, or null to
99 * indicate this repository should allocate its own private
102 * GIT_DIR (the location of the repository metadata).
103 * @throws IOException
104 * the repository appears to already exist but cannot be
107 public Repository(final WindowCache wc
, final File d
) throws IOException
{
108 gitDir
= d
.getAbsoluteFile();
110 objectsDirs
= readObjectsDirs(new File(gitDir
, "objects"), 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
= new File(gitDir
, "refs");
117 packs
= new PackFile
[0];
118 config
= new RepositoryConfig(this);
120 final boolean isExisting
= objectsDirs
[0].exists();
123 final String repositoryFormatVersion
= getConfig().getString(
124 "core", null, "repositoryFormatVersion");
125 if (!"0".equals(repositoryFormatVersion
)) {
126 throw new IOException("Unknown repository format \""
127 + repositoryFormatVersion
+ "\"; expected \"0\".");
130 getConfig().create();
132 windows
= wc
!= null ? wc
: new WindowCache(getConfig());
137 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
139 File alternatesFile
= new File(objectsDir
,"info/alternates");
140 if (alternatesFile
.exists()) {
141 BufferedReader ar
= new BufferedReader(new FileReader(alternatesFile
));
142 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
143 readObjectsDirs(new File(alt
), ret
);
151 * Create a new Git repository initializing the necessary files and
154 * @throws IOException
156 public void create() throws IOException
{
157 if (gitDir
.exists()) {
158 throw new IllegalStateException("Repository already exists: "
164 objectsDirs
[0].mkdirs();
165 new File(objectsDirs
[0], "pack").mkdir();
166 new File(objectsDirs
[0], "info").mkdir();
169 new File(refsDir
, "heads").mkdir();
170 new File(refsDir
, "tags").mkdir();
172 new File(gitDir
, "branches").mkdir();
173 new File(gitDir
, "remotes").mkdir();
174 writeSymref("HEAD", "refs/heads/master");
176 getConfig().create();
183 public File
getDirectory() {
188 * @return the directory containg the objects owned by this repository.
190 public File
getObjectsDirectory() {
191 return objectsDirs
[0];
195 * @return the configuration of this repository
197 public RepositoryConfig
getConfig() {
202 * @return the cache needed for accessing packed objects in this repository.
204 public WindowCache
getWindowCache() {
209 * Construct a filename where the loose object having a specified SHA-1
210 * should be stored. If the object is stored in a shared repository the path
211 * to the alternative repo will be returned. If the object is not yet store
212 * a usable path in this repo will be returned. It is assumed that callers
213 * will look for objects in a pack first.
216 * @return suggested file name
218 public File
toFile(final AnyObjectId objectId
) {
219 final String n
= objectId
.toString();
220 String d
=n
.substring(0, 2);
221 String f
=n
.substring(2);
222 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
223 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
227 return new File(new File(objectsDirs
[0], d
), f
);
232 * @return true if the specified object is stored in this repo or any of the
233 * known shared repositories.
235 public boolean hasObject(final AnyObjectId objectId
) {
236 int k
= packs
.length
;
239 if (packs
[--k
].hasObject(objectId
))
243 return toFile(objectId
).isFile();
248 * SHA-1 of an object.
250 * @return a {@link ObjectLoader} for accessing the data of the named
251 * object, or null if the object does not exist.
252 * @throws IOException
254 public ObjectLoader
openObject(final AnyObjectId id
)
256 return openObject(new WindowCursor(),id
);
261 * temporary working space associated with the calling thread.
263 * SHA-1 of an object.
265 * @return a {@link ObjectLoader} for accessing the data of the named
266 * object, or null if the object does not exist.
267 * @throws IOException
269 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
271 int k
= packs
.length
;
275 final ObjectLoader ol
= packs
[--k
].get(curs
, id
);
278 } catch (IOException ioe
) {
279 // This shouldn't happen unless the pack was corrupted
280 // after we opened it or the VM runs out of memory. This is
281 // a know problem with memory mapped I/O in java and have
282 // been noticed with JDK < 1.6. Tell the gc that now is a good
283 // time to collect and try once more.
287 final ObjectLoader ol
= packs
[k
].get(curs
, id
);
290 } catch (IOException ioe2
) {
291 ioe2
.printStackTrace();
292 ioe
.printStackTrace();
293 // Still fails.. that's BAD, maybe the pack has
294 // been corrupted after all, or the gc didn't manage
295 // to release enough previously mmaped areas.
301 return new UnpackedObjectLoader(this, id
.toObjectId());
302 } catch (FileNotFoundException fnfe
) {
310 * @return an {@link ObjectLoader} for accessing the data of a named blob
311 * @throws IOException
313 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
314 return openObject(id
);
320 * @return an {@link ObjectLoader} for accessing the data of a named tree
321 * @throws IOException
323 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
324 return openObject(id
);
328 * Access a Commit object using a symbolic reference. This reference may
329 * be a SHA-1 or ref in combination with a number of symbols translating
330 * from one ref or SHA1-1 to another, such as HEAD^ etc.
332 * @param revstr a reference to a git commit object
333 * @return a Commit named by the specified string
334 * @throws IOException for I/O error or unexpected object type.
336 * @see #resolve(String)
338 public Commit
mapCommit(final String revstr
) throws IOException
{
339 final ObjectId id
= resolve(revstr
);
340 return id
!= null ?
mapCommit(id
) : null;
344 * Access a Commit by SHA'1 id.
346 * @return Commit or null
347 * @throws IOException for I/O error or unexpected object type.
349 public Commit
mapCommit(final ObjectId id
) throws IOException
{
350 Reference
<Commit
> retr
= commitCache
.get(id
);
352 Commit ret
= retr
.get();
355 // System.out.println("Found a null id, size was "+commitCache.size());
358 final ObjectLoader or
= openObject(id
);
361 final byte[] raw
= or
.getBytes();
362 if (Constants
.OBJ_COMMIT
== or
.getType()) {
363 Commit ret
= new Commit(this, id
, raw
);
364 // The key must not be the referenced strongly
365 // by the value in WeakHashMaps
366 commitCache
.put(id
, new SoftReference
<Commit
>(ret
));
369 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
373 * Access a Tree object using a symbolic reference. This reference may
374 * be a SHA-1 or ref in combination with a number of symbols translating
375 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
377 * @param revstr a reference to a git commit object
378 * @return a Tree named by the specified string
379 * @throws IOException
381 * @see #resolve(String)
383 public Tree
mapTree(final String revstr
) throws IOException
{
384 final ObjectId id
= resolve(revstr
);
385 return id
!= null ?
mapTree(id
) : null;
389 * Access a Tree by SHA'1 id.
391 * @return Tree or null
392 * @throws IOException for I/O error or unexpected object type.
394 public Tree
mapTree(final ObjectId id
) throws IOException
{
395 Reference
<Tree
> wret
= treeCache
.get(id
);
397 Tree ret
= wret
.get();
402 final ObjectLoader or
= openObject(id
);
405 final byte[] raw
= or
.getBytes();
406 if (Constants
.OBJ_TREE
== or
.getType()) {
407 Tree ret
= new Tree(this, id
, raw
);
408 treeCache
.put(id
, new SoftReference
<Tree
>(ret
));
411 if (Constants
.OBJ_COMMIT
== or
.getType())
412 return mapTree(ObjectId
.fromString(raw
, 5));
413 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
417 * Access a tag by symbolic name.
420 * @return a Tag or null
421 * @throws IOException on I/O error or unexpected type
423 public Tag
mapTag(String revstr
) throws IOException
{
424 final ObjectId id
= resolve(revstr
);
425 return id
!= null ?
mapTag(revstr
, id
) : null;
429 * Access a Tag by SHA'1 id
432 * @return Commit or null
433 * @throws IOException for I/O error or unexpected object type.
435 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
436 final ObjectLoader or
= openObject(id
);
439 final byte[] raw
= or
.getBytes();
440 if (Constants
.OBJ_TAG
== or
.getType())
441 return new Tag(this, id
, refName
, raw
);
442 return new Tag(this, id
, refName
, null);
446 * Get a locked handle to a ref suitable for updating or creating.
448 * @param ref name to lock
449 * @return a locked ref
450 * @throws IOException
452 public RefLock
lockRef(final String ref
) throws IOException
{
453 final Ref r
= readRef(ref
, true);
454 final RefLock l
= new RefLock(new File(gitDir
, r
.getName()));
455 return l
.lock() ? l
: null;
459 * Parse a git revision string and return an object id.
461 * Currently supported is combinations of these.
463 * <li>SHA-1 - a SHA-1</li>
464 * <li>refs/... - a ref name</li>
465 * <li>ref^n - nth parent reference</li>
466 * <li>ref~n - distance via parent reference</li>
467 * <li>ref@{n} - nth version of ref</li>
468 * <li>ref^{tree} - tree references by ref</li>
469 * <li>ref^{commit} - commit references by ref</li>
474 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
475 * <li>abbreviated SHA-1's</li>
478 * @param revstr A git object references expression
479 * @return an ObjectId
480 * @throws IOException on serious errors
482 public ObjectId
resolve(final String revstr
) throws IOException
{
483 char[] rev
= revstr
.toCharArray();
486 for (int i
= 0; i
< rev
.length
; ++i
) {
490 String refstr
= new String(rev
,0,i
);
491 ObjectId refId
= resolveSimple(refstr
);
492 ref
= mapCommit(refId
);
494 if (i
+ 1 < rev
.length
) {
495 switch (rev
[i
+ 1]) {
507 for (j
=i
+1; j
<rev
.length
; ++j
) {
508 if (!Character
.isDigit(rev
[j
]))
511 String parentnum
= new String(rev
, i
+1, j
-i
-1);
512 int pnum
= Integer
.parseInt(parentnum
);
514 ref
= mapCommit(ref
.getParentIds()[pnum
- 1]);
520 for (k
=i
+2; k
<rev
.length
; ++k
) {
522 item
= new String(rev
, i
+2, k
-i
-2);
528 if (item
.equals("tree"))
529 ret
= ref
.getTreeId();
530 else if (item
.equals("commit"))
531 ; // just reference self
533 return null; // invalid
535 return null; // invalid
538 ref
= mapCommit(ref
.getParentIds()[0]);
541 ref
= mapCommit(ref
.getParentIds()[0]);
546 String refstr
= new String(rev
,0,i
);
547 ObjectId refId
= resolveSimple(refstr
);
548 ref
= mapCommit(refId
);
551 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
552 if (!Character
.isDigit(rev
[l
]))
555 String distnum
= new String(rev
, i
+1, l
-i
-1);
556 int dist
= Integer
.parseInt(distnum
);
558 ref
= mapCommit(ref
.getParentIds()[0]);
566 for (m
=i
+2; m
<rev
.length
; ++m
) {
568 time
= new String(rev
, i
+2, m
-i
-2);
573 throw new IllegalArgumentException("reflogs not yet supprted");
578 return null; // cannot parse, return null
583 ret
= ref
.getCommitId();
585 ret
= resolveSimple(revstr
);
589 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
590 if (ObjectId
.isId(revstr
))
591 return ObjectId
.fromString(revstr
);
592 final Ref r
= readRef(revstr
, false);
594 return r
.getObjectId();
600 * Close all resources used by this repository
602 public void close() {
607 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
610 packs
= new PackFile
[0];
614 * Scan the object dirs, including alternates for packs
617 public void scanForPacks() {
618 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
619 for (int i
=0; i
<objectsDirs
.length
; ++i
)
620 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
621 final PackFile
[] arr
= new PackFile
[p
.size()];
626 private void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
627 final String
[] idxList
= packDir
.list(new FilenameFilter() {
628 public boolean accept(final File baseDir
, final String n
) {
629 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
630 return n
.length() == 49 && n
.endsWith(".idx")
631 && n
.startsWith("pack-");
634 if (idxList
!= null) {
635 for (final String indexName
: idxList
) {
636 final String n
= indexName
.substring(0, indexName
.length() - 4);
637 final File idxFile
= new File(packDir
, n
+ ".idx");
638 final File packFile
= new File(packDir
, n
+ ".pack");
640 packList
.add(new PackFile(this, idxFile
, packFile
));
641 } catch (IOException ioe
) {
642 // Whoops. That's not a pack!
644 ioe
.printStackTrace();
651 * Writes a symref (e.g. HEAD) to disk
653 * @param name symref name
654 * @param target pointed to ref
655 * @throws IOException
657 public void writeSymref(final String name
, final String target
)
659 final byte[] content
= ("ref: " + target
+ "\n").getBytes("UTF-8");
660 final RefLock lck
= new RefLock(new File(gitDir
, name
));
662 throw new ObjectWritingException("Unable to lock " + name
);
665 } catch (IOException ioe
) {
666 throw new ObjectWritingException("Unable to write " + name
, ioe
);
669 throw new ObjectWritingException("Unable to write " + name
);
672 private Ref
readRef(final String revstr
, final boolean missingOk
)
674 refreshPackedRefsCache();
675 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
676 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
677 if (missingOk
|| r
.getObjectId() != null) {
684 private Ref
readRefBasic(String name
) throws IOException
{
687 // prefer unpacked ref to packed ref
688 final File f
= new File(getDirectory(), name
);
690 // look for packed ref, since this one doesn't exist
691 ObjectId id
= packedRefs
.get(name
);
693 return new Ref(name
, id
);
695 // no packed ref found, return blank one
696 return new Ref(name
, null);
699 final BufferedReader br
= new BufferedReader(new FileReader(f
));
701 final String line
= br
.readLine();
702 if (line
== null || line
.length() == 0)
703 return new Ref(name
, null);
704 else if (line
.startsWith("ref: ")) {
705 name
= line
.substring("ref: ".length());
706 continue REF_READING
;
707 } else if (ObjectId
.isId(line
))
708 return new Ref(name
, ObjectId
.fromString(line
));
709 throw new IOException("Not a ref: " + name
+ ": " + line
);
713 } while (depth
++ < 5);
714 throw new IOException("Exceed maximum ref depth. Circular reference?");
717 public String
toString() {
718 return "Repository[" + getDirectory() + "]";
722 * @return name of topmost Stacked Git patch.
723 * @throws IOException
725 public String
getPatch() throws IOException
{
726 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
727 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
731 while ((line
=br
.readLine())!=null) {
741 * @return name of current branch
742 * @throws IOException
744 public String
getFullBranch() throws IOException
{
745 final File ptr
= new File(getDirectory(),"HEAD");
746 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
753 if (ref
.startsWith("ref: "))
754 ref
= ref
.substring(5);
759 * @return name of current branch.
760 * @throws IOException
762 public String
getBranch() throws IOException
{
764 final File ptr
= new File(getDirectory(),"HEAD");
765 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
772 if (ref
.startsWith("ref: "))
773 ref
= ref
.substring(5);
774 if (ref
.startsWith("refs/heads/"))
775 ref
= ref
.substring(11);
777 } catch (FileNotFoundException e
) {
778 final File ptr
= new File(getDirectory(),"head-name");
779 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
791 * @return names of all local branches
793 public Collection
<String
> getBranches() {
794 return listRefs("heads");
798 * @return the names of all refs (local and remotes branches, tags)
800 public Collection
<String
> getAllRefs() {
804 private Collection
<String
> listRefs(String refSubDir
) {
805 // add / to end, unless empty
806 if (refSubDir
.length() > 0 && refSubDir
.charAt(refSubDir
.length() -1 ) != '/')
809 Collection
<String
> branchesRaw
= listFilesRecursively(new File(refsDir
, refSubDir
), null);
810 ArrayList
<String
> branches
= new ArrayList
<String
>();
811 for (String b
: branchesRaw
) {
812 branches
.add("refs/" + refSubDir
+ b
);
815 refreshPackedRefsCache();
816 Set
<String
> keySet
= packedRefs
.keySet();
817 for (String s
: keySet
)
818 if (s
.startsWith("refs/" + refSubDir
) && !branches
.contains(s
))
824 * @return all git tags
826 public Collection
<String
> getTags() {
827 return listRefs("tags");
830 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
831 private long packedrefstime
= 0;
833 private void refreshPackedRefsCache() {
834 File file
= new File(gitDir
, "packed-refs");
835 if (!file
.exists()) {
836 if (packedRefs
.size() > 0)
837 packedRefs
= new HashMap
<String
,ObjectId
>();
840 if (file
.lastModified() == packedrefstime
)
842 Map
<String
,ObjectId
> newPackedRefs
= new HashMap
<String
,ObjectId
>();
843 FileReader fileReader
= null;
845 fileReader
= new FileReader(file
);
846 BufferedReader b
=new BufferedReader(fileReader
);
848 while ((p
= b
.readLine()) != null) {
849 if (p
.charAt(0) == '#')
851 if (p
.charAt(0) == '^') {
854 int spos
= p
.indexOf(' ');
855 ObjectId id
= ObjectId
.fromString(p
.substring(0,spos
));
856 String name
= p
.substring(spos
+1);
857 newPackedRefs
.put(name
, id
);
859 } catch (IOException e
) {
860 throw new Error("Cannot read packed refs",e
);
862 if (fileReader
!= null) {
865 } catch (IOException e
) {
866 // Cannot do anything more here
871 packedRefs
= newPackedRefs
;
875 * @return true if HEAD points to a StGit patch.
877 public boolean isStGitMode() {
879 File file
= new File(getDirectory(), "HEAD");
880 BufferedReader reader
= new BufferedReader(new FileReader(file
));
881 String string
= reader
.readLine();
882 if (!string
.startsWith("ref: refs/heads/"))
884 String branch
= string
.substring("ref: refs/heads/".length());
885 File currentPatches
= new File(new File(new File(getDirectory(),
886 "patches"), branch
), "applied");
887 if (!currentPatches
.exists())
889 if (currentPatches
.length() == 0)
893 } catch (IOException e
) {
900 * @return applied patches in a map indexed on current commit id
901 * @throws IOException
903 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
904 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
906 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
907 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
908 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
909 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
910 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
911 String objectId
= tfr
.readLine();
912 ObjectId id
= ObjectId
.fromString(objectId
);
913 ret
.put(id
, new StGitPatch(patchName
, id
));
921 private Collection
<String
> listFilesRecursively(File root
, File start
) {
924 Collection
<String
> ret
= new ArrayList
<String
>();
925 File
[] files
= start
.listFiles();
926 for (int i
= 0; i
< files
.length
; ++i
) {
927 if (files
[i
].isDirectory())
928 ret
.addAll(listFilesRecursively(root
, files
[i
]));
929 else if (files
[i
].length() == 41) {
930 String name
= files
[i
].toString().substring(
931 root
.toString().length() + 1);
932 if (File
.separatorChar
!= '/')
933 name
= name
.replace(File
.separatorChar
, '/');
940 /** Clean up stale caches */
941 public void refreshFromDisk() {
946 * @return a representation of the index associated with this repo
947 * @throws IOException
949 public GitIndex
getIndex() throws IOException
{
951 index
= new GitIndex(this);
954 index
.rereadIfNecessary();
959 static byte[] gitInternalSlash(byte[] bytes
) {
960 if (File
.separatorChar
== '/')
962 for (int i
=0; i
<bytes
.length
; ++i
)
963 if (bytes
[i
] == File
.separatorChar
)
969 * @return an important state
971 public RepositoryState
getRepositoryState() {
972 if (new File(gitDir
.getParentFile(), ".dotest").exists())
973 return RepositoryState
.REBASING
;
974 if (new File(gitDir
,".dotest-merge").exists())
975 return RepositoryState
.REBASING_INTERACTIVE
;
976 if (new File(gitDir
,"MERGE_HEAD").exists())
977 return RepositoryState
.MERGING
;
978 if (new File(gitDir
,"BISECT_LOG").exists())
979 return RepositoryState
.BISECTING
;
980 return RepositoryState
.SAFE
;
984 * String work dir and return normalized repository path
987 * @param f File whose path shall be stripp off it's workdir
988 * @return normalized repository relative path
990 public static String
stripWorkDir(File wd
, File f
) {
991 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
992 relName
= relName
.replace(File
.separatorChar
, '/');