Added removePackedRef method to RefDatabase for packed branch deletion
[egit/charleso.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RefDatabase.java
blobbe5a2fe8d1e855714a275a93f0b568e39bba769c
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.util.HashMap;
49 import java.util.Map;
51 import org.spearce.jgit.errors.ObjectWritingException;
52 import org.spearce.jgit.util.FS;
53 import org.spearce.jgit.util.NB;
55 class RefDatabase {
56 private static final String REFS_SLASH = "refs/";
58 private static final String HEADS_SLASH = Constants.HEADS_PREFIX + "/";
60 private static final String TAGS_SLASH = Constants.TAGS_PREFIX + "/";
62 private static final String[] refSearchPaths = { "", REFS_SLASH,
63 TAGS_SLASH, HEADS_SLASH, Constants.REMOTES_PREFIX + "/" };
65 private final Repository db;
67 private final File gitDir;
69 private final File refsDir;
71 private Map<String, Ref> looseRefs;
72 private Map<String, Long> looseRefsMTime;
74 private final File packedRefsFile;
76 private Map<String, Ref> packedRefs;
78 private long packedRefsLastModified;
80 private long packedRefsLength;
82 int lastRefModification;
84 int lastNotifiedRefModification;
86 private int refModificationCounter;
88 RefDatabase(final Repository r) {
89 db = r;
90 gitDir = db.getDirectory();
91 refsDir = FS.resolve(gitDir, "refs");
92 packedRefsFile = FS.resolve(gitDir, "packed-refs");
93 clearCache();
96 void clearCache() {
97 looseRefs = new HashMap<String, Ref>();
98 looseRefsMTime = new HashMap<String, Long>();
99 packedRefs = new HashMap<String, Ref>();
100 packedRefsLastModified = 0;
101 packedRefsLength = 0;
104 Repository getRepository() {
105 return db;
108 void create() {
109 refsDir.mkdir();
110 new File(refsDir, "heads").mkdir();
111 new File(refsDir, "tags").mkdir();
114 ObjectId idOf(final String name) throws IOException {
115 refreshPackedRefs();
116 final Ref r = readRefBasic(name, 0);
117 return r != null ? r.getObjectId() : null;
121 * Create a command to update, create or delete a ref in this repository.
123 * @param name
124 * name of the ref the caller wants to modify.
125 * @return an update command. The caller must finish populating this command
126 * and then invoke one of the update methods to actually make a
127 * change.
128 * @throws IOException
129 * a symbolic ref was passed in and could not be resolved back
130 * to the base ref, as the symbolic ref could not be read.
132 RefUpdate newUpdate(final String name) throws IOException {
133 refreshPackedRefs();
134 Ref r = readRefBasic(name, 0);
135 if (r == null)
136 r = new Ref(Ref.Storage.NEW, name, null);
137 return new RefUpdate(this, r, fileForRef(r.getName()));
140 void stored(final String name, final ObjectId id, final long time) {
141 looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, id));
142 looseRefsMTime.put(name, time);
143 setModified();
144 db.fireRefsMaybeChanged();
148 * Writes a symref (e.g. HEAD) to disk
150 * @param name
151 * symref name
152 * @param target
153 * pointed to ref
154 * @throws IOException
156 void link(final String name, final String target) throws IOException {
157 final byte[] content = Constants.encode("ref: " + target + "\n");
158 lockAndWriteFile(fileForRef(name), content);
159 setModified();
160 db.fireRefsMaybeChanged();
163 void setModified() {
164 lastRefModification = refModificationCounter++;
167 Ref readRef(final String partialName) throws IOException {
168 refreshPackedRefs();
169 for (int k = 0; k < refSearchPaths.length; k++) {
170 final Ref r = readRefBasic(refSearchPaths[k] + partialName, 0);
171 if (r != null && r.getObjectId() != null)
172 return r;
174 return null;
178 * @return all known refs (heads, tags, remotes).
180 Map<String, Ref> getAllRefs() {
181 return readRefs();
185 * @return all tags; key is short tag name ("v1.0") and value of the entry
186 * contains the ref with the full tag name ("refs/tags/v1.0").
188 Map<String, Ref> getTags() {
189 final Map<String, Ref> tags = new HashMap<String, Ref>();
190 for (final Ref r : readRefs().values()) {
191 if (r.getName().startsWith(TAGS_SLASH))
192 tags.put(r.getName().substring(TAGS_SLASH.length()), r);
194 return tags;
197 private Map<String, Ref> readRefs() {
198 final HashMap<String, Ref> avail = new HashMap<String, Ref>();
199 readPackedRefs(avail);
200 readLooseRefs(avail, REFS_SLASH, refsDir);
201 try {
202 final Ref r = readRefBasic(Constants.HEAD, 0);
203 if (r != null && r.getObjectId() != null)
204 avail.put(Constants.HEAD, r);
205 } catch (IOException e) {
206 // ignore here
208 db.fireRefsMaybeChanged();
209 return avail;
212 private void readPackedRefs(final Map<String, Ref> avail) {
213 refreshPackedRefs();
214 avail.putAll(packedRefs);
217 private void readLooseRefs(final Map<String, Ref> avail,
218 final String prefix, final File dir) {
219 final File[] entries = dir.listFiles();
220 if (entries == null)
221 return;
223 for (final File ent : entries) {
224 final String entName = ent.getName();
225 if (".".equals(entName) || "..".equals(entName))
226 continue;
227 readOneLooseRef(avail, prefix + entName, ent);
231 private void readOneLooseRef(final Map<String, Ref> avail,
232 final String refName, final File ent) {
233 // Unchanged and cached? Don't read it again.
235 Ref ref = looseRefs.get(refName);
236 if (ref != null) {
237 Long cachedlastModified = looseRefsMTime.get(refName);
238 if (cachedlastModified != null && cachedlastModified == ent.lastModified()) {
239 avail.put(ref.getName(), ref);
240 return;
242 looseRefs.remove(refName);
243 looseRefsMTime.remove(refName);
246 // Recurse into the directory.
248 if (ent.isDirectory()) {
249 readLooseRefs(avail, refName + "/", ent);
250 return;
253 // Assume its a valid loose reference we need to cache.
255 try {
256 final FileInputStream in = new FileInputStream(ent);
257 try {
258 final ObjectId id;
259 try {
260 final byte[] str = new byte[Constants.OBJECT_ID_LENGTH * 2];
261 NB.readFully(in, str, 0, str.length);
262 id = ObjectId.fromString(str, 0);
263 } catch (EOFException tooShortToBeRef) {
264 // Its below the minimum length needed. It could
265 // be a symbolic reference.
267 return;
268 } catch (IllegalArgumentException notRef) {
269 // It is not a well-formed ObjectId. It may be
270 // a symbolic reference ("ref: ").
272 return;
275 ref = new Ref(Ref.Storage.LOOSE, refName, id);
276 looseRefs.put(ref.getName(), ref);
277 looseRefsMTime.put(ref.getName(), ent.lastModified());
278 avail.put(ref.getName(), ref);
279 } finally {
280 in.close();
282 } catch (FileNotFoundException noFile) {
283 // Deleted while we were reading? Its gone now!
285 } catch (IOException err) {
286 // Whoops.
288 throw new RuntimeException("Cannot read ref " + ent, err);
292 private File fileForRef(final String name) {
293 if (name.startsWith(REFS_SLASH))
294 return new File(refsDir, name.substring(REFS_SLASH.length()));
295 return new File(gitDir, name);
298 private Ref readRefBasic(final String name, final int depth)
299 throws IOException {
300 // Prefer loose ref to packed ref as the loose
301 // file can be more up-to-date than a packed one.
303 Ref ref = looseRefs.get(name);
304 final File loose = fileForRef(name);
305 final long mtime = loose.lastModified();
306 if (ref != null) {
307 Long cachedlastModified = looseRefsMTime.get(name);
308 if (cachedlastModified != null && cachedlastModified == mtime)
309 return ref;
310 looseRefs.remove(name);
311 looseRefsMTime.remove(name);
314 if (mtime == 0) {
315 // If last modified is 0 the file does not exist.
316 // Try packed cache.
318 return packedRefs.get(name);
321 final String line;
322 try {
323 line = readLine(loose);
324 } catch (FileNotFoundException notLoose) {
325 return packedRefs.get(name);
328 if (line == null || line.length() == 0)
329 return new Ref(Ref.Storage.LOOSE, name, null);
331 if (line.startsWith("ref: ")) {
332 if (depth >= 5) {
333 throw new IOException("Exceeded maximum ref depth of " + depth
334 + " at " + name + ". Circular reference?");
337 final String target = line.substring("ref: ".length());
338 final Ref r = readRefBasic(target, depth + 1);
339 Long cachedMtime = looseRefsMTime.get(name);
340 if (cachedMtime != null && cachedMtime != mtime)
341 setModified();
342 looseRefsMTime.put(name, mtime);
343 return r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);
346 setModified();
348 final ObjectId id;
349 try {
350 id = ObjectId.fromString(line);
351 } catch (IllegalArgumentException notRef) {
352 throw new IOException("Not a ref: " + name + ": " + line);
355 ref = new Ref(Ref.Storage.LOOSE, name, id);
356 looseRefs.put(name, ref);
357 looseRefsMTime.put(name, mtime);
358 return ref;
361 private 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 .getObjectId(), id);
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, 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 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));