blob: df9ae6a0fb61f20334d7e498475fed4864103169 [file] [log] [blame]
/*
* Copyright (C) 2013, CloudBees, Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.api;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class DescribeCommandTest extends RepositoryTestCase {
private Git git;
@Parameter(0)
public boolean useAnnotatedTags;
@Parameter(1)
public boolean describeUseAllTags;
@Parameters(name = "git tag -a {0}?-a: with git describe {1}?--tags:")
public static Collection<Boolean[]> getUseAnnotatedTagsValues() {
return Arrays.asList(new Boolean[][] { { Boolean.TRUE, Boolean.FALSE },
{ Boolean.FALSE, Boolean.FALSE },
{ Boolean.TRUE, Boolean.TRUE },
{ Boolean.FALSE, Boolean.TRUE } });
}
@Override
public void setUp() throws Exception {
super.setUp();
git = new Git(db);
}
@Test(expected = RefNotFoundException.class)
public void noTargetSet() throws Exception {
git.describe().call();
}
@Test
public void testDescribe() throws Exception {
ObjectId c1 = modify("aaa");
ObjectId c2 = modify("bbb");
tag("alice-t1");
ObjectId c3 = modify("ccc");
tag("bob-t2");
ObjectId c4 = modify("ddd");
assertNameStartsWith(c4, "3e563c5");
assertNull(describe(c1));
assertNull(describe(c1, true, false));
assertNull(describe(c1, "a*", "b*", "c*"));
assertNull(describe(c2, "bob*"));
assertNull(describe(c2, "?ob*"));
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("alice-t1", describe(c2));
assertEquals("alice-t1", describe(c2, "alice*"));
assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
assertEquals("bob-t2", describe(c3));
assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
assertEquals("bob-t2", describe(c3, "bob*"));
assertEquals("bob-t2", describe(c3, "?ob*"));
assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
// the value verified with git-describe(1)
assertEquals("bob-t2-1-g3e563c5", describe(c4));
assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
} else {
assertEquals(null, describe(c2));
assertEquals(null, describe(c3));
assertEquals(null, describe(c4));
assertEquals("3747db3", describe(c2, false, true));
assertEquals("44579eb", describe(c3, false, true));
assertEquals("3e563c5", describe(c4, false, true));
}
// test default target
if (useAnnotatedTags) {
assertEquals("bob-t2-1-g3e563c5", git.describe().call());
assertEquals("bob-t2-1-g3e563c5",
git.describe().setTags(false).call());
assertEquals("bob-t2-1-g3e563c5",
git.describe().setTags(true).call());
} else {
assertEquals(null, git.describe().call());
assertEquals(null, git.describe().setTags(false).call());
assertEquals("bob-t2-1-g3e563c5",
git.describe().setTags(true).call());
}
}
@Test
public void testDescribeMultiMatch() throws Exception {
ObjectId c1 = modify("aaa");
tag("v1.0.0");
tick();
tag("v1.0.1");
tick();
tag("v1.1.0");
tick();
tag("v1.1.1");
ObjectId c2 = modify("bbb");
if (!useAnnotatedTags && !describeUseAllTags) {
assertEquals(null, describe(c1));
assertEquals(null, describe(c2));
assertEquals("fd70040", describe(c1, false, true));
assertEquals("b89dead", describe(c2, false, true));
return;
}
// Ensure that if we're interested in any tags, we get the most recent tag
// as per Git behaviour since 1.7.1.1
if (useAnnotatedTags) {
assertEquals("v1.1.1", describe(c1));
assertEquals("v1.1.1-1-gb89dead", describe(c2));
// Ensure that if we're only interested in one of multiple tags, we get the right match
assertEquals("v1.0.1", describe(c1, "v1.0*"));
assertEquals("v1.1.1", describe(c1, "v1.1*"));
assertEquals("v1.0.1-1-gb89dead", describe(c2, "v1.0*"));
assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*"));
// Ensure that ordering of match precedence is preserved as per Git behaviour
assertEquals("v1.1.1", describe(c1, "v1.0*", "v1.1*"));
assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.0*", "v1.1*"));
assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*", "v1.0*"));
} else {
// no timestamps so no guarantees on which tag is chosen
assertNotNull(describe(c1));
assertNotNull(describe(c2));
assertNotNull(describe(c1, "v1.0*"));
assertNotNull(describe(c1, "v1.1*"));
assertNotNull(describe(c2, "v1.0*"));
assertNotNull(describe(c2, "v1.1*"));
// Ensure that ordering of match precedence is preserved as per Git behaviour
assertNotNull(describe(c1, "v1.0*", "v1.1*"));
assertNotNull(describe(c1, "v1.1*", "v1.0*"));
assertNotNull(describe(c2, "v1.0*", "v1.1*"));
assertNotNull(describe(c2, "v1.1*", "v1.0*"));
}
}
/**
* Make sure it finds a tag when not all ancestries include a tag.
*
* <pre>
* c1 -+-&gt; T -
* | |
* +-&gt; c3 -+-&gt; c4
* </pre>
*
* @throws Exception
*/
@Test
public void testDescribeBranch() throws Exception {
ObjectId c1 = modify("aaa");
ObjectId c2 = modify("bbb");
tag("t");
branch("b", c1);
ObjectId c3 = modify("ccc");
ObjectId c4 = merge(c2);
assertNameStartsWith(c4, "119892b");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
} else {
assertEquals(null, describe(c4));
assertEquals("119892b", describe(c4, false, true));
}
assertNull(describe(c3));
assertNull(describe(c3, true, false));
}
private void branch(String name, ObjectId base) throws GitAPIException {
git.checkout().setCreateBranch(true).setName(name)
.setStartPoint(base.name()).call();
}
/**
* When t2 dominates t1, it's clearly preferable to describe by using t2.
*
* <pre>
* t1 -+-&gt; t2 -
* | |
* +-&gt; c3 -+-&gt; c4
* </pre>
*
* @throws Exception
*/
@Test
public void t1DominatesT2() throws Exception {
ObjectId c1 = modify("aaa");
tag("t1");
ObjectId c2 = modify("bbb");
tag("t2");
branch("b", c1);
ObjectId c3 = modify("ccc");
assertNameStartsWith(c3, "0244e7f");
ObjectId c4 = merge(c2);
assertNameStartsWith(c4, "119892b");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
assertEquals("t1-1-g0244e7f", describe(c3));
} else {
assertEquals(null, describe(c4));
assertEquals(null, describe(c3));
assertEquals("119892b", describe(c4, false, true));
assertEquals("0244e7f", describe(c3, false, true));
}
}
/**
* When t1 annotated dominates t2 lightweight tag
*
* <pre>
* t1 -+-> t2 -
* | |
* +-> c3 -+-> c4
* </pre>
*
* @throws Exception
*/
@Test
public void t1AnnotatedDominatesT2lightweight() throws Exception {
ObjectId c1 = modify("aaa");
tag("t1", useAnnotatedTags);
ObjectId c2 = modify("bbb");
tag("t2", false);
assertNameStartsWith(c2, "3747db3");
if (useAnnotatedTags && !describeUseAllTags) {
assertEquals(
"only annotated tag t1 expected to be used for describe",
"t1-1-g3747db3", describe(c2)); // 1 commits: t2 overridden
// by t1
} else if (!useAnnotatedTags && !describeUseAllTags) {
assertEquals("no commits to describe expected", null, describe(c2));
} else {
assertEquals("lightweight tag t2 expected in describe", "t2",
describe(c2));
}
branch("b", c1);
ObjectId c3 = modify("ccc");
assertNameStartsWith(c3, "0244e7f");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("t1-1-g0244e7f", describe(c3));
}
ObjectId c4 = merge(c2);
assertNameStartsWith(c4, "119892b");
if (describeUseAllTags) {
assertEquals(
"2 commits for describe commit increment expected since lightweight tag: c4 and c3",
"t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
} else if (!useAnnotatedTags && !describeUseAllTags) {
assertEquals("no matching commits expected", null, describe(c4));
} else {
assertEquals(
"3 commits for describe commit increment expected since annotated tag: c4 and c3 and c2",
"t1-3-g119892b", describe(c4)); //
}
}
/**
* When t1 is nearer than t2, t2 should be found
*
* <pre>
* c1 -+-&gt; c2 -&gt; t1 -+
* | |
* +-&gt; t2 -&gt; c3 -+-&gt; c4
* </pre>
*
* @throws Exception
*/
@Test
public void t1nearerT2() throws Exception {
ObjectId c1 = modify("aaa");
modify("bbb");
ObjectId t1 = modify("ccc");
tag("t1");
branch("b", c1);
modify("ddd");
tag("t2");
modify("eee");
ObjectId c4 = merge(t1);
assertNameStartsWith(c4, "bb389a4");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("t1-3-gbb389a4", describe(c4));
} else {
assertEquals(null, describe(c4));
assertEquals("bb389a4", describe(c4, false, true));
}
}
/**
* When t1 and t2 have same depth native git seems to add the depths of both
* paths
*
* <pre>
* c1 -+-&gt; t1 -&gt; c2 -+
* | |
* +-&gt; t2 -&gt; c3 -+-&gt; c4
* </pre>
*
* @throws Exception
*/
@Test
public void t1sameDepthT2() throws Exception {
ObjectId c1 = modify("aaa");
modify("bbb");
tag("t1");
ObjectId c2 = modify("ccc");
branch("b", c1);
modify("ddd");
tag("t2");
modify("eee");
ObjectId c4 = merge(c2);
assertNameStartsWith(c4, "bb389a4");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("t2-4-gbb389a4", describe(c4));
} else {
assertEquals(null, describe(c4));
assertEquals("bb389a4", describe(c4, false, true));
}
}
@Test
public void globMatchWithSlashes() throws Exception {
ObjectId c1 = modify("aaa");
tag("a/b/version");
ObjectId c2 = modify("bbb");
tag("a/b/version2");
if (useAnnotatedTags || describeUseAllTags) {
assertEquals("a/b/version", describe(c1, "*/version*"));
assertEquals("a/b/version2", describe(c2, "*/version*"));
} else {
assertNull(describe(c1));
assertNull(describe(c1, "*/version*"));
assertNull(describe(c2));
assertNull(describe(c2, "*/version*"));
}
}
private ObjectId merge(ObjectId c2) throws GitAPIException {
return git.merge().include(c2).call().getNewHead();
}
private ObjectId modify(String content) throws Exception {
File a = new File(db.getWorkTree(), "a.txt");
touch(a, content);
return git.commit().setAll(true).setMessage(content).call().getId();
}
private void tag(String tag) throws GitAPIException {
tag(tag, this.useAnnotatedTags);
}
private void tag(String tag, boolean annotatedTag) throws GitAPIException {
TagCommand tagCommand = git.tag().setName(tag)
.setAnnotated(annotatedTag);
if (annotatedTag) {
tagCommand.setMessage(tag);
}
tagCommand.call();
}
private static void touch(File f, String contents) throws Exception {
try (BufferedWriter w = Files.newBufferedWriter(f.toPath(), UTF_8)) {
w.write(contents);
}
}
private String describe(ObjectId c1, boolean longDesc, boolean always)
throws GitAPIException, IOException {
return git.describe().setTarget(c1).setTags(describeUseAllTags)
.setLong(longDesc).setAlways(always).call();
}
private String describe(ObjectId c1) throws GitAPIException, IOException {
return describe(c1, false, false);
}
private String describe(ObjectId c1, String... patterns) throws Exception {
return git.describe().setTarget(c1).setTags(describeUseAllTags)
.setMatch(patterns).call();
}
private static void assertNameStartsWith(ObjectId c4, String prefix) {
assertTrue(c4.name(), c4.name().startsWith(prefix));
}
}