diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java | 336 |
1 files changed, 79 insertions, 257 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index 1fb81b71e9..18d77482e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -5,7 +5,7 @@ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com> - * Copyright (C) 2017, 2022, Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2017, 2025, Thomas Wolf <twolf@apache.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -19,11 +19,9 @@ package org.eclipse.jgit.dircache; import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; @@ -33,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.FilterFailedException; @@ -49,7 +48,6 @@ import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.CoreConfig.EolStreamType; -import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; @@ -69,9 +67,6 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.ExecutionResult; -import org.eclipse.jgit.util.FileUtils; -import org.eclipse.jgit.util.IntList; -import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.EolStreamTypeUtil; import org.slf4j.Logger; @@ -100,7 +95,9 @@ public class DirCacheCheckout { /** * @param eolStreamType + * how to convert EOL characters during stream conversion * @param smudgeFilterCommand + * command used as smudge filter during checkout */ public CheckoutMetadata(EolStreamType eolStreamType, String smudgeFilterCommand) { @@ -116,9 +113,11 @@ public class DirCacheCheckout { private Map<String, CheckoutMetadata> updated = new LinkedHashMap<>(); + private Set<String> existing; + private ArrayList<String> conflicts = new ArrayList<>(); - private ArrayList<String> removed = new ArrayList<>(); + private TreeSet<String> removed; private ArrayList<String> kept = new ArrayList<>(); @@ -144,7 +143,7 @@ public class DirCacheCheckout { private boolean performingCheckout; - private WorkingTreeOptions options; + private Checkout checkout; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; @@ -188,7 +187,7 @@ public class DirCacheCheckout { * @return a list of all files removed by this checkout */ public List<String> getRemoved() { - return removed; + return new ArrayList<>(removed); } /** @@ -206,6 +205,7 @@ public class DirCacheCheckout { * @param workingTree * an iterator over the repositories Working Tree * @throws java.io.IOException + * if an IO error occurred */ public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc, ObjectId mergeCommitTree, WorkingTreeIterator workingTree) @@ -216,6 +216,14 @@ public class DirCacheCheckout { this.mergeCommitTree = mergeCommitTree; this.workingTree = workingTree; this.initialCheckout = !repo.isBare() && !repo.getIndexFile().exists(); + boolean caseInsensitive = !repo.isBare() + && repo.isWorkTreeCaseInsensitive(); + this.removed = caseInsensitive + ? new TreeSet<>(String::compareToIgnoreCase) + : new TreeSet<>(); + this.existing = caseInsensitive + ? new TreeSet<>(String::compareToIgnoreCase) + : null; } /** @@ -233,6 +241,7 @@ public class DirCacheCheckout { * @param mergeCommitTree * the id of the tree we want to fast-forward to * @throws java.io.IOException + * if an IO error occurred */ public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc, ObjectId mergeCommitTree) throws IOException { @@ -252,6 +261,7 @@ public class DirCacheCheckout { * @param workingTree * an iterator over the repositories Working Tree * @throws java.io.IOException + * if an IO error occurred */ public DirCacheCheckout(Repository repo, DirCache dc, ObjectId mergeCommitTree, WorkingTreeIterator workingTree) @@ -271,6 +281,7 @@ public class DirCacheCheckout { * @param mergeCommitTree * the id of the tree of the * @throws java.io.IOException + * if an IO error occurred */ public DirCacheCheckout(Repository repo, DirCache dc, ObjectId mergeCommitTree) throws IOException { @@ -294,7 +305,9 @@ public class DirCacheCheckout { * operations. * * @throws org.eclipse.jgit.errors.CorruptObjectException + * if a corrupt object was found * @throws java.io.IOException + * if an IO error occurred */ public void preScanTwoTrees() throws CorruptObjectException, IOException { removed.clear(); @@ -324,9 +337,13 @@ public class DirCacheCheckout { * there is no head yet. * * @throws org.eclipse.jgit.errors.MissingObjectException + * if an object was found missing * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * if an object didn't have the expected type * @throws org.eclipse.jgit.errors.CorruptObjectException + * if an object is corrupt * @throws java.io.IOException + * if an IO error occurred */ public void prescanOneTree() throws MissingObjectException, IncorrectObjectTypeException, @@ -372,6 +389,7 @@ public class DirCacheCheckout { * @param f * the working tree * @throws IOException + * if an IO error occurred */ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException { @@ -392,9 +410,11 @@ public class DirCacheCheckout { // content to be checked out. update(m); } - } else + } else { update(m); - } else if (f == null || !m.idEqual(i)) { + } + } else if (f == null || !m.idEqual(i) + || m.getEntryRawMode() != i.getEntryRawMode()) { // The working tree file is missing or the merge content differs // from index content update(m); @@ -402,11 +422,11 @@ public class DirCacheCheckout { // The index contains a file (and not a folder) if (f.isModified(i.getDirCacheEntry(), true, this.walk.getObjectReader()) - || i.getDirCacheEntry().getStage() != 0) + || i.getDirCacheEntry().getStage() != 0) { // The working tree file is dirty or the index contains a // conflict update(m); - else { + } else { // update the timestamp of the index with the one from the // file if not set, as we are sure to be in sync here. DirCacheEntry entry = i.getDirCacheEntry(); @@ -416,9 +436,10 @@ public class DirCacheCheckout { } keep(i.getEntryPathString(), entry, f); } - } else + } else { // The index contains a folder keep(i.getEntryPathString(), i.getDirCacheEntry(), f); + } } else { // There is no entry in the merge commit. Means: we want to delete // what's currently in the index and working tree @@ -465,6 +486,7 @@ public class DirCacheCheckout { * successful and the working tree was updated for all other files. * <code>true</code> is returned when no such problem occurred * @throws java.io.IOException + * if an IO error occurred */ public boolean checkout() throws IOException { try { @@ -495,9 +517,8 @@ public class DirCacheCheckout { MissingObjectException, IncorrectObjectTypeException, CheckoutConflictException, IndexWriteException, CanceledException { toBeDeleted.clear(); - options = repo.getConfig() - .get(WorkingTreeOptions.KEY); try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) { + checkout = new Checkout(repo, null); if (headCommitTree != null) preScanTwoTrees(); else @@ -513,6 +534,13 @@ public class DirCacheCheckout { // update our index builder.finish(); + // On case-insensitive file systems we may have a case variant kept + // and another one removed. In that case, don't remove it. + if (existing != null) { + removed.removeAll(existing); + existing.clear(); + } + // init progress reporting int numTotal = removed.size() + updated.size() + conflicts.size(); monitor.beginTask(JGitText.get().checkingOutFiles, numTotal); @@ -523,9 +551,9 @@ public class DirCacheCheckout { // when deleting files process them in the opposite order as they have // been reported. This ensures the files are deleted before we delete // their parent folders - IntList nonDeleted = new IntList(); - for (int i = removed.size() - 1; i >= 0; i--) { - String r = removed.get(i); + Iterator<String> iter = removed.descendingIterator(); + while (iter.hasNext()) { + String r = iter.next(); file = new File(repo.getWorkTree(), r); if (!file.delete() && repo.getFS().exists(file)) { // The list of stuff to delete comes from the index @@ -534,7 +562,7 @@ public class DirCacheCheckout { // to delete it. A submodule is not empty, so it // is safe to check this after a failed delete. if (!repo.getFS().isDirectory(file)) { - nonDeleted.add(i); + iter.remove(); toBeDeleted.add(r); } } else { @@ -552,8 +580,6 @@ public class DirCacheCheckout { if (file != null) { removeEmptyParents(file); } - removed = filterOut(removed, nonDeleted); - nonDeleted = null; Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated .entrySet().iterator(); Map.Entry<String, CheckoutMetadata> e = null; @@ -564,10 +590,9 @@ public class DirCacheCheckout { CheckoutMetadata meta = e.getValue(); DirCacheEntry entry = dc.getEntry(path); if (FileMode.GITLINK.equals(entry.getRawMode())) { - checkoutGitlink(path, entry); + checkout.checkoutGitlink(entry, path); } else { - checkoutEntry(repo, entry, objectReader, false, meta, - options); + checkout.checkout(entry, meta, objectReader, path); } e = null; @@ -602,8 +627,8 @@ public class DirCacheCheckout { break; } if (entry.getStage() == DirCacheEntry.STAGE_3) { - checkoutEntry(repo, entry, objectReader, false, - null, options); + checkout.checkout(entry, null, objectReader, + conflict); break; } ++entryIdx; @@ -626,44 +651,6 @@ public class DirCacheCheckout { return toBeDeleted.isEmpty(); } - private void checkoutGitlink(String path, DirCacheEntry entry) - throws IOException { - File gitlinkDir = new File(repo.getWorkTree(), path); - FileUtils.mkdirs(gitlinkDir, true); - FS fs = repo.getFS(); - entry.setLastModified(fs.lastModifiedInstant(gitlinkDir)); - } - - private static ArrayList<String> filterOut(ArrayList<String> strings, - IntList indicesToRemove) { - int n = indicesToRemove.size(); - if (n == strings.size()) { - return new ArrayList<>(0); - } - switch (n) { - case 0: - return strings; - case 1: - strings.remove(indicesToRemove.get(0)); - return strings; - default: - int length = strings.size(); - ArrayList<String> result = new ArrayList<>(length - n); - // Process indicesToRemove from the back; we know that it - // contains indices in descending order. - int j = n - 1; - int idx = indicesToRemove.get(j); - for (int i = 0; i < length; i++) { - if (i == idx) { - idx = (--j >= 0) ? indicesToRemove.get(j) : -1; - } else { - result.add(strings.get(i)); - } - } - return result; - } - } - private static boolean isSamePrefix(String a, String b) { int as = a.lastIndexOf('/'); int bs = b.lastIndexOf('/'); @@ -684,9 +671,13 @@ public class DirCacheCheckout { * Compares whether two pairs of ObjectId and FileMode are equal. * * @param id1 + * id of first object * @param mode1 + * mode of first object * @param id2 + * id of second object * @param mode2 + * mode of second object * @return <code>true</code> if FileModes and ObjectIds are equal. * <code>false</code> otherwise */ @@ -712,6 +703,7 @@ public class DirCacheCheckout { * @param f * the file in the working tree * @throws IOException + * if an IO error occurred */ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m, @@ -1229,13 +1221,17 @@ public class DirCacheCheckout { if (!FileMode.TREE.equals(e.getFileMode())) { builder.add(e); } + if (existing != null) { + existing.add(path); + } if (force) { if (f == null || f.isModified(e, true, walk.getObjectReader())) { kept.add(path); - checkoutEntry(repo, e, walk.getObjectReader(), false, + checkout.checkout(e, new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP), walk.getFilterCommand( - Constants.ATTR_FILTER_TYPE_SMUDGE)), options); + Constants.ATTR_FILTER_TYPE_SMUDGE)), + walk.getObjectReader(), path); } } } @@ -1295,6 +1291,7 @@ public class DirCacheCheckout { * {@link #failOnConflict} is false * * @throws CheckoutConflictException + * if a conflict occurred during merge checkout */ private void cleanUpConflicts() throws CheckoutConflictException { // TODO: couldn't we delete unsaved worktree content here? @@ -1308,13 +1305,16 @@ public class DirCacheCheckout { } /** - * Checks whether the subtree starting at a given path differs between Index and - * workingtree. + * Checks whether the subtree starting at a given path differs between Index + * and workingtree. * * @param path + * given subtree path * @return true if the subtrees differ * @throws CorruptObjectException + * if a corrupt object was found * @throws IOException + * if an IO error occurred */ private boolean isModifiedSubtree_IndexWorkingtree(String path) throws CorruptObjectException, IOException { @@ -1355,15 +1355,18 @@ public class DirCacheCheckout { } /** - * Checks whether the subtree starting at a given path differs between Index and - * some tree. + * Checks whether the subtree starting at a given path differs between Index + * and some tree. * * @param path + * given path * @param tree * the tree to compare * @return true if the subtrees differ * @throws CorruptObjectException + * if a corrupt object was found * @throws IOException + * if an IO error occurred */ private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree) throws CorruptObjectException, IOException { @@ -1389,191 +1392,6 @@ public class DirCacheCheckout { } /** - * Updates the file in the working tree with content and mode from an entry - * in the index. The new content is first written to a new temporary file in - * the same directory as the real file. Then that new file is renamed to the - * final filename. - * - * <p> - * <b>Note:</b> if the entry path on local file system exists as a non-empty - * directory, and the target entry type is a link or file, the checkout will - * fail with {@link java.io.IOException} since existing non-empty directory - * cannot be renamed to file or link without deleting it recursively. - * </p> - * - * @param repo - * repository managing the destination work tree. - * @param entry - * the entry containing new mode and content - * @param or - * object reader to use for checkout - * @throws java.io.IOException - * @since 3.6 - * @deprecated since 5.1, use - * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)} - * instead - */ - @Deprecated - public static void checkoutEntry(Repository repo, DirCacheEntry entry, - ObjectReader or) throws IOException { - checkoutEntry(repo, entry, or, false, null, null); - } - - - /** - * Updates the file in the working tree with content and mode from an entry - * in the index. The new content is first written to a new temporary file in - * the same directory as the real file. Then that new file is renamed to the - * final filename. - * - * <p> - * <b>Note:</b> if the entry path on local file system exists as a file, it - * will be deleted and if it exists as a directory, it will be deleted - * recursively, independently if has any content. - * </p> - * - * @param repo - * repository managing the destination work tree. - * @param entry - * the entry containing new mode and content - * @param or - * object reader to use for checkout - * @param deleteRecursive - * true to recursively delete final path if it exists on the file - * system - * @param checkoutMetadata - * containing - * <ul> - * <li>smudgeFilterCommand to be run for smudging the entry to be - * checked out</li> - * <li>eolStreamType used for stream conversion</li> - * </ul> - * @throws java.io.IOException - * @since 4.2 - * @deprecated since 6.3, use - * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)} - * instead - */ - @Deprecated - public static void checkoutEntry(Repository repo, DirCacheEntry entry, - ObjectReader or, boolean deleteRecursive, - CheckoutMetadata checkoutMetadata) throws IOException { - checkoutEntry(repo, entry, or, deleteRecursive, checkoutMetadata, null); - } - - /** - * Updates the file in the working tree with content and mode from an entry - * in the index. The new content is first written to a new temporary file in - * the same directory as the real file. Then that new file is renamed to the - * final filename. - * - * <p> - * <b>Note:</b> if the entry path on local file system exists as a file, it - * will be deleted and if it exists as a directory, it will be deleted - * recursively, independently if has any content. - * </p> - * - * @param repo - * repository managing the destination work tree. - * @param entry - * the entry containing new mode and content - * @param or - * object reader to use for checkout - * @param deleteRecursive - * true to recursively delete final path if it exists on the file - * system - * @param checkoutMetadata - * containing - * <ul> - * <li>smudgeFilterCommand to be run for smudging the entry to be - * checked out</li> - * <li>eolStreamType used for stream conversion</li> - * </ul> - * @param options - * {@link WorkingTreeOptions} that are effective; if {@code null} - * they are loaded from the repository config - * @throws java.io.IOException - * @since 6.3 - */ - public static void checkoutEntry(Repository repo, DirCacheEntry entry, - ObjectReader or, boolean deleteRecursive, - CheckoutMetadata checkoutMetadata, WorkingTreeOptions options) - throws IOException { - if (checkoutMetadata == null) { - checkoutMetadata = CheckoutMetadata.EMPTY; - } - ObjectLoader ol = or.open(entry.getObjectId()); - File f = new File(repo.getWorkTree(), entry.getPathString()); - File parentDir = f.getParentFile(); - if (parentDir.isFile()) { - FileUtils.delete(parentDir); - } - FileUtils.mkdirs(parentDir, true); - FS fs = repo.getFS(); - WorkingTreeOptions opt = options != null ? options - : repo.getConfig().get(WorkingTreeOptions.KEY); - if (entry.getFileMode() == FileMode.SYMLINK - && opt.getSymLinks() == SymLinks.TRUE) { - byte[] bytes = ol.getBytes(); - String target = RawParseUtils.decode(bytes); - if (deleteRecursive && f.isDirectory()) { - FileUtils.delete(f, FileUtils.RECURSIVE); - } - fs.createSymLink(f, target); - entry.setLength(bytes.length); - entry.setLastModified(fs.lastModifiedInstant(f)); - return; - } - - String name = f.getName(); - if (name.length() > 200) { - name = name.substring(0, 200); - } - File tmpFile = File.createTempFile( - "._" + name, null, parentDir); //$NON-NLS-1$ - - getContent(repo, entry.getPathString(), checkoutMetadata, ol, opt, - new FileOutputStream(tmpFile)); - - // The entry needs to correspond to the on-disk filesize. If the content - // was filtered (either by autocrlf handling or smudge filters) ask the - // filesystem again for the length. Otherwise the objectloader knows the - // size - if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT - && checkoutMetadata.smudgeFilterCommand == null) { - entry.setLength(ol.getSize()); - } else { - entry.setLength(tmpFile.length()); - } - - if (opt.isFileMode() && fs.supportsExecute()) { - if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) { - if (!fs.canExecute(tmpFile)) - fs.setExecute(tmpFile, true); - } else { - if (fs.canExecute(tmpFile)) - fs.setExecute(tmpFile, false); - } - } - try { - if (deleteRecursive && f.isDirectory()) { - FileUtils.delete(f, FileUtils.RECURSIVE); - } - FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE); - } catch (IOException e) { - throw new IOException( - MessageFormat.format(JGitText.get().renameFileFailed, - tmpFile.getPath(), f.getPath()), - e); - } finally { - if (tmpFile.exists()) { - FileUtils.delete(tmpFile); - } - } - entry.setLastModified(fs.lastModifiedInstant(f)); - } - - /** * Return filtered content for a specific object (blob). EOL handling and * smudge-filter handling are applied in the same way as it would be done * during a checkout. @@ -1599,6 +1417,7 @@ public class DirCacheCheckout { * the output stream the filtered content is written to. The * caller is responsible to close the stream. * @throws IOException + * if an IO error occurred * * @since 5.7 */ @@ -1654,6 +1473,7 @@ public class DirCacheCheckout { * the output stream the filtered content is written to. The * caller is responsible to close the stream. * @throws IOException + * if an IO error occurred * @since 6.3 */ public static void getContent(Repository repo, String path, @@ -1697,6 +1517,8 @@ public class DirCacheCheckout { filterProcessBuilder.directory(repo.getWorkTree()); filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath()); + filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY, + repo.getCommonDirectory().getAbsolutePath()); ExecutionResult result; int rc; try { |