diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java | 576 |
1 files changed, 116 insertions, 460 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index fb6a27db30..21e7041b32 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * Copyright (C) 2010, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -44,499 +43,156 @@ package org.eclipse.jgit.lib; -import static org.eclipse.jgit.lib.Constants.R_TAGS; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.HashMap; import java.util.Map; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.lib.Ref.Storage; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; - -class RefDatabase { - private static final String REFS_SLASH = "refs/"; - - private static final String[] refSearchPaths = { "", REFS_SLASH, - R_TAGS, Constants.R_HEADS, Constants.R_REMOTES }; - - private final Repository db; - - private final File gitDir; - - private final File refsDir; - - private Map<String, Ref> looseRefs; - private Map<String, Long> looseRefsMTime; - private Map<String, String> looseSymRefs; - - private final File packedRefsFile; - - private Map<String, Ref> packedRefs; - - private long packedRefsLastModified; - - private long packedRefsLength; - - int lastRefModification; - - int lastNotifiedRefModification; - - private int refModificationCounter; - - RefDatabase(final Repository r) { - db = r; - gitDir = db.getDirectory(); - refsDir = FS.resolve(gitDir, "refs"); - packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS); - clearCache(); - } +/** + * Abstraction of name to {@link ObjectId} mapping. + * <p> + * A reference database stores a mapping of reference names to {@link ObjectId}. + * Every {@link Repository} has a single reference database, mapping names to + * the tips of the object graph contained by the {@link ObjectDatabase}. + */ +public abstract class RefDatabase { + /** + * Order of prefixes to search when using non-absolute references. + * <p> + * The implementation's {@link #getRef(String)} method must take this search + * space into consideration when locating a reference by name. The first + * entry in the path is always {@code ""}, ensuring that absolute references + * are resolved without further mangling. + */ + protected static final String[] SEARCH_PATH = { "", //$NON-NLS-1$ + Constants.R_REFS, // + Constants.R_TAGS, // + Constants.R_HEADS, // + Constants.R_REMOTES // + }; - synchronized void clearCache() { - looseRefs = new HashMap<String, Ref>(); - looseRefsMTime = new HashMap<String, Long>(); - packedRefs = new HashMap<String, Ref>(); - looseSymRefs = new HashMap<String, String>(); - packedRefsLastModified = 0; - packedRefsLength = 0; - } + /** + * Maximum number of times a {@link SymbolicRef} can be traversed. + * <p> + * If the reference is nested deeper than this depth, the implementation + * should either fail, or at least claim the reference does not exist. + */ + protected static final int MAX_SYMBOLIC_REF_DEPTH = 5; - Repository getRepository() { - return db; - } + /** Magic value for {@link #getRefs(String)} to return all references. */ + public static final String ALL = "";//$NON-NLS-1$ - void create() { - refsDir.mkdir(); - new File(refsDir, "heads").mkdir(); - new File(refsDir, "tags").mkdir(); - } + /** + * Initialize a new reference database at this location. + * + * @throws IOException + * the database could not be created. + */ + public abstract void create() throws IOException; - ObjectId idOf(final String name) throws IOException { - refreshPackedRefs(); - final Ref r = readRefBasic(name, 0); - return r != null ? r.getObjectId() : null; - } + /** Close any resources held by this database. */ + public abstract void close(); /** - * Create a command to update, create or delete a ref in this repository. + * Determine if a proposed reference name overlaps with an existing one. + * <p> + * Reference names use '/' as a component separator, and may be stored in a + * hierarchical storage such as a directory on the local filesystem. + * <p> + * If the reference "refs/heads/foo" exists then "refs/heads/foo/bar" must + * not exist, as a reference cannot have a value and also be a container for + * other references at the same time. + * <p> + * If the reference "refs/heads/foo/bar" exists than the reference + * "refs/heads/foo" cannot exist, for the same reason. * * @param name - * name of the ref the caller wants to modify. - * @return an update command. The caller must finish populating this command - * and then invoke one of the update methods to actually make a - * change. + * proposed name. + * @return true if the name overlaps with an existing reference; false if + * using this name right now would be safe. * @throws IOException - * a symbolic ref was passed in and could not be resolved back - * to the base ref, as the symbolic ref could not be read. + * the database could not be read to check for conflicts. */ - RefUpdate newUpdate(final String name) throws IOException { - return newUpdate(name, false); - } + public abstract boolean isNameConflicting(String name) throws IOException; /** - * Create a command to update, create or delete a ref in this repository. + * Create a new update command to create, modify or delete a reference. * * @param name - * name of the ref the caller wants to modify. + * the name of the reference. * @param detach - * true to detach the ref, i.e. replace symref with object ref - * @return an update command. The caller must finish populating this command - * and then invoke one of the update methods to actually make a - * change. + * if {@code true} and {@code name} is currently a + * {@link SymbolicRef}, the update will replace it with an + * {@link ObjectIdRef}. Otherwise, the update will recursively + * traverse {@link SymbolicRef}s and operate on the leaf + * {@link ObjectIdRef}. + * @return a new update for the requested name; never null. * @throws IOException - * a symbolic ref was passed in and could not be resolved back - * to the base ref, as the symbolic ref could not be read. + * the reference space cannot be accessed. */ - RefUpdate newUpdate(final String name, boolean detach) throws IOException { - refreshPackedRefs(); - Ref r = readRefBasic(name, 0); - if (r == null) - r = new Ref(Ref.Storage.NEW, name, null); - else if (detach) - r = new Ref(Ref.Storage.NEW, name, r.getObjectId()); - return new RefUpdate(this, r, fileForRef(r.getName())); - } - - void stored(final String origName, final String name, final ObjectId id, final long time) { - synchronized (this) { - looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, name, id)); - looseRefsMTime.put(name, time); - setModified(); - } - db.fireRefsMaybeChanged(); - } + public abstract RefUpdate newUpdate(String name, boolean detach) + throws IOException; /** - * An set of update operations for renaming a ref + * Create a new update command to rename a reference. * - * @param fromRef Old ref name - * @param toRef New ref name - * @return a RefUpdate operation to rename a ref + * @param fromName + * name of reference to rename from + * @param toName + * name of reference to rename to + * @return an update command that knows how to rename a branch to another. * @throws IOException + * the reference space cannot be accessed. */ - RefRename newRename(String fromRef, String toRef) throws IOException { - refreshPackedRefs(); - Ref f = readRefBasic(fromRef, 0); - Ref t = new Ref(Ref.Storage.NEW, toRef, null); - RefUpdate refUpdateFrom = new RefUpdate(this, f, fileForRef(f.getName())); - RefUpdate refUpdateTo = new RefUpdate(this, t, fileForRef(t.getName())); - return new RefRename(refUpdateTo, refUpdateFrom); - } + public abstract RefRename newRename(String fromName, String toName) + throws IOException; /** - * Writes a symref (e.g. HEAD) to disk + * Read a single reference. + * <p> + * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be + * able to more quickly resolve a single reference name than obtaining the + * complete namespace by {@code getRefs(ALL).get(name)}. * * @param name - * symref name - * @param target - * pointed to ref + * the name of the reference. May be a short name which must be + * searched for using the standard {@link #SEARCH_PATH}. + * @return the reference (if it exists); else {@code null}. * @throws IOException + * the reference space cannot be accessed. */ - void link(final String name, final String target) throws IOException { - final byte[] content = Constants.encode("ref: " + target + "\n"); - lockAndWriteFile(fileForRef(name), content); - synchronized (this) { - looseSymRefs.remove(name); - setModified(); - } - db.fireRefsMaybeChanged(); - } - - void uncacheSymRef(String name) { - synchronized(this) { - looseSymRefs.remove(name); - setModified(); - } - } - - void uncacheRef(String name) { - looseRefs.remove(name); - looseRefsMTime.remove(name); - packedRefs.remove(name); - } - - private void setModified() { - lastRefModification = refModificationCounter++; - } - - Ref readRef(final String partialName) throws IOException { - refreshPackedRefs(); - for (int k = 0; k < refSearchPaths.length; k++) { - final Ref r = readRefBasic(refSearchPaths[k] + partialName, 0); - if (r != null && r.getObjectId() != null) - return r; - } - return null; - } + public abstract Ref getRef(String name) throws IOException; /** - * @return all known refs (heads, tags, remotes). + * Get a section of the reference namespace. + * + * @param prefix + * prefix to search the namespace with; must end with {@code /}. + * If the empty string ({@link #ALL}), obtain a complete snapshot + * of all references. + * @return modifiable map that is a complete snapshot of the current + * reference namespace, with {@code prefix} removed from the start + * of each key. The map can be an unsorted map. + * @throws IOException + * the reference space cannot be accessed. */ - Map<String, Ref> getAllRefs() { - return readRefs(); - } + public abstract Map<String, Ref> getRefs(String prefix) throws IOException; /** - * @return all tags; key is short tag name ("v1.0") and value of the entry - * contains the ref with the full tag name ("refs/tags/v1.0"). + * Peel a possibly unpeeled reference by traversing the annotated tags. + * <p> + * If the reference cannot be peeled (as it does not refer to an annotated + * tag) the peeled id stays null, but {@link Ref#isPeeled()} will be true. + * <p> + * Implementors should check {@link Ref#isPeeled()} before performing any + * additional work effort. + * + * @param ref + * The reference to peel + * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new + * Ref object representing the same data as Ref, but isPeeled() will + * be true and getPeeledObjectId() will contain the peeled object + * (or null). + * @throws IOException + * the reference space or object space cannot be accessed. */ - Map<String, Ref> getTags() { - final Map<String, Ref> tags = new HashMap<String, Ref>(); - for (final Ref r : readRefs().values()) { - if (r.getName().startsWith(R_TAGS)) - tags.put(r.getName().substring(R_TAGS.length()), r); - } - return tags; - } - - private Map<String, Ref> readRefs() { - final HashMap<String, Ref> avail = new HashMap<String, Ref>(); - readPackedRefs(avail); - readLooseRefs(avail, REFS_SLASH, refsDir); - try { - final Ref r = readRefBasic(Constants.HEAD, 0); - if (r != null && r.getObjectId() != null) - avail.put(Constants.HEAD, r); - } catch (IOException e) { - // ignore here - } - db.fireRefsMaybeChanged(); - return avail; - } - - private synchronized void readPackedRefs(final Map<String, Ref> avail) { - refreshPackedRefs(); - avail.putAll(packedRefs); - } - - private void readLooseRefs(final Map<String, Ref> avail, - final String prefix, final File dir) { - final File[] entries = dir.listFiles(); - if (entries == null) - return; - - for (final File ent : entries) { - final String entName = ent.getName(); - if (".".equals(entName) || "..".equals(entName)) - continue; - if (ent.isDirectory()) { - readLooseRefs(avail, prefix + entName + "/", ent); - } else { - try { - final Ref ref = readRefBasic(prefix + entName, 0); - if (ref != null) - avail.put(ref.getOrigName(), ref); - } catch (IOException e) { - continue; - } - } - } - } - - Ref peel(final Ref ref) { - if (ref.isPeeled()) - return ref; - ObjectId peeled = null; - try { - Object target = db.mapObject(ref.getObjectId(), ref.getName()); - while (target instanceof Tag) { - final Tag tag = (Tag)target; - peeled = tag.getObjId(); - if (Constants.TYPE_TAG.equals(tag.getType())) - target = db.mapObject(tag.getObjId(), ref.getName()); - else - break; - } - } catch (IOException e) { - // Ignore a read error. Callers will also get the same error - // if they try to use the result of getPeeledObjectId. - } - return new Ref(ref.getStorage(), ref.getName(), ref.getObjectId(), peeled, true); - - } - - private File fileForRef(final String name) { - if (name.startsWith(REFS_SLASH)) - return new File(refsDir, name.substring(REFS_SLASH.length())); - return new File(gitDir, name); - } - - private Ref readRefBasic(final String name, final int depth) throws IOException { - return readRefBasic(name, name, depth); - } - - private synchronized Ref readRefBasic(final String origName, - final String name, final int depth) throws IOException { - // Prefer loose ref to packed ref as the loose - // file can be more up-to-date than a packed one. - // - Ref ref = looseRefs.get(origName); - final File loose = fileForRef(name); - final long mtime = loose.lastModified(); - if (ref != null) { - Long cachedlastModified = looseRefsMTime.get(name); - if (cachedlastModified != null && cachedlastModified == mtime) { - if (packedRefs.containsKey(origName)) - return new Ref(Storage.LOOSE_PACKED, origName, ref - .getObjectId(), ref.getPeeledObjectId(), ref - .isPeeled()); - else - return ref; - } - looseRefs.remove(origName); - looseRefsMTime.remove(origName); - } - - if (mtime == 0) { - // If last modified is 0 the file does not exist. - // Try packed cache. - // - ref = packedRefs.get(name); - if (ref != null) - if (!ref.getOrigName().equals(origName)) - ref = new Ref(Storage.LOOSE_PACKED, origName, name, ref.getObjectId()); - return ref; - } - - String line = null; - try { - Long cachedlastModified = looseRefsMTime.get(name); - if (cachedlastModified != null && cachedlastModified == mtime) { - line = looseSymRefs.get(name); - } - if (line == null) { - line = readLine(loose); - looseRefsMTime.put(name, mtime); - looseSymRefs.put(name, line); - } - } catch (FileNotFoundException notLoose) { - return packedRefs.get(name); - } - - if (line == null || line.length() == 0) { - looseRefs.remove(origName); - looseRefsMTime.remove(origName); - return new Ref(Ref.Storage.LOOSE, origName, name, null); - } - - if (line.startsWith("ref: ")) { - if (depth >= 5) { - throw new IOException("Exceeded maximum ref depth of " + depth - + " at " + name + ". Circular reference?"); - } - - final String target = line.substring("ref: ".length()); - Ref r = readRefBasic(target, target, depth + 1); - Long cachedMtime = looseRefsMTime.get(name); - if (cachedMtime != null && cachedMtime != mtime) - setModified(); - looseRefsMTime.put(name, mtime); - if (r == null) - return new Ref(Ref.Storage.LOOSE, origName, target, null); - if (!origName.equals(r.getName())) - r = new Ref(Ref.Storage.LOOSE_PACKED, origName, r.getName(), r.getObjectId(), r.getPeeledObjectId(), true); - return r; - } - - setModified(); - - final ObjectId id; - try { - id = ObjectId.fromString(line); - } catch (IllegalArgumentException notRef) { - throw new IOException("Not a ref: " + name + ": " + line); - } - - Storage storage; - if (packedRefs.containsKey(name)) - storage = Ref.Storage.LOOSE_PACKED; - else - storage = Ref.Storage.LOOSE; - ref = new Ref(storage, name, id); - looseRefs.put(name, ref); - looseRefsMTime.put(name, mtime); - - if (!origName.equals(name)) { - ref = new Ref(Ref.Storage.LOOSE, origName, name, id); - looseRefs.put(origName, ref); - } - - return ref; - } - - private synchronized void refreshPackedRefs() { - final long currTime = packedRefsFile.lastModified(); - final long currLen = currTime == 0 ? 0 : packedRefsFile.length(); - if (currTime == packedRefsLastModified && currLen == packedRefsLength) - return; - if (currTime == 0) { - packedRefsLastModified = 0; - packedRefsLength = 0; - packedRefs = new HashMap<String, Ref>(); - return; - } - - final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>(); - try { - final BufferedReader b = openReader(packedRefsFile); - try { - String p; - Ref last = null; - while ((p = b.readLine()) != null) { - if (p.charAt(0) == '#') - continue; - - if (p.charAt(0) == '^') { - if (last == null) - throw new IOException("Peeled line before ref."); - - final ObjectId id = ObjectId.fromString(p.substring(1)); - last = new Ref(Ref.Storage.PACKED, last.getName(), last - .getName(), last.getObjectId(), id, true); - newPackedRefs.put(last.getName(), last); - continue; - } - - final int sp = p.indexOf(' '); - final ObjectId id = ObjectId.fromString(p.substring(0, sp)); - final String name = copy(p, sp + 1, p.length()); - last = new Ref(Ref.Storage.PACKED, name, name, id); - newPackedRefs.put(last.getName(), last); - } - } finally { - b.close(); - } - packedRefsLastModified = currTime; - packedRefsLength = currLen; - packedRefs = newPackedRefs; - setModified(); - } catch (FileNotFoundException noPackedRefs) { - // Ignore it and leave the new map empty. - // - packedRefsLastModified = 0; - packedRefsLength = 0; - packedRefs = newPackedRefs; - } catch (IOException e) { - throw new RuntimeException("Cannot read packed refs", e); - } - } - - private static String copy(final String src, final int off, final int end) { - return new StringBuilder(end - off).append(src, off, end).toString(); - } - - private void lockAndWriteFile(File file, byte[] content) throws IOException { - String name = file.getName(); - final LockFile lck = new LockFile(file); - if (!lck.lock()) - throw new ObjectWritingException("Unable to lock " + name); - try { - lck.write(content); - } catch (IOException ioe) { - throw new ObjectWritingException("Unable to write " + name, ioe); - } - if (!lck.commit()) - throw new ObjectWritingException("Unable to write " + name); - } - - synchronized void removePackedRef(String name) throws IOException { - packedRefs.remove(name); - writePackedRefs(); - } - - private void writePackedRefs() throws IOException { - new RefWriter(packedRefs.values()) { - @Override - protected void writeFile(String name, byte[] content) throws IOException { - lockAndWriteFile(new File(db.getDirectory(), name), content); - } - }.writePackedRefs(); - } - - private static String readLine(final File file) - throws FileNotFoundException, IOException { - final byte[] buf = IO.readFully(file, 4096); - int n = buf.length; - - // remove trailing whitespaces - while (n > 0 && Character.isWhitespace(buf[n - 1])) - n--; - - if (n == 0) - return null; - return RawParseUtils.decode(buf, 0, n); - } - - private static BufferedReader openReader(final File fileLocation) - throws FileNotFoundException { - return new BufferedReader(new InputStreamReader(new FileInputStream( - fileLocation), Constants.CHARSET)); - } + public abstract Ref peel(Ref ref) throws IOException; } |