Merge "Let RefDirectory use FileSnapShot to handle fast updates"

This commit is contained in:
Chris Aniszczyk 2011-05-25 09:46:49 -04:00 committed by Code Review
commit aa05559fd6
4 changed files with 98 additions and 49 deletions

View File

@ -152,4 +152,47 @@ public void testTrackingUpdate() throws Exception {
assertEquals(commit2.getId(), db2.resolve(branch)); assertEquals(commit2.getId(), db2.resolve(branch));
} }
/**
* Check that pushes over file protocol lead to appropriate ref-updates.
*
* @throws Exception
*/
@Test
public void testPushRefUpdate() throws Exception {
Git git = new Git(db);
Git git2 = new Git(createBareRepository());
final StoredConfig config = git.getRepository().getConfig();
RemoteConfig remoteConfig = new RemoteConfig(config, "test");
URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
.toURL());
remoteConfig.addURI(uri);
remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*"));
remoteConfig.update(config);
config.save();
writeTrashFile("f", "content of f");
git.add().addFilepattern("f").call();
RevCommit commit = git.commit().setMessage("adding f").call();
assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
git.push().setRemote("test").call();
assertEquals(commit.getId(),
git2.getRepository().resolve("refs/heads/master"));
git.branchCreate().setName("refs/heads/test").call();
git.checkout().setName("refs/heads/test").call();
for (int i = 0; i < 6; i++) {
writeTrashFile("f" + i, "content of f" + i);
git.add().addFilepattern("f" + i).call();
commit = git.commit().setMessage("adding f" + i).call();
git.push().setRemote("test").call();
git2.getRepository().getAllRefs();
assertEquals("failed to update on attempt " + i, commit.getId(),
git2.getRepository().resolve("refs/heads/test"));
}
}
} }

View File

@ -169,7 +169,6 @@ public void testGetRefs_DeatchedHead1() throws IOException {
Ref head; Ref head;
writeLooseRef(HEAD, A); writeLooseRef(HEAD, A);
BUG_WorkAroundRacyGitIssues(HEAD);
all = refdir.getRefs(RefDatabase.ALL); all = refdir.getRefs(RefDatabase.ALL);
assertEquals(1, all.size()); assertEquals(1, all.size());
@ -190,7 +189,6 @@ public void testGetRefs_DeatchedHead2() throws IOException {
writeLooseRef(HEAD, A); writeLooseRef(HEAD, A);
writeLooseRef("refs/heads/master", B); writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues(HEAD);
all = refdir.getRefs(RefDatabase.ALL); all = refdir.getRefs(RefDatabase.ALL);
assertEquals(2, all.size()); assertEquals(2, all.size());
@ -328,7 +326,6 @@ public void testGetRefs_IgnoresGarbageRef4() throws IOException {
assertTrue(heads.containsKey("refs/heads/C")); assertTrue(heads.containsKey("refs/heads/C"));
writeLooseRef("refs/heads/B", "FAIL\n"); writeLooseRef("refs/heads/B", "FAIL\n");
BUG_WorkAroundRacyGitIssues("refs/heads/B");
heads = refdir.getRefs(RefDatabase.ALL); heads = refdir.getRefs(RefDatabase.ALL);
assertEquals(2, heads.size()); assertEquals(2, heads.size());
@ -547,7 +544,6 @@ public void testGetRefs_DiscoversModifiedLoose() throws IOException {
assertEquals(A, all.get(HEAD).getObjectId()); assertEquals(A, all.get(HEAD).getObjectId());
writeLooseRef("refs/heads/master", B); writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues("refs/heads/master");
all = refdir.getRefs(RefDatabase.ALL); all = refdir.getRefs(RefDatabase.ALL);
assertEquals(B, all.get(HEAD).getObjectId()); assertEquals(B, all.get(HEAD).getObjectId());
} }
@ -561,7 +557,6 @@ public void testGetRef_DiscoversModifiedLoose() throws IOException {
assertEquals(A, all.get(HEAD).getObjectId()); assertEquals(A, all.get(HEAD).getObjectId());
writeLooseRef("refs/heads/master", B); writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues("refs/heads/master");
Ref master = refdir.getRef("refs/heads/master"); Ref master = refdir.getRef("refs/heads/master");
assertEquals(B, master.getObjectId()); assertEquals(B, master.getObjectId());
@ -760,7 +755,6 @@ public void testGetRefs_CycleInSymbolicRef() throws IOException {
writeLooseRef("refs/5", "ref: refs/6\n"); writeLooseRef("refs/5", "ref: refs/6\n");
writeLooseRef("refs/6", "ref: refs/end\n"); writeLooseRef("refs/6", "ref: refs/end\n");
BUG_WorkAroundRacyGitIssues("refs/5");
all = refdir.getRefs(RefDatabase.ALL); all = refdir.getRefs(RefDatabase.ALL);
r = all.get("refs/1"); r = all.get("refs/1");
assertNull("mising 1 due to cycle", r); assertNull("mising 1 due to cycle", r);
@ -1078,23 +1072,4 @@ private void deleteLooseRef(String name) {
File path = new File(diskRepo.getDirectory(), name); File path = new File(diskRepo.getDirectory(), name);
assertTrue("deleted " + name, path.delete()); assertTrue("deleted " + name, path.delete());
} }
/**
* Kick the timestamp of a local file.
* <p>
* We shouldn't have to make these method calls. The cache is using file
* system timestamps, and on many systems unit tests run faster than the
* modification clock. Dumping the cache after we make an edit behind
* RefDirectory's back allows the tests to pass.
*
* @param name
* the file in the repository to force a time change on.
*/
private void BUG_WorkAroundRacyGitIssues(String name) {
File path = new File(diskRepo.getDirectory(), name);
long old = path.lastModified();
long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
path.setLastModified(set);
assertTrue("time changed", old != path.lastModified());
}
} }

View File

@ -101,6 +101,22 @@ public static FileSnapshot save(File path) {
return new FileSnapshot(read, modified); return new FileSnapshot(read, modified);
} }
/**
* Record a snapshot for a file for which the last modification time is
* already known.
* <p>
* This method should be invoked before the file is accessed.
*
* @param modified
* the last modification time of the file
*
* @return the snapshot.
*/
public static FileSnapshot save(long modified) {
final long read = System.currentTimeMillis();
return new FileSnapshot(read, modified);
}
/** Last observed modification time of the path. */ /** Last observed modification time of the path. */
private final long lastModified; private final long lastModified;

