From 2a2362fbb399582bf0f1be9f0f55101ac9daa201 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Thu, 12 Jul 2012 08:04:35 +0200 Subject: [PATCH] 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 --- .../eclipse/jgit/lib/ReflogResolveTest.java | 29 +++- .../jgit/lib/RepositoryResolveTest.java | 12 ++ .../src/org/eclipse/jgit/lib/Repository.java | 142 +++++++++++++----- 3 files changed, 138 insertions(+), 45 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java index 15cbbcbdf..fe28b47bb 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java @@ -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 diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java index f975a8773..1c6a54772 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java @@ -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); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 2916c29b6..db030952a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -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 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)