2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org
.spearce
.jgit
.lib
;
41 import java
.io
.BufferedReader
;
42 import java
.io
.EOFException
;
44 import java
.io
.FileInputStream
;
45 import java
.io
.FileNotFoundException
;
46 import java
.io
.IOException
;
47 import java
.io
.InputStreamReader
;
48 import java
.io
.UnsupportedEncodingException
;
49 import java
.util
.HashMap
;
52 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
53 import org
.spearce
.jgit
.util
.FS
;
54 import org
.spearce
.jgit
.util
.NB
;
57 private static final String CHAR_ENC
= Constants
.CHARACTER_ENCODING
;
59 private static final String REFS_SLASH
= "refs/";
61 private static final String HEADS_SLASH
= Constants
.HEADS_PREFIX
+ "/";
63 private static final String TAGS_SLASH
= Constants
.TAGS_PREFIX
+ "/";
65 private static final String
[] refSearchPaths
= { "", REFS_SLASH
,
66 TAGS_SLASH
, HEADS_SLASH
, Constants
.REMOTES_PREFIX
+ "/" };
68 private final Repository db
;
70 private final File gitDir
;
72 private final File refsDir
;
74 private Map
<String
, Ref
> looseRefs
;
75 private Map
<String
, Long
> looseRefsMTime
;
77 private final File packedRefsFile
;
79 private Map
<String
, Ref
> packedRefs
;
81 private long packedRefsLastModified
;
83 private long packedRefsLength
;
85 long lastRefModification
;
87 long lastNotifiedRefModification
;
89 static int refModificationCounter
;
91 RefDatabase(final Repository r
) {
93 gitDir
= db
.getDirectory();
94 refsDir
= FS
.resolve(gitDir
, "refs");
95 packedRefsFile
= FS
.resolve(gitDir
, "packed-refs");
100 looseRefs
= new HashMap
<String
, Ref
>();
101 looseRefsMTime
= new HashMap
<String
, Long
>();
102 packedRefs
= new HashMap
<String
, Ref
>();
103 packedRefsLastModified
= 0;
104 packedRefsLength
= 0;
107 Repository
getRepository() {
113 new File(refsDir
, "heads").mkdir();
114 new File(refsDir
, "tags").mkdir();
117 ObjectId
idOf(final String name
) throws IOException
{
118 final Ref r
= readRefBasic(name
, 0);
119 return r
!= null ? r
.getObjectId() : null;
123 * Create a command to update (or create) a ref in this repository.
126 * name of the ref the caller wants to modify.
127 * @return an update command. The caller must finish populating this command
128 * and then invoke one of the update methods to actually make a
130 * @throws IOException
131 * a symbolic ref was passed in and could not be resolved back
132 * to the base ref, as the symbolic ref could not be read.
134 RefUpdate
newUpdate(final String name
) throws IOException
{
135 Ref r
= readRefBasic(name
, 0);
137 r
= new Ref(Ref
.Storage
.NEW
, name
, null);
138 return new RefUpdate(this, r
, fileForRef(r
.getName()));
141 void stored(final String name
, final ObjectId id
, final long time
) {
142 looseRefs
.put(name
, new Ref(Ref
.Storage
.LOOSE
, name
, id
));
143 looseRefsMTime
.put(name
, time
);
145 db
.fireRefsMaybeChanged();
149 * Writes a symref (e.g. HEAD) to disk
155 * @throws IOException
157 void link(final String name
, final String target
) throws IOException
{
158 final byte[] content
= ("ref: " + target
+ "\n").getBytes(CHAR_ENC
);
159 final LockFile lck
= new LockFile(fileForRef(name
));
161 throw new ObjectWritingException("Unable to lock " + name
);
164 } catch (IOException ioe
) {
165 throw new ObjectWritingException("Unable to write " + name
, ioe
);
168 throw new ObjectWritingException("Unable to write " + name
);
170 db
.fireRefsMaybeChanged();
174 lastRefModification
= refModificationCounter
++;
177 Ref
readRef(final String partialName
) throws IOException
{
179 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
180 final Ref r
= readRefBasic(refSearchPaths
[k
] + partialName
, 0);
181 if (r
!= null && r
.getObjectId() != null)
188 * @return all known refs (heads, tags, remotes).
190 Map
<String
, Ref
> getAllRefs() {
195 * @return all tags; key is short tag name ("v1.0") and value of the entry
196 * contains the ref with the full tag name ("refs/tags/v1.0").
198 Map
<String
, Ref
> getTags() {
199 final Map
<String
, Ref
> tags
= new HashMap
<String
, Ref
>();
200 for (final Ref r
: readRefs().values()) {
201 if (r
.getName().startsWith(TAGS_SLASH
))
202 tags
.put(r
.getName().substring(TAGS_SLASH
.length()), r
);
207 private Map
<String
, Ref
> readRefs() {
208 final HashMap
<String
, Ref
> avail
= new HashMap
<String
, Ref
>();
209 readPackedRefs(avail
);
210 readLooseRefs(avail
, REFS_SLASH
, refsDir
);
212 avail
.put(Constants
.HEAD
, readRefBasic(Constants
.HEAD
, 0));
213 } catch (IOException e
) {
216 db
.fireRefsMaybeChanged();
220 private void readPackedRefs(final Map
<String
, Ref
> avail
) {
222 avail
.putAll(packedRefs
);
225 private void readLooseRefs(final Map
<String
, Ref
> avail
,
226 final String prefix
, final File dir
) {
227 final File
[] entries
= dir
.listFiles();
231 for (final File ent
: entries
) {
232 final String entName
= ent
.getName();
233 if (".".equals(entName
) || "..".equals(entName
))
235 readOneLooseRef(avail
, prefix
+ entName
, ent
);
239 private void readOneLooseRef(final Map
<String
, Ref
> avail
,
240 final String refName
, final File ent
) {
241 // Unchanged and cached? Don't read it again.
243 Ref ref
= looseRefs
.get(refName
);
245 Long cachedlastModified
= looseRefsMTime
.get(refName
);
246 if (cachedlastModified
!= null && cachedlastModified
== ent
.lastModified()) {
247 avail
.put(ref
.getName(), ref
);
250 looseRefs
.remove(refName
);
251 looseRefsMTime
.remove(refName
);
254 // Recurse into the directory.
256 if (ent
.isDirectory()) {
257 readLooseRefs(avail
, refName
+ "/", ent
);
261 // Assume its a valid loose reference we need to cache.
264 final FileInputStream in
= new FileInputStream(ent
);
268 final byte[] str
= new byte[Constants
.OBJECT_ID_LENGTH
* 2];
269 NB
.readFully(in
, str
, 0, str
.length
);
270 id
= ObjectId
.fromString(str
, 0);
271 } catch (EOFException tooShortToBeRef
) {
272 // Its below the minimum length needed. It could
273 // be a symbolic reference.
276 } catch (IllegalArgumentException notRef
) {
277 // It is not a well-formed ObjectId. It may be
278 // a symbolic reference ("ref: ").
283 ref
= new Ref(Ref
.Storage
.LOOSE
, refName
, id
);
284 looseRefs
.put(ref
.getName(), ref
);
285 looseRefsMTime
.put(ref
.getName(), ent
.lastModified());
286 avail
.put(ref
.getName(), ref
);
290 } catch (FileNotFoundException noFile
) {
291 // Deleted while we were reading? Its gone now!
293 } catch (IOException err
) {
296 throw new RuntimeException("Cannot read ref " + ent
, err
);
300 private File
fileForRef(final String name
) {
301 if (name
.startsWith(REFS_SLASH
))
302 return new File(refsDir
, name
.substring(REFS_SLASH
.length()));
303 return new File(gitDir
, name
);
306 private Ref
readRefBasic(final String name
, final int depth
)
308 // Prefer loose ref to packed ref as the loose
309 // file can be more up-to-date than a packed one.
311 Ref ref
= looseRefs
.get(name
);
312 final File loose
= fileForRef(name
);
313 final long mtime
= loose
.lastModified();
315 Long cachedlastModified
= looseRefsMTime
.get(name
);
316 if (cachedlastModified
!= null && cachedlastModified
== mtime
)
318 looseRefs
.remove(name
);
319 looseRefsMTime
.remove(name
);
323 // If last modified is 0 the file does not exist.
326 return packedRefs
.get(name
);
331 line
= readLine(loose
);
332 } catch (FileNotFoundException notLoose
) {
333 return packedRefs
.get(name
);
336 if (line
== null || line
.length() == 0)
337 return new Ref(Ref
.Storage
.LOOSE
, name
, null);
339 if (line
.startsWith("ref: ")) {
341 throw new IOException("Exceeded maximum ref depth of " + depth
342 + " at " + name
+ ". Circular reference?");
345 final String target
= line
.substring("ref: ".length());
346 final Ref r
= readRefBasic(target
, depth
+ 1);
347 Long cachedMtime
= looseRefsMTime
.get(name
);
348 if (cachedMtime
!= null && cachedMtime
!= mtime
)
350 looseRefsMTime
.put(name
, mtime
);
351 return r
!= null ? r
: new Ref(Ref
.Storage
.LOOSE
, target
, null);
358 id
= ObjectId
.fromString(line
);
359 } catch (IllegalArgumentException notRef
) {
360 throw new IOException("Not a ref: " + name
+ ": " + line
);
363 ref
= new Ref(Ref
.Storage
.LOOSE
, name
, id
);
364 looseRefs
.put(name
, ref
);
365 looseRefsMTime
.put(name
, mtime
);
369 private void refreshPackedRefs() {
370 final long currTime
= packedRefsFile
.lastModified();
371 final long currLen
= currTime
== 0 ?
0 : packedRefsFile
.length();
372 if (currTime
== packedRefsLastModified
&& currLen
== packedRefsLength
)
375 packedRefsLastModified
= 0;
376 packedRefsLength
= 0;
377 packedRefs
= new HashMap
<String
, Ref
>();
381 final Map
<String
, Ref
> newPackedRefs
= new HashMap
<String
, Ref
>();
383 final BufferedReader b
= openReader(packedRefsFile
);
387 while ((p
= b
.readLine()) != null) {
388 if (p
.charAt(0) == '#')
391 if (p
.charAt(0) == '^') {
393 throw new IOException("Peeled line before ref.");
395 final ObjectId id
= ObjectId
.fromString(p
.substring(1));
396 last
= new Ref(Ref
.Storage
.PACKED
, last
.getName(), last
398 newPackedRefs
.put(last
.getName(), last
);
402 final int sp
= p
.indexOf(' ');
403 final ObjectId id
= ObjectId
.fromString(p
.substring(0, sp
));
404 final String name
= new String(p
.substring(sp
+ 1));
405 last
= new Ref(Ref
.Storage
.PACKED
, name
, id
);
406 newPackedRefs
.put(last
.getName(), last
);
411 packedRefsLastModified
= currTime
;
412 packedRefsLength
= currLen
;
413 packedRefs
= newPackedRefs
;
415 } catch (FileNotFoundException noPackedRefs
) {
416 // Ignore it and leave the new map empty.
418 packedRefsLastModified
= 0;
419 packedRefsLength
= 0;
420 packedRefs
= newPackedRefs
;
421 } catch (IOException e
) {
422 throw new RuntimeException("Cannot read packed refs", e
);
426 private static String
readLine(final File file
)
427 throws FileNotFoundException
, IOException
{
428 final BufferedReader br
= openReader(file
);
430 return br
.readLine();
436 private static BufferedReader
openReader(final File fileLocation
)
437 throws UnsupportedEncodingException
, FileNotFoundException
{
438 return new BufferedReader(new InputStreamReader(new FileInputStream(
439 fileLocation
), CHAR_ENC
));