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
;
156 if (packs
[--k
].hasObject(objectId
))
160 return toFile(objectId
).isFile();
163 public ObjectLoader
openObject(final ObjectId id
) throws IOException
{
164 int k
= packs
.length
;
168 final ObjectLoader ol
= packs
[--k
].get(id
);
171 } catch (IOException ioe
) {
172 // This shouldn't happen unless the pack was corrupted
173 // after we opened it or the VM runs out of memory. This is
174 // a know problem with memory mapped I/O in java and have
175 // been noticed with JDK < 1.6. Tell the gc that now is a good
176 // time to collect and try once more.
179 final ObjectLoader ol
= packs
[k
].get(id
);
182 } catch (IOException ioe2
) {
183 ioe2
.printStackTrace();
184 ioe
.printStackTrace();
185 // Still fails.. that's BAD, maybe the pack has
186 // been corrupted after all, or the gc didn't manage
187 // to release enough previously mmaped areas.
193 return new UnpackedObjectLoader(this, id
);
194 } catch (FileNotFoundException fnfe
) {
199 public ObjectLoader
openBlob(final ObjectId id
) throws IOException
{
200 return openObject(id
);
203 public ObjectLoader
openTree(final ObjectId id
) throws IOException
{
204 return openObject(id
);
207 public Commit
mapCommit(final String revstr
) throws IOException
{
208 final ObjectId id
= resolve(revstr
);
209 return id
!= null ?
mapCommit(id
) : null;
212 public Commit
mapCommit(final ObjectId id
) throws IOException
{
213 // System.out.println("commitcache.size="+commitCache.size());
214 Reference retr
= (Reference
)commitCache
.get(id
);
216 Commit ret
= (Commit
)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(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 wret
= (Reference
)treeCache
.get(id
);
244 Tree ret
= (Tree
)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(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 RefLock l
= new RefLock(readRef(ref
, true));
280 return l
.lock() ? l
: null;
283 public ObjectId
resolve(final String revstr
) throws IOException
{
286 if (ObjectId
.isId(revstr
)) {
287 id
= new ObjectId(revstr
);
291 final Ref r
= readRef(revstr
, false);
293 id
= r
.getObjectId();
300 public void close() throws IOException
{
304 public void closePacks() throws IOException
{
305 for (int k
= packs
.length
- 1; k
>= 0; k
--) {
308 packs
= new PackFile
[0];
311 public void scanForPacks() {
312 final ArrayList p
= new ArrayList();
313 for (int i
=0; i
<objectsDirs
.length
; ++i
)
314 scanForPacks(new File(objectsDirs
[i
], "pack"), p
);
315 final PackFile
[] arr
= new PackFile
[p
.size()];
320 public void scanForPacks(final File packDir
, Collection packList
) {
321 final File
[] list
= packDir
.listFiles(new FileFilter() {
322 public boolean accept(final File f
) {
323 final String n
= f
.getName();
324 if (!n
.endsWith(".pack")) {
327 final String nBase
= n
.substring(0, n
.lastIndexOf('.'));
328 final File idx
= new File(packDir
, nBase
+ ".idx");
329 return f
.isFile() && f
.canRead() && idx
.isFile()
333 for (int k
= 0; k
< list
.length
; k
++) {
335 packList
.add(new PackFile(this, list
[k
]));
336 } catch (IOException ioe
) {
337 // Whoops. That's not a pack!
343 public void writeRef(String name
, ObjectId id
) throws IOException
{
344 File f
= new File(gitDir
, name
);
345 File t
= File
.createTempFile("ref", null, gitDir
);
346 FileWriter w
= new FileWriter(t
);
348 w
.write(id
.toString());
352 if (!t
.renameTo(f
)) {
353 f
.getParentFile().mkdirs();
354 if (!t
.renameTo(f
)) {
356 if (!t
.renameTo(f
)) {
358 throw new ObjectWritingException("Unable to"
359 + " write ref " + name
+ " to point to "
372 public void writeSymref(final String name
, final String target
)
374 final File s
= new File(gitDir
, name
);
375 final File t
= File
.createTempFile("srf", null, gitDir
);
376 FileWriter w
= new FileWriter(t
);
383 if (!t
.renameTo(s
)) {
384 s
.getParentFile().mkdirs();
385 if (!t
.renameTo(s
)) {
387 throw new ObjectWritingException("Unable to"
388 + " write symref " + name
+ " to point to "
400 private Ref
readRef(final String revstr
, final boolean missingOk
)
402 refreshPackredRefsCache();
403 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
404 final Ref r
= readRefBasic(refSearchPaths
[k
] + revstr
);
405 if (missingOk
|| r
.getObjectId() != null) {
412 private Ref
readRefBasic(String name
) throws IOException
{
415 ObjectId id
= packedRefs
.get(name
);
417 return new Ref(null, id
);
419 final File f
= new File(getDirectory(), name
);
421 return new Ref(f
, null);
424 final BufferedReader br
= new BufferedReader(new FileReader(f
));
426 final String line
= br
.readLine();
427 if (line
== null || line
.length() == 0) {
428 return new Ref(f
, null);
429 } else if (line
.startsWith("ref: ")) {
430 name
= line
.substring("ref: ".length());
431 continue REF_READING
;
432 } else if (ObjectId
.isId(line
)) {
433 return new Ref(f
, new ObjectId(line
));
435 throw new IOException("Not a ref: " + name
+ ": " + line
);
439 } while (depth
++ < 5);
440 throw new IOException("Exceed maximum ref depth. Circular reference?");
443 public String
toString() {
444 return "Repository[" + getDirectory() + "]";
447 public String
getPatch() throws IOException
{
448 final File ptr
= new File(getDirectory(),"patches/"+getBranch()+"/current");
449 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
451 return br
.readLine();
457 public String
getBranch() throws IOException
{
458 final File ptr
= new File(getDirectory(),"HEAD");
459 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
466 if (ref
.startsWith("ref: "))
467 ref
= ref
.substring(5);
468 if (ref
.startsWith("refs/heads/"))
469 ref
= ref
.substring(11);
473 public Collection
<String
> getBranches() {
474 return listFilesRecursively(new File(refsDir
, "heads"), null);
477 public Collection
<String
> getTags() {
478 Collection
<String
> tags
= listFilesRecursively(new File(refsDir
, "tags"), null);
479 refreshPackredRefsCache();
480 tags
.addAll(packedRefs
.keySet());
484 private Map
<String
,ObjectId
> packedRefs
= new HashMap
<String
,ObjectId
>();
485 private long packedrefstime
= 0;
487 private void refreshPackredRefsCache() {
488 File file
= new File(gitDir
, "packed-refs");
489 if (!file
.exists()) {
490 if (packedRefs
.size() > 0)
491 packedRefs
= new HashMap();
494 if (file
.lastModified() == packedrefstime
)
496 Map newPackedRefs
= new HashMap();
498 BufferedReader b
=new BufferedReader(new FileReader(file
));
500 while ((p
= b
.readLine()) != null) {
501 if (p
.charAt(0) == '#')
503 if (p
.charAt(0) == '^') {
506 int spos
= p
.indexOf(' ');
507 ObjectId id
= new ObjectId(p
.substring(0,spos
));
508 String name
= p
.substring(spos
+1);
509 newPackedRefs
.put(name
, id
);
511 } catch (IOException e
) {
514 packedRefs
= newPackedRefs
;
518 * @return true if HEAD points to a StGit patch.
520 public boolean isStGitMode() {
522 File file
= new File(getDirectory(), "HEAD");
523 BufferedReader reader
= new BufferedReader(new FileReader(file
));
524 String string
= reader
.readLine();
525 if (!string
.startsWith("ref: refs/heads/"))
527 String branch
= string
.substring("ref: refs/heads/".length());
528 File currentPatch
= new File(new File(new File(getDirectory(),
529 "patches"), branch
), "current");
530 if (!currentPatch
.exists())
532 if (currentPatch
.length() == 0)
536 } catch (IOException e
) {
542 public static class StGitPatch
{
543 public StGitPatch(String patchName
, ObjectId id
) {
547 public ObjectId
getGitId() {
550 public String
getName() {
554 private ObjectId gitId
;
558 * @return applied patches in a map indexed on current commit id
559 * @throws IOException
561 public Map
getAppliedPatches() throws IOException
{
562 Map ret
= new HashMap();
564 File patchDir
= new File(new File(getDirectory(),"patches"),getBranch());
565 BufferedReader apr
= new BufferedReader(new FileReader(new File(patchDir
,"applied")));
566 for (String patchName
=apr
.readLine(); patchName
!=null; patchName
=apr
.readLine()) {
567 File topFile
= new File(new File(new File(patchDir
,"patches"), patchName
), "top");
568 BufferedReader tfr
= new BufferedReader(new FileReader(topFile
));
569 String objectId
= tfr
.readLine();
570 ObjectId id
= new ObjectId(objectId
);
571 ret
.put(id
, new StGitPatch(patchName
, id
));
579 private Collection
<String
> listFilesRecursively(File root
, File start
) {
582 Collection
<String
> ret
= new ArrayList();
583 File
[] files
= start
.listFiles();
584 for (int i
= 0; i
< files
.length
; ++i
) {
585 if (files
[i
].isDirectory())
586 ret
.addAll(listFilesRecursively(root
, files
[i
]));
587 else if (files
[i
].length() == 41) {
588 String name
= files
[i
].toString().substring(
589 root
.toString().length() + 1);
596 /** Clean up stale caches */
597 public void refreshFromDisk() {