2 * Copyright (C) 2008 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
;
20 import java
.io
.EOFException
;
22 import java
.io
.FileInputStream
;
23 import java
.io
.FileNotFoundException
;
24 import java
.io
.IOException
;
25 import java
.io
.InputStreamReader
;
26 import java
.io
.UnsupportedEncodingException
;
27 import java
.util
.HashMap
;
30 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
31 import org
.spearce
.jgit
.util
.FS
;
32 import org
.spearce
.jgit
.util
.NB
;
35 private static final String CHAR_ENC
= Constants
.CHARACTER_ENCODING
;
37 private static final String REFS_SLASH
= "refs/";
39 private static final String HEADS_SLASH
= Constants
.HEADS_PREFIX
+ "/";
41 private static final String TAGS_SLASH
= Constants
.TAGS_PREFIX
+ "/";
43 private static final String
[] refSearchPaths
= { "", REFS_SLASH
,
44 TAGS_SLASH
, HEADS_SLASH
, Constants
.REMOTES_PREFIX
+ "/" };
46 private final Repository db
;
48 private final File gitDir
;
50 private final File refsDir
;
52 private Map
<String
, CachedRef
> looseRefs
;
54 private final File packedRefsFile
;
56 private Map
<String
, Ref
> packedRefs
;
58 private long packedRefsLastModified
;
60 private long packedRefsLength
;
62 RefDatabase(final Repository r
) {
64 gitDir
= db
.getDirectory();
65 refsDir
= FS
.resolve(gitDir
, "refs");
66 packedRefsFile
= FS
.resolve(gitDir
, "packed-refs");
71 looseRefs
= new HashMap
<String
, CachedRef
>();
72 packedRefs
= new HashMap
<String
, Ref
>();
73 packedRefsLastModified
= 0;
77 Repository
getRepository() {
83 new File(refsDir
, "heads").mkdir();
84 new File(refsDir
, "tags").mkdir();
87 ObjectId
idOf(final String name
) throws IOException
{
88 final Ref r
= readRefBasic(name
, 0);
89 return r
!= null ? r
.getObjectId() : null;
93 * Create a command to update (or create) a ref in this repository.
96 * name of the ref the caller wants to modify.
97 * @return an update command. The caller must finish populating this command
98 * and then invoke one of the update methods to actually make a
100 * @throws IOException
101 * a symbolic ref was passed in and could not be resolved back
102 * to the base ref, as the symbolic ref could not be read.
104 RefUpdate
newUpdate(final String name
) throws IOException
{
105 Ref r
= readRefBasic(name
, 0);
107 r
= new Ref(name
, null);
108 return new RefUpdate(this, r
, fileForRef(r
.getName()));
111 void stored(final String name
, final ObjectId id
, final long time
) {
112 looseRefs
.put(name
, new CachedRef(name
, id
, time
));
116 * Writes a symref (e.g. HEAD) to disk
122 * @throws IOException
124 void link(final String name
, final String target
) throws IOException
{
125 final byte[] content
= ("ref: " + target
+ "\n").getBytes(CHAR_ENC
);
126 final LockFile lck
= new LockFile(fileForRef(name
));
128 throw new ObjectWritingException("Unable to lock " + name
);
131 } catch (IOException ioe
) {
132 throw new ObjectWritingException("Unable to write " + name
, ioe
);
135 throw new ObjectWritingException("Unable to write " + name
);
138 Ref
readRef(final String partialName
) throws IOException
{
140 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
141 final Ref r
= readRefBasic(refSearchPaths
[k
] + partialName
, 0);
142 if (r
!= null && r
.getObjectId() != null)
149 * @return all known refs (heads, tags, remotes).
151 Map
<String
, Ref
> getAllRefs() {
156 * @return all tags; key is short tag name ("v1.0") and value of the entry
157 * contains the ref with the full tag name ("refs/tags/v1.0").
159 Map
<String
, Ref
> getTags() {
160 final Map
<String
, Ref
> tags
= new HashMap
<String
, Ref
>();
161 for (final Ref r
: readRefs().values()) {
162 if (r
.getName().startsWith(TAGS_SLASH
))
163 tags
.put(r
.getName().substring(TAGS_SLASH
.length()), r
);
168 private Map
<String
, Ref
> readRefs() {
169 final HashMap
<String
, Ref
> avail
= new HashMap
<String
, Ref
>();
170 readPackedRefs(avail
);
171 readLooseRefs(avail
, REFS_SLASH
, refsDir
);
172 readOneLooseRef(avail
, Constants
.HEAD
, new File(gitDir
, Constants
.HEAD
));
176 private void readPackedRefs(final Map
<String
, Ref
> avail
) {
178 avail
.putAll(packedRefs
);
181 private void readLooseRefs(final Map
<String
, Ref
> avail
,
182 final String prefix
, final File dir
) {
183 final File
[] entries
= dir
.listFiles();
187 for (final File ent
: entries
) {
188 final String entName
= ent
.getName();
189 if (".".equals(entName
) || "..".equals(entName
))
191 readOneLooseRef(avail
, prefix
+ entName
, ent
);
195 private void readOneLooseRef(final Map
<String
, Ref
> avail
,
196 final String refName
, final File ent
) {
197 // Unchanged and cached? Don't read it again.
199 CachedRef ref
= looseRefs
.get(refName
);
201 if (ref
.lastModified
== ent
.lastModified()) {
202 avail
.put(ref
.getName(), ref
);
205 looseRefs
.remove(refName
);
208 // Recurse into the directory.
210 if (ent
.isDirectory()) {
211 readLooseRefs(avail
, refName
+ "/", ent
);
215 // Assume its a valid loose reference we need to cache.
218 final FileInputStream in
= new FileInputStream(ent
);
222 final byte[] str
= new byte[Constants
.OBJECT_ID_LENGTH
* 2];
223 NB
.readFully(in
, str
, 0, str
.length
);
224 id
= ObjectId
.fromString(str
, 0);
225 } catch (EOFException tooShortToBeRef
) {
226 // Its below the minimum length needed. It could
227 // be a symbolic reference.
230 } catch (IllegalArgumentException notRef
) {
231 // It is not a well-formed ObjectId. It may be
232 // a symbolic reference ("ref: ").
237 ref
= new CachedRef(refName
, id
, ent
.lastModified());
238 looseRefs
.put(ref
.getName(), ref
);
239 avail
.put(ref
.getName(), ref
);
243 } catch (FileNotFoundException noFile
) {
244 // Deleted while we were reading? Its gone now!
246 } catch (IOException err
) {
249 throw new RuntimeException("Cannot read ref " + ent
, err
);
253 private File
fileForRef(final String name
) {
254 if (name
.startsWith(REFS_SLASH
))
255 return new File(refsDir
, name
.substring(REFS_SLASH
.length()));
256 return new File(gitDir
, name
);
259 private Ref
readRefBasic(final String name
, final int depth
)
261 // Prefer loose ref to packed ref as the loose
262 // file can be more up-to-date than a packed one.
264 CachedRef ref
= looseRefs
.get(name
);
265 final File loose
= fileForRef(name
);
266 final long mtime
= loose
.lastModified();
268 if (ref
.lastModified
== mtime
)
270 looseRefs
.remove(name
);
274 // If last modified is 0 the file does not exist.
277 return packedRefs
.get(name
);
282 line
= readLine(loose
);
283 } catch (FileNotFoundException notLoose
) {
284 return packedRefs
.get(name
);
287 if (line
== null || line
.length() == 0)
288 return new Ref(name
, null);
290 if (line
.startsWith("ref: ")) {
292 throw new IOException("Exceeded maximum ref depth of " + depth
293 + " at " + name
+ ". Circular reference?");
296 final String target
= line
.substring("ref: ".length());
297 final Ref r
= readRefBasic(target
, depth
+ 1);
298 return r
!= null ? r
: new Ref(target
, null);
303 id
= ObjectId
.fromString(line
);
304 } catch (IllegalArgumentException notRef
) {
305 throw new IOException("Not a ref: " + name
+ ": " + line
);
308 ref
= new CachedRef(name
, id
, mtime
);
309 looseRefs
.put(name
, ref
);
313 private void refreshPackedRefs() {
314 final long currTime
= packedRefsFile
.lastModified();
315 final long currLen
= currTime
== 0 ?
0 : packedRefsFile
.length();
316 if (currTime
== packedRefsLastModified
&& currLen
== packedRefsLength
)
319 packedRefsLastModified
= 0;
320 packedRefsLength
= 0;
321 packedRefs
= new HashMap
<String
, Ref
>();
325 final Map
<String
, Ref
> newPackedRefs
= new HashMap
<String
, Ref
>();
327 final BufferedReader b
= openReader(packedRefsFile
);
331 while ((p
= b
.readLine()) != null) {
332 if (p
.charAt(0) == '#')
335 if (p
.charAt(0) == '^') {
337 throw new IOException("Peeled line before ref.");
339 final ObjectId id
= ObjectId
.fromString(p
.substring(1));
340 last
= new Ref(last
.getName(), last
.getObjectId(), id
);
341 newPackedRefs
.put(last
.getName(), last
);
345 final int sp
= p
.indexOf(' ');
346 final ObjectId id
= ObjectId
.fromString(p
.substring(0, sp
));
347 final String name
= new String(p
.substring(sp
+ 1));
348 last
= new Ref(name
, id
);
349 newPackedRefs
.put(last
.getName(), last
);
354 packedRefsLastModified
= currTime
;
355 packedRefsLength
= currLen
;
356 packedRefs
= newPackedRefs
;
357 } catch (FileNotFoundException noPackedRefs
) {
358 // Ignore it and leave the new map empty.
360 packedRefsLastModified
= 0;
361 packedRefsLength
= 0;
362 packedRefs
= newPackedRefs
;
363 } catch (IOException e
) {
364 throw new RuntimeException("Cannot read packed refs", e
);
368 private static String
readLine(final File file
)
369 throws FileNotFoundException
, IOException
{
370 final BufferedReader br
= openReader(file
);
372 return br
.readLine();
378 private static BufferedReader
openReader(final File fileLocation
)
379 throws UnsupportedEncodingException
, FileNotFoundException
{
380 return new BufferedReader(new InputStreamReader(new FileInputStream(
381 fileLocation
), CHAR_ENC
));
384 private static class CachedRef
extends Ref
{
385 final long lastModified
;
387 CachedRef(final String refName
, final ObjectId id
, final long mtime
) {
389 lastModified
= mtime
;