From f82d1cb5c0ee593eb7a68b2d9ea539399f9204ba Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Mon, 16 Jul 2012 23:47:46 +0200 Subject: [PATCH 1/3] Allow a @ without branch in revision syntax No branch before @ is interpreted as the currently checked out branch. For detached heads it would be HEAD, but normally it is the branch that HEAD refers to. Change-Id: I051a1724fa390b8212e8986ba832b1347a20371e --- .../eclipse/jgit/lib/ReflogResolveTest.java | 32 +++++++++++++++++++ .../src/org/eclipse/jgit/lib/Repository.java | 18 +++++++++-- 2 files changed, 47 insertions(+), 3 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 c3de79b6c..15cbbcbdf 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 @@ -71,6 +71,38 @@ public class ReflogResolveTest extends RepositoryTestCase { assertEquals(c1, db.resolve("master@{1}")); } + @Test + public void resolveUnnamedCurrentBranchCommits() throws Exception { + Git git = new Git(db); + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").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(); + + assertEquals(c2, db.resolve("master@{0}")); + assertEquals(c1, db.resolve("master@{1}")); + + git.checkout().setCreateBranch(true).setName("newbranch") + .setStartPoint(c1).call(); + + // same as current branch, e.g. master + assertEquals(c1, db.resolve("@{0}")); + try { + assertEquals(c1, db.resolve("@{1}")); + fail(); // Looking at wrong ref, e.g HEAD + } catch (RevisionSyntaxException e) { + assertNotNull(e); + } + + // detached head, read HEAD reflog + git.checkout().setName(c2.getName()).call(); + assertEquals(c2, db.resolve("@{0}")); + assertEquals(c1, db.resolve("@{1}")); + assertEquals(c2, db.resolve("@{2}")); + } + @Test public void resolveReflogParent() throws Exception { Git git = new Git(db); 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 1f9286871..2916c29b6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -533,10 +533,22 @@ public abstract class Repository { } if (time != null) { String refName = new String(revChars, 0, i); - Ref resolved = getRefDatabase().getRef(refName); - if (resolved == null) + Ref ref; + if (refName.equals("")) { + // Currently checked out branch, HEAD if + // detached + ref = getRef(Constants.HEAD); + if (ref == null) + return null; + if (ref.isSymbolic()) + ref = ref.getLeaf(); + if (ref.getObjectId() == null) + return null; + } else + ref = getRef(refName); + if (ref == null) return null; - rev = resolveReflog(rw, resolved, time); + rev = resolveReflog(rw, ref, time); i = m; } else i = m - 1; From 2a2362fbb399582bf0f1be9f0f55101ac9daa201 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Thu, 12 Jul 2012 08:04:35 +0200 Subject: [PATCH 2/3] 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 class ReflogResolveTest extends RepositoryTestCase { } @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 static org.junit.Assert.fail; 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 class RepositoryResolveTest extends SampleDataRepositoryTestCase { 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.RevCommit; 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 abstract class Repository { 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 @@ public abstract class Repository { rev = commit.getParent(pnum - 1); } i = j - 1; + done = i; break; case '{': int k; @@ -456,6 +496,7 @@ public abstract class Repository { throw new RevisionSyntaxException(revstr); else throw new RevisionSyntaxException(revstr); + done = k; break; default: rev = rw.parseAny(rev); @@ -485,7 +526,9 @@ public abstract class Repository { 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 @@ public abstract class Repository { 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 @@ public abstract class Repository { } } 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 @@ public abstract class Repository { 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 @@ public abstract class Repository { 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 @@ public abstract class Repository { 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) From c010c93694eb76e90f60de01b5a6b946c2472e9d Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Thu, 19 Jul 2012 16:05:28 +0200 Subject: [PATCH 3/3] Support []@{upstream} revision syntax Resolves into a ref name corresponding to the named (or current) branch's upstream ref. Change-Id: I98df46cedb498724cf14343fbb168f24ff667929 --- .../jgit/lib/RepositoryResolveTest.java | 23 ++++++++++ .../src/org/eclipse/jgit/lib/Repository.java | 43 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) 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 1c6a54772..deb830ff5 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 @@ -55,6 +55,7 @@ import java.io.IOException; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; public class RepositoryResolveTest extends SampleDataRepositoryTestCase { @@ -243,6 +244,28 @@ public class RepositoryResolveTest extends SampleDataRepositoryTestCase { assertEquals("HEAD", db.simplify("HEAD")); } + @Test + public void resolveUpstream() throws Exception { + Git git = new Git(db); + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file2.txt", "content"); + RefUpdate updateRemoteRef = db.updateRef("refs/remotes/origin/main"); + updateRemoteRef.setNewObjectId(c1); + updateRemoteRef.update(); + db.getConfig().setString("branch", "master", "remote", "origin"); + db.getConfig() + .setString("branch", "master", "merge", "refs/heads/main"); + db.getConfig().setString("remote", "origin", "url", + "git://example.com/here"); + db.getConfig().setString("remote", "origin", "fetch", + "+refs/heads/*:refs/remotes/origin/*"); + git.add().addFilepattern("file2.txt").call(); + git.commit().setMessage("create file").call(); + assertEquals("refs/remotes/origin/main", db.simplify("@{upstream}")); + } + 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 db030952a..82394dd75 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -51,6 +51,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; @@ -82,6 +83,8 @@ 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.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; @@ -577,7 +580,45 @@ public abstract class Repository { } } if (time != null) { - if (time.matches("^-\\d+$")) { + if (time.equals("upstream")) { + if (name == null) + name = new String(revChars, done, i); + if (name.equals("")) + // Currently checked out branch, HEAD if + // detached + name = Constants.HEAD; + Ref ref = getRef(name); + if (ref == null) + return null; + if (ref.isSymbolic()) + ref = ref.getLeaf(); + name = ref.getName(); + + RemoteConfig remoteConfig; + try { + remoteConfig = new RemoteConfig(getConfig(), + "origin"); + } catch (URISyntaxException e) { + throw new RevisionSyntaxException(revstr); + } + String remoteBranchName = getConfig() + .getString( + ConfigConstants.CONFIG_BRANCH_SECTION, + Repository.shortenRefName(ref.getName()), + ConfigConstants.CONFIG_KEY_MERGE); + List fetchRefSpecs = remoteConfig + .getFetchRefSpecs(); + for (RefSpec refSpec : fetchRefSpecs) { + if (refSpec.matchSource(remoteBranchName)) { + RefSpec expandFromSource = refSpec + .expandFromSource(remoteBranchName); + name = expandFromSource.getDestination(); + break; + } + } + if (name == null) + throw new RevisionSyntaxException(revstr); + } else if (time.matches("^-\\d+$")) { if (name != null) throw new RevisionSyntaxException(revstr); else {