Support parsing previous checkout as a revision expresion.

Repository.resolve can only return an ObjectId and will
continue to do so, but another method, simplify(), will
be able to return a branch name for some cases.

Previous checkouts can be specified as @{-n}, where n is an
integer speifying the n:th previous branch. The result
is the branch name, unless the checkout was a detached head,
in which case the object id is returned. Since the result
is a branch it may be followed by a references to the reflog,
such as @{-1}@{1} if necessary.

A simple expression like "master" is resolved to master in
simplify, but anything starting with refs gets resolved to
its object id, even if it is a branch.

A symbolic ref is resolved to its leaf ref, e.g. "HEAD" might
be resolved to "master".

Change-Id: Ifb815a1247ba2a3e2d9c46249c09be9d47f2b693
This commit is contained in:
Robin Rosenberg 2012-07-12 08:04:35 +02:00
parent f82d1cb5c0
commit 2a2362fbb3
3 changed files with 138 additions and 45 deletions

View File

@ -126,17 +126,36 @@ public void resolveNonExistingBranch() throws Exception {
}
@Test
public void resolveNegativeEntryNumber() throws Exception {
public void resolvePreviousBranch() throws Exception {
Git git = new Git(db);
writeTrashFile("file.txt", "content");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("create file").call();
RevCommit c1 = git.commit().setMessage("create file").call();
writeTrashFile("file.txt", "content2");
git.add().addFilepattern("file.txt").call();
RevCommit c2 = git.commit().setMessage("edit file").call();
git.checkout().setCreateBranch(true).setName("newbranch")
.setStartPoint(c1).call();
git.checkout().setName(c1.getName()).call();
git.checkout().setName("master").call();
assertEquals(c1.getName(), db.simplify("@{-1}"));
assertEquals("newbranch", db.simplify("@{-2}"));
assertEquals("master", db.simplify("@{-3}"));
// chained expression
try {
db.resolve("master@{-12}");
fail("Exception not thrown");
// Cannot refer to reflog of detached head
db.resolve("@{-1}@{0}");
fail();
} catch (RevisionSyntaxException e) {
assertNotNull(e);
}
assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName());
assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName());
}
@Test

View File

@ -53,6 +53,7 @@
import java.io.IOException;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.junit.Test;
@ -231,6 +232,17 @@ public void testParseLookupPath() throws IOException {
assertNull("no not-a-branch:", db.resolve("not-a-branch:"));
}
@Test
public void resolveExprSimple() throws Exception {
Git git = new Git(db);
writeTrashFile("file.txt", "content");
git.add().addFilepattern("file.txt").call();
git.commit().setMessage("create file").call();
assertEquals("master", db.simplify("master"));
assertEquals("refs/heads/master", db.simplify("refs/heads/master"));
assertEquals("HEAD", db.simplify("HEAD"));
}
private static ObjectId id(String name) {
return ObjectId.fromString(name);
}

View File

