2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.lib
;
19 import java
.io
.BufferedReader
;
21 import java
.io
.FileNotFoundException
;
22 import java
.io
.FileReader
;
23 import java
.io
.FilenameFilter
;
24 import java
.io
.IOException
;
25 import java
.util
.ArrayList
;
26 import java
.util
.Collection
;
27 import java
.util
.HashMap
;
31 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
32 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
33 import org
.spearce
.jgit
.stgit
.StGitPatch
;
34 import org
.spearce
.jgit
.util
.FS
;
37 * Represents a Git repository. A repository holds all objects and refs used for
38 * managing source code (could by any type of file, but source code is what
39 * SCM's are typically used for).
41 * In Git terms all data is stored in GIT_DIR, typically a directory called
42 * .git. A work tree is maintained unless the repository is a bare repository.
43 * Typically the .git directory is located at the root of the work dir.
48 * <li>objects/ - objects</li>
49 * <li>refs/ - tags and heads</li>
50 * <li>config - configuration</li>
51 * <li>info/ - more configurations</li>
56 * This implementation only handles a subtly undocumented subset of git features.
59 public class Repository
{
60 private static final String
[] refSearchPaths
= { "", "refs/", "refs/tags/",
61 Constants
.HEADS_PREFIX
+ "/", "refs/" + Constants
.REMOTES_PREFIX
+ "/" };
63 private final File gitDir
;
65 private final File
[] objectsDirs
;
67 private final File refsDir
;
69 private final File packedRefsFile
;
71 private final RepositoryConfig config
;
73 private PackFile
[] packs
;
75 private final WindowCache windows
;
77 private GitIndex index
;
80 * Construct a representation of this git repo managing a Git repository.
86 public Repository(final File d
) throws IOException
{
91 * Construct a representation of a Git repository.
94 * cache this repository's data will be cached through during
95 * access. May be shared with another repository, or null to
96 * indicate this repository should allocate its own private
99 * GIT_DIR (the location of the repository metadata).
100 * @throws IOException
101 * the repository appears to already exist but cannot be
104 public Repository(final WindowCache wc
, final File d
) throws IOException
{
105 gitDir
= d
.getAbsoluteFile();
107 objectsDirs
= readObjectsDirs(FS
.resolve(gitDir
, "objects"),
108 new ArrayList
<File
>()).toArray(new File
[0]);
109 } catch (IOException e
) {
110 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
114 refsDir
= FS
.resolve(gitDir
, "refs");
115 packedRefsFile
= FS
.resolve(gitDir
, "packed-refs");
116 packs
= new PackFile
[0];
117 config
= new RepositoryConfig(this);
119 final boolean isExisting
= objectsDirs
[0].exists();
122 final String repositoryFormatVersion
= getConfig().getString(
123 "core", null, "repositoryFormatVersion");
124 if (!"0".equals(repositoryFormatVersion
)) {
125 throw new IOException("Unknown repository format \""
126 + repositoryFormatVersion
+ "\"; expected \"0\".");
129 getConfig().create();
131 windows
= wc
!= null ? wc
: new WindowCache(getConfig());
136 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
138 final File altFile
= FS
.resolve(objectsDir
, "info/alternates");
139 if (altFile
.exists()) {
140 BufferedReader ar
= new BufferedReader(new FileReader(altFile
));
141 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
142 readObjectsDirs(FS
.resolve(objectsDir
, alt
), ret
);
150 * Create a new Git repository initializing the necessary files and
153 * @throws IOException
155 public void create() throws IOException
{
156 if (gitDir
.exists()) {
157 throw new IllegalStateException("Repository already exists: "
163 objectsDirs
[0].mkdirs();
164 new File(objectsDirs
[0], "pack").mkdir();
165 new File(objectsDirs
[0], "info").mkdir();
168 new File(refsDir
, "heads").mkdir();
169 new File(refsDir
, "tags").mkdir();
171 new File(gitDir
, "branches").mkdir();
172 new File(gitDir
, "remotes").mkdir();
173 writeSymref("HEAD", "refs/heads/master");
175 getConfig().create();
182 public File
getDirectory() {
187 * @return the directory containg the objects owned by this repository.
189 public File
getObjectsDirectory() {
190 return objectsDirs
[0];
194 * @return the configuration of this repository
196 public RepositoryConfig
getConfig() {
201 * @return the cache needed for accessing packed objects in this repository.
203 public WindowCache
getWindowCache() {
208 * Construct a filename where the loose object having a specified SHA-1
209 * should be stored. If the object is stored in a shared repository the path
210 * to the alternative repo will be returned. If the object is not yet store
211 * a usable path in this repo will be returned. It is assumed that callers
212 * will look for objects in a pack first.
215 * @return suggested file name
217 public File
toFile(final AnyObjectId objectId
) {
218 final String n
= objectId
.toString();
219 String d
=n
.substring(0, 2);
220 String f
=n
.substring(2);
221 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
222 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
226 return new File(new File(objectsDirs
[0], d
), f
);
231 * @return true if the specified object is stored in this repo or any of the
232 * known shared repositories.
234 public boolean hasObject(final AnyObjectId objectId
) {
235 int k
= packs
.length
;
238 if (packs
[--k
].hasObject(objectId
))
242 return toFile(objectId
).isFile();
247 * SHA-1 of an object.
249 * @return a {@link ObjectLoader} for accessing the data of the named
250 * object, or null if the object does not exist.
251 * @throws IOException
253 public ObjectLoader
openObject(final AnyObjectId id
)
255 return openObject(new WindowCursor(),id
);
260 * temporary working space associated with the calling thread.
262 * SHA-1 of an object.
264 * @return a {@link ObjectLoader} for accessing the data of the named
265 * object, or null if the object does not exist.
266 * @throws IOException
268 public ObjectLoader
openObject(final WindowCursor curs
, final AnyObjectId id
)
270 int k
= packs
.length
;
274 final ObjectLoader ol
= packs
[--k
].get(curs
, id
);
277 } catch (IOException ioe
) {
278 // This shouldn't happen unless the pack was corrupted
279 // after we opened it or the VM runs out of memory. This is
280 // a know problem with memory mapped I/O in java and have
281 // been noticed with JDK < 1.6. Tell the gc that now is a good
282 // time to collect and try once more.
286 final ObjectLoader ol
= packs
[k
].get(curs
, id
);
289 } catch (IOException ioe2
) {
290 ioe2
.printStackTrace();
291 ioe
.printStackTrace();
292 // Still fails.. that's BAD, maybe the pack has
293 // been corrupted after all, or the gc didn't manage
294 // to release enough previously mmaped areas.
300 return new UnpackedObjectLoader(this, id
.toObjectId());
301 } catch (FileNotFoundException fnfe
) {
309 * @return an {@link ObjectLoader} for accessing the data of a named blob
310 * @throws IOException
312 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
313 return openObject(id
);
319 * @return an {@link ObjectLoader} for accessing the data of a named tree
320 * @throws IOException
322 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
323 return openObject(id
);
327 * Access a Commit object using a symbolic reference. This reference may
328 * be a SHA-1 or ref in combination with a number of symbols translating
329 * from one ref or SHA1-1 to another, such as HEAD^ etc.
331 * @param revstr a reference to a git commit object
332 * @return a Commit named by the specified string
333 * @throws IOException for I/O error or unexpected object type.
335 * @see #resolve(String)
337 public Commit
mapCommit(final String revstr
) throws IOException
{
338 final ObjectId id
= resolve(revstr
);
339 return id
!= null ?
mapCommit(id
) : null;
343 * Access any type of Git object by id and
346 * SHA-1 of object to read
347 * @param refName optional, only relevant for simple tags
348 * @return The Git object if found or null
349 * @throws IOException
351 public Object
mapObject(final ObjectId id
, final String refName
) throws IOException
{
352 final ObjectLoader or
= openObject(id
);
353 final byte[] raw
= or
.getBytes();
354 if (or
.getType() == Constants
.OBJ_TREE
)
355 return makeTree(id
, raw
);
356 if (or
.getType() == Constants
.OBJ_COMMIT
)
357 return makeCommit(id
, raw
);
358 if (or
.getType() == Constants
.OBJ_TAG
)
359 return makeTag(id
, refName
, raw
);
360 if (or
.getType() == Constants
.OBJ_BLOB
)
366 * Access a Commit by SHA'1 id.
368 * @return Commit or null
369 * @throws IOException for I/O error or unexpected object type.
371 public Commit
mapCommit(final ObjectId id
) throws IOException
{
372 final ObjectLoader or
= openObject(id
);
375 final byte[] raw
= or
.getBytes();
376 if (Constants
.OBJ_COMMIT
== or
.getType())
377 return new Commit(this, id
, raw
);
378 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
381 private Commit
makeCommit(final ObjectId id
, final byte[] raw
) {
382 Commit ret
= new Commit(this, id
, raw
);
387 * Access a Tree object using a symbolic reference. This reference may
388 * be a SHA-1 or ref in combination with a number of symbols translating
389 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
391 * @param revstr a reference to a git commit object
392 * @return a Tree named by the specified string
393 * @throws IOException
395 * @see #resolve(String)
397 public Tree
mapTree(final String revstr
) throws IOException
{
398 final ObjectId id
= resolve(revstr
);
399 return id
!= null ?
mapTree(id
) : null;
403 * Access a Tree by SHA'1 id.
405 * @return Tree or null
406 * @throws IOException for I/O error or unexpected object type.
408 public Tree
mapTree(final ObjectId id
) throws IOException
{
409 final ObjectLoader or
= openObject(id
);
412 final byte[] raw
= or
.getBytes();
413 if (Constants
.OBJ_TREE
== or
.getType()) {
414 return new Tree(this, id
, raw
);
416 if (Constants
.OBJ_COMMIT
== or
.getType())
417 return mapTree(ObjectId
.fromString(raw
, 5));
418 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
421 private Tree
makeTree(final ObjectId id
, final byte[] raw
) throws IOException
{
422 Tree ret
= new Tree(this, id
, raw
);
426 private Tag
makeTag(final ObjectId id
, final String refName
, final byte[] raw
) {
427 Tag ret
= new Tag(this, id
, refName
, raw
);
432 * Access a tag by symbolic name.
435 * @return a Tag or null
436 * @throws IOException on I/O error or unexpected type
438 public Tag
mapTag(String revstr
) throws IOException
{
439 final ObjectId id
= resolve(revstr
);
440 return id
!= null ?
mapTag(revstr
, id
) : null;
444 * Access a Tag by SHA'1 id
447 * @return Commit or null
448 * @throws IOException for I/O error or unexpected object type.
450 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
451 final ObjectLoader or
= openObject(id
);
454 final byte[] raw
= or
.getBytes();
455 if (Constants
.OBJ_TAG
== or
.getType())
456 return new Tag(this, id
, refName
, raw
);
457 return new Tag(this, id
, refName
, null);
461 * Get a locked handle to a ref suitable for updating or creating.
463 * @param ref name to lock
464 * @return a locked ref
465 * @throws IOException
467 public LockFile
lockRef(final String ref
) throws IOException
{
468 final Ref r
= readRef(ref
, true);
469 final LockFile l
= new LockFile(fileForRef(r
.getName()));
470 return l
.lock() ? l
: null;
474 * Parse a git revision string and return an object id.
476 * Currently supported is combinations of these.
478 * <li>SHA-1 - a SHA-1</li>
479 * <li>refs/... - a ref name</li>
480 * <li>ref^n - nth parent reference</li>
481 * <li>ref~n - distance via parent reference</li>
482 * <li>ref@{n} - nth version of ref</li>
483 * <li>ref^{tree} - tree references by ref</li>
484 * <li>ref^{commit} - commit references by ref</li>
489 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
490 * <li>abbreviated SHA-1's</li>
493 * @param revstr A git object references expression
494 * @return an ObjectId
495 * @throws IOException on serious errors
497 public ObjectId
resolve(final String revstr
) throws IOException
{
498 char[] rev
= revstr
.toCharArray();
501 for (int i
= 0; i
< rev
.length
; ++i
) {
505 String refstr
= new String(rev
,0,i
);
506 ObjectId refId
= resolveSimple(refstr
);
509 ref
= mapCommit(refId
);
511 if (i
+ 1 < rev
.length
) {
512 switch (rev
[i
+ 1]) {
524 for (j
=i
+1; j
<rev
.length
; ++j
) {
525 if (!Character
.isDigit(rev
[j
]))
528 String parentnum
= new String(rev
, i
+1, j
-i
-1);
529 int pnum
= Integer
.parseInt(parentnum
);
531 ref
= mapCommit(ref
.getParentIds()[pnum
- 1]);
537 for (k
=i
+2; k
<rev
.length
; ++k
) {
539 item
= new String(rev
, i
+2, k
-i
-2);
545 if (item
.equals("tree"))
546 ret
= ref
.getTreeId();
547 else if (item
.equals("commit"))
548 ; // just reference self
550 return null; // invalid
552 return null; // invalid
555 ref
= mapCommit(ref
.getParentIds()[0]);
558 ref
= mapCommit(ref
.getParentIds()[0]);
563 String refstr
= new String(rev
,0,i
);
564 ObjectId refId
= resolveSimple(refstr
);
565 ref
= mapCommit(refId
);
568 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
569 if (!Character
.isDigit(rev
[l
]))
572 String distnum
= new String(rev
, i
+1, l
-i
-1);
573 int dist
= Integer
.parseInt(distnum
);
575 ref
= mapCommit(ref
.getParentIds()[0]);
583 for (m
=i
+2; m
<rev
.length
; ++m
) {
585 time
= new String(rev
, i
+2, m
-i
-2);
590 throw new IllegalArgumentException("reflogs not yet supprted");
595 return null; // cannot parse, return null
600 ret
= ref
.getCommitId();
602 ret
= resolveSimple(revstr
);
606 private ObjectId
resolveSimple(final String revstr
) throws IOException
{
607 if (ObjectId
.isId(revstr
))
608 return ObjectId
.fromString(revstr
);
609 final Ref r
= readRef(revstr
, false);
611 return r
.getObjectId();
617 * Close all resources used by this repository
619 public void close() {
624 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
627 packs
= new PackFile
[0];
631 * Scan the object dirs, including alternates for packs
634 public void scanForPacks() {
635 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
636 for (int i
=0; i
<objectsDirs
.length
; ++i
)
637 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
638 final PackFile
[] arr
= new PackFile
[p
.size()];
643 private void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
644 final String
[] idxList
= packDir
.list(new FilenameFilter() {
645 public boolean accept(final File baseDir
, final String n
) {
646 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
647 return n
.length() == 49 && n
.endsWith(".idx")
648 && n
.startsWith("pack-");
651 if (idxList
!= null) {
652 for (final String indexName
: idxList
) {
653 final String n
= indexName
.substring(0, indexName
.length() - 4);
654 final File idxFile
= new File(packDir
, n
+ ".idx");
655 final File packFile
= new File(packDir
, n
+ ".pack");
657 packList
.add(new PackFile(this, idxFile
, packFile
));
658 } catch (IOException ioe
) {
659 // Whoops. That's not a pack!
661 ioe
.printStackTrace();
668 * Writes a symref (e.g. HEAD) to disk
670 * @param name symref name
671 * @param target pointed to ref
672 * @throws IOException
674 public void writeSymref(final String name
, final String target
)
676 final byte[] content
= ("ref: " + target
+ "\n").getBytes("UTF-8");
677 final LockFile lck
= new LockFile(fileForRef(name
));
679 throw new ObjectWritingException("Unable to lock " + name
);
682 } catch (IOException ioe
) {
683 throw new ObjectWritingException("Unable to write " + name
, ioe
);
686 throw new ObjectWritingException("Unable to write " + name
);
689 private Ref
readRef(final String revstr
, final boolean missingOk
)
691 refreshPackedRefsCache();
692 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
693 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
694 if (missingOk
|| r
.getObjectId() != null) {
701 private Ref
readRefBasic(String name
) throws IOException
{
704 // prefer unpacked ref to packed ref
705 final File f
= fileForRef(name
);
707 // look for packed ref, since this one doesn't exist
708 ObjectId id
= packedRefs
.get(name
);
710 return new Ref(name
, id
);
712 // no packed ref found, return blank one
713 return new Ref(name
, null);
716 final BufferedReader br
= new BufferedReader(new FileReader(f
));
718 final String line
= br
.readLine();
719 if (line
== null || line
.length() == 0)
720 return new Ref(name
, null);
721 else if (line
.startsWith("ref: ")) {
722 name
= line
.substring("ref: ".length());
723 continue REF_READING
;
724 } else if (ObjectId
.isId(line
))
725 return new Ref(name
, ObjectId
.fromString(line
));
726 throw new IOException("Not a ref: " + name
+ ": " + line
);
730 } while (depth
++ < 5);
731 throw new IOException("Exceed maximum ref depth. Circular reference?");
734 private File
fileForRef(final String name
) {
735 if (name
.startsWith("refs/"))
736 return new File(refsDir
, name
.substring("refs/".length()));
737 return new File(gitDir
, name
);
740 public String
toString() {
741 return "Repository[" + getDirectory() + "]";
745 * @return name of topmost Stacked Git patch.
746 * @throws IOException
748 public String
getPatch() throws IOException
{
749 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
750 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
754 while ((line
=br
.readLine())!=null) {
764 * @return name of current branch
765 * @throws IOException
767 public String
getFullBranch() throws IOException
{
768 final File ptr
= new File(getDirectory(),"HEAD");
769 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
776 if (ref
.startsWith("ref: "))
777 ref
= ref
.substring(5);
782 * @return name of current branch.
783 * @throws IOException
785 public String
getBranch() throws IOException
{
787 final File ptr
= new File(getDirectory(),"HEAD");
788 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
795 if (ref
.startsWith("ref: "))
796 ref
= ref
.substring(5);
797 if (ref
.startsWith("refs/heads/"))
798 ref
= ref
.substring(11);
800 } catch (FileNotFoundException e
) {
801 final File ptr
= new File(getDirectory(),"head-name");
802 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
814 * @return names of all local branches
816 public Collection
<String
> getBranches() {
817 return listRefs("heads");
821 * @return the names of all refs (local and remotes branches, tags)
823 public Collection
<String
> getAllRefs() {
827 private Collection
<String
> listRefs(String refSubDir
) {
828 // add / to end, unless empty
829 if (refSubDir
.length() > 0 && refSubDir
.charAt(refSubDir
.length() -1 ) != '/')
832 Collection
<String
> branchesRaw
= listFilesRecursively(new File(refsDir
, refSubDir
), null);
833 ArrayList
<String
> branches
= new ArrayList
<String
>();
834 for (String b
: branchesRaw
) {
835 branches
.add("refs/" + refSubDir
+ b
);
838 refreshPackedRefsCache();
839 Set
<String
> keySet
= packedRefs
.keySet();
840 for (String s
: keySet
)
841 if (s
.startsWith("refs/" + refSubDir
) && !branches
.contains(s
))
847 * @return all git tags
849 public Collection
<String
> getTags() {
850 return listRefs("tags");
853 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
854 private long packedrefstime
= 0;
856 private void refreshPackedRefsCache() {
857 if (!packedRefsFile
.exists()) {
858 if (packedRefs
.size() > 0)
859 packedRefs
= new HashMap
<String
,ObjectId
>();
862 if (packedRefsFile
.lastModified() == packedrefstime
)
864 Map
<String
,ObjectId
> newPackedRefs
= new HashMap
<String
,ObjectId
>();
865 FileReader fileReader
= null;
867 fileReader
= new FileReader(packedRefsFile
);
868 BufferedReader b
=new BufferedReader(fileReader
);
870 while ((p
= b
.readLine()) != null) {
871 if (p
.charAt(0) == '#')
873 if (p
.charAt(0) == '^') {
876 int spos
= p
.indexOf(' ');
877 ObjectId id
= ObjectId
.fromString(p
.substring(0,spos
));
878 String name
= p
.substring(spos
+1);
879 newPackedRefs
.put(name
, id
);
881 } catch (IOException e
) {
882 throw new Error("Cannot read packed refs",e
);
884 if (fileReader
!= null) {
887 } catch (IOException e
) {
888 // Cannot do anything more here
893 packedRefs
= newPackedRefs
;
897 * @return true if HEAD points to a StGit patch.
899 public boolean isStGitMode() {
901 File file
= new File(getDirectory(), "HEAD");
902 BufferedReader reader
= new BufferedReader(new FileReader(file
));
903 String string
= reader
.readLine();
904 if (!string
.startsWith("ref: refs/heads/"))
906 String branch
= string
.substring("ref: refs/heads/".length());
907 File currentPatches
= new File(new File(new File(getDirectory(),
908 "patches"), branch
), "applied");
909 if (!currentPatches
.exists())
911 if (currentPatches
.length() == 0)
915 } catch (IOException e
) {
922 * @return applied patches in a map indexed on current commit id
923 * @throws IOException
925 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
926 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
928 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
929 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
930 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
931 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
932 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
933 String objectId
= tfr
.readLine();
934 ObjectId id
= ObjectId
.fromString(objectId
);
935 ret
.put(id
, new StGitPatch(patchName
, id
));
943 private Collection
<String
> listFilesRecursively(File root
, File start
) {
946 Collection
<String
> ret
= new ArrayList
<String
>();
947 File
[] files
= start
.listFiles();
948 for (int i
= 0; i
< files
.length
; ++i
) {
949 if (files
[i
].isDirectory())
950 ret
.addAll(listFilesRecursively(root
, files
[i
]));
951 else if (files
[i
].length() == 41) {
952 String name
= files
[i
].toString().substring(
953 root
.toString().length() + 1);
954 if (File
.separatorChar
!= '/')
955 name
= name
.replace(File
.separatorChar
, '/');
962 /** Clean up stale caches */
963 public void refreshFromDisk() {
968 * @return a representation of the index associated with this repo
969 * @throws IOException
971 public GitIndex
getIndex() throws IOException
{
973 index
= new GitIndex(this);
976 index
.rereadIfNecessary();
981 static byte[] gitInternalSlash(byte[] bytes
) {
982 if (File
.separatorChar
== '/')
984 for (int i
=0; i
<bytes
.length
; ++i
)
985 if (bytes
[i
] == File
.separatorChar
)
991 * @return an important state
993 public RepositoryState
getRepositoryState() {
994 if (new File(getWorkDir(), ".dotest").exists())
995 return RepositoryState
.REBASING
;
996 if (new File(gitDir
,".dotest-merge").exists())
997 return RepositoryState
.REBASING_INTERACTIVE
;
998 if (new File(gitDir
,"MERGE_HEAD").exists())
999 return RepositoryState
.MERGING
;
1000 if (new File(gitDir
,"BISECT_LOG").exists())
1001 return RepositoryState
.BISECTING
;
1002 return RepositoryState
.SAFE
;
1006 * Check validty of a ref name. It must not contain character that has
1007 * a special meaning in a Git object reference expression. Some other
1008 * dangerous characters are also excluded.
1012 * @return true if refName is a valid ref name
1014 public static boolean isValidRefName(final String refName
) {
1015 final int len
= refName
.length();
1017 for (int i
=0; i
<len
; ++i
) {
1018 char c
= refName
.charAt(i
);
1036 case '~': case '^': case ':':
1048 * String work dir and return normalized repository path
1050 * @param wd Work dir
1051 * @param f File whose path shall be stripp off it's workdir
1052 * @return normalized repository relative path
1054 public static String
stripWorkDir(File wd
, File f
) {
1055 String relName
= f
.getPath().substring(wd
.getPath().length() + 1);
1056 relName
= relName
.replace(File
.separatorChar
, '/');
1062 * The "remote" name in this repo
1063 * @return information about how a remote repository is beging tracked
1065 public RemoteSpec
getRemoteSpec(String name
) {
1066 String url
= getConfig().getString("remote."+name
, null, "url");
1067 String fetchPattern
= getConfig().getString("remote."+name
, null, "fetch");
1068 String pushPattern
= getConfig().getString("remote."+name
, null, "push");
1069 return new RemoteSpec(name
, url
, fetchPattern
, pushPattern
);
1073 * Setup repository configuration for a new remote
1076 * remote name, e.g. "origin"
1078 * fetch url, e.g. "git://repo.or.cz/egit.git"
1080 * local branch name, e.g. "master"
1082 public void configureDefaultBranch(final String remote
, final String url
, final String branch
) {
1083 config
.putString(RepositoryConfig
.REMOTE_SECTION
, remote
, "url",
1085 config
.putString(RepositoryConfig
.REMOTE_SECTION
, remote
, "fetch",
1086 "+" + Constants
.HEADS_PREFIX
+ "/*:" + Constants
.REMOTES_PREFIX
+ "/" + remote
+ "/*");
1087 config
.putString(RepositoryConfig
.BRANCH_SECTION
, branch
, "remote",
1089 config
.putString(RepositoryConfig
.BRANCH_SECTION
, Constants
.MASTER
, "merge",
1090 Constants
.HEADS_PREFIX
+ "/" + branch
);
1094 * @return the workdir file, i.e. where the files are checked out
1096 public File
getWorkDir() {
1097 return getDirectory().getParentFile();
1101 * Setup HEAD and "master" refs for a new repository.
1103 * @param remoteBranch
1104 * The remote branch to start with
1106 * The local branch to configure, initially starting at
1108 * @return the commit references by the new HEAD
1109 * @throws IOException
1111 public Commit
setupHEADRef(final String remoteBranch
, final String branch
) throws IOException
{
1112 Commit mapCommit
= mapCommit(remoteBranch
);
1113 String refName
= Constants
.HEADS_PREFIX
+ "/" + branch
;
1114 LockFile masterRef
= lockRef(refName
);
1116 masterRef
.write(mapCommit
.getCommitId());
1121 writeSymref(Constants
.HEAD
, refName
);