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:
parent
f82d1cb5c0
commit
2a2362fbb3
|
@ -126,17 +126,36 @@ public void resolveNonExistingBranch() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveNegativeEntryNumber() throws Exception {
|
public void resolvePreviousBranch() throws Exception {
|
||||||
Git git = new Git(db);
|
Git git = new Git(db);
|
||||||
writeTrashFile("file.txt", "content");
|
writeTrashFile("file.txt", "content");
|
||||||
git.add().addFilepattern("file.txt").call();
|
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 {
|
try {
|
||||||
db.resolve("master@{-12}");
|
// Cannot refer to reflog of detached head
|
||||||
fail("Exception not thrown");
|
db.resolve("@{-1}@{0}");
|
||||||
|
fail();
|
||||||
} catch (RevisionSyntaxException e) {
|
} catch (RevisionSyntaxException e) {
|
||||||
assertNotNull(e);
|
|
||||||
}
|
}
|
||||||
|
assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName());
|
||||||
|
|
||||||
|
assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -231,6 +232,17 @@ public void testParseLookupPath() throws IOException {
|
||||||
assertNull("no not-a-branch:", db.resolve("not-a-branch:"));
|
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) {
|
private static ObjectId id(String name) {
|
||||||
return ObjectId.fromString(name);
|
return ObjectId.fromString(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
import org.eclipse.jgit.revwalk.RevObject;
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
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.ReflogEntry;
|
||||||
import org.eclipse.jgit.storage.file.ReflogReader;
|
import org.eclipse.jgit.storage.file.ReflogReader;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
@ -376,20 +377,58 @@ public ObjectId resolve(final String revstr)
|
||||||
throws AmbiguousObjectException, IOException {
|
throws AmbiguousObjectException, IOException {
|
||||||
RevWalk rw = new RevWalk(this);
|
RevWalk rw = new RevWalk(this);
|
||||||
try {
|
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 {
|
} finally {
|
||||||
rw.release();
|
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();
|
char[] revChars = revstr.toCharArray();
|
||||||
RevObject rev = null;
|
RevObject rev = null;
|
||||||
|
String name = null;
|
||||||
|
int done = 0;
|
||||||
for (int i = 0; i < revChars.length; ++i) {
|
for (int i = 0; i < revChars.length; ++i) {
|
||||||
switch (revChars[i]) {
|
switch (revChars[i]) {
|
||||||
case '^':
|
case '^':
|
||||||
if (rev == null) {
|
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)
|
if (rev == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -429,6 +468,7 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
|
||||||
rev = commit.getParent(pnum - 1);
|
rev = commit.getParent(pnum - 1);
|
||||||
}
|
}
|
||||||
i = j - 1;
|
i = j - 1;
|
||||||
|
done = i;
|
||||||
break;
|
break;
|
||||||
case '{':
|
case '{':
|
||||||
int k;
|
int k;
|
||||||
|
@ -456,6 +496,7 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
|
||||||
throw new RevisionSyntaxException(revstr);
|
throw new RevisionSyntaxException(revstr);
|
||||||
else
|
else
|
||||||
throw new RevisionSyntaxException(revstr);
|
throw new RevisionSyntaxException(revstr);
|
||||||
|
done = k;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rev = rw.parseAny(rev);
|
rev = rw.parseAny(rev);
|
||||||
|
@ -485,7 +526,9 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
|
||||||
break;
|
break;
|
||||||
case '~':
|
case '~':
|
||||||
if (rev == null) {
|
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)
|
if (rev == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -523,6 +566,8 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
|
||||||
i = l - 1;
|
i = l - 1;
|
||||||
break;
|
break;
|
||||||
case '@':
|
case '@':
|
||||||
|
if (rev != null)
|
||||||
|
throw new RevisionSyntaxException(revstr);
|
||||||
int m;
|
int m;
|
||||||
String time = null;
|
String time = null;
|
||||||
for (m = i + 2; m < revChars.length; ++m) {
|
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) {
|
if (time != null) {
|
||||||
String refName = new String(revChars, 0, i);
|
if (time.matches("^-\\d+$")) {
|
||||||
Ref ref;
|
if (name != null)
|
||||||
if (refName.equals("")) {
|
throw new RevisionSyntaxException(revstr);
|
||||||
// Currently checked out branch, HEAD if
|
else {
|
||||||
// detached
|
String previousCheckout = resolveReflogCheckout(-Integer
|
||||||
ref = getRef(Constants.HEAD);
|
.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)
|
if (ref == null)
|
||||||
return null;
|
return null;
|
||||||
|
// @{n} means current branch, not HEAD@{1} unless
|
||||||
|
// detached
|
||||||
if (ref.isSymbolic())
|
if (ref.isSymbolic())
|
||||||
ref = ref.getLeaf();
|
ref = ref.getLeaf();
|
||||||
if (ref.getObjectId() == null)
|
rev = resolveReflog(rw, ref, time);
|
||||||
return null;
|
name = null;
|
||||||
} else
|
}
|
||||||
ref = getRef(refName);
|
|
||||||
if (ref == null)
|
|
||||||
return null;
|
|
||||||
rev = resolveReflog(rw, ref, time);
|
|
||||||
i = m;
|
i = m;
|
||||||
} else
|
} else
|
||||||
i = m - 1;
|
throw new RevisionSyntaxException(revstr);
|
||||||
break;
|
break;
|
||||||
case ':': {
|
case ':': {
|
||||||
RevTree tree;
|
RevTree tree;
|
||||||
if (rev == null) {
|
if (rev == null) {
|
||||||
// We might not yet have parsed the left hand side.
|
if (name == null)
|
||||||
ObjectId id;
|
name = new String(revChars, done, i);
|
||||||
try {
|
if (name.equals(""))
|
||||||
if (i == 0)
|
name = Constants.HEAD;
|
||||||
id = resolve(rw, Constants.HEAD);
|
rev = parseSimple(rw, name);
|
||||||
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 (rev == null)
|
||||||
|
return null;
|
||||||
|
tree = rw.parseTree(rev);
|
||||||
if (i == revChars.length - 1)
|
if (i == revChars.length - 1)
|
||||||
return tree.copy();
|
return tree.copy();
|
||||||
|
|
||||||
|
@ -581,13 +627,19 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti
|
||||||
tree);
|
tree);
|
||||||
return tw != null ? tw.getObjectId(0) : null;
|
return tw != null ? tw.getObjectId(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (rev != null)
|
if (rev != null)
|
||||||
throw new RevisionSyntaxException(revstr);
|
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) {
|
private static boolean isHex(char c) {
|
||||||
|
@ -634,6 +686,19 @@ && isAllHex(revstr, dashg + 4)) {
|
||||||
return null;
|
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)
|
private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int number;
|
int number;
|
||||||
|
@ -643,10 +708,7 @@ private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
|
||||||
throw new RevisionSyntaxException(MessageFormat.format(
|
throw new RevisionSyntaxException(MessageFormat.format(
|
||||||
JGitText.get().invalidReflogRevision, time));
|
JGitText.get().invalidReflogRevision, time));
|
||||||
}
|
}
|
||||||
if (number < 0)
|
assert number >= 0;
|
||||||
throw new RevisionSyntaxException(MessageFormat.format(
|
|
||||||
JGitText.get().invalidReflogRevision, time));
|
|
||||||
|
|
||||||
ReflogReader reader = new ReflogReader(this, ref.getName());
|
ReflogReader reader = new ReflogReader(this, ref.getName());
|
||||||
ReflogEntry entry = reader.getReverseEntry(number);
|
ReflogEntry entry = reader.getReverseEntry(number);
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
|
|
Loading…
Reference in New Issue