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 }