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
.FileWriter
;
25 import java
.io
.IOException
;
26 import java
.lang
.ref
.Reference
;
27 import java
.lang
.ref
.SoftReference
;
28 import java
.util
.ArrayList
;
29 import java
.util
.Collection
;
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 objectsDir
;
44 private final File refsDir
;
46 private final RepositoryConfig config
;
48 private PackFile
[] packs
;
50 private WindowCache windows
;
52 private Map treeCache
= new WeakHashMap(30000);
53 private Map commitCache
= new WeakHashMap(30000);
55 public Repository(final File d
) throws IOException
{
56 gitDir
= d
.getAbsoluteFile();
57 objectsDir
= new File(gitDir
, "objects");
58 refsDir
= new File(gitDir
, "refs");
59 packs
= new PackFile
[0];
60 config
= new RepositoryConfig(this);
61 if (objectsDir
.exists()) {
63 final String repositoryFormatVersion
= getConfig().getString(
64 "core", "repositoryFormatVersion");
65 if (!"0".equals(repositoryFormatVersion
)) {
66 throw new IOException("Unknown repository format \""
67 + repositoryFormatVersion
+ "\"; expected \"0\".");
69 initializeWindowCache();
74 public void create() throws IOException
{
75 if (gitDir
.exists()) {
76 throw new IllegalStateException("Repository already exists: "
83 new File(objectsDir
, "pack").mkdir();
84 new File(objectsDir
, "info").mkdir();
87 new File(refsDir
, "heads").mkdir();
88 new File(refsDir
, "tags").mkdir();
90 new File(gitDir
, "branches").mkdir();
91 new File(gitDir
, "remotes").mkdir();
92 writeSymref("HEAD", "refs/heads/master");
96 initializeWindowCache();
99 private void initializeWindowCache() {
100 // FIXME these should be configurable...
101 windows
= new WindowCache(256 * 1024 * 1024, 4);
104 public File
getDirectory() {
108 public File
getObjectsDirectory() {
112 public RepositoryConfig
getConfig() {
116 public WindowCache
getWindowCache() {
120 public File
toFile(final ObjectId objectId
) {
121 final String n
= objectId
.toString();
122 return new File(new File(objectsDir
, n
.substring(0, 2)), n
.substring(2));
125 public boolean hasObject(final ObjectId objectId
) {
126 int k
= packs
.length
;
128 final byte[] tmp
= new byte[Constants
.OBJECT_ID_LENGTH
];
131 if (packs
[--k
].hasObject(objectId
, tmp
))
133 } catch (IOException ioe
) {
134 // This shouldn't happen unless the pack was corrupted
135 // after we opened it. We'll ignore the error as though
136 // the object does not exist in this pack.
141 return toFile(objectId
).isFile();
144 public ObjectLoader
openObject(final ObjectId id
) throws IOException
{
145 int k
= packs
.length
;
147 final byte[] tmp
= new byte[Constants
.OBJECT_ID_LENGTH
];
150 final ObjectLoader ol
= packs
[--k
].get(id
, tmp
);
153 } catch (IOException ioe
) {
154 // This shouldn't happen unless the pack was corrupted
155 // after we opened it or the VM runs out of memory. This is
156 // a know problem with memory mapped I/O in java and have
157 // been noticed with JDK < 1.6. Tell the gc that now is a good
158 // time to collect and try once more.
161 final ObjectLoader ol
= packs
[k
].get(id
, tmp
);
164 } catch (IOException ioe2
) {
165 ioe2
.printStackTrace();
166 ioe
.printStackTrace();
167 // Still fails.. that's BAD, maybe the pack has
168 // been corrupted after all, or the gc didn't manage
169 // to release enough previously mmaped areas.
175 return new UnpackedObjectLoader(this, id
);
176 } catch (FileNotFoundException fnfe
) {
181 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
182 return openObject(id
);
185 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
186 return openObject(id
);
189 public Commit
mapCommit(final String revstr
) throws IOException
{
190 final ObjectId id
= resolve(revstr
);
191 return id
!= null ?
mapCommit(id
) : null;
194 public Commit
mapCommit(final ObjectId id
) throws IOException
{
195 // System.out.println("commitcache.size="+commitCache.size());
196 Reference retr
= (Reference
)commitCache
.get(id
);
198 Commit ret
= (Commit
)retr
.get();
201 System
.out
.println("Found a null id, size was "+commitCache
.size());
204 final ObjectLoader or
= openObject(id
);
207 final byte[] raw
= or
.getBytes();
208 if (Constants
.TYPE_COMMIT
.equals(or
.getType())) {
209 Commit ret
= new Commit(this, id
, raw
);
210 // The key must not be the referenced strongly
211 // by the value in WeakHashMaps
212 commitCache
.put(id
, new SoftReference(ret
));
215 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
218 public Tree
mapTree(final String revstr
) throws IOException
{
219 final ObjectId id
= resolve(revstr
);
220 return id
!= null ?
mapTree(id
) : null;
223 public Tree
mapTree(final ObjectId id
) throws IOException
{
224 Reference wret
= (Reference
)treeCache
.get(id
);
226 Tree ret
= (Tree
)wret
.get();
231 final ObjectLoader or
= openObject(id
);
234 final byte[] raw
= or
.getBytes();
235 if (Constants
.TYPE_TREE
.equals(or
.getType())) {
236 Tree ret
= new Tree(this, id
, raw
);
237 treeCache
.put(id
, new SoftReference(ret
));
240 if (Constants
.TYPE_COMMIT
.equals(or
.getType()))
241 return mapTree(ObjectId
.fromString(raw
, 5));
242 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
245 public Tag
mapTag(String revstr
) throws IOException
{
246 final ObjectId id
= resolve(revstr
);
247 return id
!= null ?
mapTag(id
) : null;
250 public Tag
mapTag(final ObjectId id
) throws IOException
{
251 final ObjectLoader or
= openObject(id
);
254 final byte[] raw
= or
.getBytes();
255 if (Constants
.TYPE_TAG
.equals(or
.getType()))
256 return new Tag(this, id
, raw
);
257 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TAG
);
260 public RefLock
lockRef(final String ref
) throws IOException
{
261 final RefLock l
= new RefLock(readRef(ref
, true));
262 return l
.lock() ? l
: null;
265 public ObjectId
resolve(final String revstr
) throws IOException
{
268 if (ObjectId
.isId(revstr
)) {
269 id
= new ObjectId(revstr
);
273 final Ref r
= readRef(revstr
, false);
275 id
= r
.getObjectId();
282 public void close() throws IOException
{
286 public void closePacks() throws IOException
{
287 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
290 packs
= new PackFile
[0];
293 public void scanForPacks() {
294 final File packDir
= new File(objectsDir
, "pack");
295 final File
[] list
= packDir
.listFiles(new FileFilter() {
296 public boolean accept(final File f
) {
297 final String n
= f
.getName();
298 if (!n
.endsWith(".pack")) {
301 final String nBase
= n
.substring(0, n
.lastIndexOf('.'));
302 final File idx
= new File(packDir
, nBase
+ ".idx");
303 return f
.isFile() && f
.canRead() && idx
.isFile()
307 final ArrayList p
= new ArrayList(list
.length
);
308 for (int k
= 0; k
< list
.length
; k
++) {
310 p
.add(new PackFile(this, list
[k
]));
311 } catch (IOException ioe
) {
312 // Whoops. That's not a pack!
316 final PackFile
[] arr
= new PackFile
[p
.size()];
321 private void writeSymref(final String name
, final String target
)
323 final File s
= new File(gitDir
, name
);
324 final File t
= File
.createTempFile("srf", null, gitDir
);
325 FileWriter w
= new FileWriter(t
);
332 if (!t
.renameTo(s
)) {
333 s
.getParentFile().mkdirs();
334 if (!t
.renameTo(s
)) {
336 throw new ObjectWritingException("Unable to"
337 + " write symref " + name
+ " to point to "
349 private Ref
readRef(final String revstr
, final boolean missingOk
)
351 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
352 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
353 if (missingOk
|| r
.getObjectId() != null) {
360 private Ref
readRefBasic(String name
) throws IOException
{
363 final File f
= new File(getDirectory(), name
);
365 return new Ref(f
, null);
368 final BufferedReader br
= new BufferedReader(new FileReader(f
));
370 final String line
= br
.readLine();
371 if (line
== null || line
.length() == 0) {
372 return new Ref(f
, null);
373 } else if (line
.startsWith("ref: ")) {
374 name
= line
.substring("ref: ".length());
375 continue REF_READING
;
376 } else if (ObjectId
.isId(line
)) {
377 return new Ref(f
, new ObjectId(line
));
379 throw new IOException("Not a ref: " + name
+ ": " + line
);
383 } while (depth
++ < 5);
384 throw new IOException("Exceed maximum ref depth. Circular reference?");
387 public String
toString() {
388 return "Repository[" + getDirectory() + "]";
391 public String
getPatch() throws IOException
{
392 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/current");
393 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
395 return br
.readLine();
401 public String
getBranch() throws IOException
{
402 final File ptr
= new File(getDirectory(),"HEAD");
403 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
410 if (ref
.startsWith("ref: "))
411 ref
= ref
.substring(5);
412 if (ref
.startsWith("refs/heads/"))
413 ref
= ref
.substring(11);
417 public Collection
getBranches() {
418 return listFilesRecursively(new File(refsDir
, "heads"), null);
421 public Collection
getTags() {
422 return listFilesRecursively(new File(refsDir
, "tags"), null);
426 * @return true if HEAD points to a StGit patch.
428 public boolean isStGitMode() {
430 File file
= new File(getDirectory(), "HEAD");
431 BufferedReader reader
= new BufferedReader(new FileReader(file
));
432 String string
= reader
.readLine();
433 if (!string
.startsWith("ref: refs/heads/"))
435 String branch
= string
.substring("ref: refs/heads/".length());
436 File currentPatch
= new File(new File(new File(getDirectory(),
437 "patches"), branch
), "current");
438 if (!currentPatch
.exists())
440 if (currentPatch
.length() == 0)
444 } catch (IOException e
) {
450 private Collection
listFilesRecursively(File root
, File start
) {
453 Collection ret
= new ArrayList();
454 File
[] files
= start
.listFiles();
455 for (int i
= 0; i
< files
.length
; ++i
) {
456 if (files
[i
].isDirectory())
457 ret
.addAll(listFilesRecursively(root
, files
[i
]));
458 else if (files
[i
].length() == 41) {
459 String name
= files
[i
].toString().substring(
460 root
.toString().length() + 1);