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..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 @@ -71,6 +71,38 @@ public void resolveMasterCommits() throws Exception { 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); @@ -94,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..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 @@ -53,7 +53,9 @@ 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 { @@ -231,6 +233,39 @@ 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")); + } + + @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 1f9286871..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.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; @@ -79,8 +80,11 @@ 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.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; @@ -376,20 +380,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 +471,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 +499,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 +529,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 +569,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,35 +580,86 @@ private ObjectId resolve(final RevWalk rw, final String revstr) throws IOExcepti } } if (time != null) { - String refName = new String(revChars, 0, i); - Ref resolved = getRefDatabase().getRef(refName); - if (resolved == null) - return null; - rev = resolveReflog(rw, resolved, time); + 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 { + 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(); + 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(); @@ -569,13 +668,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) { @@ -622,6 +727,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; @@ -631,10 +749,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)