View File

@ -842,19 +842,22 @@ private Ref readRef(String name, RefList<Ref> packed) throws IOException {
return n; return n;
} }
@SuppressWarnings("null")
private LooseRef scanRef(LooseRef ref, String name) throws IOException { private LooseRef scanRef(LooseRef ref, String name) throws IOException {
final File path = fileFor(name); final File path = fileFor(name);
final long modified = path.lastModified(); FileSnapshot currentSnapshot = null;
if (ref != null) { if (ref != null) {
if (ref.getLastModified() == modified) currentSnapshot = ref.getSnapShot();
if (!currentSnapshot.isModified(path))
return ref; return ref;
name = ref.getName(); name = ref.getName();
} else if (modified == 0) } else if (!path.exists())
return null; return null;
final int limit = 4096; final int limit = 4096;
final byte[] buf; final byte[] buf;
FileSnapshot otherSnapshot = FileSnapshot.save(path);
try { try {
buf = IO.readSome(path, limit); buf = IO.readSome(path, limit);
} catch (FileNotFoundException noFile) { } catch (FileNotFoundException noFile) {
@ -877,7 +880,12 @@ private LooseRef scanRef(LooseRef ref, String name) throws IOException {
throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
} }
final String target = RawParseUtils.decode(buf, 5, n); final String target = RawParseUtils.decode(buf, 5, n);
return newSymbolicRef(modified, name, target); if (ref != null && ref.isSymbolic()
&& ref.getTarget().getName().equals(target)) {
currentSnapshot.setClean(otherSnapshot);
return ref;
}
return newSymbolicRef(path.lastModified(), name, target);
} }
if (n < OBJECT_ID_STRING_LENGTH) if (n < OBJECT_ID_STRING_LENGTH)
@ -886,13 +894,19 @@ private LooseRef scanRef(LooseRef ref, String name) throws IOException {
final ObjectId id; final ObjectId id;
try { try {
id = ObjectId.fromString(buf, 0); id = ObjectId.fromString(buf, 0);
if (ref != null && !ref.isSymbolic()
&& ref.getTarget().getObjectId().equals(id)) {
currentSnapshot.setClean(otherSnapshot);
return ref;
}
} catch (IllegalArgumentException notRef) { } catch (IllegalArgumentException notRef) {
while (0 < n && Character.isWhitespace(buf[n - 1])) while (0 < n && Character.isWhitespace(buf[n - 1]))
n--; n--;
String content = RawParseUtils.decode(buf, 0, n); String content = RawParseUtils.decode(buf, 0, n);
throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
} }
return new LooseUnpeeled(modified, name, id); return new LooseUnpeeled(path.lastModified(), name, id);
} }
private static boolean isSymRef(final byte[] buf, int n) { private static boolean isSymRef(final byte[] buf, int n) {
@ -997,22 +1011,22 @@ private static LooseSymbolicRef newSymbolicRef(long lastModified,
} }
private static interface LooseRef extends Ref { private static interface LooseRef extends Ref {
long getLastModified(); FileSnapshot getSnapShot();
LooseRef peel(ObjectIdRef newLeaf); LooseRef peel(ObjectIdRef newLeaf);
} }
private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
implements LooseRef { implements LooseRef {
private final long lastModified; private final FileSnapshot snapShot;
LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) { LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) {
super(LOOSE, refName, id, p); super(LOOSE, refName, id, p);
this.lastModified = mtime; snapShot = FileSnapshot.save(mtime);
} }
public long getLastModified() { public FileSnapshot getSnapShot() {
return lastModified; return snapShot;
} }
public LooseRef peel(ObjectIdRef newLeaf) { public LooseRef peel(ObjectIdRef newLeaf) {
@ -1022,15 +1036,15 @@ public LooseRef peel(ObjectIdRef newLeaf) {
private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
implements LooseRef { implements LooseRef {
private final long lastModified; private final FileSnapshot snapShot;
LooseNonTag(long mtime, String refName, ObjectId id) { LooseNonTag(long mtime, String refName, ObjectId id) {
super(LOOSE, refName, id); super(LOOSE, refName, id);
this.lastModified = mtime; snapShot = FileSnapshot.save(mtime);
} }
public long getLastModified() { public FileSnapshot getSnapShot() {
return lastModified; return snapShot;
} }
public LooseRef peel(ObjectIdRef newLeaf) { public LooseRef peel(ObjectIdRef newLeaf) {
@ -1040,37 +1054,38 @@ public LooseRef peel(ObjectIdRef newLeaf) {
private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
implements LooseRef { implements LooseRef {
private final long lastModified; private final FileSnapshot snapShot;
LooseUnpeeled(long mtime, String refName, ObjectId id) { LooseUnpeeled(long mtime, String refName, ObjectId id) {
super(LOOSE, refName, id); super(LOOSE, refName, id);
this.lastModified = mtime; snapShot = FileSnapshot.save(mtime);
} }
public long getLastModified() { public FileSnapshot getSnapShot() {
return lastModified; return snapShot;
} }
public LooseRef peel(ObjectIdRef newLeaf) { public LooseRef peel(ObjectIdRef newLeaf) {
if (newLeaf.getPeeledObjectId() != null) if (newLeaf.getPeeledObjectId() != null)
return new LoosePeeledTag(lastModified, getName(), return new LoosePeeledTag(snapShot.lastModified(), getName(),
getObjectId(), newLeaf.getPeeledObjectId()); getObjectId(), newLeaf.getPeeledObjectId());
else else
return new LooseNonTag(lastModified, getName(), getObjectId()); return new LooseNonTag(snapShot.lastModified(), getName(),
getObjectId());
} }
} }
private final static class LooseSymbolicRef extends SymbolicRef implements private final static class LooseSymbolicRef extends SymbolicRef implements
LooseRef { LooseRef {
private final long lastModified; private final FileSnapshot snapShot;
LooseSymbolicRef(long mtime, String refName, Ref target) { LooseSymbolicRef(long mtime, String refName, Ref target) {
super(refName, target); super(refName, target);
this.lastModified = mtime; snapShot = FileSnapshot.save(mtime);
} }
public long getLastModified() { public FileSnapshot getSnapShot() {
return lastModified; return snapShot;
} }
public LooseRef peel(ObjectIdRef newLeaf) { public LooseRef peel(ObjectIdRef newLeaf) {