View Javadoc
1   /*
2    * Copyright (C) 2008, Google 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  
44  package org.eclipse.jgit.treewalk;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertNotNull;
49  import static org.junit.Assert.assertTrue;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.security.MessageDigest;
54  
55  import org.eclipse.jgit.api.Git;
56  import org.eclipse.jgit.dircache.DirCache;
57  import org.eclipse.jgit.dircache.DirCacheCheckout;
58  import org.eclipse.jgit.dircache.DirCacheEditor;
59  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
60  import org.eclipse.jgit.dircache.DirCacheEntry;
61  import org.eclipse.jgit.dircache.DirCacheIterator;
62  import org.eclipse.jgit.errors.CorruptObjectException;
63  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
64  import org.eclipse.jgit.errors.MissingObjectException;
65  import org.eclipse.jgit.junit.RepositoryTestCase;
66  import org.eclipse.jgit.lib.ConfigConstants;
67  import org.eclipse.jgit.lib.Constants;
68  import org.eclipse.jgit.lib.FileMode;
69  import org.eclipse.jgit.lib.ObjectId;
70  import org.eclipse.jgit.lib.ObjectReader;
71  import org.eclipse.jgit.revwalk.RevCommit;
72  import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
73  import org.eclipse.jgit.treewalk.filter.PathFilter;
74  import org.eclipse.jgit.util.FileUtils;
75  import org.eclipse.jgit.util.RawParseUtils;
76  import org.junit.Before;
77  import org.junit.Test;
78  
79  public class FileTreeIteratorTest extends RepositoryTestCase {
80  	private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
81  
82  	private long[] mtime;
83  
84  	@Before
85  	public void setUp() throws Exception {
86  		super.setUp();
87  
88  		// We build the entries backwards so that on POSIX systems we
89  		// are likely to get the entries in the trash directory in the
90  		// opposite order of what they should be in for the iteration.
91  		// This should stress the sorting code better than doing it in
92  		// the correct order.
93  		//
94  		mtime = new long[paths.length];
95  		for (int i = paths.length - 1; i >= 0; i--) {
96  			final String s = paths[i];
97  			writeTrashFile(s, s);
98  			mtime[i] = new File(trash, s).lastModified();
99  		}
100 	}
101 
102 	@Test
103 	public void testGetEntryContentLength() throws Exception {
104 		final FileTreeIterator fti = new FileTreeIterator(db);
105 		fti.next(1);
106 		assertEquals(3, fti.getEntryContentLength());
107 		fti.back(1);
108 		assertEquals(2, fti.getEntryContentLength());
109 		fti.next(1);
110 		assertEquals(3, fti.getEntryContentLength());
111 		fti.reset();
112 		assertEquals(2, fti.getEntryContentLength());
113 	}
114 
115 	@Test
116 	public void testEmptyIfRootIsFile() throws Exception {
117 		final File r = new File(trash, paths[0]);
118 		assertTrue(r.isFile());
119 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
120 				db.getConfig().get(WorkingTreeOptions.KEY));
121 		assertTrue(fti.first());
122 		assertTrue(fti.eof());
123 	}
124 
125 	@Test
126 	public void testEmptyIfRootDoesNotExist() throws Exception {
127 		final File r = new File(trash, "not-existing-file");
128 		assertFalse(r.exists());
129 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
130 				db.getConfig().get(WorkingTreeOptions.KEY));
131 		assertTrue(fti.first());
132 		assertTrue(fti.eof());
133 	}
134 
135 	@Test
136 	public void testEmptyIfRootIsEmpty() throws Exception {
137 		final File r = new File(trash, "not-existing-file");
138 		assertFalse(r.exists());
139 		FileUtils.mkdir(r);
140 
141 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
142 				db.getConfig().get(WorkingTreeOptions.KEY));
143 		assertTrue(fti.first());
144 		assertTrue(fti.eof());
145 	}
146 
147 	@Test
148 	public void testEmptyIteratorOnEmptyDirectory() throws Exception {
149 		String nonExistingFileName = "not-existing-file";
150 		final File r = new File(trash, nonExistingFileName);
151 		assertFalse(r.exists());
152 		FileUtils.mkdir(r);
153 
154 		final FileTreeIterator parent = new FileTreeIterator(db);
155 
156 		while (!parent.getEntryPathString().equals(nonExistingFileName))
157 			parent.next(1);
158 
159 		final FileTreeIterator childIter = new FileTreeIterator(parent, r,
160 				db.getFS());
161 		assertTrue(childIter.first());
162 		assertTrue(childIter.eof());
163 
164 		String parentPath = parent.getEntryPathString();
165 		assertEquals(nonExistingFileName, parentPath);
166 
167 		// must be "not-existing-file/", but getEntryPathString() was broken by
168 		// 445363 too
169 		String childPath = childIter.getEntryPathString();
170 
171 		// in bug 445363 the iterator wrote garbage to the parent "path" field
172 		EmptyTreeIterator e = childIter.createEmptyTreeIterator();
173 		assertNotNull(e);
174 
175 		// check if parent path is not overridden by empty iterator (bug 445363)
176 		// due bug 445363 this was "/ot-existing-file" instead of
177 		// "not-existing-file"
178 		assertEquals(parentPath, parent.getEntryPathString());
179 		assertEquals(parentPath + "/", childPath);
180 		assertEquals(parentPath + "/", childIter.getEntryPathString());
181 		assertEquals(childPath + "/", e.getEntryPathString());
182 	}
183 
184 	@Test
185 	public void testSimpleIterate() throws Exception {
186 		final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
187 				db.getConfig().get(WorkingTreeOptions.KEY));
188 
189 		assertTrue(top.first());
190 		assertFalse(top.eof());
191 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
192 		assertEquals(paths[0], nameOf(top));
193 		assertEquals(paths[0].length(), top.getEntryLength());
194 		assertEquals(mtime[0], top.getEntryLastModified());
195 
196 		top.next(1);
197 		assertFalse(top.first());
198 		assertFalse(top.eof());
199 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
200 		assertEquals(paths[1], nameOf(top));
201 		assertEquals(paths[1].length(), top.getEntryLength());
202 		assertEquals(mtime[1], top.getEntryLastModified());
203 
204 		top.next(1);
205 		assertFalse(top.first());
206 		assertFalse(top.eof());
207 		assertEquals(FileMode.TREE.getBits(), top.mode);
208 
209 		final ObjectReader reader = db.newObjectReader();
210 		final AbstractTreeIterator sub = top.createSubtreeIterator(reader);
211 		assertTrue(sub instanceof FileTreeIterator);
212 		final FileTreeIterator subfti = (FileTreeIterator) sub;
213 		assertTrue(sub.first());
214 		assertFalse(sub.eof());
215 		assertEquals(paths[2], nameOf(sub));
216 		assertEquals(paths[2].length(), subfti.getEntryLength());
217 		assertEquals(mtime[2], subfti.getEntryLastModified());
218 
219 		sub.next(1);
220 		assertTrue(sub.eof());
221 
222 		top.next(1);
223 		assertFalse(top.first());
224 		assertFalse(top.eof());
225 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
226 		assertEquals(paths[3], nameOf(top));
227 		assertEquals(paths[3].length(), top.getEntryLength());
228 		assertEquals(mtime[3], top.getEntryLastModified());
229 
230 		top.next(1);
231 		assertTrue(top.eof());
232 	}
233 
234 	@Test
235 	public void testComputeFileObjectId() throws Exception {
236 		final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
237 				db.getConfig().get(WorkingTreeOptions.KEY));
238 
239 		final MessageDigest md = Constants.newMessageDigest();
240 		md.update(Constants.encodeASCII(Constants.TYPE_BLOB));
241 		md.update((byte) ' ');
242 		md.update(Constants.encodeASCII(paths[0].length()));
243 		md.update((byte) 0);
244 		md.update(Constants.encode(paths[0]));
245 		final ObjectId expect = ObjectId.fromRaw(md.digest());
246 
247 		assertEquals(expect, top.getEntryObjectId());
248 
249 		// Verify it was cached by removing the file and getting it again.
250 		//
251 		FileUtils.delete(new File(trash, paths[0]));
252 		assertEquals(expect, top.getEntryObjectId());
253 	}
254 
255 	@Test
256 	public void testDirCacheMatchingId() throws Exception {
257 		File f = writeTrashFile("file", "content");
258 		Git git = new Git(db);
259 		writeTrashFile("file", "content");
260 		fsTick(f);
261 		git.add().addFilepattern("file").call();
262 		DirCacheEntry dce = db.readDirCache().getEntry("file");
263 		TreeWalk tw = new TreeWalk(db);
264 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
265 				.getConfig().get(WorkingTreeOptions.KEY));
266 		tw.addTree(fti);
267 		DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
268 		tw.addTree(dci);
269 		fti.setDirCacheIterator(tw, 1);
270 		while (tw.next() && !tw.getPathString().equals("file")) {
271 			//
272 		}
273 		assertEquals(MetadataDiff.EQUAL, fti.compareMetadata(dce));
274 		ObjectId fromRaw = ObjectId.fromRaw(fti.idBuffer(), fti.idOffset());
275 		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
276 				fromRaw.getName());
277 		try (ObjectReader objectReader = db.newObjectReader()) {
278 			assertFalse(fti.isModified(dce, false, objectReader));
279 		}
280 	}
281 
282 	@Test
283 	public void testIsModifiedSymlinkAsFile() throws Exception {
284 		writeTrashFile("symlink", "content");
285 		Git git = new Git(db);
286 		db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
287 				ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
288 		git.add().addFilepattern("symlink").call();
289 		git.commit().setMessage("commit").call();
290 
291 		// Modify previously committed DirCacheEntry and write it back to disk
292 		DirCacheEntry dce = db.readDirCache().getEntry("symlink");
293 		dce.setFileMode(FileMode.SYMLINK);
294 		try (ObjectReader objectReader = db.newObjectReader()) {
295 			DirCacheCheckout.checkoutEntry(db, dce, objectReader);
296 
297 			FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(),
298 					db.getConfig().get(WorkingTreeOptions.KEY));
299 			while (!fti.getEntryPathString().equals("symlink"))
300 				fti.next(1);
301 			assertFalse(fti.isModified(dce, false, objectReader));
302 		}
303 	}
304 
305 	@Test
306 	public void testIsModifiedFileSmudged() throws Exception {
307 		File f = writeTrashFile("file", "content");
308 		Git git = new Git(db);
309 		// The idea of this test is to check the smudged handling
310 		// Hopefully fsTick will make sure our entry gets smudged
311 		fsTick(f);
312 		writeTrashFile("file", "content");
313 		long lastModified = f.lastModified();
314 		git.add().addFilepattern("file").call();
315 		writeTrashFile("file", "conten2");
316 		f.setLastModified(lastModified);
317 		// We cannot trust this to go fast enough on
318 		// a system with less than one-second lastModified
319 		// resolution, so we force the index to have the
320 		// same timestamp as the file we look at.
321 		db.getIndexFile().setLastModified(lastModified);
322 		DirCacheEntry dce = db.readDirCache().getEntry("file");
323 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
324 				.getConfig().get(WorkingTreeOptions.KEY));
325 		while (!fti.getEntryPathString().equals("file"))
326 			fti.next(1);
327 		// If the rounding trick does not work we could skip the compareMetaData
328 		// test and hope that we are usually testing the intended code path.
329 		assertEquals(MetadataDiff.SMUDGED, fti.compareMetadata(dce));
330 		try (ObjectReader objectReader = db.newObjectReader()) {
331 			assertTrue(fti.isModified(dce, false, objectReader));
332 		}
333 	}
334 
335 	@Test
336 	public void submoduleHeadMatchesIndex() throws Exception {
337 		Git git = new Git(db);
338 		writeTrashFile("file.txt", "content");
339 		git.add().addFilepattern("file.txt").call();
340 		final RevCommit id = git.commit().setMessage("create file").call();
341 		final String path = "sub";
342 		DirCache cache = db.lockDirCache();
343 		DirCacheEditor editor = cache.editor();
344 		editor.add(new PathEdit(path) {
345 
346 			public void apply(DirCacheEntry ent) {
347 				ent.setFileMode(FileMode.GITLINK);
348 				ent.setObjectId(id);
349 			}
350 		});
351 		editor.commit();
352 
353 		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
354 				.setDirectory(new File(db.getWorkTree(), path)).call()
355 				.getRepository().close();
356 
357 		TreeWalk walk = new TreeWalk(db);
358 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
359 		FileTreeIterator workTreeIter = new FileTreeIterator(db);
360 		walk.addTree(indexIter);
361 		walk.addTree(workTreeIter);
362 		walk.setFilter(PathFilter.create(path));
363 
364 		assertTrue(walk.next());
365 		assertTrue(indexIter.idEqual(workTreeIter));
366 	}
367 
368 	@Test
369 	public void submoduleWithNoGitDirectory() throws Exception {
370 		Git git = new Git(db);
371 		writeTrashFile("file.txt", "content");
372 		git.add().addFilepattern("file.txt").call();
373 		final RevCommit id = git.commit().setMessage("create file").call();
374 		final String path = "sub";
375 		DirCache cache = db.lockDirCache();
376 		DirCacheEditor editor = cache.editor();
377 		editor.add(new PathEdit(path) {
378 
379 			public void apply(DirCacheEntry ent) {
380 				ent.setFileMode(FileMode.GITLINK);
381 				ent.setObjectId(id);
382 			}
383 		});
384 		editor.commit();
385 
386 		File submoduleRoot = new File(db.getWorkTree(), path);
387 		assertTrue(submoduleRoot.mkdir());
388 		assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir());
389 
390 		TreeWalk walk = new TreeWalk(db);
391 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
392 		FileTreeIterator workTreeIter = new FileTreeIterator(db);
393 		walk.addTree(indexIter);
394 		walk.addTree(workTreeIter);
395 		walk.setFilter(PathFilter.create(path));
396 
397 		assertTrue(walk.next());
398 		assertFalse(indexIter.idEqual(workTreeIter));
399 		assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
400 	}
401 
402 	@Test
403 	public void submoduleWithNoHead() throws Exception {
404 		Git git = new Git(db);
405 		writeTrashFile("file.txt", "content");
406 		git.add().addFilepattern("file.txt").call();
407 		final RevCommit id = git.commit().setMessage("create file").call();
408 		final String path = "sub";
409 		DirCache cache = db.lockDirCache();
410 		DirCacheEditor editor = cache.editor();
411 		editor.add(new PathEdit(path) {
412 
413 			public void apply(DirCacheEntry ent) {
414 				ent.setFileMode(FileMode.GITLINK);
415 				ent.setObjectId(id);
416 			}
417 		});
418 		editor.commit();
419 
420 		assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path))
421 				.call().getRepository());
422 
423 		TreeWalk walk = new TreeWalk(db);
424 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
425 		FileTreeIterator workTreeIter = new FileTreeIterator(db);
426 		walk.addTree(indexIter);
427 		walk.addTree(workTreeIter);
428 		walk.setFilter(PathFilter.create(path));
429 
430 		assertTrue(walk.next());
431 		assertFalse(indexIter.idEqual(workTreeIter));
432 		assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
433 	}
434 
435 	@Test
436 	public void submoduleDirectoryIterator() throws Exception {
437 		Git git = new Git(db);
438 		writeTrashFile("file.txt", "content");
439 		git.add().addFilepattern("file.txt").call();
440 		final RevCommit id = git.commit().setMessage("create file").call();
441 		final String path = "sub";
442 		DirCache cache = db.lockDirCache();
443 		DirCacheEditor editor = cache.editor();
444 		editor.add(new PathEdit(path) {
445 
446 			public void apply(DirCacheEntry ent) {
447 				ent.setFileMode(FileMode.GITLINK);
448 				ent.setObjectId(id);
449 			}
450 		});
451 		editor.commit();
452 
453 		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
454 				.setDirectory(new File(db.getWorkTree(), path)).call()
455 				.getRepository().close();
456 
457 		TreeWalk walk = new TreeWalk(db);
458 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
459 		FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(),
460 				db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY));
461 		walk.addTree(indexIter);
462 		walk.addTree(workTreeIter);
463 		walk.setFilter(PathFilter.create(path));
464 
465 		assertTrue(walk.next());
466 		assertTrue(indexIter.idEqual(workTreeIter));
467 	}
468 
469 	@Test
470 	public void submoduleNestedWithHeadMatchingIndex() throws Exception {
471 		Git git = new Git(db);
472 		writeTrashFile("file.txt", "content");
473 		git.add().addFilepattern("file.txt").call();
474 		final RevCommit id = git.commit().setMessage("create file").call();
475 		final String path = "sub/dir1/dir2";
476 		DirCache cache = db.lockDirCache();
477 		DirCacheEditor editor = cache.editor();
478 		editor.add(new PathEdit(path) {
479 
480 			public void apply(DirCacheEntry ent) {
481 				ent.setFileMode(FileMode.GITLINK);
482 				ent.setObjectId(id);
483 			}
484 		});
485 		editor.commit();
486 
487 		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
488 				.setDirectory(new File(db.getWorkTree(), path)).call()
489 				.getRepository().close();
490 
491 		TreeWalk walk = new TreeWalk(db);
492 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
493 		FileTreeIterator workTreeIter = new FileTreeIterator(db);
494 		walk.addTree(indexIter);
495 		walk.addTree(workTreeIter);
496 		walk.setFilter(PathFilter.create(path));
497 
498 		assertTrue(walk.next());
499 		assertTrue(indexIter.idEqual(workTreeIter));
500 	}
501 
502 	@Test
503 	public void idOffset() throws Exception {
504 		Git git = new Git(db);
505 		writeTrashFile("fileAinfsonly", "A");
506 		File fileBinindex = writeTrashFile("fileBinindex", "B");
507 		fsTick(fileBinindex);
508 		git.add().addFilepattern("fileBinindex").call();
509 		writeTrashFile("fileCinfsonly", "C");
510 		TreeWalk tw = new TreeWalk(db);
511 		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
512 		FileTreeIterator workTreeIter = new FileTreeIterator(db);
513 		tw.addTree(indexIter);
514 		tw.addTree(workTreeIter);
515 		workTreeIter.setDirCacheIterator(tw, 0);
516 		assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw);
517 		assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw);
518 		assertEntry("0000000000000000000000000000000000000000", "a", tw);
519 		assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw);
520 		// The reason for adding this test. Check that the id is correct for
521 		// mixed
522 		assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220",
523 				"fileAinfsonly", tw);
524 		assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex",
525 				tw);
526 		assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c",
527 				"fileCinfsonly", tw);
528 		assertFalse(tw.next());
529 	}
530 
531 	private static void assertEntry(String sha1string, String path, TreeWalk tw)
532 			throws MissingObjectException, IncorrectObjectTypeException,
533 			CorruptObjectException, IOException {
534 		assertTrue(tw.next());
535 		assertEquals(path, tw.getPathString());
536 		assertEquals(sha1string, tw.getObjectId(1).getName() /* 1=filetree here */);
537 	}
538 
539 	private static String nameOf(final AbstractTreeIterator i) {
540 		return RawParseUtils.decode(Constants.CHARSET, i.path, 0, i.pathLen);
541 	}
542 }