Use Constants.PACKED_REFS in RefWriter
[egit/chris.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RefDatabase.java
blob8a474123fe26e145f16c5289e72d13650a8eec51
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 static org.spearce.jgit.lib.Constants.R_TAGS;
43 import java.io.BufferedReader;
44 import java.io.File;
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;
50 import java.util.Map;
52 import org.spearce.jgit.errors.ObjectWritingException;
53 import org.spearce.jgit.lib.Ref.Storage;
54 import org.spearce.jgit.util.FS;
56 class RefDatabase {
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) {
87 db = r;
88 gitDir = db.getDirectory();
89 refsDir = FS.resolve(gitDir, "refs");
90 packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS);
91 clearCache();
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() {
104 return db;
107 void create() {
108 refsDir.mkdir();
109 new File(refsDir, "heads").mkdir();
110 new File(refsDir, "tags").mkdir();
113 ObjectId idOf(final String name) throws IOException {
114 refreshPackedRefs();
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.
122 * @param name
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
126 * change.
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 {
132 refreshPackedRefs();
133 Ref r = readRefBasic(name, 0);
134 if (r == null)
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);
143 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 = Constants.encode("ref: " + target + "\n");
159 lockAndWriteFile(fileForRef(name), content);
160 synchronized (this) {
161 setModified();
163 db.fireRefsMaybeChanged();
166 private void setModified() {
167 lastRefModification = refModificationCounter++;
170 Ref readRef(final String partialName) throws IOException {
171 refreshPackedRefs();
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)
175 return r;
177 return null;
181 * @return all known refs (heads, tags, remotes).
183 Map<String, Ref> getAllRefs() {
184 return readRefs();
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);
197 return tags;
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);
204 try {
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) {
209 // ignore here
211 db.fireRefsMaybeChanged();
212 return avail;
215 private synchronized void readPackedRefs(final Map<String, Ref> avail) {
216 refreshPackedRefs();
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();
223 if (entries == null)
224 return;
226 for (final File ent : entries) {
227 final String entName = ent.getName();
228 if (".".equals(entName) || "..".equals(entName))
229 continue;
230 if (ent.isDirectory()) {
231 readLooseRefs(avail, prefix + entName + "/", ent);
232 } else {
233 try {
234 final Ref ref = readRefBasic(prefix + entName, 0);
235 if (ref != null)
236 avail.put(ref.getOrigName(), ref);
237 } catch (IOException e) {
238 continue;
244 Ref peel(final Ref ref) {
245 if (ref.isPeeled())
246 return ref;
247 ObjectId peeled = null;
248 try {
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());
255 else
256 break;
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();
284 if (ref != null) {
285 Long cachedlastModified = looseRefsMTime.get(name);
286 if (cachedlastModified != null && cachedlastModified == mtime)
287 return ref;
288 looseRefs.remove(origName);
289 looseRefsMTime.remove(origName);
292 if (mtime == 0) {
293 // If last modified is 0 the file does not exist.
294 // Try packed cache.
296 ref = packedRefs.get(name);
297 if (ref != null)
298 if (!ref.getOrigName().equals(origName))
299 ref = new Ref(Storage.LOOSE_PACKED, origName, name, ref.getObjectId());
300 return ref;
303 String line = null;
304 try {
305 Long cachedlastModified = looseRefsMTime.get(name);
306 if (cachedlastModified != null && cachedlastModified == mtime) {
307 line = looseSymRefs.get(name);
309 if (line == null) {
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: ")) {
325 if (depth >= 5) {
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)
334 setModified();
335 looseRefsMTime.put(name, mtime);
336 if (r == null)
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);
340 return r;
343 setModified();
345 final ObjectId id;
346 try {
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);
358 return ref;
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)
365 return;
366 if (currTime == 0) {
367 packedRefsLastModified = 0;
368 packedRefsLength = 0;
369 packedRefs = new HashMap<String, Ref>();
370 return;
373 final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>();
374 try {
375 final BufferedReader b = openReader(packedRefsFile);
376 try {
377 String p;
378 Ref last = null;
379 while ((p = b.readLine()) != null) {
380 if (p.charAt(0) == '#')
381 continue;
383 if (p.charAt(0) == '^') {
384 if (last == null)
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);
391 continue;
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);
400 } finally {
401 b.close();
403 packedRefsLastModified = currTime;
404 packedRefsLength = currLen;
405 packedRefs = newPackedRefs;
406 setModified();
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);
421 if (!lck.lock())
422 throw new ObjectWritingException("Unable to lock " + name);
423 try {
424 lck.write(content);
425 } catch (IOException ioe) {
426 throw new ObjectWritingException("Unable to write " + name, ioe);
428 if (!lck.commit())
429 throw new ObjectWritingException("Unable to write " + name);
432 synchronized void removePackedRef(String name) throws IOException {
433 packedRefs.remove(name);
434 writePackedRefs();
437 private void writePackedRefs() throws IOException {
438 new RefWriter(packedRefs.values()) {
439 @Override
440 protected void writeFile(String name, byte[] content) throws IOException {
441 lockAndWriteFile(new File(db.getDirectory(), name), content);
443 }.writePackedRefs();
446 private static String readLine(final File file)
447 throws FileNotFoundException, IOException {
448 final BufferedReader br = openReader(file);
449 try {
450 return br.readLine();
451 } finally {
452 br.close();
456 private static BufferedReader openReader(final File fileLocation)
457 throws FileNotFoundException {
458 return new BufferedReader(new InputStreamReader(new FileInputStream(
459 fileLocation), Constants.CHARSET));