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.assertNotNull;
48  import static org.junit.Assert.assertTrue;
49  
50  import java.io.File;
51  import java.io.FileWriter;
52  import java.io.IOException;
53  import java.util.Arrays;
54  import java.util.Collection;
55  
56  import org.eclipse.jgit.api.errors.GitAPIException;
57  import org.eclipse.jgit.api.errors.RefNotFoundException;
58  import org.eclipse.jgit.junit.RepositoryTestCase;
59  import org.eclipse.jgit.lib.ObjectId;
60  import org.junit.Test;
61  import org.junit.runner.RunWith;
62  import org.junit.runners.Parameterized;
63  import org.junit.runners.Parameterized.Parameter;
64  import org.junit.runners.Parameterized.Parameters;
65  
66  @RunWith(Parameterized.class)
67  public class DescribeCommandTest extends RepositoryTestCase {
68  
69  	private Git git;
70  
71  	@Parameter(0)
72  	public boolean useAnnotatedTags;
73  
74  	@Parameter(1)
75  	public boolean describeUseAllTags;
76  
77  	@Parameters(name = "git tag -a {0}?-a: with git describe {1}?--tags:")
78  	public static Collection<Boolean[]> getUseAnnotatedTagsValues() {
79  		return Arrays.asList(new Boolean[][] { { Boolean.TRUE, Boolean.FALSE },
80  				{ Boolean.FALSE, Boolean.FALSE },
81  				{ Boolean.TRUE, Boolean.TRUE },
82  				{ Boolean.FALSE, Boolean.TRUE } });
83  	}
84  
85  	@Override
86  	public void setUp() throws Exception {
87  		super.setUp();
88  		git = new Git(db);
89  	}
90  
91  	@Test(expected = RefNotFoundException.class)
92  	public void noTargetSet() throws Exception {
93  		git.describe().call();
94  	}
95  
96  	@Test
97  	public void testDescribe() throws Exception {
98  		ObjectId c1 = modify("aaa");
99  
100 		ObjectId c2 = modify("bbb");
101 		tag("alice-t1");
102 
103 		ObjectId c3 = modify("ccc");
104 		tag("bob-t2");
105 
106 		ObjectId c4 = modify("ddd");
107 		assertNameStartsWith(c4, "3e563c5");
108 
109 		assertNull(describe(c1));
110 		assertNull(describe(c1, true));
111 		assertNull(describe(c1, "a*", "b*", "c*"));
112 		assertNull(describe(c2, "bob*"));
113 		assertNull(describe(c2, "?ob*"));
114 
115 		if (useAnnotatedTags || describeUseAllTags) {
116 			assertEquals("alice-t1", describe(c2));
117 			assertEquals("alice-t1", describe(c2, "alice*"));
118 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
119 
120 			assertEquals("bob-t2", describe(c3));
121 			assertEquals("bob-t2-0-g44579eb", describe(c3, true));
122 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
123 			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
124 			assertEquals("bob-t2", describe(c3, "bob*"));
125 			assertEquals("bob-t2", describe(c3, "?ob*"));
126 			assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
127 
128 			// the value verified with git-describe(1)
129 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
130 			assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
131 			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
132 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
133 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
134 		} else {
135 			assertEquals(null, describe(c2));
136 			assertEquals(null, describe(c3));
137 			assertEquals(null, describe(c4));
138 		}
139 
140 		// test default target
141 		if (useAnnotatedTags) {
142 			assertEquals("bob-t2-1-g3e563c5", git.describe().call());
143 			assertEquals("bob-t2-1-g3e563c5",
144 					git.describe().setTags(false).call());
145 			assertEquals("bob-t2-1-g3e563c5",
146 					git.describe().setTags(true).call());
147 		} else {
148 			assertEquals(null, git.describe().call());
149 			assertEquals(null, git.describe().setTags(false).call());
150 			assertEquals("bob-t2-1-g3e563c5",
151 					git.describe().setTags(true).call());
152 		}
153 	}
154 
155 	@Test
156 	public void testDescribeMultiMatch() throws Exception {
157 		ObjectId c1 = modify("aaa");
158 		tag("v1.0.0");
159 		tick();
160 		tag("v1.0.1");
161 		tick();
162 		tag("v1.1.0");
163 		tick();
164 		tag("v1.1.1");
165 		ObjectId c2 = modify("bbb");
166 
167 		if (!useAnnotatedTags && !describeUseAllTags) {
168 			assertEquals(null, describe(c1));
169 			assertEquals(null, describe(c2));
170 			return;
171 		}
172 
173 		// Ensure that if we're interested in any tags, we get the most recent tag
174 		// as per Git behaviour since 1.7.1.1
175 		if (useAnnotatedTags) {
176 			assertEquals("v1.1.1", describe(c1));
177 			assertEquals("v1.1.1-1-gb89dead", describe(c2));
178 			// Ensure that if we're only interested in one of multiple tags, we get the right match
179 			assertEquals("v1.0.1", describe(c1, "v1.0*"));
180 			assertEquals("v1.1.1", describe(c1, "v1.1*"));
181 			assertEquals("v1.0.1-1-gb89dead", describe(c2, "v1.0*"));
182 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*"));
183 
184 			// Ensure that ordering of match precedence is preserved as per Git behaviour
185 			assertEquals("v1.1.1", describe(c1, "v1.0*", "v1.1*"));
186 			assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
187 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.0*", "v1.1*"));
188 			assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*", "v1.0*"));
189 		} else {
190 			// no timestamps so no guarantees on which tag is chosen
191 			assertNotNull(describe(c1));
192 			assertNotNull(describe(c2));
193 
194 			assertNotNull(describe(c1, "v1.0*"));
195 			assertNotNull(describe(c1, "v1.1*"));
196 			assertNotNull(describe(c2, "v1.0*"));
197 			assertNotNull(describe(c2, "v1.1*"));
198 
199 			// Ensure that ordering of match precedence is preserved as per Git behaviour
200 			assertNotNull(describe(c1, "v1.0*", "v1.1*"));
201 			assertNotNull(describe(c1, "v1.1*", "v1.0*"));
202 			assertNotNull(describe(c2, "v1.0*", "v1.1*"));
203 			assertNotNull(describe(c2, "v1.1*", "v1.0*"));
204 
205 		}
206 	}
207 
208 	/**
209 	 * Make sure it finds a tag when not all ancestries include a tag.
210 	 *
211 	 * <pre>
212 	 * c1 -+-&gt; T  -
213 	 *     |       |
214 	 *     +-&gt; c3 -+-&gt; c4
215 	 * </pre>
216 	 *
217 	 * @throws Exception
218 	 */
219 	@Test
220 	public void testDescribeBranch() throws Exception {
221 		ObjectId c1 = modify("aaa");
222 
223 		ObjectId c2 = modify("bbb");
224 		tag("t");
225 
226 		branch("b", c1);
227 
228 		ObjectId c3 = modify("ccc");
229 
230 		ObjectId c4 = merge(c2);
231 
232 		assertNameStartsWith(c4, "119892b");
233 		if (useAnnotatedTags || describeUseAllTags) {
234 			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
235 		} else {
236 			assertEquals(null, describe(c4));
237 		}
238 		assertNull(describe(c3));
239 		assertNull(describe(c3, true));
240 	}
241 
242 	private void branch(String name, ObjectId base) throws GitAPIException {
243 		git.checkout().setCreateBranch(true).setName(name)
244 				.setStartPoint(base.name()).call();
245 	}
246 
247 	/**
248 	 * When t2 dominates t1, it's clearly preferable to describe by using t2.
249 	 *
250 	 * <pre>
251 	 * t1 -+-&gt; t2  -
252 	 *     |       |
253 	 *     +-&gt; c3 -+-&gt; c4
254 	 * </pre>
255 	 *
256 	 * @throws Exception
257 	 */
258 	@Test
259 	public void t1DominatesT2() throws Exception {
260 		ObjectId c1 = modify("aaa");
261 		tag("t1");
262 
263 		ObjectId c2 = modify("bbb");
264 		tag("t2");
265 
266 		branch("b", c1);
267 
268 		ObjectId c3 = modify("ccc");
269 		assertNameStartsWith(c3, "0244e7f");
270 
271 		ObjectId c4 = merge(c2);
272 
273 		assertNameStartsWith(c4, "119892b");
274 
275 		if (useAnnotatedTags || describeUseAllTags) {
276 			assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
277 			assertEquals("t1-1-g0244e7f", describe(c3));
278 		} else {
279 			assertEquals(null, describe(c4));
280 			assertEquals(null, describe(c3));
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 && !describeUseAllTags) {
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 	}
372 
373 	/**
374 	 * When t1 and t2 have same depth native git seems to add the depths of both
375 	 * paths
376 	 *
377 	 * <pre>
378 	 * c1 -+-&gt; t1 -&gt; c2 -+
379 	 *     |             |
380 	 *     +-&gt; t2 -&gt; c3 -+-&gt; c4
381 	 * </pre>
382 	 *
383 	 * @throws Exception
384 	 */
385 	@Test
386 	public void t1sameDepthT2() throws Exception {
387 		ObjectId c1 = modify("aaa");
388 		modify("bbb");
389 		tag("t1");
390 		ObjectId c2 = modify("ccc");
391 
392 		branch("b", c1);
393 		modify("ddd");
394 		tag("t2");
395 		modify("eee");
396 		ObjectId c4 = merge(c2);
397 
398 		assertNameStartsWith(c4, "bb389a4");
399 		if (useAnnotatedTags || describeUseAllTags) {
400 			assertEquals("t2-4-gbb389a4", describe(c4));
401 		} else {
402 			assertEquals(null, describe(c4));
403 		}
404 	}
405 
406 	private ObjectId merge(ObjectId c2) throws GitAPIException {
407 		return git.merge().include(c2).call().getNewHead();
408 	}
409 
410 	private ObjectId modify(String content) throws Exception {
411 		File a = new File(db.getWorkTree(), "a.txt");
412 		touch(a, content);
413 		return git.commit().setAll(true).setMessage(content).call().getId();
414 	}
415 
416 	private void tag(String tag) throws GitAPIException {
417 		tag(tag, this.useAnnotatedTags);
418 	}
419 
420 	private void tag(String tag, boolean annotatedTag) throws GitAPIException {
421 		TagCommand tagCommand = git.tag().setName(tag)
422 				.setAnnotated(annotatedTag);
423 		if (annotatedTag) {
424 			tagCommand.setMessage(tag);
425 		}
426 		tagCommand.call();
427 	}
428 
429 	private static void touch(File f, String contents) throws Exception {
430 		try (FileWriter w = new FileWriter(f)) {
431 			w.write(contents);
432 		}
433 	}
434 
435 	private String describe(ObjectId c1, boolean longDesc)
436 			throws GitAPIException, IOException {
437 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
438 				.setLong(longDesc).call();
439 	}
440 
441 	private String describe(ObjectId c1) throws GitAPIException, IOException {
442 		return describe(c1, false);
443 	}
444 
445 	private String describe(ObjectId c1, String... patterns) throws Exception {
446 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
447 				.setMatch(patterns).call();
448 	}
449 
450 	private static void assertNameStartsWith(ObjectId c4, String prefix) {
451 		assertTrue(c4.name(), c4.name().startsWith(prefix));
452 	}
453 }