View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google 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  
11  package org.eclipse.jgit.treewalk;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
15  import static org.eclipse.jgit.lib.FileMode.SYMLINK;
16  import static org.junit.Assert.assertEquals;
17  import static org.junit.Assert.assertFalse;
18  import static org.junit.Assert.assertSame;
19  import static org.junit.Assert.assertTrue;
20  
21  import java.io.ByteArrayOutputStream;
22  
23  import org.eclipse.jgit.errors.CorruptObjectException;
24  import org.eclipse.jgit.lib.Constants;
25  import org.eclipse.jgit.lib.FileMode;
26  import org.eclipse.jgit.lib.ObjectId;
27  import org.eclipse.jgit.lib.TreeFormatter;
28  import org.eclipse.jgit.util.RawParseUtils;
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  public class CanonicalTreeParserTest {
33  	private final CanonicalTreeParser ctp = new CanonicalTreeParser();
34  
35  	private final FileMode m644 = FileMode.REGULAR_FILE;
36  
37  	private final FileMode mt = FileMode.TREE;
38  
39  	private final ObjectId hash_a = ObjectId
40  			.fromString("6b9c715d21d5486e59083fb6071566aa6ecd4d42");
41  
42  	private final ObjectId hash_foo = ObjectId
43  			.fromString("a213e8e25bb2442326e86cbfb9ef56319f482869");
44  
45  	private final ObjectId hash_sometree = ObjectId
46  			.fromString("daf4bdb0d7bb24319810fe0e73aa317663448c93");
47  
48  	private byte[] tree1;
49  
50  	private byte[] tree2;
51  
52  	private byte[] tree3;
53  
54  	@Before
55  	public void setUp() throws Exception {
56  		tree1 = mktree(entry(m644, "a", hash_a));
57  		tree2 = mktree(entry(m644, "a", hash_a), entry(m644, "foo", hash_foo));
58  		tree3 = mktree(entry(m644, "a", hash_a), entry(mt, "b_sometree",
59  				hash_sometree), entry(m644, "foo", hash_foo));
60  	}
61  
62  	private static byte[] mktree(byte[]... data) throws Exception {
63  		final ByteArrayOutputStream out = new ByteArrayOutputStream();
64  		for (byte[] e : data)
65  			out.write(e);
66  		return out.toByteArray();
67  	}
68  
69  	private static byte[] entry(final FileMode mode, final String name,
70  			final ObjectId id) throws Exception {
71  		final ByteArrayOutputStream out = new ByteArrayOutputStream();
72  		mode.copyTo(out);
73  		out.write(' ');
74  		out.write(Constants.encode(name));
75  		out.write(0);
76  		id.copyRawTo(out);
77  		return out.toByteArray();
78  	}
79  
80  	private String path() {
81  		return RawParseUtils.decode(UTF_8, ctp.path,
82  				ctp.pathOffset, ctp.pathLen);
83  	}
84  
85  	@Test
86  	public void testEmptyTree_AtEOF() throws Exception {
87  		ctp.reset(new byte[0]);
88  		assertTrue(ctp.eof());
89  	}
90  
91  	@Test
92  	public void testOneEntry_Forward() throws Exception {
93  		ctp.reset(tree1);
94  
95  		assertTrue(ctp.first());
96  		assertFalse(ctp.eof());
97  		assertEquals(m644.getBits(), ctp.mode);
98  		assertEquals("a", path());
99  		assertEquals(hash_a, ctp.getEntryObjectId());
100 
101 		ctp.next(1);
102 		assertFalse(ctp.first());
103 		assertTrue(ctp.eof());
104 	}
105 
106 	@Test
107 	public void testTwoEntries_ForwardOneAtATime() throws Exception {
108 		ctp.reset(tree2);
109 
110 		assertTrue(ctp.first());
111 		assertFalse(ctp.eof());
112 		assertEquals(m644.getBits(), ctp.mode);
113 		assertEquals("a", path());
114 		assertEquals(hash_a, ctp.getEntryObjectId());
115 
116 		ctp.next(1);
117 		assertFalse(ctp.eof());
118 		assertEquals(m644.getBits(), ctp.mode);
119 		assertEquals("foo", path());
120 		assertEquals(hash_foo, ctp.getEntryObjectId());
121 
122 		ctp.next(1);
123 		assertFalse(ctp.first());
124 		assertTrue(ctp.eof());
125 	}
126 
127 	@Test
128 	public void testOneEntry_Seek1IsEOF() throws Exception {
129 		ctp.reset(tree1);
130 		ctp.next(1);
131 		assertTrue(ctp.eof());
132 	}
133 
134 	@Test
135 	public void testTwoEntries_Seek2IsEOF() throws Exception {
136 		ctp.reset(tree2);
137 		ctp.next(2);
138 		assertTrue(ctp.eof());
139 	}
140 
141 	@Test
142 	public void testThreeEntries_Seek3IsEOF() throws Exception {
143 		ctp.reset(tree3);
144 		ctp.next(3);
145 		assertTrue(ctp.eof());
146 	}
147 
148 	@Test
149 	public void testThreeEntries_Seek2() throws Exception {
150 		ctp.reset(tree3);
151 
152 		ctp.next(2);
153 		assertFalse(ctp.eof());
154 		assertFalse(ctp.eof());
155 		assertEquals(m644.getBits(), ctp.mode);
156 		assertEquals("foo", path());
157 		assertEquals(hash_foo, ctp.getEntryObjectId());
158 
159 		ctp.next(1);
160 		assertTrue(ctp.eof());
161 	}
162 
163 	@Test
164 	public void testOneEntry_Backwards() throws Exception {
165 		ctp.reset(tree1);
166 		ctp.next(1);
167 		assertFalse(ctp.first());
168 		assertTrue(ctp.eof());
169 
170 		ctp.back(1);
171 		assertTrue(ctp.first());
172 		assertFalse(ctp.eof());
173 		assertEquals(m644.getBits(), ctp.mode);
174 		assertEquals("a", path());
175 		assertEquals(hash_a, ctp.getEntryObjectId());
176 	}
177 
178 	@Test
179 	public void testTwoEntries_BackwardsOneAtATime() throws Exception {
180 		ctp.reset(tree2);
181 		ctp.next(2);
182 		assertTrue(ctp.eof());
183 
184 		ctp.back(1);
185 		assertFalse(ctp.eof());
186 		assertEquals(m644.getBits(), ctp.mode);
187 		assertEquals("foo", path());
188 		assertEquals(hash_foo, ctp.getEntryObjectId());
189 
190 		ctp.back(1);
191 		assertFalse(ctp.eof());
192 		assertEquals(m644.getBits(), ctp.mode);
193 		assertEquals("a", path());
194 		assertEquals(hash_a, ctp.getEntryObjectId());
195 	}
196 
197 	@Test
198 	public void testTwoEntries_BackwardsTwo() throws Exception {
199 		ctp.reset(tree2);
200 		ctp.next(2);
201 		assertTrue(ctp.eof());
202 
203 		ctp.back(2);
204 		assertFalse(ctp.eof());
205 		assertEquals(m644.getBits(), ctp.mode);
206 		assertEquals("a", path());
207 		assertEquals(hash_a, ctp.getEntryObjectId());
208 
209 		ctp.next(1);
210 		assertFalse(ctp.eof());
211 		assertEquals(m644.getBits(), ctp.mode);
212 		assertEquals("foo", path());
213 		assertEquals(hash_foo, ctp.getEntryObjectId());
214 
215 		ctp.next(1);
216 		assertTrue(ctp.eof());
217 	}
218 
219 	@Test
220 	public void testThreeEntries_BackwardsTwo() throws Exception {
221 		ctp.reset(tree3);
222 		ctp.next(3);
223 		assertTrue(ctp.eof());
224 
225 		ctp.back(2);
226 		assertFalse(ctp.eof());
227 		assertEquals(mt.getBits(), ctp.mode);
228 		assertEquals("b_sometree", path());
229 		assertEquals(hash_sometree, ctp.getEntryObjectId());
230 
231 		ctp.next(1);
232 		assertFalse(ctp.eof());
233 		assertEquals(m644.getBits(), ctp.mode);
234 		assertEquals("foo", path());
235 		assertEquals(hash_foo, ctp.getEntryObjectId());
236 
237 		ctp.next(1);
238 		assertTrue(ctp.eof());
239 	}
240 
241 	@Test
242 	public void testBackwards_ConfusingPathName() throws Exception {
243 		final String aVeryConfusingName = "confusing 644 entry 755 and others";
244 		ctp.reset(mktree(entry(m644, "a", hash_a), entry(mt, aVeryConfusingName,
245 				hash_sometree), entry(m644, "foo", hash_foo)));
246 		ctp.next(3);
247 		assertTrue(ctp.eof());
248 
249 		ctp.back(2);
250 		assertFalse(ctp.eof());
251 		assertEquals(mt.getBits(), ctp.mode);
252 		assertEquals(aVeryConfusingName, path());
253 		assertEquals(hash_sometree, ctp.getEntryObjectId());
254 
255 		ctp.back(1);
256 		assertFalse(ctp.eof());
257 		assertEquals(m644.getBits(), ctp.mode);
258 		assertEquals("a", path());
259 		assertEquals(hash_a, ctp.getEntryObjectId());
260 	}
261 
262 	@Test
263 	public void testBackwords_Prebuilts1() throws Exception {
264 		// What is interesting about this test is the ObjectId for the
265 		// "darwin-x86" path entry ends in an octal digit (37 == '7').
266 		// Thus when scanning backwards we could over scan and consume
267 		// part of the SHA-1, and miss the path terminator.
268 		//
269 		final ObjectId common = ObjectId
270 				.fromString("af7bf97cb9bce3f60f1d651a0ef862e9447dd8bc");
271 		final ObjectId darwinx86 = ObjectId
272 				.fromString("e927f7398240f78face99e1a738dac54ef738e37");
273 		final ObjectId linuxx86 = ObjectId
274 				.fromString("ac08dd97120c7cb7d06e98cd5b152011183baf21");
275 		final ObjectId windows = ObjectId
276 				.fromString("6c4c64c221a022bb973165192cca4812033479df");
277 
278 		ctp.reset(mktree(entry(mt, "common", common), entry(mt, "darwin-x86",
279 				darwinx86), entry(mt, "linux-x86", linuxx86), entry(mt,
280 				"windows", windows)));
281 		ctp.next(3);
282 		assertEquals("windows", ctp.getEntryPathString());
283 		assertSame(mt, ctp.getEntryFileMode());
284 		assertEquals(windows, ctp.getEntryObjectId());
285 
286 		ctp.back(1);
287 		assertEquals("linux-x86", ctp.getEntryPathString());
288 		assertSame(mt, ctp.getEntryFileMode());
289 		assertEquals(linuxx86, ctp.getEntryObjectId());
290 
291 		ctp.next(1);
292 		assertEquals("windows", ctp.getEntryPathString());
293 		assertSame(mt, ctp.getEntryFileMode());
294 		assertEquals(windows, ctp.getEntryObjectId());
295 	}
296 
297 	@Test
298 	public void testBackwords_Prebuilts2() throws Exception {
299 		// What is interesting about this test is the ObjectId for the
300 		// "darwin-x86" path entry ends in an octal digit (37 == '7').
301 		// Thus when scanning backwards we could over scan and consume
302 		// part of the SHA-1, and miss the path terminator.
303 		//
304 		final ObjectId common = ObjectId
305 				.fromString("af7bf97cb9bce3f60f1d651a0ef862e9447dd8bc");
306 		final ObjectId darwinx86 = ObjectId
307 				.fromString("0000000000000000000000000000000000000037");
308 		final ObjectId linuxx86 = ObjectId
309 				.fromString("ac08dd97120c7cb7d06e98cd5b152011183baf21");
310 		final ObjectId windows = ObjectId
311 				.fromString("6c4c64c221a022bb973165192cca4812033479df");
312 
313 		ctp.reset(mktree(entry(mt, "common", common), entry(mt, "darwin-x86",
314 				darwinx86), entry(mt, "linux-x86", linuxx86), entry(mt,
315 				"windows", windows)));
316 		ctp.next(3);
317 		assertEquals("windows", ctp.getEntryPathString());
318 		assertSame(mt, ctp.getEntryFileMode());
319 		assertEquals(windows, ctp.getEntryObjectId());
320 
321 		ctp.back(1);
322 		assertEquals("linux-x86", ctp.getEntryPathString());
323 		assertSame(mt, ctp.getEntryFileMode());
324 		assertEquals(linuxx86, ctp.getEntryObjectId());
325 
326 		ctp.next(1);
327 		assertEquals("windows", ctp.getEntryPathString());
328 		assertSame(mt, ctp.getEntryFileMode());
329 		assertEquals(windows, ctp.getEntryObjectId());
330 	}
331 
332 	@Test
333 	public void testFreakingHugePathName() throws Exception {
334 		final int n = AbstractTreeIterator.DEFAULT_PATH_SIZE * 4;
335 		final StringBuilder b = new StringBuilder(n);
336 		for (int i = 0; i < n; i++)
337 			b.append('q');
338 		final String name = b.toString();
339 		ctp.reset(entry(m644, name, hash_a));
340 		assertFalse(ctp.eof());
341 		assertEquals(name, RawParseUtils.decode(UTF_8, ctp.path,
342 				ctp.pathOffset, ctp.pathLen));
343 	}
344 
345 	@Test
346 	public void testFindAttributesWhenFirst() throws CorruptObjectException {
347 		TreeFormatter tree = new TreeFormatter();
348 		tree.append(".gitattributes", REGULAR_FILE, hash_a);
349 		ctp.reset(tree.toByteArray());
350 
351 		assertTrue(ctp.findFile(".gitattributes"));
352 		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
353 		assertEquals(".gitattributes", ctp.getEntryPathString());
354 		assertEquals(hash_a, ctp.getEntryObjectId());
355 	}
356 
357 	@Test
358 	public void testFindAttributesWhenSecond() throws CorruptObjectException {
359 		TreeFormatter tree = new TreeFormatter();
360 		tree.append(".config", SYMLINK, hash_a);
361 		tree.append(".gitattributes", REGULAR_FILE, hash_foo);
362 		ctp.reset(tree.toByteArray());
363 
364 		assertTrue(ctp.findFile(".gitattributes"));
365 		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
366 		assertEquals(".gitattributes", ctp.getEntryPathString());
367 		assertEquals(hash_foo, ctp.getEntryObjectId());
368 	}
369 
370 	@Test
371 	public void testFindAttributesWhenMissing() throws CorruptObjectException {
372 		TreeFormatter tree = new TreeFormatter();
373 		tree.append("src", REGULAR_FILE, hash_a);
374 		tree.append("zoo", REGULAR_FILE, hash_foo);
375 		ctp.reset(tree.toByteArray());
376 
377 		assertFalse(ctp.findFile(".gitattributes"));
378 		assertEquals(11, ctp.idOffset()); // Did not walk the entire tree.
379 		assertEquals("src", ctp.getEntryPathString());
380 	}
381 }