aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
diff options
context:
space:
mode:
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.java336
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 {