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
;
30 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 treeCache
= new WeakHashMap(30000);
54 private Map commitCache
= new WeakHashMap(30000);
56 public Repository(final File d
) throws IOException
{
57 gitDir
= d
.getAbsoluteFile();
59 objectsDirs
= (File
[])readObjectsDirs(new File(gitDir
, "objects"), new ArrayList()).toArray(new File
[0]);
60 } catch (IOException e
) {
61 IOException ex
= new IOException("Cannot find all object dirs for " + gitDir
);
65 refsDir
= new File(gitDir
, "refs");
66 packs
= new PackFile
[0];
67 config
= new RepositoryConfig(this);
68 if (objectsDirs
[0].exists()) {
70 final String repositoryFormatVersion
= getConfig().getString(
71 "core", "repositoryFormatVersion");
72 if (!"0".equals(repositoryFormatVersion
)) {
73 throw new IOException("Unknown repository format \""
74 + repositoryFormatVersion
+ "\"; expected \"0\".");
76 initializeWindowCache();
81 private Collection
readObjectsDirs(File objectsDir
, Collection ret
) throws IOException
{
83 File alternatesFile
= new File(objectsDir
,"info/alternates");
84 if (alternatesFile
.exists()) {
85 BufferedReader ar
= new BufferedReader(new FileReader(alternatesFile
));
86 for (String alt
=ar
.readLine(); alt
!=null; alt
=ar
.readLine()) {
87 readObjectsDirs(new File(alt
), ret
);
94 public void create() throws IOException
{
95 if (gitDir
.exists()) {
96 throw new IllegalStateException("Repository already exists: "
102 objectsDirs
[0].mkdirs();
103 new File(objectsDirs
[0], "pack").mkdir();
104 new File(objectsDirs
[0], "info").mkdir();
107 new File(refsDir
, "heads").mkdir();
108 new File(refsDir
, "tags").mkdir();
110 new File(gitDir
, "branches").mkdir();
111 new File(gitDir
, "remotes").mkdir();
112 writeSymref("HEAD", "refs/heads/master");
114 getConfig().create();
116 initializeWindowCache();
119 private void initializeWindowCache() {
120 // FIXME these should be configurable...
121 windows
= new WindowCache(256 * 1024 * 1024, 4);
124 public File
getDirectory() {
128 public File
getObjectsDirectory() {
129 return objectsDirs
[0];
132 public RepositoryConfig
getConfig() {
136 public WindowCache
getWindowCache() {
140 public File
toFile(final ObjectId objectId
) {
141 final String n
= objectId
.toString();
142 String d
=n
.substring(0, 2);
143 String f
=n
.substring(2);
144 for (int i
=0; i
<objectsDirs
.length
; ++i
) {
145 File ret
= new File(new File(objectsDirs
[i
], d
), f
);
149 return new File(new File(objectsDirs
[0], d
), f
);
152 public boolean hasObject(final ObjectId objectId
) {
153 int k
= packs
.length
;
155 final byte[] tmp
= new byte[Constants
.OBJECT_ID_LENGTH
];
158 if (packs
[--k
].hasObject(objectId
, tmp
))
160 } catch (IOException ioe
) {
161 // This shouldn't happen unless the pack was corrupted
162 // after we opened it. We'll ignore the error as though
163 // the object does not exist in this pack.
168 return toFile(objectId
).isFile();
171 public ObjectLoader
openObject(final ObjectId id
) throws IOException
{
172 int k
= packs
.length
;
174 final byte[] tmp
= new byte[Constants
.OBJECT_ID_LENGTH
];
177 final ObjectLoader ol
= packs
[--k
].get(id
, tmp
);
180 } catch (IOException ioe
) {
181 // This shouldn't happen unless the pack was corrupted
182 // after we opened it or the VM runs out of memory. This is
183 // a know problem with memory mapped I/O in java and have
184 // been noticed with JDK < 1.6. Tell the gc that now is a good
185 // time to collect and try once more.
188 final ObjectLoader ol
= packs
[k
].get(id
, tmp
);
191 } catch (IOException ioe2
) {
192 ioe2
.printStackTrace();
193 ioe
.printStackTrace();
194 // Still fails.. that's BAD, maybe the pack has
195 // been corrupted after all, or the gc didn't manage
196 // to release enough previously mmaped areas.
202 return new UnpackedObjectLoader(this, id
);
203 } catch (FileNotFoundException fnfe
) {
208 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
209 return openObject(id
);
212 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
213 return openObject(id
);
216 public Commit
mapCommit(final String revstr
) throws IOException
{
217 final ObjectId id
= resolve(revstr
);
218 return id
!= null ?
mapCommit(id
) : null;
221 public Commit
mapCommit(final ObjectId id
) throws IOException
{
222 // System.out.println("commitcache.size="+commitCache.size());
223 Reference retr
= (Reference
)commitCache
.get(id
);
225 Commit ret
= (Commit
)retr
.get();
228 System
.out
.println("Found a null id, size was "+commitCache
.size());
231 final ObjectLoader or
= openObject(id
);
234 final byte[] raw
= or
.getBytes();
235 if (Constants
.TYPE_COMMIT
.equals(or
.getType())) {
236 Commit ret
= new Commit(this, id
, raw
);
237 // The key must not be the referenced strongly
238 // by the value in WeakHashMaps
239 commitCache
.put(id
, new SoftReference(ret
));
242 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_COMMIT
);
245 public Tree
mapTree(final String revstr
) throws IOException
{
246 final ObjectId id
= resolve(revstr
);
247 return id
!= null ?
mapTree(id
) : null;
250 public Tree
mapTree(final ObjectId id
) throws IOException
{
251 Reference wret
= (Reference
)treeCache
.get(id
);
253 Tree ret
= (Tree
)wret
.get();
258 final ObjectLoader or
= openObject(id
);
261 final byte[] raw
= or
.getBytes();
262 if (Constants
.TYPE_TREE
.equals(or
.getType())) {
263 Tree ret
= new Tree(this, id
, raw
);
264 treeCache
.put(id
, new SoftReference(ret
));
267 if (Constants
.TYPE_COMMIT
.equals(or
.getType()))
268 return mapTree(ObjectId
.fromString(raw
, 5));
269 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TREE
);
272 public Tag
mapTag(String revstr
) throws IOException
{
273 final ObjectId id
= resolve(revstr
);
274 return id
!= null ?
mapTag(id
) : null;
277 public Tag
mapTag(final ObjectId id
) throws IOException
{
278 final ObjectLoader or
= openObject(id
);
281 final byte[] raw
= or
.getBytes();
282 if (Constants
.TYPE_TAG
.equals(or
.getType()))
283 return new Tag(this, id
, raw
);
284 throw new IncorrectObjectTypeException(id
, Constants
.TYPE_TAG
);
287 public RefLock
lockRef(final String ref
) throws IOException
{
288 final RefLock l
= new RefLock(readRef(ref
, true));
289 return l
.lock() ? l
: null;
292 public ObjectId
resolve(final String revstr
) throws IOException
{
295 if (ObjectId
.isId(revstr
)) {
296 id
= new ObjectId(revstr
);
300 final Ref r
= readRef(revstr
, false);
302 id
= r
.getObjectId();
309 public void close() throws IOException
{
313 public void closePacks() throws IOException
{
314 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
317 packs
= new PackFile
[0];
320 public void scanForPacks() {
321 final ArrayList p
= new ArrayList();
322 for (int i
=0; i
<objectsDirs
.length
; ++i
)
323 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
324 final PackFile
[] arr
= new PackFile
[p
.size()];
329 public void scanForPacks(final File packDir
, Collection packList
) {
330 final File
[] list
= packDir
.listFiles(new FileFilter() {
331 public boolean accept(final File f
) {
332 final String n
= f
.getName();
333 if (!n
.endsWith(".pack")) {
336 final String nBase
= n
.substring(0, n
.lastIndexOf('.'));
337 final File idx
= new File(packDir
, nBase
+ ".idx");
338 return f
.isFile() && f
.canRead() && idx
.isFile()
342 for (int k
= 0; k
< list
.length
; k
++) {
344 packList
.add(new PackFile(this, list
[k
]));
345 } catch (IOException ioe
) {
346 // Whoops. That's not a pack!
352 private void writeSymref(final String name
, final String target
)
354 final File s
= new File(gitDir
, name
);
355 final File t
= File
.createTempFile("srf", null, gitDir
);
356 FileWriter w
= new FileWriter(t
);
363 if (!t
.renameTo(s
)) {
364 s
.getParentFile().mkdirs();
365 if (!t
.renameTo(s
)) {
367 throw new ObjectWritingException("Unable to"
368 + " write symref " + name
+ " to point to "
380 private Ref
readRef(final String revstr
, final boolean missingOk
)
382 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
383 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
384 if (missingOk
|| r
.getObjectId() != null) {
391 private Ref
readRefBasic(String name
) throws IOException
{
394 final File f
= new File(getDirectory(), name
);
396 return new Ref(f
, null);
399 final BufferedReader br
= new BufferedReader(new FileReader(f
));
401 final String line
= br
.readLine();
402 if (line
== null || line
.length() == 0) {
403 return new Ref(f
, null);
404 } else if (line
.startsWith("ref: ")) {
405 name
= line
.substring("ref: ".length());
406 continue REF_READING
;
407 } else if (ObjectId
.isId(line
)) {
408 return new Ref(f
, new ObjectId(line
));
410 throw new IOException("Not a ref: " + name
+ ": " + line
);
414 } while (depth
++ < 5);
415 throw new IOException("Exceed maximum ref depth. Circular reference?");
418 public String
toString() {
419 return "Repository[" + getDirectory() + "]";
422 public String
getPatch() throws IOException
{
423 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/current");
424 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
426 return br
.readLine();
432 public String
getBranch() throws IOException
{
433 final File ptr
= new File(getDirectory(),"HEAD");
434 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
441 if (ref
.startsWith("ref: "))
442 ref
= ref
.substring(5);
443 if (ref
.startsWith("refs/heads/"))
444 ref
= ref
.substring(11);
448 public Collection
getBranches() {
449 return listFilesRecursively(new File(refsDir
, "heads"), null);
452 public Collection
getTags() {
453 return listFilesRecursively(new File(refsDir
, "tags"), null);
457 * @return true if HEAD points to a StGit patch.
459 public boolean isStGitMode() {
461 File file
= new File(getDirectory(), "HEAD");
462 BufferedReader reader
= new BufferedReader(new FileReader(file
));
463 String string
= reader
.readLine();
464 if (!string
.startsWith("ref: refs/heads/"))
466 String branch
= string
.substring("ref: refs/heads/".length());
467 File currentPatch
= new File(new File(new File(getDirectory(),
468 "patches"), branch
), "current");
469 if (!currentPatch
.exists())
471 if (currentPatch
.length() == 0)
475 } catch (IOException e
) {
481 public static class StGitPatch
{
482 public StGitPatch(String patchName
, ObjectId id
) {
486 public ObjectId
getGitId() {
489 public String
getName() {
493 private ObjectId gitId
;
497 * @return applied patches in a map indexed on current commit id
498 * @throws IOException
500 public Map
getAppliedPatches() throws IOException
{
501 Map ret
= new HashMap();
503 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
504 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
505 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
506 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
507 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
508 String objectId
= tfr
.readLine();
509 ObjectId id
= new ObjectId(objectId
);
510 ret
.put(id
, new StGitPatch(patchName
, id
));
518 private Collection
listFilesRecursively(File root
, File start
) {
521 Collection ret
= new ArrayList();
522 File
[] files
= start
.listFiles();
523 for (int i
= 0; i
< files
.length
; ++i
) {
524 if (files
[i
].isDirectory())
525 ret
.addAll(listFilesRecursively(root
, files
[i
]));
526 else if (files
[i
].length() == 41) {
527 String name
= files
[i
].toString().substring(
528 root
.toString().length() + 1);