Merge "Let RefDirectory use FileSnapShot to handle fast updates"
This commit is contained in:
commit
aa05559fd6
|
@ -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"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue