View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
5    * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
6    * Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com> and
7    * other copyright owners as documented in the project's IP log.
8    *
9    * This program and the accompanying materials are made available under the
10   * terms of the Eclipse Distribution License v1.0 which accompanies this
11   * distribution, is reproduced below, and is available at
12   * http://www.eclipse.org/org/documents/edl-v10.php
13   *
14   * All rights reserved.
15   *
16   * Redistribution and use in source and binary forms, with or without
17   * modification, are permitted provided that the following conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright notice, this
20   * list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above copyright notice,
23   * this list of conditions and the following disclaimer in the documentation
24   * and/or other materials provided with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
27   * contributors may be used to endorse or promote products derived from this
28   * software without specific prior written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
34   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
35   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
37   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
40   * POSSIBILITY OF SUCH DAMAGE.
41   */
42  
43  package org.eclipse.jgit.dircache;
44  
45  import java.io.File;
46  import java.io.FileOutputStream;
47  import java.io.IOException;
48  import java.io.OutputStream;
49  import java.nio.file.StandardCopyOption;
50  import java.text.MessageFormat;
51  import java.util.ArrayList;
52  import java.util.HashMap;
53  import java.util.List;
54  import java.util.Map;
55  
56  import org.eclipse.jgit.api.errors.FilterFailedException;
57  import org.eclipse.jgit.errors.CheckoutConflictException;
58  import org.eclipse.jgit.errors.CorruptObjectException;
59  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
60  import org.eclipse.jgit.errors.IndexWriteException;
61  import org.eclipse.jgit.errors.MissingObjectException;
62  import org.eclipse.jgit.internal.JGitText;
63  import org.eclipse.jgit.lib.Constants;
64  import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
65  import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
66  import org.eclipse.jgit.lib.CoreConfig.SymLinks;
67  import org.eclipse.jgit.lib.FileMode;
68  import org.eclipse.jgit.lib.NullProgressMonitor;
69  import org.eclipse.jgit.lib.ObjectChecker;
70  import org.eclipse.jgit.lib.ObjectId;
71  import org.eclipse.jgit.lib.ObjectLoader;
72  import org.eclipse.jgit.lib.ObjectReader;
73  import org.eclipse.jgit.lib.Repository;
74  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
75  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
76  import org.eclipse.jgit.treewalk.EmptyTreeIterator;
77  import org.eclipse.jgit.treewalk.FileTreeIterator;
78  import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
79  import org.eclipse.jgit.treewalk.TreeWalk;
80  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
81  import org.eclipse.jgit.treewalk.WorkingTreeOptions;
82  import org.eclipse.jgit.treewalk.filter.PathFilter;
83  import org.eclipse.jgit.util.FS;
84  import org.eclipse.jgit.util.FS.ExecutionResult;
85  import org.eclipse.jgit.util.FileUtils;
86  import org.eclipse.jgit.util.RawParseUtils;
87  import org.eclipse.jgit.util.SystemReader;
88  import org.eclipse.jgit.util.io.EolStreamTypeUtil;
89  
90  /**
91   * This class handles checking out one or two trees merging with the index.
92   */
93  public class DirCacheCheckout {
94  	private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
95  
96  	/**
97  	 * Metadata used in checkout process
98  	 *
99  	 * @since 4.3
100 	 */
101 	public static class CheckoutMetadata {
102 		/** git attributes */
103 		public final EolStreamType eolStreamType;
104 
105 		/** filter command to apply */
106 		public final String smudgeFilterCommand;
107 
108 		/**
109 		 * @param eolStreamType
110 		 * @param smudgeFilterCommand
111 		 */
112 		public CheckoutMetadata(EolStreamType eolStreamType,
113 				String smudgeFilterCommand) {
114 			this.eolStreamType = eolStreamType;
115 			this.smudgeFilterCommand = smudgeFilterCommand;
116 		}
117 
118 		static CheckoutMetadata EMPTY = new CheckoutMetadata(
119 				EolStreamType.DIRECT, null);
120 	}
121 
122 	private Repository repo;
123 
124 	private HashMap<String, CheckoutMetadata> updated = new HashMap<String, CheckoutMetadata>();
125 
126 	private ArrayList<String> conflicts = new ArrayList<String>();
127 
128 	private ArrayList<String> removed = new ArrayList<String>();
129 
130 	private ObjectId mergeCommitTree;
131 
132 	private DirCache dc;
133 
134 	private DirCacheBuilder builder;
135 
136 	private NameConflictTreeWalk walk;
137 
138 	private ObjectId headCommitTree;
139 
140 	private WorkingTreeIterator workingTree;
141 
142 	private boolean failOnConflict = true;
143 
144 	private ArrayList<String> toBeDeleted = new ArrayList<String>();
145 
146 	private boolean emptyDirCache;
147 
148 	/**
149 	 * @return a list of updated paths and smudgeFilterCommands
150 	 */
151 	public Map<String, CheckoutMetadata> getUpdated() {
152 		return updated;
153 	}
154 
155 	/**
156 	 * @return a list of conflicts created by this checkout
157 	 */
158 	public List<String> getConflicts() {
159 		return conflicts;
160 	}
161 
162 	/**
163 	 * @return a list of paths (relative to the start of the working tree) of
164 	 *         files which couldn't be deleted during last call to
165 	 *         {@link #checkout()} . {@link #checkout()} detected that these
166 	 *         files should be deleted but the deletion in the filesystem failed
167 	 *         (e.g. because a file was locked). To have a consistent state of
168 	 *         the working tree these files have to be deleted by the callers of
169 	 *         {@link DirCacheCheckout}.
170 	 */
171 	public List<String> getToBeDeleted() {
172 		return toBeDeleted;
173 	}
174 
175 	/**
176 	 * @return a list of all files removed by this checkout
177 	 */
178 	public List<String> getRemoved() {
179 		return removed;
180 	}
181 
182 	/**
183 	 * Constructs a DirCacheCeckout for merging and checking out two trees (HEAD
184 	 * and mergeCommitTree) and the index.
185 	 *
186 	 * @param repo
187 	 *            the repository in which we do the checkout
188 	 * @param headCommitTree
189 	 *            the id of the tree of the head commit
190 	 * @param dc
191 	 *            the (already locked) Dircache for this repo
192 	 * @param mergeCommitTree
193 	 *            the id of the tree we want to fast-forward to
194 	 * @param workingTree
195 	 *            an iterator over the repositories Working Tree
196 	 * @throws IOException
197 	 */
198 	public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
199 			ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
200 			throws IOException {
201 		this.repo = repo;
202 		this.dc = dc;
203 		this.headCommitTree = headCommitTree;
204 		this.mergeCommitTree = mergeCommitTree;
205 		this.workingTree = workingTree;
206 		this.emptyDirCache = (dc == null) || (dc.getEntryCount() == 0);
207 	}
208 
209 	/**
210 	 * Constructs a DirCacheCeckout for merging and checking out two trees (HEAD
211 	 * and mergeCommitTree) and the index. As iterator over the working tree
212 	 * this constructor creates a standard {@link FileTreeIterator}
213 	 *
214 	 * @param repo
215 	 *            the repository in which we do the checkout
216 	 * @param headCommitTree
217 	 *            the id of the tree of the head commit
218 	 * @param dc
219 	 *            the (already locked) Dircache for this repo
220 	 * @param mergeCommitTree
221 	 *            the id of the tree we want to fast-forward to
222 	 * @throws IOException
223 	 */
224 	public DirCacheCheckout(Repository repo, ObjectId headCommitTree,
225 			DirCache dc, ObjectId mergeCommitTree) throws IOException {
226 		this(repo, headCommitTree, dc, mergeCommitTree, new FileTreeIterator(repo));
227 	}
228 
229 	/**
230 	 * Constructs a DirCacheCeckout for checking out one tree, merging with the
231 	 * index.
232 	 *
233 	 * @param repo
234 	 *            the repository in which we do the checkout
235 	 * @param dc
236 	 *            the (already locked) Dircache for this repo
237 	 * @param mergeCommitTree
238 	 *            the id of the tree we want to fast-forward to
239 	 * @param workingTree
240 	 *            an iterator over the repositories Working Tree
241 	 * @throws IOException
242 	 */
243 	public DirCacheCheckout(Repository repo, DirCache dc,
244 			ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
245 			throws IOException {
246 		this(repo, null, dc, mergeCommitTree, workingTree);
247 	}
248 
249 	/**
250 	 * Constructs a DirCacheCeckout for checking out one tree, merging with the
251 	 * index. As iterator over the working tree this constructor creates a
252 	 * standard {@link FileTreeIterator}
253 	 *
254 	 * @param repo
255 	 *            the repository in which we do the checkout
256 	 * @param dc
257 	 *            the (already locked) Dircache for this repo
258 	 * @param mergeCommitTree
259 	 *            the id of the tree of the
260 	 * @throws IOException
261 	 */
262 	public DirCacheCheckout(Repository repo, DirCache dc,
263 			ObjectId mergeCommitTree) throws IOException {
264 		this(repo, null, dc, mergeCommitTree, new FileTreeIterator(repo));
265 	}
266 
267 	/**
268 	 * Scan head, index and merge tree. Used during normal checkout or merge
269 	 * operations.
270 	 *
271 	 * @throws CorruptObjectException
272 	 * @throws IOException
273 	 */
274 	public void preScanTwoTrees() throws CorruptObjectException, IOException {
275 		removed.clear();
276 		updated.clear();
277 		conflicts.clear();
278 		walk = new NameConflictTreeWalk(repo);
279 		builder = dc.builder();
280 
281 		addTree(walk, headCommitTree);
282 		addTree(walk, mergeCommitTree);
283 		int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
284 		walk.addTree(workingTree);
285 		workingTree.setDirCacheIterator(walk, dciPos);
286 
287 		while (walk.next()) {
288 			processEntry(walk.getTree(0, CanonicalTreeParser.class),
289 					walk.getTree(1, CanonicalTreeParser.class),
290 					walk.getTree(2, DirCacheBuildIterator.class),
291 					walk.getTree(3, WorkingTreeIterator.class));
292 			if (walk.isSubtree())
293 				walk.enterSubtree();
294 		}
295 	}
296 
297 	private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
298 		if (id == null)
299 			tw.addTree(new EmptyTreeIterator());
300 		else
301 			tw.addTree(id);
302 	}
303 
304 	/**
305 	 * Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
306 	 * there is no head yet.
307 	 *
308 	 * @throws MissingObjectException
309 	 * @throws IncorrectObjectTypeException
310 	 * @throws CorruptObjectException
311 	 * @throws IOException
312 	 */
313 	public void prescanOneTree()
314 			throws MissingObjectException, IncorrectObjectTypeException,
315 			CorruptObjectException, IOException {
316 		removed.clear();
317 		updated.clear();
318 		conflicts.clear();
319 
320 		builder = dc.builder();
321 
322 		walk = new NameConflictTreeWalk(repo);
323 		addTree(walk, mergeCommitTree);
324 		int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
325 		walk.addTree(workingTree);
326 		workingTree.setDirCacheIterator(walk, dciPos);
327 
328 		while (walk.next()) {
329 			processEntry(walk.getTree(0, CanonicalTreeParser.class),
330 					walk.getTree(1, DirCacheBuildIterator.class),
331 					walk.getTree(2, WorkingTreeIterator.class));
332 			if (walk.isSubtree())
333 				walk.enterSubtree();
334 		}
335 		conflicts.removeAll(removed);
336 	}
337 
338 	/**
339 	 * Processing an entry in the context of {@link #prescanOneTree()} when only
340 	 * one tree is given
341 	 *
342 	 * @param m the tree to merge
343 	 * @param i the index
344 	 * @param f the working tree
345 	 * @throws IOException
346 	 */
347 	void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
348 			WorkingTreeIterator f) throws IOException {
349 		if (m != null) {
350 			checkValidPath(m);
351 			// There is an entry in the merge commit. Means: we want to update
352 			// what's currently in the index and working-tree to that one
353 			if (i == null) {
354 				// The index entry is missing
355 				if (f != null && !FileMode.TREE.equals(f.getEntryFileMode())
356 						&& !f.isEntryIgnored()) {
357 					if (failOnConflict) {
358 						// don't overwrite an untracked and not ignored file
359 						conflicts.add(walk.getPathString());
360 					} else {
361 						// failOnConflict is false. Putting something to conflicts
362 						// would mean we delete it. Instead we want the mergeCommit
363 						// content to be checked out.
364 						update(m.getEntryPathString(), m.getEntryObjectId(),
365 								m.getEntryFileMode());
366 					}
367 				} else
368 					update(m.getEntryPathString(), m.getEntryObjectId(),
369 						m.getEntryFileMode());
370 			} else if (f == null || !m.idEqual(i)) {
371 				// The working tree file is missing or the merge content differs
372 				// from index content
373 				update(m.getEntryPathString(), m.getEntryObjectId(),
374 						m.getEntryFileMode());
375 			} else if (i.getDirCacheEntry() != null) {
376 				// The index contains a file (and not a folder)
377 				if (f.isModified(i.getDirCacheEntry(), true,
378 						this.walk.getObjectReader())
379 						|| i.getDirCacheEntry().getStage() != 0)
380 					// The working tree file is dirty or the index contains a
381 					// conflict
382 					update(m.getEntryPathString(), m.getEntryObjectId(),
383 							m.getEntryFileMode());
384 				else {
385 					// update the timestamp of the index with the one from the
386 					// file if not set, as we are sure to be in sync here.
387 					DirCacheEntry entry = i.getDirCacheEntry();
388 					if (entry.getLastModified() == 0)
389 						entry.setLastModified(f.getEntryLastModified());
390 					keep(entry);
391 				}
392 			} else
393 				// The index contains a folder
394 				keep(i.getDirCacheEntry());
395 		} else {
396 			// There is no entry in the merge commit. Means: we want to delete
397 			// what's currently in the index and working tree
398 			if (f != null) {
399 				// There is a file/folder for that path in the working tree
400 				if (walk.isDirectoryFileConflict()) {
401 					// We put it in conflicts. Even if failOnConflict is false
402 					// this would cause the path to be deleted. Thats exactly what
403 					// we want in this situation
404 					conflicts.add(walk.getPathString());
405 				} else {
406 					// No file/folder conflict exists. All entries are files or
407 					// all entries are folders
408 					if (i != null) {
409 						// ... and the working tree contained a file or folder
410 						// -> add it to the removed set and remove it from
411 						// conflicts set
412 						remove(i.getEntryPathString());
413 						conflicts.remove(i.getEntryPathString());
414 					} else {
415 						// untracked file, neither contained in tree to merge
416 						// nor in index
417 					}
418 				}
419 			} else {
420 				// There is no file/folder for that path in the working tree,
421 				// nor in the merge head.
422 				// The only entry we have is the index entry. Like the case
423 				// where there is a file with the same name, remove it,
424 			}
425 		}
426 	}
427 
428 	/**
429 	 * Execute this checkout
430 	 *
431 	 * @return <code>false</code> if this method could not delete all the files
432 	 *         which should be deleted (e.g. because of of the files was
433 	 *         locked). In this case {@link #getToBeDeleted()} lists the files
434 	 *         which should be tried to be deleted outside of this method.
435 	 *         Although <code>false</code> is returned the checkout was
436 	 *         successful and the working tree was updated for all other files.
437 	 *         <code>true</code> is returned when no such problem occurred
438 	 *
439 	 * @throws IOException
440 	 */
441 	public boolean checkout() throws IOException {
442 		try {
443 			return doCheckout();
444 		} finally {
445 			dc.unlock();
446 		}
447 	}
448 
449 	private boolean doCheckout() throws CorruptObjectException, IOException,
450 			MissingObjectException, IncorrectObjectTypeException,
451 			CheckoutConflictException, IndexWriteException {
452 		toBeDeleted.clear();
453 		try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
454 			if (headCommitTree != null)
455 				preScanTwoTrees();
456 			else
457 				prescanOneTree();
458 
459 			if (!conflicts.isEmpty()) {
460 				if (failOnConflict)
461 					throw new CheckoutConflictException(conflicts.toArray(new String[conflicts.size()]));
462 				else
463 					cleanUpConflicts();
464 			}
465 
466 			// update our index
467 			builder.finish();
468 
469 			File file = null;
470 			String last = null;
471 			// when deleting files process them in the opposite order as they have
472 			// been reported. This ensures the files are deleted before we delete
473 			// their parent folders
474 			for (int i = removed.size() - 1; i >= 0; i--) {
475 				String r = removed.get(i);
476 				file = new File(repo.getWorkTree(), r);
477 				if (!file.delete() && repo.getFS().exists(file)) {
478 					// The list of stuff to delete comes from the index
479 					// which will only contain a directory if it is
480 					// a submodule, in which case we shall not attempt
481 					// to delete it. A submodule is not empty, so it
482 					// is safe to check this after a failed delete.
483 					if (!repo.getFS().isDirectory(file))
484 						toBeDeleted.add(r);
485 				} else {
486 					if (last != null && !isSamePrefix(r, last))
487 						removeEmptyParents(new File(repo.getWorkTree(), last));
488 					last = r;
489 				}
490 			}
491 			if (file != null)
492 				removeEmptyParents(file);
493 
494 			for (Map.Entry<String, CheckoutMetadata> e : updated.entrySet()) {
495 				String path = e.getKey();
496 				CheckoutMetadata meta = e.getValue();
497 				DirCacheEntry entry = dc.getEntry(path);
498 				if (!FileMode.GITLINK.equals(entry.getRawMode()))
499 					checkoutEntry(repo, entry, objectReader, false, meta);
500 			}
501 
502 			// commit the index builder - a new index is persisted
503 			if (!builder.commit())
504 				throw new IndexWriteException();
505 		}
506 		return toBeDeleted.size() == 0;
507 	}
508 
509 	private static boolean isSamePrefix(String a, String b) {
510 		int as = a.lastIndexOf('/');
511 		int bs = b.lastIndexOf('/');
512 		return a.substring(0, as + 1).equals(b.substring(0, bs + 1));
513 	}
514 
515 	 private void removeEmptyParents(File f) {
516 		File parentFile = f.getParentFile();
517 
518 		while (parentFile != null && !parentFile.equals(repo.getWorkTree())) {
519 			if (!parentFile.delete())
520 				break;
521 			parentFile = parentFile.getParentFile();
522 		}
523 	}
524 
525 	/**
526 	 * Compares whether two pairs of ObjectId and FileMode are equal.
527 	 *
528 	 * @param id1
529 	 * @param mode1
530 	 * @param id2
531 	 * @param mode2
532 	 * @return <code>true</code> if FileModes and ObjectIds are equal.
533 	 *         <code>false</code> otherwise
534 	 */
535 	private boolean equalIdAndMode(ObjectId id1, FileMode mode1, ObjectId id2,
536 			FileMode mode2) {
537 		if (!mode1.equals(mode2))
538 			return false;
539 		return id1 != null ? id1.equals(id2) : id2 == null;
540 	}
541 
542 	/**
543 	 * Here the main work is done. This method is called for each existing path
544 	 * in head, index and merge. This method decides what to do with the
545 	 * corresponding index entry: keep it, update it, remove it or mark a
546 	 * conflict.
547 	 *
548 	 * @param h
549 	 *            the entry for the head
550 	 * @param m
551 	 *            the entry for the merge
552 	 * @param i
553 	 *            the entry for the index
554 	 * @param f
555 	 *            the file in the working tree
556 	 * @throws IOException
557 	 */
558 
559 	void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
560 			DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException {
561 		DirCacheEntry dce = i != null ? i.getDirCacheEntry() : null;
562 
563 		String name = walk.getPathString();
564 
565 		if (m != null)
566 			checkValidPath(m);
567 
568 		if (i == null && m == null && h == null) {
569 			// File/Directory conflict case #20
570 			if (walk.isDirectoryFileConflict())
571 				// TODO: check whether it is always correct to report a conflict here
572 				conflict(name, null, null, null);
573 
574 			// file only exists in working tree -> ignore it
575 			return;
576 		}
577 
578 		ObjectId iId = (i == null ? null : i.getEntryObjectId());
579 		ObjectId mId = (m == null ? null : m.getEntryObjectId());
580 		ObjectId hId = (h == null ? null : h.getEntryObjectId());
581 		FileMode iMode = (i == null ? null : i.getEntryFileMode());
582 		FileMode mMode = (m == null ? null : m.getEntryFileMode());
583 		FileMode hMode = (h == null ? null : h.getEntryFileMode());
584 
585 		/**
586 		 * <pre>
587 		 *  File/Directory conflicts:
588 		 *  the following table from ReadTreeTest tells what to do in case of directory/file
589 		 *  conflicts. I give comments here
590 		 *
591 		 *      H        I       M     Clean     H==M     H==I    I==M         Result
592 		 *      ------------------------------------------------------------------
593 		 * 1    D        D       F       Y         N       Y       N           Update
594 		 * 2    D        D       F       N         N       Y       N           Conflict
595 		 * 3    D        F       D                 Y       N       N           Keep
596 		 * 4    D        F       D                 N       N       N           Conflict
597 		 * 5    D        F       F       Y         N       N       Y           Keep
598 		 * 5b   D        F       F       Y         N       N       N           Conflict
599 		 * 6    D        F       F       N         N       N       Y           Keep
600 		 * 6b   D        F       F       N         N       N       N           Conflict
601 		 * 7    F        D       F       Y         Y       N       N           Update
602 		 * 8    F        D       F       N         Y       N       N           Conflict
603 		 * 9    F        D       F                 N       N       N           Conflict
604 		 * 10   F        D       D                 N       N       Y           Keep
605 		 * 11   F        D       D                 N       N       N           Conflict
606 		 * 12   F        F       D       Y         N       Y       N           Update
607 		 * 13   F        F       D       N         N       Y       N           Conflict
608 		 * 14   F        F       D                 N       N       N           Conflict
609 		 * 15   0        F       D                 N       N       N           Conflict
610 		 * 16   0        D       F       Y         N       N       N           Update
611 		 * 17   0        D       F                 N       N       N           Conflict
612 		 * 18   F        0       D                                             Update
613 		 * 19   D        0       F                                             Update
614 		 * 20   0        0       F       N (worktree=dir)                      Conflict
615 		 * </pre>
616 		 */
617 
618 		// The information whether head,index,merge iterators are currently
619 		// pointing to file/folder/non-existing is encoded into this variable.
620 		//
621 		// To decode write down ffMask in hexadecimal form. The last digit
622 		// represents the state for the merge iterator, the second last the
623 		// state for the index iterator and the third last represents the state
624 		// for the head iterator. The hexadecimal constant "F" stands for
625 		// "file", a "D" stands for "directory" (tree), and a "0" stands for
626 		// non-existing. Symbolic links and git links are treated as File here.
627 		//
628 		// Examples:
629 		// ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree
630 		// ffMask == 0xDD0 -> Head=Tree, Index=Tree, Merge=Non-Existing
631 
632 		int ffMask = 0;
633 		if (h != null)
634 			ffMask = FileMode.TREE.equals(hMode) ? 0xD00 : 0xF00;
635 		if (i != null)
636 			ffMask |= FileMode.TREE.equals(iMode) ? 0x0D0 : 0x0F0;
637 		if (m != null)
638 			ffMask |= FileMode.TREE.equals(mMode) ? 0x00D : 0x00F;
639 
640 		// Check whether we have a possible file/folder conflict. Therefore we
641 		// need a least one file and one folder.
642 		if (((ffMask & 0x222) != 0x000)
643 				&& (((ffMask & 0x00F) == 0x00D) || ((ffMask & 0x0F0) == 0x0D0) || ((ffMask & 0xF00) == 0xD00))) {
644 
645 			// There are 3*3*3=27 possible combinations of file/folder
646 			// conflicts. Some of them are not-relevant because
647 			// they represent no conflict, e.g. 0xFFF, 0xDDD, ... The following
648 			// switch processes all relevant cases.
649 			switch (ffMask) {
650 			case 0xDDF: // 1 2
651 				if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
652 					conflict(name, dce, h, m); // 1
653 				} else {
654 					update(name, mId, mMode); // 2
655 				}
656 
657 				break;
658 			case 0xDFD: // 3 4
659 				keep(dce);
660 				break;
661 			case 0xF0D: // 18
662 				remove(name);
663 				break;
664 			case 0xDFF: // 5 5b 6 6b
665 				if (equalIdAndMode(iId, iMode, mId, mMode))
666 					keep(dce); // 5 6
667 				else
668 					conflict(name, dce, h, m); // 5b 6b
669 				break;
670 			case 0xFDD: // 10 11
671 				// TODO: make use of tree extension as soon as available in jgit
672 				// we would like to do something like
673 				// if (!equalIdAndMode(iId, iMode, mId, mMode)
674 				//   conflict(name, i.getDirCacheEntry(), h, m);
675 				// But since we don't know the id of a tree in the index we do
676 				// nothing here and wait that conflicts between index and merge
677 				// are found later
678 				break;
679 			case 0xD0F: // 19
680 				update(name, mId, mMode);
681 				break;
682 			case 0xDF0: // conflict without a rule
683 			case 0x0FD: // 15
684 				conflict(name, dce, h, m);
685 				break;
686 			case 0xFDF: // 7 8 9
687 				if (equalIdAndMode(hId, hMode, mId, mMode)) {
688 					if (isModifiedSubtree_IndexWorkingtree(name))
689 						conflict(name, dce, h, m); // 8
690 					else
691 						update(name, mId, mMode); // 7
692 				} else
693 					conflict(name, dce, h, m); // 9
694 				break;
695 			case 0xFD0: // keep without a rule
696 				keep(dce);
697 				break;
698 			case 0xFFD: // 12 13 14
699 				if (equalIdAndMode(hId, hMode, iId, iMode))
700 					if (f != null
701 							&& f.isModified(dce, true,
702 									this.walk.getObjectReader()))
703 						conflict(name, dce, h, m); // 13
704 					else
705 						remove(name); // 12
706 				else
707 					conflict(name, dce, h, m); // 14
708 				break;
709 			case 0x0DF: // 16 17
710 				if (!isModifiedSubtree_IndexWorkingtree(name))
711 					update(name, mId, mMode);
712 				else
713 					conflict(name, dce, h, m);
714 				break;
715 			default:
716 				keep(dce);
717 			}
718 			return;
719 		}
720 
721 		// if we have no file at all then there is nothing to do
722 		if ((ffMask & 0x222) == 0
723 				&& (f == null || FileMode.TREE.equals(f.getEntryFileMode())))
724 			return;
725 
726 		if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
727 			// File/Directory conflict case #20
728 			conflict(name, null, h, m);
729 			return;
730 		}
731 
732 		if (i == null) {
733 			// Nothing in Index
734 			// At least one of Head, Index, Merge is not empty
735 			// make sure not to overwrite untracked files
736 			if (f != null && !f.isEntryIgnored()) {
737 				// A submodule is not a file. We should ignore it
738 				if (!FileMode.GITLINK.equals(mMode)) {
739 					// a dirty worktree: the index is empty but we have a
740 					// workingtree-file
741 					if (mId == null
742 							|| !equalIdAndMode(mId, mMode,
743 									f.getEntryObjectId(), f.getEntryFileMode())) {
744 						conflict(name, null, h, m);
745 						return;
746 					}
747 				}
748 			}
749 
750 			/**
751 			 * <pre>
752 			 * 	          I (index)     H        M     H==M  Result
753 			 * 	        -------------------------------------------
754 			 * 	        0 nothing    nothing  nothing        (does not happen)
755 			 * 	        1 nothing    nothing  exists         use M
756 			 * 	        2 nothing    exists   nothing        remove path from index
757 			 * 	        3 nothing    exists   exists   yes   keep index if not in initial checkout
758 			 *                                               , otherwise use M
759 			 * 	          nothing    exists   exists   no    fail
760 			 * </pre>
761 			 */
762 
763 			if (h == null)
764 				// Nothing in Head
765 				// Nothing in Index
766 				// At least one of Head, Index, Merge is not empty
767 				// -> only Merge contains something for this path. Use it!
768 				// Potentially update the file
769 				update(name, mId, mMode); // 1
770 			else if (m == null)
771 				// Nothing in Merge
772 				// Something in Head
773 				// Nothing in Index
774 				// -> only Head contains something for this path and it should
775 				// be deleted. Potentially removes the file!
776 				remove(name); // 2
777 			else { // 3
778 				// Something in Merge
779 				// Something in Head
780 				// Nothing in Index
781 				// -> Head and Merge contain something (maybe not the same) and
782 				// in the index there is nothing (e.g. 'git rm ...' was
783 				// called before). Ignore the cached deletion and use what we
784 				// find in Merge. Potentially updates the file.
785 				if (equalIdAndMode(hId, hMode, mId, mMode)) {
786 					if (emptyDirCache)
787 						update(name, mId, mMode);
788 					else
789 						keep(dce);
790 				} else
791 					conflict(name, dce, h, m);
792 			}
793 		} else {
794 			// Something in Index
795 			if (h == null) {
796 				// Nothing in Head
797 				// Something in Index
798 				/**
799 				 * <pre>
800 				 * 	          clean I==H  I==M       H        M        Result
801 				 * 	         -----------------------------------------------------
802 				 * 	        4 yes   N/A   N/A     nothing  nothing  keep index
803 				 * 	        5 no    N/A   N/A     nothing  nothing  keep index
804 				 *
805 				 * 	        6 yes   N/A   yes     nothing  exists   keep index
806 				 * 	        7 no    N/A   yes     nothing  exists   keep index
807 				 * 	        8 yes   N/A   no      nothing  exists   fail
808 				 * 	        9 no    N/A   no      nothing  exists   fail
809 				 * </pre>
810 				 */
811 
812 				if (m == null
813 						|| !isModified_IndexTree(name, iId, iMode, mId, mMode,
814 								mergeCommitTree)) {
815 					// Merge contains nothing or the same as Index
816 					// Nothing in Head
817 					// Something in Index
818 					if (m==null && walk.isDirectoryFileConflict()) {
819 						// Nothing in Merge and current path is part of
820 						// File/Folder conflict
821 						// Nothing in Head
822 						// Something in Index
823 						if (dce != null
824 								&& (f == null || f.isModified(dce, true,
825 										this.walk.getObjectReader())))
826 							// No file or file is dirty
827 							// Nothing in Merge and current path is part of
828 							// File/Folder conflict
829 							// Nothing in Head
830 							// Something in Index
831 							// -> File folder conflict and Merge wants this
832 							// path to be removed. Since the file is dirty
833 							// report a conflict
834 							conflict(name, dce, h, m);
835 						else
836 							// A file is present and file is not dirty
837 							// Nothing in Merge and current path is part of
838 							// File/Folder conflict
839 							// Nothing in Head
840 							// Something in Index
841 							// -> File folder conflict and Merge wants this path
842 							// to be removed. Since the file is not dirty remove
843 							// file and index entry
844 							remove(name);
845 					} else
846 						// Something in Merge or current path is not part of
847 						// File/Folder conflict
848 						// Merge contains nothing or the same as Index
849 						// Nothing in Head
850 						// Something in Index
851 						// -> Merge contains nothing new. Keep the index.
852 						keep(dce);
853 				} else
854 					// Merge contains something and it is not the same as Index
855 					// Nothing in Head
856 					// Something in Index
857 					// -> Index contains something new (different from Head)
858 					// and Merge is different from Index. Report a conflict
859 					conflict(name, dce, h, m);
860 			} else if (m == null) {
861 				// Nothing in Merge
862 				// Something in Head
863 				// Something in Index
864 
865 				/**
866 				 * <pre>
867 				 * 	           clean I==H  I==M       H        M        Result
868 				 * 	         -----------------------------------------------------
869 				 * 	        10 yes   yes   N/A     exists   nothing  remove path from index
870 				 * 	        11 no    yes   N/A     exists   nothing  keep file
871 				 * 	        12 yes   no    N/A     exists   nothing  fail
872 				 * 	        13 no    no    N/A     exists   nothing  fail
873 				 * </pre>
874 				 */
875 
876 				if (iMode == FileMode.GITLINK) {
877 					// A submodule in Index
878 					// Nothing in Merge
879 					// Something in Head
880 					// Submodules that disappear from the checkout must
881 					// be removed from the index, but not deleted from disk.
882 					remove(name);
883 				} else {
884 					// Something different from a submodule in Index
885 					// Nothing in Merge
886 					// Something in Head
887 					if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
888 							headCommitTree)) {
889 						// Index contains the same as Head
890 						// Something different from a submodule in Index
891 						// Nothing in Merge
892 						// Something in Head
893 						if (f != null
894 								&& f.isModified(dce, true,
895 										this.walk.getObjectReader())) {
896 							// file is dirty
897 							// Index contains the same as Head
898 							// Something different from a submodule in Index
899 							// Nothing in Merge
900 							// Something in Head
901 
902 							if (!FileMode.TREE.equals(f.getEntryFileMode())
903 									&& FileMode.TREE.equals(iMode))
904 								// The workingtree contains a file and the index semantically contains a folder.
905 								// Git considers the workingtree file as untracked. Just keep the untracked file.
906 								return;
907 							else
908 								// -> file is dirty and tracked but is should be
909 								// removed. That's a conflict
910 								conflict(name, dce, h, m);
911 						} else
912 							// file doesn't exist or is clean
913 							// Index contains the same as Head
914 							// Something different from a submodule in Index
915 							// Nothing in Merge
916 							// Something in Head
917 							// -> Remove from index and delete the file
918 							remove(name);
919 					} else
920 						// Index contains something different from Head
921 						// Something different from a submodule in Index
922 						// Nothing in Merge
923 						// Something in Head
924 						// -> Something new is in index (and maybe even on the
925 						// filesystem). But Merge wants the path to be removed.
926 						// Report a conflict
927 						conflict(name, dce, h, m);
928 				}
929 			} else {
930 				// Something in Merge
931 				// Something in Head
932 				// Something in Index
933 				if (!equalIdAndMode(hId, hMode, mId, mMode)
934 						&& isModified_IndexTree(name, iId, iMode, hId, hMode,
935 								headCommitTree)
936 						&& isModified_IndexTree(name, iId, iMode, mId, mMode,
937 								mergeCommitTree))
938 					// All three contents in Head, Merge, Index differ from each
939 					// other
940 					// -> All contents differ. Report a conflict.
941 					conflict(name, dce, h, m);
942 				else
943 					// At least two of the contents of Head, Index, Merge
944 					// are the same
945 					// Something in Merge
946 					// Something in Head
947 					// Something in Index
948 
949 				if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
950 						headCommitTree)
951 						&& isModified_IndexTree(name, iId, iMode, mId, mMode,
952 								mergeCommitTree)) {
953 						// Head contains the same as Index. Merge differs
954 						// Something in Merge
955 
956 					// For submodules just update the index with the new SHA-1
957 					if (dce != null
958 							&& FileMode.GITLINK.equals(dce.getFileMode())) {
959 						// Index and Head contain the same submodule. Merge
960 						// differs
961 						// Something in Merge
962 						// -> Nothing new in index. Move to merge.
963 						// Potentially updates the file
964 
965 						// TODO check that we don't overwrite some unsaved
966 						// file content
967 						update(name, mId, mMode);
968 					} else if (dce != null
969 							&& (f != null && f.isModified(dce, true,
970 									this.walk.getObjectReader()))) {
971 						// File exists and is dirty
972 						// Head and Index don't contain a submodule
973 						// Head contains the same as Index. Merge differs
974 						// Something in Merge
975 						// -> Merge wants the index and file to be updated
976 						// but the file is dirty. Report a conflict
977 						conflict(name, dce, h, m);
978 					} else {
979 						// File doesn't exist or is clean
980 						// Head and Index don't contain a submodule
981 						// Head contains the same as Index. Merge differs
982 						// Something in Merge
983 						// -> Standard case when switching between branches:
984 						// Nothing new in index but something different in
985 						// Merge. Update index and file
986 						update(name, mId, mMode);
987 					}
988 				} else {
989 					// Head differs from index or merge is same as index
990 					// At least two of the contents of Head, Index, Merge
991 					// are the same
992 					// Something in Merge
993 					// Something in Head
994 					// Something in Index
995 
996 					// Can be formulated as: Either all three states are
997 					// equal or Merge is equal to Head or Index and differs
998 					// to the other one.
999 					// -> In all three cases we don't touch index and file.
1000 
1001 					keep(dce);
1002 				}
1003 			}
1004 		}
1005 	}
1006 
1007 	/**
1008 	 * A conflict is detected - add the three different stages to the index
1009 	 * @param path the path of the conflicting entry
1010 	 * @param e the previous index entry
1011 	 * @param h the first tree you want to merge (the HEAD)
1012 	 * @param m the second tree you want to merge
1013 	 */
1014 	private void conflict(String path, DirCacheEntry e, AbstractTreeIterator h, AbstractTreeIterator m) {
1015 		conflicts.add(path);
1016 
1017 		DirCacheEntry entry;
1018 		if (e != null) {
1019 			entry = new DirCacheEntry(e.getPathString(), DirCacheEntry.STAGE_1);
1020 			entry.copyMetaData(e, true);
1021 			builder.add(entry);
1022 		}
1023 
1024 		if (h != null && !FileMode.TREE.equals(h.getEntryFileMode())) {
1025 			entry = new DirCacheEntry(h.getEntryPathString(), DirCacheEntry.STAGE_2);
1026 			entry.setFileMode(h.getEntryFileMode());
1027 			entry.setObjectId(h.getEntryObjectId());
1028 			builder.add(entry);
1029 		}
1030 
1031 		if (m != null && !FileMode.TREE.equals(m.getEntryFileMode())) {
1032 			entry = new DirCacheEntry(m.getEntryPathString(), DirCacheEntry.STAGE_3);
1033 			entry.setFileMode(m.getEntryFileMode());
1034 			entry.setObjectId(m.getEntryObjectId());
1035 			builder.add(entry);
1036 		}
1037 	}
1038 
1039 	private void keep(DirCacheEntry e) {
1040 		if (e != null && !FileMode.TREE.equals(e.getFileMode()))
1041 			builder.add(e);
1042 	}
1043 
1044 	private void remove(String path) {
1045 		removed.add(path);
1046 	}
1047 
1048 	private void update(String path, ObjectId mId, FileMode mode)
1049 			throws IOException {
1050 		if (!FileMode.TREE.equals(mode)) {
1051 			updated.put(path, new CheckoutMetadata(walk.getEolStreamType(),
1052 					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
1053 
1054 			DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
1055 			entry.setObjectId(mId);
1056 			entry.setFileMode(mode);
1057 			builder.add(entry);
1058 		}
1059 	}
1060 
1061 	/**
1062 	 * If <code>true</code>, will scan first to see if it's possible to check
1063 	 * out, otherwise throw {@link CheckoutConflictException}. If
1064 	 * <code>false</code>, it will silently deal with the problem.
1065 	 *
1066 	 * @param failOnConflict
1067 	 */
1068 	public void setFailOnConflict(boolean failOnConflict) {
1069 		this.failOnConflict = failOnConflict;
1070 	}
1071 
1072 	/**
1073 	 * This method implements how to handle conflicts when
1074 	 * {@link #failOnConflict} is false
1075 	 *
1076 	 * @throws CheckoutConflictException
1077 	 */
1078 	private void cleanUpConflicts() throws CheckoutConflictException {
1079 		// TODO: couldn't we delete unsaved worktree content here?
1080 		for (String c : conflicts) {
1081 			File conflict = new File(repo.getWorkTree(), c);
1082 			if (!conflict.delete())
1083 				throw new CheckoutConflictException(MessageFormat.format(
1084 						JGitText.get().cannotDeleteFile, c));
1085 			removeEmptyParents(conflict);
1086 		}
1087 		for (String r : removed) {
1088 			File file = new File(repo.getWorkTree(), r);
1089 			if (!file.delete())
1090 				throw new CheckoutConflictException(
1091 						MessageFormat.format(JGitText.get().cannotDeleteFile,
1092 								file.getAbsolutePath()));
1093 			removeEmptyParents(file);
1094 		}
1095 	}
1096 
1097 	/**
1098 	 * Checks whether the subtree starting at a given path differs between Index and
1099 	 * workingtree.
1100 	 *
1101 	 * @param path
1102 	 * @return true if the subtrees differ
1103 	 * @throws CorruptObjectException
1104 	 * @throws IOException
1105 	 */
1106 	private boolean isModifiedSubtree_IndexWorkingtree(String path)
1107 			throws CorruptObjectException, IOException {
1108 		try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1109 			int dciPos = tw.addTree(new DirCacheIterator(dc));
1110 			FileTreeIterator fti = new FileTreeIterator(repo);
1111 			tw.addTree(fti);
1112 			fti.setDirCacheIterator(tw, dciPos);
1113 			tw.setRecursive(true);
1114 			tw.setFilter(PathFilter.create(path));
1115 			DirCacheIterator dcIt;
1116 			WorkingTreeIterator wtIt;
1117 			while (tw.next()) {
1118 				dcIt = tw.getTree(0, DirCacheIterator.class);
1119 				wtIt = tw.getTree(1, WorkingTreeIterator.class);
1120 				if (dcIt == null || wtIt == null)
1121 					return true;
1122 				if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
1123 						this.walk.getObjectReader())) {
1124 					return true;
1125 				}
1126 			}
1127 			return false;
1128 		}
1129 	}
1130 
1131 	private boolean isModified_IndexTree(String path, ObjectId iId,
1132 			FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree)
1133 			throws CorruptObjectException, IOException {
1134 		if (iMode != tMode)
1135 			return true;
1136 		if (FileMode.TREE.equals(iMode)
1137 				&& (iId == null || ObjectId.zeroId().equals(iId)))
1138 			return isModifiedSubtree_IndexTree(path, rootTree);
1139 		else
1140 			return !equalIdAndMode(iId, iMode, tId, tMode);
1141 	}
1142 
1143 	/**
1144 	 * Checks whether the subtree starting at a given path differs between Index and
1145 	 * some tree.
1146 	 *
1147 	 * @param path
1148 	 * @param tree
1149 	 *            the tree to compare
1150 	 * @return true if the subtrees differ
1151 	 * @throws CorruptObjectException
1152 	 * @throws IOException
1153 	 */
1154 	private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
1155 			throws CorruptObjectException, IOException {
1156 		try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1157 			tw.addTree(new DirCacheIterator(dc));
1158 			tw.addTree(tree);
1159 			tw.setRecursive(true);
1160 			tw.setFilter(PathFilter.create(path));
1161 			while (tw.next()) {
1162 				AbstractTreeIterator dcIt = tw.getTree(0,
1163 						DirCacheIterator.class);
1164 				AbstractTreeIterator treeIt = tw.getTree(1,
1165 						AbstractTreeIterator.class);
1166 				if (dcIt == null || treeIt == null)
1167 					return true;
1168 				if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode())
1169 					return true;
1170 				if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId()))
1171 					return true;
1172 			}
1173 			return false;
1174 		}
1175 	}
1176 
1177 	/**
1178 	 * Updates the file in the working tree with content and mode from an entry
1179 	 * in the index. The new content is first written to a new temporary file in
1180 	 * the same directory as the real file. Then that new file is renamed to the
1181 	 * final filename.
1182 	 *
1183 	 * <p>
1184 	 * <b>Note:</b> if the entry path on local file system exists as a non-empty
1185 	 * directory, and the target entry type is a link or file, the checkout will
1186 	 * fail with {@link IOException} since existing non-empty directory cannot
1187 	 * be renamed to file or link without deleting it recursively.
1188 	 * </p>
1189 	 *
1190 	 * <p>
1191 	 * TODO: this method works directly on File IO, we may need another
1192 	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
1193 	 * Eclipse that Files in the workspace got changed
1194 	 * </p>
1195 	 *
1196 	 * @param repo
1197 	 *            repository managing the destination work tree.
1198 	 * @param entry
1199 	 *            the entry containing new mode and content
1200 	 * @param or
1201 	 *            object reader to use for checkout
1202 	 * @throws IOException
1203 	 * @since 3.6
1204 	 */
1205 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1206 			ObjectReader or) throws IOException {
1207 		checkoutEntry(repo, entry, or, false, null);
1208 	}
1209 
1210 	/**
1211 	 * Updates the file in the working tree with content and mode from an entry
1212 	 * in the index. The new content is first written to a new temporary file in
1213 	 * the same directory as the real file. Then that new file is renamed to the
1214 	 * final filename.
1215 	 *
1216 	 * <p>
1217 	 * <b>Note:</b> if the entry path on local file system exists as a file, it
1218 	 * will be deleted and if it exists as a directory, it will be deleted
1219 	 * recursively, independently if has any content.
1220 	 * </p>
1221 	 *
1222 	 * <p>
1223 	 * TODO: this method works directly on File IO, we may need another
1224 	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
1225 	 * Eclipse that Files in the workspace got changed
1226 	 * </p>
1227 	 *
1228 	 * @param repo
1229 	 *            repository managing the destination work tree.
1230 	 * @param entry
1231 	 *            the entry containing new mode and content
1232 	 * @param or
1233 	 *            object reader to use for checkout
1234 	 * @param deleteRecursive
1235 	 *            true to recursively delete final path if it exists on the file
1236 	 *            system
1237 	 * @param checkoutMetadata
1238 	 *            containing
1239 	 *            <ul>
1240 	 *            <li>smudgeFilterCommand to be run for smudging the entry to be
1241 	 *            checked out</li>
1242 	 *            <li>eolStreamType used for stream conversion</li>
1243 	 *            </ul>
1244 	 *
1245 	 * @throws IOException
1246 	 * @since 4.2
1247 	 */
1248 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1249 			ObjectReader or, boolean deleteRecursive,
1250 			CheckoutMetadata checkoutMetadata) throws IOException {
1251 		if (checkoutMetadata == null)
1252 			checkoutMetadata = CheckoutMetadata.EMPTY;
1253 		ObjectLoader ol = or.open(entry.getObjectId());
1254 		File f = new File(repo.getWorkTree(), entry.getPathString());
1255 		File parentDir = f.getParentFile();
1256 		FileUtils.mkdirs(parentDir, true);
1257 		FS fs = repo.getFS();
1258 		WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
1259 		if (entry.getFileMode() == FileMode.SYMLINK
1260 				&& opt.getSymLinks() == SymLinks.TRUE) {
1261 			byte[] bytes = ol.getBytes();
1262 			String target = RawParseUtils.decode(bytes);
1263 			if (deleteRecursive && f.isDirectory()) {
1264 				FileUtils.delete(f, FileUtils.RECURSIVE);
1265 			}
1266 			fs.createSymLink(f, target);
1267 			entry.setLength(bytes.length);
1268 			entry.setLastModified(fs.lastModified(f));
1269 			return;
1270 		}
1271 
1272 		File tmpFile = File.createTempFile(
1273 				"._" + f.getName(), null, parentDir); //$NON-NLS-1$
1274 		EolStreamType nonNullEolStreamType;
1275 		if (checkoutMetadata.eolStreamType != null) {
1276 			nonNullEolStreamType = checkoutMetadata.eolStreamType;
1277 		} else if (opt.getAutoCRLF() == AutoCRLF.TRUE) {
1278 			nonNullEolStreamType = EolStreamType.AUTO_CRLF;
1279 		} else {
1280 			nonNullEolStreamType = EolStreamType.DIRECT;
1281 		}
1282 		OutputStream channel = EolStreamTypeUtil.wrapOutputStream(
1283 				new FileOutputStream(tmpFile), nonNullEolStreamType);
1284 		if (checkoutMetadata.smudgeFilterCommand != null) {
1285 			ProcessBuilder filterProcessBuilder = fs.runInShell(
1286 					checkoutMetadata.smudgeFilterCommand, new String[0]);
1287 			filterProcessBuilder.directory(repo.getWorkTree());
1288 			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
1289 					repo.getDirectory().getAbsolutePath());
1290 			ExecutionResult result;
1291 			int rc;
1292 			try {
1293 				// TODO: wire correctly with AUTOCRLF
1294 				result = fs.execute(filterProcessBuilder, ol.openStream());
1295 				rc = result.getRc();
1296 				if (rc == 0) {
1297 					result.getStdout().writeTo(channel,
1298 							NullProgressMonitor.INSTANCE);
1299 				}
1300 			} catch (IOException | InterruptedException e) {
1301 				throw new IOException(new FilterFailedException(e,
1302 						checkoutMetadata.smudgeFilterCommand,
1303 						entry.getPathString()));
1304 
1305 			} finally {
1306 				channel.close();
1307 			}
1308 			if (rc != 0) {
1309 				throw new IOException(new FilterFailedException(rc,
1310 						checkoutMetadata.smudgeFilterCommand,
1311 						entry.getPathString(),
1312 						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
1313 						RawParseUtils.decode(result.getStderr()
1314 								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
1315 			}
1316 		} else {
1317 			try {
1318 				ol.copyTo(channel);
1319 			} finally {
1320 				channel.close();
1321 			}
1322 		}
1323 		// The entry needs to correspond to the on-disk filesize. If the content
1324 		// was filtered (either by autocrlf handling or smudge filters) ask the
1325 		// filesystem again for the length. Otherwise the objectloader knows the
1326 		// size
1327 		if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
1328 				&& checkoutMetadata.smudgeFilterCommand == null) {
1329 			entry.setLength(ol.getSize());
1330 		} else {
1331 			entry.setLength(tmpFile.length());
1332 		}
1333 
1334 		if (opt.isFileMode() && fs.supportsExecute()) {
1335 			if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
1336 				if (!fs.canExecute(tmpFile))
1337 					fs.setExecute(tmpFile, true);
1338 			} else {
1339 				if (fs.canExecute(tmpFile))
1340 					fs.setExecute(tmpFile, false);
1341 			}
1342 		}
1343 		try {
1344 			if (deleteRecursive && f.isDirectory()) {
1345 				FileUtils.delete(f, FileUtils.RECURSIVE);
1346 			}
1347 			FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
1348 		} catch (IOException e) {
1349 			throw new IOException(
1350 					MessageFormat.format(JGitText.get().renameFileFailed,
1351 							tmpFile.getPath(), f.getPath()),
1352 					e);
1353 		} finally {
1354 			if (tmpFile.exists()) {
1355 				FileUtils.delete(tmpFile);
1356 			}
1357 		}
1358 		entry.setLastModified(f.lastModified());
1359 	}
1360 
1361 	@SuppressWarnings("deprecation")
1362 	private static void checkValidPath(CanonicalTreeParser t)
1363 			throws InvalidPathException {
1364 		ObjectChecker chk = new ObjectChecker()
1365 			.setSafeForWindows(SystemReader.getInstance().isWindows())
1366 			.setSafeForMacOS(SystemReader.getInstance().isMacOS());
1367 		for (CanonicalTreeParser i = t; i != null; i = i.getParent())
1368 			checkValidPathSegment(chk, i);
1369 	}
1370 
1371 	private static void checkValidPathSegment(ObjectChecker chk,
1372 			CanonicalTreeParser t) throws InvalidPathException {
1373 		try {
1374 			int ptr = t.getNameOffset();
1375 			int end = ptr + t.getNameLength();
1376 			chk.checkPathSegment(t.getEntryPathBuffer(), ptr, end);
1377 		} catch (CorruptObjectException err) {
1378 			String path = t.getEntryPathString();
1379 			InvalidPathException i = new InvalidPathException(path);
1380 			i.initCause(err);
1381 			throw i;
1382 		}
1383 	}
1384 }