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