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 static org
.spearce
.jgit
.lib
.Constants
.R_TAGS
;
43 import java
.io
.BufferedReader
;
45 import java
.io
.FileInputStream
;
46 import java
.io
.FileNotFoundException
;
47 import java
.io
.IOException
;
48 import java
.io
.InputStreamReader
;
49 import java
.util
.HashMap
;
52 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
53 import org
.spearce
.jgit
.lib
.Ref
.Storage
;
54 import org
.spearce
.jgit
.util
.FS
;
57 private static final String REFS_SLASH
= "refs/";
59 private static final String
[] refSearchPaths
= { "", REFS_SLASH
,
60 R_TAGS
, Constants
.R_HEADS
, Constants
.R_REMOTES
};
62 private final Repository db
;
64 private final File gitDir
;
66 private final File refsDir
;
68 private Map
<String
, Ref
> looseRefs
;
69 private Map
<String
, Long
> looseRefsMTime
;
70 private Map
<String
, String
> looseSymRefs
;
72 private final File packedRefsFile
;
74 private Map
<String
, Ref
> packedRefs
;
76 private long packedRefsLastModified
;
78 private long packedRefsLength
;
80 int lastRefModification
;
82 int lastNotifiedRefModification
;
84 private int refModificationCounter
;
86 RefDatabase(final Repository r
) {
88 gitDir
= db
.getDirectory();
89 refsDir
= FS
.resolve(gitDir
, "refs");
90 packedRefsFile
= FS
.resolve(gitDir
, Constants
.PACKED_REFS
);
94 synchronized void clearCache() {
95 looseRefs
= new HashMap
<String
, Ref
>();
96 looseRefsMTime
= new HashMap
<String
, Long
>();
97 packedRefs
= new HashMap
<String
, Ref
>();
98 looseSymRefs
= new HashMap
<String
, String
>();
99 packedRefsLastModified
= 0;
100 packedRefsLength
= 0;
103 Repository
getRepository() {
109 new File(refsDir
, "heads").mkdir();
110 new File(refsDir
, "tags").mkdir();
113 ObjectId
idOf(final String name
) throws IOException
{
115 final Ref r
= readRefBasic(name
, 0);
116 return r
!= null ? r
.getObjectId() : null;
120 * Create a command to update, create or delete a ref in this repository.
123 * name of the ref the caller wants to modify.
124 * @return an update command. The caller must finish populating this command
125 * and then invoke one of the update methods to actually make a
127 * @throws IOException
128 * a symbolic ref was passed in and could not be resolved back
129 * to the base ref, as the symbolic ref could not be read.
131 RefUpdate
newUpdate(final String name
) throws IOException
{
133 Ref r
= readRefBasic(name
, 0);
135 r
= new Ref(Ref
.Storage
.NEW
, name
, null);
136 return new RefUpdate(this, r
, fileForRef(r
.getName()));
139 void stored(final String origName
, final String name
, final ObjectId id
, final long time
) {
140 synchronized (this) {
141 looseRefs
.put(name
, new Ref(Ref
.Storage
.LOOSE
, origName
, name
, id
));
142 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
= Constants
.encode("ref: " + target
+ "\n");
159 lockAndWriteFile(fileForRef(name
), content
);
160 synchronized (this) {
163 db
.fireRefsMaybeChanged();
166 private void setModified() {
167 lastRefModification
= refModificationCounter
++;
170 Ref
readRef(final String partialName
) throws IOException
{
172 for (int k
= 0; k
< refSearchPaths
.length
; k
++) {
173 final Ref r
= readRefBasic(refSearchPaths
[k
] + partialName
, 0);
174 if (r
!= null && r
.getObjectId() != null)
181 * @return all known refs (heads, tags, remotes).
183 Map
<String
, Ref
> getAllRefs() {
188 * @return all tags; key is short tag name ("v1.0") and value of the entry
189 * contains the ref with the full tag name ("refs/tags/v1.0").
191 Map
<String
, Ref
> getTags() {
192 final Map
<String
, Ref
> tags
= new HashMap
<String
, Ref
>();
193 for (final Ref r
: readRefs().values()) {
194 if (r
.getName().startsWith(R_TAGS
))
195 tags
.put(r
.getName().substring(R_TAGS
.length()), r
);
200 private Map
<String
, Ref
> readRefs() {
201 final HashMap
<String
, Ref
> avail
= new HashMap
<String
, Ref
>();
202 readPackedRefs(avail
);
203 readLooseRefs(avail
, REFS_SLASH
, refsDir
);
205 final Ref r
= readRefBasic(Constants
.HEAD
, 0);
206 if (r
!= null && r
.getObjectId() != null)
207 avail
.put(Constants
.HEAD
, r
);
208 } catch (IOException e
) {
211 db
.fireRefsMaybeChanged();
215 private synchronized void readPackedRefs(final Map
<String
, Ref
> avail
) {
217 avail
.putAll(packedRefs
);
220 private void readLooseRefs(final Map
<String
, Ref
> avail
,
221 final String prefix
, final File dir
) {
222 final File
[] entries
= dir
.listFiles();
226 for (final File ent
: entries
) {
227 final String entName
= ent
.getName();
228 if (".".equals(entName
) || "..".equals(entName
))
230 if (ent
.isDirectory()) {
231 readLooseRefs(avail
, prefix
+ entName
+ "/", ent
);
234 final Ref ref
= readRefBasic(prefix
+ entName
, 0);
236 avail
.put(ref
.getOrigName(), ref
);
237 } catch (IOException e
) {
244 Ref
peel(final Ref ref
) {
247 ObjectId peeled
= null;
249 Object target
= db
.mapObject(ref
.getObjectId(), ref
.getName());
250 while (target
instanceof Tag
) {
251 final Tag tag
= (Tag
)target
;
252 peeled
= tag
.getObjId();
253 if (Constants
.TYPE_TAG
.equals(tag
.getType()))
254 target
= db
.mapObject(tag
.getObjId(), ref
.getName());
258 } catch (IOException e
) {
259 // Ignore a read error. Â Callers will also get the same error
260 // if they try to use the result of getPeeledObjectId.
262 return new Ref(ref
.getStorage(), ref
.getName(), ref
.getObjectId(), peeled
, true);
266 private File
fileForRef(final String name
) {
267 if (name
.startsWith(REFS_SLASH
))
268 return new File(refsDir
, name
.substring(REFS_SLASH
.length()));
269 return new File(gitDir
, name
);
272 private Ref
readRefBasic(final String name
, final int depth
) throws IOException
{
273 return readRefBasic(name
, name
, depth
);
276 private synchronized Ref
readRefBasic(final String origName
,
277 final String name
, final int depth
) throws IOException
{
278 // Prefer loose ref to packed ref as the loose
279 // file can be more up-to-date than a packed one.
281 Ref ref
= looseRefs
.get(origName
);
282 final File loose
= fileForRef(name
);
283 final long mtime
= loose
.lastModified();
285 Long cachedlastModified
= looseRefsMTime
.get(name
);
286 if (cachedlastModified
!= null && cachedlastModified
== mtime
)
288 looseRefs
.remove(origName
);
289 looseRefsMTime
.remove(origName
);
293 // If last modified is 0 the file does not exist.
296 ref
= packedRefs
.get(name
);
298 if (!ref
.getOrigName().equals(origName
))
299 ref
= new Ref(Storage
.LOOSE_PACKED
, origName
, name
, ref
.getObjectId());
305 Long cachedlastModified
= looseRefsMTime
.get(name
);
306 if (cachedlastModified
!= null && cachedlastModified
== mtime
) {
307 line
= looseSymRefs
.get(name
);
310 line
= readLine(loose
);
311 looseRefsMTime
.put(name
, mtime
);
312 looseSymRefs
.put(name
, line
);
314 } catch (FileNotFoundException notLoose
) {
315 return packedRefs
.get(name
);
318 if (line
== null || line
.length() == 0) {
319 looseRefs
.remove(origName
);
320 looseRefsMTime
.remove(origName
);
321 return new Ref(Ref
.Storage
.LOOSE
, origName
, name
, null);
324 if (line
.startsWith("ref: ")) {
326 throw new IOException("Exceeded maximum ref depth of " + depth
327 + " at " + name
+ ". Circular reference?");
330 final String target
= line
.substring("ref: ".length());
331 Ref r
= readRefBasic(target
, target
, depth
+ 1);
332 Long cachedMtime
= looseRefsMTime
.get(name
);
333 if (cachedMtime
!= null && cachedMtime
!= mtime
)
335 looseRefsMTime
.put(name
, mtime
);
337 return new Ref(Ref
.Storage
.LOOSE
, origName
, target
, null);
338 if (!origName
.equals(r
.getName()))
339 r
= new Ref(Ref
.Storage
.LOOSE_PACKED
, origName
, r
.getName(), r
.getObjectId(), r
.getPeeledObjectId(), true);
347 id
= ObjectId
.fromString(line
);
348 } catch (IllegalArgumentException notRef
) {
349 throw new IOException("Not a ref: " + name
+ ": " + line
);
352 ref
= new Ref(Ref
.Storage
.LOOSE
, origName
, name
, id
);
354 looseRefs
.put(origName
, ref
);
355 ref
= new Ref(Ref
.Storage
.LOOSE
, origName
, id
);
356 looseRefs
.put(name
, ref
);
357 looseRefsMTime
.put(name
, mtime
);
361 private synchronized void refreshPackedRefs() {
362 final long currTime
= packedRefsFile
.lastModified();
363 final long currLen
= currTime
== 0 ?
0 : packedRefsFile
.length();
364 if (currTime
== packedRefsLastModified
&& currLen
== packedRefsLength
)
367 packedRefsLastModified
= 0;
368 packedRefsLength
= 0;
369 packedRefs
= new HashMap
<String
, Ref
>();
373 final Map
<String
, Ref
> newPackedRefs
= new HashMap
<String
, Ref
>();
375 final BufferedReader b
= openReader(packedRefsFile
);
379 while ((p
= b
.readLine()) != null) {
380 if (p
.charAt(0) == '#')
383 if (p
.charAt(0) == '^') {
385 throw new IOException("Peeled line before ref.");
387 final ObjectId id
= ObjectId
.fromString(p
.substring(1));
388 last
= new Ref(Ref
.Storage
.PACKED
, last
.getName(), last
389 .getName(), last
.getObjectId(), id
, true);
390 newPackedRefs
.put(last
.getName(), last
);
394 final int sp
= p
.indexOf(' ');
395 final ObjectId id
= ObjectId
.fromString(p
.substring(0, sp
));
396 final String name
= new String(p
.substring(sp
+ 1));
397 last
= new Ref(Ref
.Storage
.PACKED
, name
, name
, id
);
398 newPackedRefs
.put(last
.getName(), last
);
403 packedRefsLastModified
= currTime
;
404 packedRefsLength
= currLen
;
405 packedRefs
= newPackedRefs
;
407 } catch (FileNotFoundException noPackedRefs
) {
408 // Ignore it and leave the new map empty.
410 packedRefsLastModified
= 0;
411 packedRefsLength
= 0;
412 packedRefs
= newPackedRefs
;
413 } catch (IOException e
) {
414 throw new RuntimeException("Cannot read packed refs", e
);
418 private void lockAndWriteFile(File file
, byte[] content
) throws IOException
{
419 String name
= file
.getName();
420 final LockFile lck
= new LockFile(file
);
422 throw new ObjectWritingException("Unable to lock " + name
);
425 } catch (IOException ioe
) {
426 throw new ObjectWritingException("Unable to write " + name
, ioe
);
429 throw new ObjectWritingException("Unable to write " + name
);
432 synchronized void removePackedRef(String name
) throws IOException
{
433 packedRefs
.remove(name
);
437 private void writePackedRefs() throws IOException
{
438 new RefWriter(packedRefs
.values()) {
440 protected void writeFile(String name
, byte[] content
) throws IOException
{
441 lockAndWriteFile(new File(db
.getDirectory(), name
), content
);
446 private static String
readLine(final File file
)
447 throws FileNotFoundException
, IOException
{
448 final BufferedReader br
= openReader(file
);
450 return br
.readLine();
456 private static BufferedReader
openReader(final File fileLocation
)
457 throws FileNotFoundException
{
458 return new BufferedReader(new InputStreamReader(new FileInputStream(
459 fileLocation
), Constants
.CHARSET
));