View Javadoc
1   /*
2    * Copyright (C) 2013, CloudBees, Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.api;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertNull;
47  import static org.junit.Assert.assertTrue;
48  
49  import java.io.File;
50  import java.io.FileWriter;
51  import java.io.IOException;
52  import java.util.Arrays;
53  import java.util.Collection;
54  
55  import org.eclipse.jgit.api.errors.GitAPIException;
56  import org.eclipse.jgit.api.errors.RefNotFoundException;
57  import org.eclipse.jgit.junit.RepositoryTestCase;
58  import org.eclipse.jgit.lib.ObjectId;
59  import org.junit.Test;
60  import org.junit.runner.RunWith;
61  import org.junit.runners.Parameterized;
62  import org.junit.runners.Parameterized.Parameter;
63  import org.junit.runners.Parameterized.Parameters;
64  
65  @RunWith(Parameterized.class)
66  public class DescribeCommandTest extends RepositoryTestCase {
67  
68  	private Git git;
69  
70  	@Parameter(0)
71  	public boolean useAnnotatedTags;
72  
73  	@Parameter(1)
74  	public boolean describeUseAllTags;
75  
76  	@Parameters(name = "git tag -a {0}?-a: with git describe {1}?--tags:")
77  	public static Collection<Boolean[]> getUseAnnotatedTagsValues() {
78  		return Arrays.asList(new Boolean[][] { { Boolean.TRUE, Boolean.FALSE },
79  				{ Boolean.FALSE, Boolean.FALSE },
80  				{ Boolean.TRUE, Boolean.TRUE },
81  				{ Boolean.FALSE, Boolean.TRUE } });
82  	}
83  
84  	@Override
85  	public void setUp() throws Exception {
86  		super.setUp();
87  		git = new Git(db);
88  	}
89  
90  	@Test(expected = RefNotFoundException.class)
91  	public void noTargetSet() throws Exception {
92  		git.describe().call();
93  	}
94  
95  	@Test
96  	public void testDescribe() throws Exception {
97  		ObjectId c1 = modify("aaa");
98  
99  		ObjectId c2 = modify("bbb");
100 		tag("alice-t1");
101 
102 		ObjectId c3 = modify("ccc");
103 		tag("bob-t2");
104 
105 		ObjectId c4 = modify("ddd");
106 		assertNameStartsWith(c4, "3e563c5");
107 
108 		assertNull(describe(c1));
109 		assertNull(describe(c1, true));
110 		assertNull(describe(c1, "a*", "b*", "c*"));
111 		assertNull(describe(c2, "bob*"));
112 		assertNull(describe(c2, "?ob*"));
113 
114 		if (useAnnotatedTags || describeUseAllTags) {
115 			assertEquals("alice-t1", describe(c2));
116 			assertEquals("alice-t1", describe(c2, "alice*"));
117 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
118 
119 			assertEquals("bob-t2", describe(c3));
120 			assertEquals("bob-t2-0-g44579eb", describe(c3, true));
121 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
122 			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
123 			assertEquals("bob-t2", describe(c3, "bob*"));
124 			assertEquals("bob-t2", describe(c3, "?ob*"));
125 			assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
126 
127 			// the value verified with git-describe(1)
128 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
129 			assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
130 			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
131 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
132 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
133 		} else {
134 			assertEquals(null, describe(c2));
135 			assertEquals(null, describe(c3));
136 			assertEquals(null, describe(c4));
137 		}
138 
139 		// test default target
140 		if (useAnnotatedTags) {
141 			assertEquals("bob-t2-1-g3e563c5", git.describe().call());
142 			assertEquals("bob-t2-1-g3e563c5",
143 					git.describe().setTags(false).call());
144 			assertEquals("bob-t2-1-g3e563c5",
145 					git.describe().setTags(true).call());
146 		} else {
147 			assertEquals(null, git.describe().call());
148 			assertEquals(null, git.describe().setTags(false).call());
149 			assertEquals("bob-t2-1-g3e563c5",
150 					git.describe().setTags(true).call());
151 		}
152 	}
153 
154 	@Test
155 	public void testDescribeMultiMatch() throws Exception {
156 		ObjectId c1 = modify("aaa");
157 		tag("v1.0.0");
158 		tag("v1.1.1");
159 		ObjectId c2 = modify("bbb");
160 
161 		if (!useAnnotatedTags && !describeUseAllTags) {
162 			assertEquals(null, describe(c1));
163 			assertEquals(null, describe(c2));
164 			return;
165 		}
166 
167 		// Ensure that if we're interested in any tags, we get the first match
168 		// as per Git behaviour
169 		assertEquals("v1.0.0", describe(c1));
170 		assertEquals("v1.0.0-1-g3747db3", describe(c2));
171 
172 		// Ensure that if we're only interested in one of multiple tags, we get the right match
173 		assertEquals("v1.0.0", describe(c1, "v1.0*"));
174 		assertEquals("v1.1.1", describe(c1, "v1.1*"));
175 		assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*"));
176 		assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*"));
177 
178 		// Ensure that ordering of match precedence is preserved as per Git behaviour
179 		assertEquals("v1.0.0", describe(c1, "v1.0*", "v1.1*"));
180 		assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
181 		assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*", "v1.1*"));
182 		assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*", "v1.0*"));
183 	}
184 
185 	/**
186 	 * Make sure it finds a tag when not all ancestries include a tag.
187 	 *
188 	 * <pre>
189 	 * c1 -+-&gt; T  -
190 	 *     |       |
191 	 *     +-&gt; c3 -+-&gt; c4
192 	 * </pre>
193 	 *
194 	 * @throws Exception
195 	 */
196 	@Test
197 	public void testDescribeBranch() throws Exception {
198 		ObjectId c1 = modify("aaa");
199 
200 		ObjectId c2 = modify("bbb");
201 		tag("t");
202 
203 		branch("b", c1);
204 
205 		ObjectId c3 = modify("ccc");
206 
207 		ObjectId c4 = merge(c2);
208 
209 		assertNameStartsWith(c4, "119892b");
210 		if (useAnnotatedTags || describeUseAllTags) {
211 			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
212 		} else {
213 			assertEquals(null, describe(c4));
214 		}
215 		assertNull(describe(c3));
216 		assertNull(describe(c3, true));
217 	}
218 
219 	private void branch(String name, ObjectId base) throws GitAPIException {
220 		git.checkout().setCreateBranch(true).setName(name)
221 				.setStartPoint(base.name()).call();
222 	}
223 
224 	/**
225 	 * When t2 dominates t1, it's clearly preferable to describe by using t2.
226 	 *
227 	 * <pre>
228 	 * t1 -+-&gt; t2  -
229 	 *     |       |
230 	 *     +-&gt; c3 -+-&gt; c4
231 	 * </pre>
232 	 *
233 	 * @throws Exception
234 	 */
235 	@Test
236 	public void t1DominatesT2() throws Exception {
237 		ObjectId c1 = modify("aaa");
238 		tag("t1");
239 
240 		ObjectId c2 = modify("bbb");
241 		tag("t2");
242 
243 		branch("b", c1);
244 
245 		ObjectId c3 = modify("ccc");
246 		assertNameStartsWith(c3, "0244e7f");
247 
248 		ObjectId c4 = merge(c2);
249 
250 		assertNameStartsWith(c4, "119892b");
251 
252 		if (useAnnotatedTags || describeUseAllTags) {
253 			assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
254 			assertEquals("t1-1-g0244e7f", describe(c3));
255 		} else {
256 			assertEquals(null, describe(c4));
257 			assertEquals(null, describe(c3));
258 		}
259 	}
260 
261 	/**
262 	 * When t1 annotated dominates t2 lightweight tag
263 	 *
264 	 * <pre>
265 	 * t1 -+-> t2  -
266 	 *     |       |
267 	 *     +-> c3 -+-> c4
268 	 * </pre>
269 	 *
270 	 * @throws Exception
271 	 */
272 	@Test
273 	public void t1AnnotatedDominatesT2lightweight() throws Exception {
274 		ObjectId c1 = modify("aaa");
275 		tag("t1", useAnnotatedTags);
276 
277 		ObjectId c2 = modify("bbb");
278 		tag("t2", false);
279 
280 		assertNameStartsWith(c2, "3747db3");
281 		if (useAnnotatedTags && !describeUseAllTags) {
282 			assertEquals(
283 					"only annotated tag t1 expected to be used for describe",
284 					"t1-1-g3747db3", describe(c2)); // 1 commits: t2 overridden
285 													// by t1
286 		} else if (!useAnnotatedTags && !describeUseAllTags) {
287 			assertEquals("no commits to describe expected", null, describe(c2));
288 		} else {
289 			assertEquals("lightweight tag t2 expected in describe", "t2",
290 					describe(c2));
291 		}
292 
293 		branch("b", c1);
294 
295 		ObjectId c3 = modify("ccc");
296 
297 		assertNameStartsWith(c3, "0244e7f");
298 		if (useAnnotatedTags || describeUseAllTags) {
299 			assertEquals("t1-1-g0244e7f", describe(c3));
300 		}
301 
302 		ObjectId c4 = merge(c2);
303 
304 		assertNameStartsWith(c4, "119892b");
305 		if (describeUseAllTags) {
306 			assertEquals(
307 					"2 commits for describe commit increment expected since lightweight tag: c4 and c3",
308 					"t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
309 		} else if (!useAnnotatedTags && !describeUseAllTags) {
310 			assertEquals("no matching commits expected", null, describe(c4));
311 		} else {
312 			assertEquals(
313 					"3 commits for describe commit increment expected since annotated tag: c4 and c3 and c2",
314 					"t1-3-g119892b", describe(c4)); //
315 		}
316 	}
317 
318 	/**
319 	 * When t1 is nearer than t2, t2 should be found
320 	 *
321 	 * <pre>
322 	 * c1 -+-&gt; c2 -&gt; t1 -+
323 	 *     |             |
324 	 *     +-&gt; t2 -&gt; c3 -+-&gt; c4
325 	 * </pre>
326 	 *
327 	 * @throws Exception
328 	 */
329 	@Test
330 	public void t1nearerT2() throws Exception {
331 		ObjectId c1 = modify("aaa");
332 		modify("bbb");
333 		ObjectId t1 = modify("ccc");
334 		tag("t1");
335 
336 		branch("b", c1);
337 		modify("ddd");
338 		tag("t2");
339 		modify("eee");
340 		ObjectId c4 = merge(t1);
341 
342 		assertNameStartsWith(c4, "bb389a4");
343 		if (useAnnotatedTags || describeUseAllTags) {
344 			assertEquals("t1-3-gbb389a4", describe(c4));
345 		} else {
346 			assertEquals(null, describe(c4));
347 		}
348 	}
349 
350 	/**
351 	 * When t1 and t2 have same depth native git seems to add the depths of both
352 	 * paths
353 	 *
354 	 * <pre>
355 	 * c1 -+-&gt; t1 -&gt; c2 -+
356 	 *     |             |
357 	 *     +-&gt; t2 -&gt; c3 -+-&gt; c4
358 	 * </pre>
359 	 *
360 	 * @throws Exception
361 	 */
362 	@Test
363 	public void t1sameDepthT2() throws Exception {
364 		ObjectId c1 = modify("aaa");
365 		modify("bbb");
366 		tag("t1");
367 		ObjectId c2 = modify("ccc");
368 
369 		branch("b", c1);
370 		modify("ddd");
371 		tag("t2");
372 		modify("eee");
373 		ObjectId c4 = merge(c2);
374 
375 		assertNameStartsWith(c4, "bb389a4");
376 		if (useAnnotatedTags || describeUseAllTags) {
377 			assertEquals("t2-4-gbb389a4", describe(c4));
378 		} else {
379 			assertEquals(null, describe(c4));
380 		}
381 	}
382 
383 	private ObjectId merge(ObjectId c2) throws GitAPIException {
384 		return git.merge().include(c2).call().getNewHead();
385 	}
386 
387 	private ObjectId modify(String content) throws Exception {
388 		File a = new File(db.getWorkTree(), "a.txt");
389 		touch(a, content);
390 		return git.commit().setAll(true).setMessage(content).call().getId();
391 	}
392 
393 	private void tag(String tag) throws GitAPIException {
394 		tag(tag, this.useAnnotatedTags);
395 	}
396 
397 	private void tag(String tag, boolean annotatedTag) throws GitAPIException {
398 		TagCommand tagCommand = git.tag().setName(tag)
399 				.setAnnotated(annotatedTag);
400 		if (annotatedTag) {
401 			tagCommand.setMessage(tag);
402 		}
403 		tagCommand.call();
404 	}
405 
406 	private static void touch(File f, String contents) throws Exception {
407 		try (FileWriter w = new FileWriter(f)) {
408 			w.write(contents);
409 		}
410 	}
411 
412 	private String describe(ObjectId c1, boolean longDesc)
413 			throws GitAPIException, IOException {
414 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
415 				.setLong(longDesc).call();
416 	}
417 
418 	private String describe(ObjectId c1) throws GitAPIException, IOException {
419 		return describe(c1, false);
420 	}
421 
422 	private String describe(ObjectId c1, String... patterns) throws Exception {
423 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
424 				.setMatch(patterns).call();
425 	}
426 
427 	private static void assertNameStartsWith(ObjectId c4, String prefix) {
428 		assertTrue(c4.name(), c4.name().startsWith(prefix));
429 	}
430 }