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
;
31 import java
.util
.WeakHashMap
;
33 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
34 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
36 public class Repository
{
37 private static final String
[] refSearchPaths
= { "", "refs/", "refs/tags/",
40 private final File gitDir
;
42 private final File
[] objectsDirs
;
44 private final File refsDir
;
46 private final RepositoryConfig config
;
48 private PackFile
[] packs
;
50 private WindowCache windows
;
52 private Map
<ObjectId
,Reference
<Tree
>> treeCache
= new WeakHashMap
<ObjectId
,Reference
<Tree
>>(30000);
53 private Map
<ObjectId
,Reference
<Commit
>> commitCache
= new WeakHashMap
<ObjectId
,Reference
<Commit
>>(30000);
55 private GitIndex index
;
57 public Repository(final File d
) throws IOException
{
58 gitDir
= d
.getAbsoluteFile();
60 objectsDirs
= readObjectsDirs(new File(gitDir
, "objects"), new ArrayList
<File
>()).toArray(new File
[0]);
61 } catch (IOException e
) {
62 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
66 refsDir
= new File(gitDir
, "refs");
67 packs
= new PackFile
[0];
68 config
= new RepositoryConfig(this);
69 if (objectsDirs
[0].exists()) {
71 final String repositoryFormatVersion
= getConfig().getString(
72 "core", "repositoryFormatVersion");
73 if (!"0".equals(repositoryFormatVersion
)) {
74 throw new IOException("Unknown repository format \""
75 + repositoryFormatVersion
+ "\"; expected \"0\".");
77 initializeWindowCache();
82 private Collection
<File
> readObjectsDirs(File objectsDir
, Collection
<File
> ret
) throws IOException
{
84 File alternatesFile
= new File(objectsDir
,"info/alternates");
85 if (alternatesFile
.exists()) {
86 BufferedReader ar
= new BufferedReader(new FileReader(alternatesFile
));
87 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
88 readObjectsDirs(new File(alt
), ret
);
95 public void create() throws IOException
{
96 if (gitDir
.exists()) {
97 throw new IllegalStateException("Repository already exists: "
103 objectsDirs
[0].mkdirs();
104 new File(objectsDirs
[0], "pack").mkdir();
105 new File(objectsDirs
[0], "info").mkdir();
108 new File(refsDir
, "heads").mkdir();
109 new File(refsDir
, "tags").mkdir();
111 new File(gitDir
, "branches").mkdir();
112 new File(gitDir
, "remotes").mkdir();
113 writeSymref("HEAD", "refs/heads/master");
115 getConfig().create();
117 initializeWindowCache();
120 private void initializeWindowCache() {
121 // FIXME these should be configurable...
122 windows
= new WindowCache(256 * 1024 * 1024, 4);
125 public File
getDirectory() {
129 public File
getObjectsDirectory() {
130 return objectsDirs
[0];
133 public RepositoryConfig
getConfig() {
137 public WindowCache
getWindowCache() {
141 public File
toFile(final ObjectId objectId
) {
142 final String n
= objectId
.toString();
143 String d
=n
.substring(0, 2);
144 String f
=n
.substring(2);
145 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
146 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
150 return new File(new File(objectsDirs
[0], d
), f
);
153 public boolean hasObject(final ObjectId objectId
) {
154 int k
= packs
.length
;
157 if (packs
[--k
].hasObject(objectId
))
161 return toFile(objectId
).isFile();
164 public ObjectLoader
openObject(final ObjectId id
) throws IOException
{
165 int k
= packs
.length
;
169 final ObjectLoader ol
= packs
[--k
].get(id
);
172 } catch (IOException ioe
) {
173 // This shouldn't happen unless the pack was corrupted
174 // after we opened it or the VM runs out of memory. This is
175 // a know problem with memory mapped I/O in java and have
176 // been noticed with JDK < 1.6. Tell the gc that now is a good
177 // time to collect and try once more.
180 final ObjectLoader ol
= packs
[k
].get(id
);
183 } catch (IOException ioe2
) {
184 ioe2
.printStackTrace();
185 ioe
.printStackTrace();
186 // Still fails.. that's BAD, maybe the pack has
187 // been corrupted after all, or the gc didn't manage
188 // to release enough previously mmaped areas.
194 return new UnpackedObjectLoader(this, id
);
195 } catch (FileNotFoundException fnfe
) {
200 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
201 return openObject(id
);
204 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
205 return openObject(id
);
208 public Commit
mapCommit(final String revstr
) throws IOException
{
209 final ObjectId id
= resolve(revstr
);
210 return id
!= null ?
mapCommit(id
) : null;
213 public Commit
mapCommit(final ObjectId id
) throws IOException
{
214 Reference
<Commit
> retr
= commitCache
.get(id
);
216 Commit ret
= retr
.get();
219 System
.out
.println("Found a null id, size was "+commitCache
.size());
222 final ObjectLoader or
= openObject(id
);
225 final byte[] raw
= or
.getBytes();
226 if (Constants
.TYPE_COMMIT
.equals(or
.getType())) {
227 Commit ret
= new Commit(this, id
, raw
);
228 // The key must not be the referenced strongly
229 // by the value in WeakHashMaps
230 commitCache
.put(id
, new SoftReference
<Commit
>(ret
));
233 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
236 public Tree
mapTree(final String revstr
) throws IOException
{
237 final ObjectId id
= resolve(revstr
);
238 return id
!= null ?
mapTree(id
) : null;
241 public Tree
mapTree(final ObjectId id
) throws IOException
{
242 Reference
<Tree
> wret
= treeCache
.get(id
);
244 Tree ret
= wret
.get();
249 final ObjectLoader or
= openObject(id
);
252 final byte[] raw
= or
.getBytes();
253 if (Constants
.TYPE_TREE
.equals(or
.getType())) {
254 Tree ret
= new Tree(this, id
, raw
);
255 treeCache
.put(id
, new SoftReference
<Tree
>(ret
));
258 if (Constants
.TYPE_COMMIT
.equals(or
.getType()))
259 return mapTree(ObjectId
.fromString(raw
, 5));
260 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
263 public Tag
mapTag(String revstr
) throws IOException
{
264 final ObjectId id
= resolve(revstr
);
265 return id
!= null ?
mapTag(revstr
, id
) : null;
268 public Tag
mapTag(final String refName
, final ObjectId id
) throws IOException
{
269 final ObjectLoader or
= openObject(id
);
272 final byte[] raw
= or
.getBytes();
273 if (Constants
.TYPE_TAG
.equals(or
.getType()))
274 return new Tag(this, id
, refName
, raw
);
275 return new Tag(this, id
, refName
, null);
278 public RefLock
lockRef(final String ref
) throws IOException
{
279 final Ref r
= readRef(ref
, true);
280 final RefLock l
= new RefLock(new File(gitDir
, r
.getName()));
281 return l
.lock() ? l
: null;
284 /** Parse a git revision string and return an object id.
286 * It is not fully implemented, so it only deals with
287 * commits and to some extent tags. Reflogs are not
289 * The plan is to implement it fully.
290 * @param revstr A git object references expression
291 * @return an ObjectId
292 * @throws IOException on serious errors
294 public ObjectId
parse(String revstr
) throws IOException
{
295 char[] rev
= revstr
.toCharArray();
298 for (int i
= 0; i
< rev
.length
; ++i
) {
302 String refstr
= new String(rev
,0,i
);
303 ObjectId refId
= resolveSimple(refstr
);
304 ref
= mapCommit(refId
);
306 if (i
+ 1 < rev
.length
) {
307 switch (rev
[i
+ 1]) {
319 for (j
=i
+1; j
<rev
.length
; ++j
) {
320 if (!Character
.isDigit(rev
[j
]))
323 String parentnum
= new String(rev
, i
+1, j
-i
-1);
324 int pnum
= Integer
.parseInt(parentnum
);
326 ref
= mapCommit(ref
.getParentIds()[pnum
- 1]);
332 for (k
=i
+2; k
<rev
.length
; ++k
) {
334 item
= new String(rev
, i
+2, k
-i
-2);
340 if (item
.equals("tree"))
341 ret
= ref
.getTreeId();
342 else if (item
.equals("commit"))
343 ; // just reference self
345 return null; // invalid
347 return null; // invalid
350 ref
= mapCommit(ref
.getParentIds()[0]);
353 ref
= mapCommit(ref
.getParentIds()[0]);
358 String refstr
= new String(rev
,0,i
);
359 ObjectId refId
= resolveSimple(refstr
);
360 ref
= mapCommit(refId
);
363 for (l
= i
+ 1; l
< rev
.length
; ++l
) {
364 if (!Character
.isDigit(rev
[l
]))
367 String distnum
= new String(rev
, i
+1, l
-i
-1);
368 int dist
= Integer
.parseInt(distnum
);
370 ref
= mapCommit(ref
.getParentIds()[0]);
378 for (m
=i
+2; m
<rev
.length
; ++m
) {
380 time
= new String(rev
, i
+2, m
-i
-2);
385 throw new IllegalArgumentException("reflogs not yet supprted");
390 return null; // cannot parse, return null
395 ret
= ref
.getCommitId();
397 ret
= resolveSimple(revstr
);
401 public ObjectId
resolve(final String revstr
) throws IOException
{
402 return parse(revstr
);
405 public ObjectId
resolveSimple(final String revstr
) throws IOException
{
408 if (ObjectId
.isId(revstr
)) {
409 id
= new ObjectId(revstr
);
413 final Ref r
= readRef(revstr
, false);
415 id
= r
.getObjectId();
422 public void close() throws IOException
{
426 public void closePacks() throws IOException
{
427 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
430 packs
= new PackFile
[0];
433 public void scanForPacks() {
434 final ArrayList
<PackFile
> p
= new ArrayList
<PackFile
>();
435 for (int i
=0; i
<objectsDirs
.length
; ++i
)
436 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
437 final PackFile
[] arr
= new PackFile
[p
.size()];
442 public void scanForPacks(final File packDir
, Collection
<PackFile
> packList
) {
443 final File
[] list
= packDir
.listFiles(new FileFilter() {
444 public boolean accept(final File f
) {
445 final String n
= f
.getName();
446 if (!n
.endsWith(".pack")) {
449 final String nBase
= n
.substring(0, n
.lastIndexOf('.'));
450 final File idx
= new File(packDir
, nBase
+ ".idx");
451 return f
.isFile() && f
.canRead() && idx
.isFile()
456 for (int k
= 0; k
< list
.length
; k
++) {
458 packList
.add(new PackFile(this, list
[k
]));
459 } catch (IOException ioe
) {
460 // Whoops. That's not a pack!
467 public void writeSymref(final String name
, final String target
)
469 final byte[] content
= ("ref: " + target
+ "\n").getBytes("UTF-8");
470 final RefLock lck
= new RefLock(new File(gitDir
, name
));
472 throw new ObjectWritingException("Unable to lock " + name
);
475 } catch (IOException ioe
) {
476 throw new ObjectWritingException("Unable to write " + name
, ioe
);
479 throw new ObjectWritingException("Unable to write " + name
);
482 private Ref
readRef(final String revstr
, final boolean missingOk
)
484 refreshPackredRefsCache();
485 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
486 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
487 if (missingOk
|| r
.getObjectId() != null) {
494 private Ref
readRefBasic(String name
) throws IOException
{
497 ObjectId id
= packedRefs
.get(name
);
499 return new Ref(null, id
);
501 final File f
= new File(getDirectory(), name
);
503 return new Ref(name
, null);
505 final BufferedReader br
= new BufferedReader(new FileReader(f
));
507 final String line
= br
.readLine();
508 if (line
== null || line
.length() == 0)
509 return new Ref(name
, null);
510 else if (line
.startsWith("ref: ")) {
511 name
= line
.substring("ref: ".length());
512 continue REF_READING
;
513 } else if (ObjectId
.isId(line
))
514 return new Ref(name
, new ObjectId(line
));
515 throw new IOException("Not a ref: " + name
+ ": " + line
);
519 } while (depth
++ < 5);
520 throw new IOException("Exceed maximum ref depth. Circular reference?");
523 public String
toString() {
524 return "Repository[" + getDirectory() + "]";
527 public String
getPatch() throws IOException
{
528 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/applied");
529 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
533 while ((line
=br
.readLine())!=null) {
542 public String
getBranch() throws IOException
{
543 final File ptr
= new File(getDirectory(),"HEAD");
544 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
551 if (ref
.startsWith("ref: "))
552 ref
= ref
.substring(5);
553 if (ref
.startsWith("refs/heads/"))
554 ref
= ref
.substring(11);
558 public Collection
<String
> getBranches() {
559 return listFilesRecursively(new File(refsDir
, "heads"), null);
562 public Collection
<String
> getTags() {
563 Collection
<String
> tags
= listFilesRecursively(new File(refsDir
, "tags"), null);
564 refreshPackredRefsCache();
565 tags
.addAll(packedRefs
.keySet());
569 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
570 private long packedrefstime
= 0;
572 private void refreshPackredRefsCache() {
573 File file
= new File(gitDir
, "packed-refs");
574 if (!file
.exists()) {
575 if (packedRefs
.size() > 0)
576 packedRefs
= new HashMap
<String
,ObjectId
>();
579 if (file
.lastModified() == packedrefstime
)
581 Map
<String
,ObjectId
> newPackedRefs
= new HashMap
<String
,ObjectId
>();
583 BufferedReader b
=new BufferedReader(new FileReader(file
));
585 while ((p
= b
.readLine()) != null) {
586 if (p
.charAt(0) == '#')
588 if (p
.charAt(0) == '^') {
591 int spos
= p
.indexOf(' ');
592 ObjectId id
= new ObjectId(p
.substring(0,spos
));
593 String name
= p
.substring(spos
+1);
594 newPackedRefs
.put(name
, id
);
596 } catch (IOException e
) {
599 packedRefs
= newPackedRefs
;
603 * @return true if HEAD points to a StGit patch.
605 public boolean isStGitMode() {
607 File file
= new File(getDirectory(), "HEAD");
608 BufferedReader reader
= new BufferedReader(new FileReader(file
));
609 String string
= reader
.readLine();
610 if (!string
.startsWith("ref: refs/heads/"))
612 String branch
= string
.substring("ref: refs/heads/".length());
613 File currentPatches
= new File(new File(new File(getDirectory(),
614 "patches"), branch
), "applied");
615 if (!currentPatches
.exists())
617 if (currentPatches
.length() == 0)
621 } catch (IOException e
) {
627 public static class StGitPatch
{
628 public StGitPatch(String patchName
, ObjectId id
) {
632 public ObjectId
getGitId() {
635 public String
getName() {
639 private ObjectId gitId
;
643 * @return applied patches in a map indexed on current commit id
644 * @throws IOException
646 public Map
<ObjectId
,StGitPatch
> getAppliedPatches() throws IOException
{
647 Map
<ObjectId
,StGitPatch
> ret
= new HashMap
<ObjectId
,StGitPatch
>();
649 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
650 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
651 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
652 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
653 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
654 String objectId
= tfr
.readLine();
655 ObjectId id
= new ObjectId(objectId
);
656 ret
.put(id
, new StGitPatch(patchName
, id
));
664 private Collection
<String
> listFilesRecursively(File root
, File start
) {
667 Collection
<String
> ret
= new ArrayList
<String
>();
668 File
[] files
= start
.listFiles();
669 for (int i
= 0; i
< files
.length
; ++i
) {
670 if (files
[i
].isDirectory())
671 ret
.addAll(listFilesRecursively(root
, files
[i
]));
672 else if (files
[i
].length() == 41) {
673 String name
= files
[i
].toString().substring(
674 root
.toString().length() + 1);
681 /** Clean up stale caches */
682 public void refreshFromDisk() {
686 public GitIndex
getIndex() throws IOException
{
688 index
= new GitIndex(this);
691 index
.rereadIfNecessary();
696 public static byte[] gitInternalSlash(byte[] bytes
) {
697 if (File
.separatorChar
== '/')
699 for (int i
=0; i
<bytes
.length
; ++i
)
700 if (bytes
[i
] == File
.separatorChar
)