Cached modification times for symbolic refs too
[egit/imyousuf.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RefDatabase.java
blobc7f7d59291d7fefb9da4460868cb062e078ef998
1 /*
2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
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
22 * written permission.
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;
43 import java.io.File;
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;
50 import java.util.Map;
52 import org.spearce.jgit.errors.ObjectWritingException;
53 import org.spearce.jgit.util.FS;
54 import org.spearce.jgit.util.NB;
56 class RefDatabase {
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) {
92 db = r;
93 gitDir = db.getDirectory();
94 refsDir = FS.resolve(gitDir, "refs");
95 packedRefsFile = FS.resolve(gitDir, "packed-refs");
96 clearCache();
99 void clearCache() {
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() {
108 return db;
111 void create() {
112 refsDir.mkdir();
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.
125 * @param name
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
129 * change.
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);
136 if (r == null)
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);
144 setModified();
145 db.fireRefsMaybeChanged();
149 * Writes a symref (e.g. HEAD) to disk
151 * @param name
152 * symref name
153 * @param target
154 * pointed to ref
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));
160 if (!lck.lock())
161 throw new ObjectWritingException("Unable to lock " + name);
162 try {
163 lck.write(content);
164 } catch (IOException ioe) {
165 throw new ObjectWritingException("Unable to write " + name, ioe);
167 if (!lck.commit())
168 throw new ObjectWritingException("Unable to write " + name);
169 setModified();
170 db.fireRefsMaybeChanged();
173 void setModified() {
174 lastRefModification = refModificationCounter++;
177 Ref readRef(final String partialName) throws IOException {
178 refreshPackedRefs();
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)
182 return r;
184 return null;
188 * @return all known refs (heads, tags, remotes).
190 Map<String, Ref> getAllRefs() {
191 return readRefs();
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);
204 return tags;
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);
211 try {
212 avail.put(Constants.HEAD, readRefBasic(Constants.HEAD, 0));
213 } catch (IOException e) {
214 // ignore here
216 db.fireRefsMaybeChanged();
217 return avail;
220 private void readPackedRefs(final Map<String, Ref> avail) {
221 refreshPackedRefs();
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();
228 if (entries == null)
229 return;
231 for (final File ent : entries) {
232 final String entName = ent.getName();
233 if (".".equals(entName) || "..".equals(entName))
234 continue;
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);
244 if (ref != null) {
245 Long cachedlastModified = looseRefsMTime.get(refName);
246 if (cachedlastModified != null && cachedlastModified == ent.lastModified()) {
247 avail.put(ref.getName(), ref);
248 return;
250 looseRefs.remove(refName);
251 looseRefsMTime.remove(refName);
254 // Recurse into the directory.
256 if (ent.isDirectory()) {
257 readLooseRefs(avail, refName + "/", ent);
258 return;
261 // Assume its a valid loose reference we need to cache.
263 try {
264 final FileInputStream in = new FileInputStream(ent);
265 try {
266 final ObjectId id;
267 try {
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.
275 return;
276 } catch (IllegalArgumentException notRef) {
277 // It is not a well-formed ObjectId. It may be
278 // a symbolic reference ("ref: ").
280 return;
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);
287 } finally {
288 in.close();
290 } catch (FileNotFoundException noFile) {
291 // Deleted while we were reading? Its gone now!
293 } catch (IOException err) {
294 // Whoops.
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)
307 throws IOException {
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();
314 if (ref != null) {
315 Long cachedlastModified = looseRefsMTime.get(name);
316 if (cachedlastModified != null && cachedlastModified == mtime)
317 return ref;
318 looseRefs.remove(name);
319 looseRefsMTime.remove(name);
322 if (mtime == 0) {
323 // If last modified is 0 the file does not exist.
324 // Try packed cache.
326 return packedRefs.get(name);
329 final String line;
330 try {
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: ")) {
340 if (depth >= 5) {
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)
349 setModified();
350 looseRefsMTime.put(name, mtime);
351 return r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);
354 setModified();
356 final ObjectId id;
357 try {
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);
366 return ref;
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)
373 return;
374 if (currTime == 0) {
375 packedRefsLastModified = 0;
376 packedRefsLength = 0;
377 packedRefs = new HashMap<String, Ref>();
378 return;
381 final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>();
382 try {
383 final BufferedReader b = openReader(packedRefsFile);
384 try {
385 String p;
386 Ref last = null;
387 while ((p = b.readLine()) != null) {
388 if (p.charAt(0) == '#')
389 continue;
391 if (p.charAt(0) == '^') {
392 if (last == null)
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
397 .getObjectId(), id);
398 newPackedRefs.put(last.getName(), last);
399 continue;
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);
408 } finally {
409 b.close();
411 packedRefsLastModified = currTime;
412 packedRefsLength = currLen;
413 packedRefs = newPackedRefs;
414 setModified();
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);
429 try {
430 return br.readLine();
431 } finally {
432 br.close();
436 private static BufferedReader openReader(final File fileLocation)
437 throws UnsupportedEncodingException, FileNotFoundException {
438 return new BufferedReader(new InputStreamReader(new FileInputStream(
439 fileLocation), CHAR_ENC));