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 java.nio.charset.StandardCharsets.UTF_8;
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertNull;
48  import static org.junit.Assert.assertNotNull;
49  import static org.junit.Assert.assertTrue;
50  
51  import java.io.BufferedWriter;
52  import java.io.File;
53  import java.io.IOException;
54  import java.nio.file.Files;
55  import java.util.Arrays;
56  import java.util.Collection;
57  
58  import org.eclipse.jgit.api.errors.GitAPIException;
59  import org.eclipse.jgit.api.errors.RefNotFoundException;
60  import org.eclipse.jgit.junit.RepositoryTestCase;
61  import org.eclipse.jgit.lib.ObjectId;
62  import org.junit.Test;
63  import org.junit.runner.RunWith;
64  import org.junit.runners.Parameterized;
65  import org.junit.runners.Parameterized.Parameter;
66  import org.junit.runners.Parameterized.Parameters;
67  
68  @RunWith(Parameterized.class)
69  public class DescribeCommandTest extends RepositoryTestCase {
70  
71  	private Git git;
72  
73  	@Parameter(0)
74  	public boolean useAnnotatedTags;
75  
76  	@Parameter(1)
77  	public boolean describeUseAllTags;
78  
79  	@Parameters(name = "git tag -a {0}?-a: with git describe {1}?--tags:")
80  	public static Collection<Boolean[]> getUseAnnotatedTagsValues() {
81  		return Arrays.asList(new Boolean[][] { { Boolean.TRUE, Boolean.FALSE },
82  				{ Boolean.FALSE, Boolean.FALSE },
83  				{ Boolean.TRUE, Boolean.TRUE },
84  				{ Boolean.FALSE, Boolean.TRUE } });
85  	}
86  
87  	@Override
88  	public void setUp() throws Exception {
89  		super.setUp();
90  		git = new Git(db);
91  	}
92  
93  	@Test(expected = RefNotFoundException.class)
94  	public void noTargetSet() throws Exception {
95  		git.describe().call();
96  	}
97  
98  	@Test
99  	public void testDescribe() throws Exception {
100 		ObjectId c1 = modify("aaa");
101 
102 		ObjectId c2 = modify("bbb");
103 		tag("alice-t1");
104 
105 		ObjectId c3 = modify("ccc");
106 		tag("bob-t2");
107 
108 		ObjectId c4 = modify("ddd");
109 		assertNameStartsWith(c4, "3e563c5");
110 
111 		assertNull(describe(c1));
112 		assertNull(describe(c1, true, false));
113 		assertNull(describe(c1, "a*", "b*", "c*"));
114 		assertNull(describe(c2, "bob*"));
115 		assertNull(describe(c2, "?ob*"));
116 
117 		if (useAnnotatedTags || describeUseAllTags) {
118 			assertEquals("alice-t1", describe(c2));
119 			assertEquals("alice-t1", describe(c2, "alice*"));
120 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
121 
122 			assertEquals("bob-t2", describe(c3));
123 			assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
124 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
125 			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
126 			assertEquals("bob-t2", describe(c3, "bob*"));
127 			assertEquals("bob-t2", describe(c3, "?ob*"));
128 			assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
129 
130 			// the value verified with git-describe(1)
131 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
132 			assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
133 			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
134 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
135 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
136 		} else {
137 			assertEquals(null, describe(c2));
138 			assertEquals(null, describe(c3));
139 			assertEquals(null, describe(c4));
140 
141 			assertEquals("3747db3", describe(c2, false, true));
142 			assertEquals("44579eb", describe(c3, false, true));
143 			assertEquals("3e563c5", describe(c4, false, true));
144 		}
145 
146 		// test default target
147 		if (useAnnotatedTags) {
148 			assertEquals("bob-t2-1-g3e563c5", git.describe().call());
149 			assertEquals("bob-t2-1-g3e563c5",
150 					git.describe().setTags(false).call());
151 			assertEquals("bob-t2-1-g3e563c5",
152 					git.describe().setTags(true).call());
153 		} else {
154 			assertEquals(null, git.describe().call());
155 			assertEquals(null, git.describe().setTags(false).call());
156 			assertEquals("bob-t2-1-g3e563c5",
157 					git.describe().setTags(true).call());
158 		}
159 	}
160 
161 	@Test
162 	public void testDescribeMultiMatch() throws Exception {
163 		ObjectId c1 = modify("aaa");
164 		tag("v1.0.0");
165 		tick();
166 		tag("v1.0.1");
167 		tick();
168 		tag("v1.1.0");
169 		tick();
170 		tag("v1.1.1");
171 		ObjectId c2 = modify("bbb");
172 
173 		if (!useAnnotatedTags && !describeUseAllTags) {
174 			assertEquals(null, describe(c1));
175 			assertEquals(null, describe(c2));
176 
177 			assertEquals("fd70040", describe(c1, false, true));
178 			assertEquals("b89dead", describe(c2, false, true));
179 
180 			return;
181 		}
182 
183 		// Ensure that if we're interested in any tags, we get the most recent tag
184 		// as per Git behaviour since 1.7.1.1
185 		if (useAnnotatedTags) {
186 			assertEquals("v1.1.1", describe(c1));
187 			assertEquals("v1.1.1-1-gb89dead", describe(c2));
188 			// Ensure that if we're only interested in one of multiple tags, we get the right match
189 			assertEquals("v1.0.1", describe(c1, "v1.0*"));
190 			assertEquals("v1.1.1", describe(c1, "v1.1*"));
191 			assertEquals("v1.0.1-1-gb89dead", describe(c2, "v1.0*"));
192 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*"));
193 
194 			// Ensure that ordering of match precedence is preserved as per Git behaviour
195 			assertEquals("v1.1.1", describe(c1, "v1.0*", "v1.1*"));
196 			assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
197 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.0*", "v1.1*"));
198 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*", "v1.0*"));
199 		} else {
200 			// no timestamps so no guarantees on which tag is chosen
201 			assertNotNull(describe(c1));
202 			assertNotNull(describe(c2));
203 
204 			assertNotNull(describe(c1, "v1.0*"));
205 			assertNotNull(describe(c1, "v1.1*"));
206 			assertNotNull(describe(c2, "v1.0*"));
207 			assertNotNull(describe(c2, "v1.1*"));
208 
209 			// Ensure that ordering of match precedence is preserved as per Git behaviour
210 			assertNotNull(describe(c1, "v1.0*", "v1.1*"));
211 			assertNotNull(describe(c1, "v1.1*", "v1.0*"));
212 			assertNotNull(describe(c2, "v1.0*", "v1.1*"));
213 			assertNotNull(describe(c2, "v1.1*", "v1.0*"));
214 		}
215 	}
216 
217 	/**
218 	 * Make sure it finds a tag when not all ancestries include a tag.
219 	 *
220 	 * <pre>
221 	 * c1 -+-&gt; T  -
222 	 *     |       |
223 	 *     +-&gt; c3 -+-&gt; c4
224 	 * </pre>
225 	 *
226 	 * @throws Exception
227 	 */
228 	@Test
229 	public void testDescribeBranch() throws Exception {
230 		ObjectId c1 = modify("aaa");
231 
232 		ObjectId c2 = modify("bbb");
233 		tag("t");
234 
235 		branch("b", c1);
236 
237 		ObjectId c3 = modify("ccc");
238 
239 		ObjectId c4 = merge(c2);
240 
241 		assertNameStartsWith(c4, "119892b");
242 		if (useAnnotatedTags || describeUseAllTags) {
243 			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
244 		} else {
245 			assertEquals(null, describe(c4));
246 
247 			assertEquals("119892b", describe(c4, false, true));
248 		}
249 		assertNull(describe(c3));
250 		assertNull(describe(c3, true, false));
251 	}
252 
253 	private void branch(String name, ObjectId base) throws GitAPIException {
254 		git.checkout().setCreateBranch(true).setName(name)
255 				.setStartPoint(base.name()).call();
256 	}
257 
258 	/**
259 	 * When t2 dominates t1, it's clearly preferable to describe by using t2.
260 	 *
261 	 * <pre>
262 	 * t1 -+-&gt; t2  -
263 	 *     |       |
264 	 *     +-&gt; c3 -+-&gt; c4
265 	 * </pre>
266 	 *
267 	 * @throws Exception
268 	 */
269 	@Test
270 	public void t1DominatesT2() throws Exception {
271 		ObjectId c1 = modify("aaa");
272 		tag("t1");
273 
274 		ObjectId c2 = modify("bbb");
275 		tag("t2");
276 
277 		branch("b", c1);
278 
279 		ObjectId c3 = modify("ccc");
280 		assertNameStartsWith(c3, "0244e7f");
281 
282 		ObjectId c4 = merge(c2);
283 
284 		assertNameStartsWith(c4, "119892b");
285 
286 		if (useAnnotatedTags || describeUseAllTags) {
287 			assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
288 			assertEquals("t1-1-g0244e7f", describe(c3));
289 		} else {
290 			assertEquals(null, describe(c4));
291 			assertEquals(null, describe(c3));
292 
293 			assertEquals("119892b", describe(c4, false, true));
294 			assertEquals("0244e7f", describe(c3, false, true));
295 		}
296 	}
297 
298 	/**
299 	 * When t1 annotated dominates t2 lightweight tag
300 	 *
301 	 * <pre>
302 	 * t1 -+-> t2  -
303 	 *     |       |
304 	 *     +-> c3 -+-> c4
305 	 * </pre>
306 	 *
307 	 * @throws Exception
308 	 */
309 	@Test
310 	public void t1AnnotatedDominatesT2lightweight() throws Exception {
311 		ObjectId c1 = modify("aaa");
312 		tag("t1", useAnnotatedTags);
313 
314 		ObjectId c2 = modify("bbb");
315 		tag("t2", false);
316 
317 		assertNameStartsWith(c2, "3747db3");
318 		if (useAnnotatedTags && !describeUseAllTags) {
319 			assertEquals(
320 					"only annotated tag t1 expected to be used for describe",
321 					"t1-1-g3747db3", describe(c2)); // 1 commits: t2 overridden
322 													// by t1
323 		} else if (!useAnnotatedTags && !describeUseAllTags) {
324 			assertEquals("no commits to describe expected", null, describe(c2));
325 		} else {
326 			assertEquals("lightweight tag t2 expected in describe", "t2",
327 					describe(c2));
328 		}
329 
330 		branch("b", c1);
331 
332 		ObjectId c3 = modify("ccc");
333 
334 		assertNameStartsWith(c3, "0244e7f");
335 		if (useAnnotatedTags || describeUseAllTags) {
336 			assertEquals("t1-1-g0244e7f", describe(c3));
337 		}
338 
339 		ObjectId c4 = merge(c2);
340 
341 		assertNameStartsWith(c4, "119892b");
342 		if (describeUseAllTags) {
343 			assertEquals(
344 					"2 commits for describe commit increment expected since lightweight tag: c4 and c3",
345 					"t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
346 		} else if (!useAnnotatedTags && !describeUseAllTags) {
347 			assertEquals("no matching commits expected", null, describe(c4));
348 		} else {
349 			assertEquals(
350 					"3 commits for describe commit increment expected since annotated tag: c4 and c3 and c2",
351 					"t1-3-g119892b", describe(c4)); //
352 		}
353 	}
354 
355 	/**
356 	 * When t1 is nearer than t2, t2 should be found
357 	 *
358 	 * <pre>
359 	 * c1 -+-&gt; c2 -&gt; t1 -+
360 	 *     |             |
361 	 *     +-&gt; t2 -&gt; c3 -+-&gt; c4
362 	 * </pre>
363 	 *
364 	 * @throws Exception
365 	 */
366 	@Test
367 	public void t1nearerT2() throws Exception {
368 		ObjectId c1 = modify("aaa");
369 		modify("bbb");
370 		ObjectId t1 = modify("ccc");
371 		tag("t1");
372 
373 		branch("b", c1);
374 		modify("ddd");
375 		tag("t2");
376 		modify("eee");
377 		ObjectId c4 = merge(t1);
378 
379 		assertNameStartsWith(c4, "bb389a4");
380 		if (useAnnotatedTags || describeUseAllTags) {
381 			assertEquals("t1-3-gbb389a4", describe(c4));
382 		} else {
383 			assertEquals(null, describe(c4));
384 
385 			assertEquals("bb389a4", describe(c4, false, true));
386 		}
387 	}
388 
389 	/**
390 	 * When t1 and t2 have same depth native git seems to add the depths of both
391 	 * paths
392 	 *
393 	 * <pre>
394 	 * c1 -+-&gt; t1 -&gt; c2 -+
395 	 *     |             |
396 	 *     +-&gt; t2 -&gt; c3 -+-&gt; c4
397 	 * </pre>
398 	 *
399 	 * @throws Exception
400 	 */
401 	@Test
402 	public void t1sameDepthT2() throws Exception {
403 		ObjectId c1 = modify("aaa");
404 		modify("bbb");
405 		tag("t1");
406 		ObjectId c2 = modify("ccc");
407 
408 		branch("b", c1);
409 		modify("ddd");
410 		tag("t2");
411 		modify("eee");
412 		ObjectId c4 = merge(c2);
413 
414 		assertNameStartsWith(c4, "bb389a4");
415 		if (useAnnotatedTags || describeUseAllTags) {
416 			assertEquals("t2-4-gbb389a4", describe(c4));
417 		} else {
418 			assertEquals(null, describe(c4));
419 
420 			assertEquals("bb389a4", describe(c4, false, true));
421 		}
422 	}
423 
424 	@Test
425 	public void globMatchWithSlashes() throws Exception {
426 		ObjectId c1 = modify("aaa");
427 		tag("a/b/version");
428 		ObjectId c2 = modify("bbb");
429 		tag("a/b/version2");
430 		if (useAnnotatedTags || describeUseAllTags) {
431 			assertEquals("a/b/version", describe(c1, "*/version*"));
432 			assertEquals("a/b/version2", describe(c2, "*/version*"));
433 		} else {
434 			assertNull(describe(c1));
435 			assertNull(describe(c1, "*/version*"));
436 			assertNull(describe(c2));
437 			assertNull(describe(c2, "*/version*"));
438 		}
439 	}
440 
441 	private ObjectId merge(ObjectId c2) throws GitAPIException {
442 		return git.merge().include(c2).call().getNewHead();
443 	}
444 
445 	private ObjectId modify(String content) throws Exception {
446 		File a = new File(db.getWorkTree(), "a.txt");
447 		touch(a, content);
448 		return git.commit().setAll(true).setMessage(content).call().getId();
449 	}
450 
451 	private void tag(String tag) throws GitAPIException {
452 		tag(tag, this.useAnnotatedTags);
453 	}
454 
455 	private void tag(String tag, boolean annotatedTag) throws GitAPIException {
456 		TagCommand tagCommand = git.tag().setName(tag)
457 				.setAnnotated(annotatedTag);
458 		if (annotatedTag) {
459 			tagCommand.setMessage(tag);
460 		}
461 		tagCommand.call();
462 	}
463 
464 	private static void touch(File f, String contents) throws Exception {
465 		try (BufferedWriter w = Files.newBufferedWriter(f.toPath(), UTF_8)) {
466 			w.write(contents);
467 		}
468 	}
469 
470 	private String describe(ObjectId c1, boolean longDesc, boolean always)
471 			throws GitAPIException, IOException {
472 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
473 				.setLong(longDesc).setAlways(always).call();
474 	}
475 
476 	private String describe(ObjectId c1) throws GitAPIException, IOException {
477 		return describe(c1, false, false);
478 	}
479 
480 	private String describe(ObjectId c1, String... patterns) throws Exception {
481 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
482 				.setMatch(patterns).call();
483 	}
484 
485 	private static void assertNameStartsWith(ObjectId c4, String prefix) {
486 		assertTrue(c4.name(), c4.name().startsWith(prefix));
487 	}
488 }