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
.FileFilter
;
22 import java
.io
.FileNotFoundException
;
23 import java
.io
.FileReader
;
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
;
37 public class Repository
{
38 private static final String
[] refSearchPaths
= { "", "refs/", "refs/tags/",
41 private final File gitDir
;
43 private final File
[] objectsDirs
;
45 private final File refsDir
;
47 private final RepositoryConfig config
;
49 private PackFile
[] packs
;
51 private WindowCache windows
;
53 private Map
<ObjectId
,Reference
<Tree
>> treeCache
= new WeakHashMap
<ObjectId
,Reference
<Tree
>>(30000);
54 private Map
<ObjectId
,Reference
<Commit
>> commitCache
= new WeakHashMap
<ObjectId
,Reference
<Commit
>>(30000);
56 private GitIndex index
;
58 public Repository(final File d
) throws IOException
{
59 gitDir
= d
.getAbsoluteFile();
61 objectsDirs
= readObjectsDirs(new File(gitDir
, "objects"), new ArrayList
<File
>()).toArray(new File
[0]);
62 } catch (IOException e
) {
63 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
67 refsDir
= new File(gitDir
, "refs");
68 packs
= new PackFile
[0];
69 config
= new RepositoryConfig(this);
70 if (objectsDirs
[0].exists()) {
72 final String repositoryFormatVersion
= getConfig().getString(
73 "core", "repositoryFormatVersion");
74 if (!"0".equals(repositoryFormatVersion
)) {
75 throw new IOException("Unknown repository format \""
76 + repositoryFormatVersion
+ "\"; expected \"0\".");
78 initializeWindowCache();
83 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
85 File alternatesFile
= new File(objectsDir
,"info/alternates");
86 if (alternatesFile
.exists()) {
87 BufferedReader ar
= new BufferedReader(new FileReader(alternatesFile
));
88 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
89 readObjectsDirs(new File(alt
), ret
);
96 public void create() throws IOException
{
97 if (gitDir
.exists()) {
98 throw new IllegalStateException("Repository already exists: "
104 objectsDirs
[0].mkdirs();
105 new File(objectsDirs
[0], "pack").mkdir();
106 new File(objectsDirs
[0], "info").mkdir();
109 new File(refsDir
, "heads").mkdir();
110 new File(refsDir
, "tags").mkdir();
112 new File(gitDir
, "branches").mkdir();
113 new File(gitDir
, "remotes").mkdir();
114 writeSymref("HEAD", "refs/heads/master");
116 getConfig().create();
118 initializeWindowCache();
121 private void initializeWindowCache() {
122 // FIXME these should be configurable...
123 windows
= new WindowCache(256 * 1024 * 1024, 4);
126 public File
getDirectory() {
130 public File
getObjectsDirectory() {
131 return objectsDirs
[0];
134 public RepositoryConfig
getConfig() {
138 public WindowCache
getWindowCache() {
142 public File
toFile(final ObjectId objectId
) {
143 final String n
= objectId
.toString();
144 String d
=n
.substring(0, 2);
145 String f
=n
.substring(2);
146 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
147 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
151 return new File(new File(objectsDirs
[0], d
), f
);
154 public boolean hasObject(final ObjectId objectId
) {
155 int k
= packs
.length
;
158 if (packs
[--k
].hasObject(objectId
))
162 return toFile(objectId
).isFile();
165 public ObjectLoader
openObject(final ObjectId id
) throws IOException
{
166 int k
= packs
.length
;
170 final ObjectLoader ol
= packs
[--k
].get(id
);
173 } catch (IOException ioe
) {
174 // This shouldn't happen unless the pack was corrupted
175 // after we opened it or the VM runs out of memory. This is
176 // a know problem with memory mapped I/O in java and have
177 // been noticed with JDK < 1.6. Tell the gc that now is a good
178 // time to collect and try once more.
181 final ObjectLoader ol
= packs
[k
].get(id
);
184 } catch (IOException ioe2
) {
185 ioe2
.printStackTrace();
186 ioe
.printStackTrace();
187 // Still fails.. that's BAD, maybe the pack has
188 // been corrupted after all, or the gc didn't manage
189 // to release enough previously mmaped areas.
195 return new UnpackedObjectLoader(this, id
);
196 } catch (FileNotFoundException fnfe
) {
201 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
202 return openObject(id
);
205 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
206 return openObject(id
);
209 public Commit
mapCommit(final String revstr
) throws IOException
{
210 final ObjectId id
= resolve(revstr
);
211 return id
!= null ?
mapCommit(id
) : null;
214 public Commit
mapCommit(final ObjectId id
) throws IOException
{
215 Reference
<Commit
> retr
= commitCache
.get(id
);
217 Commit ret
= retr
.get();
220 System
.out
.println("Found a null id, size was "+commitCache
.size());
223 final ObjectLoader or
= openObject(id
);
226 final byte[] raw
= or
.getBytes();
227 if (Constants
.TYPE_COMMIT
.equals(or
.getType())) {
228 Commit ret
= new Commit(this, id
, raw
);
229 // The key must not be the referenced strongly
230 // by the value in WeakHashMaps
231 commitCache
.put(id
, new SoftReference
<Commit
>(ret
));
234 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
237 public Tree
mapTree(final String revstr
) throws IOException
{
238 final ObjectId id
= resolve(revstr
);
239 return id
!= null ?
mapTree(id
) : null;
242 public Tree
mapTree(final ObjectId id
) throws IOException
{
243 Reference
<Tree
> wret
= treeCache
.get(id
);
245 Tree ret
= wret
.get();
250 final ObjectLoader or
= openObject(id
);
253 final byte[] raw
= or
.getBytes();
254 if (Constants
.TYPE_TREE
.equals(or
.getType())) {
255 Tree ret
= new Tree(this, id
, raw
);
256 treeCache
.put(id
, new SoftReference
<Tree
>(ret
));
259 if (Constants
.TYPE_COMMIT
.equals(or
.getType()))
260 return mapTree(ObjectId
.fromString(raw
, 5));
261 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
264 public Tag
mapTag(String revstr
) throws IOException
{
265 final ObjectId id
= resolve(revstr
);
266 return id
!= null ?
mapTag(revstr
, id
) : null;
269 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
270 final ObjectLoader or
= openObject(id
);
273 final byte[] raw
= or
.getBytes();
274 if (Constants
.TYPE_TAG
.equals(or
.getType()))
275 return new Tag(this, id
, refName
, raw
);
276 return new Tag(this, id
, refName
, null);
279 public RefLock
lockRef(final String ref
) throws IOException
{
280 final Ref r
= readRef(ref
, true);
281 final RefLock l
= new RefLock(new File(gitDir
, r
.getName()));
282 return l
.lock() ? l
: null;
285 /** Parse a git revision string and return an object id.
287 * It is not fully implemented, so it only deals with
288 * commits and to some extent tags. Reflogs are not
290 * The plan is to implement it fully.
291 * @param revstr A git object references expression
292 * @return an ObjectId
293 * @throws IOException on serious errors
295 public ObjectId
parse(String revstr
) throws IOException
{
296 char[] rev
= revstr
.toCharArray();
299 for (int i
= 0; i
< rev
.length
; ++i
) {
303 String refstr
= new String(rev
,0,i
);
304 ObjectId refId
= resolveSimple(refstr
);
305 ref
= mapCommit(refId
);
307 if (i
+ 1 < rev
.length
) {
308 switch (rev
[i
+ 1]) {
320 for (j
=i
+1; j
<rev
.length
; ++j
) {
321 if (!Character
.isDigit(rev
[j
]))
324 String parentnum
= new String(rev
, i
+1, j
-i
-1);
325 int pnum
= Integer
.parseInt(parentnum
);
327 ref
= mapCommit(ref
.getParentIds()[pnum
- 1]);
333 for (k
=i
+2; k
<rev
.length
; ++k
) {
335 item
= new String(rev
, i
+2, k
-i
-2);
341 if (item
.equals("tree"))
342 ret
= ref
.getTreeId();
343 else if (item
.equals("commit"))
344 ; // just reference self
346 return null; // invalid
348 return null; // invalid
351 ref
= mapCommit(ref
.getParentIds()[0]);
354 ref
= mapCommit(ref
.getParentIds()[0]);
359 String refstr
= new String(rev
,0,i
);
360 ObjectId refId
= resolveSimple(refstr
);
361 ref
= mapCommit(refId
);
364 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
365 if (!Character
.isDigit(rev
[l
]))
368 String distnum
= new String(rev
, i
+1, l
-i
-1);
369 int dist
= Integer
.parseInt(distnum
);
371 ref
= mapCommit(ref
.getParentIds()[0]);
379 for (m
=i
+2; m
<rev
.length
; ++m
) {
381 time
= new String(rev
, i
+2, m
-i
-2);
386 throw new IllegalArgumentException("reflogs not yet supprted");
391 return null; // cannot parse, return null
396 ret
= ref
.getCommitId();
398 ret
= resolveSimple(revstr
);
402 public ObjectId
resolve(final String revstr
) throws IOException
{
403 return parse(revstr
);
406 public ObjectId
resolveSimple(final String revstr
) throws IOException
{
409 if (ObjectId
.isId(revstr
)) {
410 id
= new ObjectId(revstr
);
414 final Ref r
= readRef(revstr
, false);
416 id
= r
.getObjectId();
423 public void close() throws IOException
{
427 public void closePacks() throws IOException
{
428 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
431 packs
= new PackFile
[0];
434 public void scanForPacks() {
435 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
436 for (int i
=0; i
<objectsDirs
.length
; ++i
)
437 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
438 final PackFile
[] arr
= new PackFile
[p
.size()];
443 public void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
444 final File
[] list
= packDir
.listFiles(new FileFilter() {
445 public boolean accept(final File f
) {
446 final String n
= f
.getName();
447 if (!n
.endsWith(".pack")) {
450 final String nBase
= n
.substring(0, n
.lastIndexOf('.'));
451 final File idx
= new File(packDir
, nBase
+ ".idx");
452 return f
.isFile() && f
.canRead() && idx
.isFile()
457 for (int k
= 0; k
< list
.length
; k
++) {
459 packList
.add(new PackFile(this, list
[k
]));
460 } catch (IOException ioe
) {
461 // Whoops. That's not a pack!
468 public void writeSymref(final String name
, final String target
)
470 final byte[] content
= ("ref: " + target
+ "\n").getBytes("UTF-8");
471 final RefLock lck
= new RefLock(new File(gitDir
, name
));
473 throw new ObjectWritingException("Unable to lock " + name
);
476 } catch (IOException ioe
) {
477 throw new ObjectWritingException("Unable to write " + name
, ioe
);
480 throw new ObjectWritingException("Unable to write " + name
);
483 private Ref
readRef(final String revstr
, final boolean missingOk
)
485 refreshPackredRefsCache();
486 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
487 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
488 if (missingOk
|| r
.getObjectId() != null) {
495 private Ref
readRefBasic(String name
) throws IOException
{
498 // prefer unpacked ref to packed ref
499 final File f
= new File(getDirectory(), name
);
501 // look for packed ref, since this one doesn't exist
502 ObjectId id
= packedRefs
.get(name
);
504 return new Ref(name
, id
);
506 // no packed ref found, return blank one
507 return new Ref(name
, null);
510 final BufferedReader br
= new BufferedReader(new FileReader(f
));
512 final String line
= br
.readLine();
513 if (line
== null || line
.length() == 0)
514 return new Ref(name
, null);
515 else if (line
.startsWith("ref: ")) {
516 name
= line
.substring("ref: ".length());
517 continue REF_READING
;
518 } else if (ObjectId
.isId(line
))
519 return new Ref(name
, new ObjectId(line
));
520 throw new IOException("Not a ref: " + name
+ ": " + line
);
524 } while (depth
++ < 5);
525 throw new IOException("Exceed maximum ref depth. Circular reference?");
528 public String
toString() {
529 return "Repository[" + getDirectory() + "]";
532 public String
getPatch() throws IOException
{
533 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
534 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
538 while ((line
=br
.readLine())!=null) {
547 public String
getFullBranch() throws IOException
{
548 final File ptr
= new File(getDirectory(),"HEAD");
549 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
556 if (ref
.startsWith("ref: "))
557 ref
= ref
.substring(5);
561 public String
getBranch() throws IOException
{
562 final File ptr
= new File(getDirectory(),"HEAD");
563 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
570 if (ref
.startsWith("ref: "))
571 ref
= ref
.substring(5);
572 if (ref
.startsWith("refs/heads/"))
573 ref
= ref
.substring(11);
577 public Collection
<String
> getBranches() {
578 return listRefs("heads");
581 public Collection
<String
> getAllRefs() {
585 private Collection
<String
> listRefs(String refSubDir
) {
586 // add / to end, unless empty
587 if (refSubDir
.length() > 0 && refSubDir
.charAt(refSubDir
.length() -1 ) != '/')
590 Collection
<String
> branchesRaw
= listFilesRecursively(new File(refsDir
, refSubDir
), null);
591 ArrayList
<String
> branches
= new ArrayList
<String
>();
592 for (String b
: branchesRaw
) {
593 branches
.add("refs/" + refSubDir
+ b
);
596 refreshPackredRefsCache();
597 Set
<String
> keySet
= packedRefs
.keySet();
598 for (String s
: keySet
)
599 if (s
.startsWith("refs/" + refSubDir
) && !branches
.contains(s
))
604 public Collection
<String
> getTags() {
605 return listRefs("tags");
608 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
609 private long packedrefstime
= 0;
611 private void refreshPackredRefsCache() {
612 File file
= new File(gitDir
, "packed-refs");
613 if (!file
.exists()) {
614 if (packedRefs
.size() > 0)
615 packedRefs
= new HashMap
<String
,ObjectId
>();
618 if (file
.lastModified() == packedrefstime
)
620 Map
<String
,ObjectId
> newPackedRefs
= new HashMap
<String
,ObjectId
>();
621 FileReader fileReader
= null;
623 fileReader
= new FileReader(file
);
624 BufferedReader b
=new BufferedReader(fileReader
);
626 while ((p
= b
.readLine()) != null) {
627 if (p
.charAt(0) == '#')
629 if (p
.charAt(0) == '^') {
632 int spos
= p
.indexOf(' ');
633 ObjectId id
= new ObjectId(p
.substring(0,spos
));
634 String name
= p
.substring(spos
+1);
635 newPackedRefs
.put(name
, id
);
637 } catch (IOException e
) {
638 throw new Error("Cannot read packed refs",e
);
640 if (fileReader
!= null) {
643 } catch (IOException e
) {
644 // Cannot do anything more here
649 packedRefs
= newPackedRefs
;
653 * @return true if HEAD points to a StGit patch.
655 public boolean isStGitMode() {
657 File file
= new File(getDirectory(), "HEAD");
658 BufferedReader reader
= new BufferedReader(new FileReader(file
));
659 String string
= reader
.readLine();
660 if (!string
.startsWith("ref: refs/heads/"))
662 String branch
= string
.substring("ref: refs/heads/".length());
663 File currentPatches
= new File(new File(new File(getDirectory(),
664 "patches"), branch
), "applied");
665 if (!currentPatches
.exists())
667 if (currentPatches
.length() == 0)
671 } catch (IOException e
) {
677 public static class StGitPatch
{
678 public StGitPatch(String patchName
, ObjectId id
) {
682 public ObjectId
getGitId() {
685 public String
getName() {
689 private ObjectId gitId
;
693 * @return applied patches in a map indexed on current commit id
694 * @throws IOException
696 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
697 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
699 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
700 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
701 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
702 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
703 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
704 String objectId
= tfr
.readLine();
705 ObjectId id
= new ObjectId(objectId
);
706 ret
.put(id
, new StGitPatch(patchName
, id
));
714 private Collection
<String
> listFilesRecursively(File root
, File start
) {
717 Collection
<String
> ret
= new ArrayList
<String
>();
718 File
[] files
= start
.listFiles();
719 for (int i
= 0; i
< files
.length
; ++i
) {
720 if (files
[i
].isDirectory())
721 ret
.addAll(listFilesRecursively(root
, files
[i
]));
722 else if (files
[i
].length() == 41) {
723 String name
= files
[i
].toString().substring(
724 root
.toString().length() + 1);
725 if (File
.separatorChar
!= '/')
726 name
= name
.replace(File
.separatorChar
, '/');
733 /** Clean up stale caches */
734 public void refreshFromDisk() {
738 public GitIndex
getIndex() throws IOException
{
740 index
= new GitIndex(this);
743 index
.rereadIfNecessary();
748 public static byte[] gitInternalSlash(byte[] bytes
) {
749 if (File
.separatorChar
== '/')
751 for (int i
=0; i
<bytes
.length
; ++i
)
752 if (bytes
[i
] == File
.separatorChar
)