@ -79,6 +79,7 @@
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.CheckoutEntry;
import org.eclipse.jgit.storage.file.ReflogEntry;
import org.eclipse.jgit.storage.file.ReflogReader;
import org.eclipse.jgit.treewalk.TreeWalk;
@ -376,20 +377,58 @@ public ObjectId resolve(final String revstr)
throws AmbiguousObjectException, IOException {
RevWalk rw = new RevWalk(this);
try {
return resolve(rw, revstr);
Object resolved = resolve(rw, revstr);
if (resolved instanceof String) {
return getRef((String) resolved).getLeaf().getObjectId();
} else {
return (ObjectId) resolved;
}
} finally {
rw.release();
}
}
private ObjectId resolve(final RevWalk rw, final String revstr) throws IOException {
/**
* Simplify an expression, but unlike {@link #resolve(String)} it will not
* resolve a branch passed or resulting from the expression, such as @{-}.
* Thus this method can be used to process an expression to a method that
* expects a branch or revision id.
*
* @param revstr
* @return object id or ref name from resolved expression
* @throws AmbiguousObjectException
* @throws IOException
*/
public String simplify(final String revstr)
throws AmbiguousObjectException, IOException {
RevWalk rw = new RevWalk(this);
try {
Object resolved = resolve(rw, revstr);
if (resolved != null)
if (resolved instanceof String)
return (String) resolved;
else
return ((AnyObjectId) resolved).getName();
return null;
} finally {
rw.release();
}
}
private Object resolve(final RevWalk rw, final String revstr)
throws IOException {
char[] revChars = revstr.toCharArray();
RevObject rev = null;
String name = null;
int done = 0;
for (int i = 0; i < revChars.length; ++i) {
switch (revChars[i]) {
case '^':
if (rev == null) {
rev = parseSimple(rw, new String(revChars, 0, i));
if (name == null)
name = new String(revChars, done, i);
rev = parseSimple(rw, name);
name = null;
if (rev == null)
return null;
}
@ -429,6 +468,7 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
rev = commit.getParent(pnum - 1);
}
i = j - 1;
done = i;
break;
case '{':
int k;
@ -456,6 +496,7 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
throw new RevisionSyntaxException(revstr);
else
throw new RevisionSyntaxException(revstr);
done = k;
break;
default:
rev = rw.parseAny(rev);
@ -485,7 +526,9 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
break;
case '~':
if (rev == null) {
rev = parseSimple(rw, new String(revChars, 0, i));
if (name == null)
name = new String(revChars, done, i);
rev = parseSimple(rw, name);
if (rev == null)
return null;
}
@ -523,6 +566,8 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
i = l - 1;
break;
case '@':
if (rev != null)
throw new RevisionSyntaxException(revstr);
int m;
String time = null;
for (m = i + 2; m < revChars.length; ++m) {
@ -532,47 +577,48 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
}
}
if (time != null) {
String refName = new String(revChars, 0, i);
Ref ref;
if (refName.equals("")) {
// Currently checked out branch, HEAD if
// detached
ref = getRef(Constants.HEAD);
if (time.matches("^-\\d+$")) {
if (name != null)
throw new RevisionSyntaxException(revstr);
else {
String previousCheckout = resolveReflogCheckout(-Integer
.parseInt(time));
if (ObjectId.isId(previousCheckout))
rev = parseSimple(rw, previousCheckout);
else
name = previousCheckout;
}
} else {
if (name == null)
name = new String(revChars, done, i);
if (name.equals(""))
name = Constants.HEAD;
Ref ref = getRef(name);
if (ref == null)
return null;
// @{n} means current branch, not HEAD@{1} unless
// detached
if (ref.isSymbolic())
ref = ref.getLeaf();
if (ref.getObjectId() == null)
return null;
} else
ref = getRef(refName);
if (ref == null)
return null;
rev = resolveReflog(rw, ref, time);
rev = resolveReflog(rw, ref, time);
name = null;
}
i = m;
} else
i = m - 1;
throw new RevisionSyntaxException(revstr);
break;
case ':': {
RevTree tree;
if (rev == null) {
// We might not yet have parsed the left hand side.
ObjectId id;
try {
if (i == 0)
id = resolve(rw, Constants.HEAD);
else
id = resolve(rw, new String(revChars, 0, i));
} catch (RevisionSyntaxException badSyntax) {
throw new RevisionSyntaxException(revstr);
}
if (id == null)
return null;
tree = rw.parseTree(id);
} else {
tree = rw.parseTree(rev);
if (name == null)
name = new String(revChars, done, i);
if (name.equals(""))
name = Constants.HEAD;
rev = parseSimple(rw, name);
}
if (rev == null)
return null;
tree = rw.parseTree(rev);
if (i == revChars.length - 1)
return tree.copy();
@ -581,13 +627,19 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
tree);
return tw != null ? tw.getObjectId(0) : null;
}
default:
if (rev != null)
throw new RevisionSyntaxException(revstr);
}
}
return rev != null ? rev.copy() : resolveSimple(revstr);
if (rev != null)
return rev.copy();
if (name != null)
return name;
name = revstr.substring(done);
if (getRef(name) != null)
return name;
return resolveSimple(name);
}
private static boolean isHex(char c) {
@ -634,6 +686,19 @@ && isAllHex(revstr, dashg + 4)) {
return null;
}
private String resolveReflogCheckout(int checkoutNo)
throws IOException {
List<ReflogEntry> reflogEntries = new ReflogReader(this, Constants.HEAD)
.getReverseEntries();
for (ReflogEntry entry : reflogEntries) {
CheckoutEntry checkout = entry.parseCheckout();
if (checkout != null)
if (checkoutNo-- == 1)
return checkout.getFromBranch();
}
return null;
}
private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
throws IOException {
int number;
@ -643,10 +708,7 @@ private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
throw new RevisionSyntaxException(MessageFormat.format(
JGitText.get().invalidReflogRevision, time));
}
if (number < 0)
throw new RevisionSyntaxException(MessageFormat.format(
JGitText.get().invalidReflogRevision, time));
assert number >= 0;
ReflogReader reader = new ReflogReader(this, ref.getName());
ReflogEntry entry = reader.getReverseEntry(number);
if (entry == null)