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));
}
/**
* 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;
writeLooseRef(HEAD, A);
BUG_WorkAroundRacyGitIssues(HEAD);
all = refdir.getRefs(RefDatabase.ALL);
assertEquals(1, all.size());
@ -190,7 +189,6 @@ public void testGetRefs_DeatchedHead2() throws IOException {
writeLooseRef(HEAD, A);
writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues(HEAD);
all = refdir.getRefs(RefDatabase.ALL);
assertEquals(2, all.size());
@ -328,7 +326,6 @@ public void testGetRefs_IgnoresGarbageRef4() throws IOException {
assertTrue(heads.containsKey("refs/heads/C"));
writeLooseRef("refs/heads/B", "FAIL\n");
BUG_WorkAroundRacyGitIssues("refs/heads/B");
heads = refdir.getRefs(RefDatabase.ALL);
assertEquals(2, heads.size());
@ -547,7 +544,6 @@ public void testGetRefs_DiscoversModifiedLoose() throws IOException {
assertEquals(A, all.get(HEAD).getObjectId());
writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues("refs/heads/master");
all = refdir.getRefs(RefDatabase.ALL);
assertEquals(B, all.get(HEAD).getObjectId());
}
@ -561,7 +557,6 @@ public void testGetRef_DiscoversModifiedLoose() throws IOException {
assertEquals(A, all.get(HEAD).getObjectId());
writeLooseRef("refs/heads/master", B);
BUG_WorkAroundRacyGitIssues("refs/heads/master");
Ref master = refdir.getRef("refs/heads/master");
assertEquals(B, master.getObjectId());
@ -760,7 +755,6 @@ public void testGetRefs_CycleInSymbolicRef() throws IOException {
writeLooseRef("refs/5", "ref: refs/6\n");
writeLooseRef("refs/6", "ref: refs/end\n");
BUG_WorkAroundRacyGitIssues("refs/5");
all = refdir.getRefs(RefDatabase.ALL);
r = all.get("refs/1");
assertNull("mising 1 due to cycle", r);
@ -1078,23 +1072,4 @@ private void deleteLooseRef(String name) {
File path = new File(diskRepo.getDirectory(), name);
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);
}
/**
* 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. */
private final long lastModified;

View File

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