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