1695 lines
58 KiB
Diff
1695 lines
58 KiB
Diff
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties 2023-10-10 15:45:07.523229821 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties 2023-10-10 16:05:28.178175915 +0200
|
||
@@ -13,6 +13,8 @@
|
||
aNewObjectIdIsRequired=A NewObjectId is required.
|
||
anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while trying to add the Id of HEAD
|
||
anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created
|
||
+applyPatchDestInvalid=Destination path in patch is invalid
|
||
+applyPatchSourceInvalid==Source path in patch is invalid
|
||
applyingCommit=Applying {0}
|
||
archiveFormatAlreadyAbsent=Archive format already absent: {0}
|
||
archiveFormatAlreadyRegistered=Archive format already registered with different implementation: {0}
|
||
@@ -522,6 +524,8 @@
|
||
packWriterStatistics=Total {0,number,#0} (delta {1,number,#0}), reused {2,number,#0} (delta {3,number,#0})
|
||
panicCantRenameIndexFile=Panic: index file {0} must be renamed to replace {1}; until then repository is corrupt
|
||
patchApplyException=Cannot apply: {0}
|
||
+patchApplyErrorWithHunk=Error applying patch in {0}, hunk {1}: {2}
|
||
+patchApplyErrorWithoutHunk=Error applying patch in {0}: {1}
|
||
patchFormatException=Format error: {0}
|
||
pathNotConfigured=Submodule path is not configured
|
||
peeledLineBeforeRef=Peeled line before ref.
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/.settings/.api_filters 2023-10-10 15:45:07.523229821 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/.settings/.api_filters 2023-10-10 16:24:34.812579919 +0200
|
||
@@ -1,5 +1,13 @@
|
||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||
<component id="org.eclipse.jgit" version="2">
|
||
+ <resource path="src/org/eclipse/jgit/dircache/Checkout.java" type="org.eclipse.jgit.dircache.Checkout">
|
||
+ <filter id="1109393411">
|
||
+ <message_arguments>
|
||
+ <message_argument value="5.11.0"/>
|
||
+ <message_argument value="org.eclipse.jgit.dircache.Checkout"/>
|
||
+ </message_arguments>
|
||
+ </filter>
|
||
+ </resource>
|
||
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
|
||
<filter id="338755678">
|
||
<message_arguments>
|
||
@@ -8,6 +16,14 @@
|
||
</message_arguments>
|
||
</filter>
|
||
</resource>
|
||
+ <resource path="src/org/eclipse/jgit/lib/FileModeCache.java" type="org.eclipse.jgit.lib.FileModeCache">
|
||
+ <filter id="1109393411">
|
||
+ <message_arguments>
|
||
+ <message_argument value="5.11.0"/>
|
||
+ <message_argument value="org.eclipse.jgit.lib.FileModeCache"/>
|
||
+ </message_arguments>
|
||
+ </filter>
|
||
+ </resource>
|
||
<resource path="src/org/eclipse/jgit/revwalk/ObjectWalk.java" type="org.eclipse.jgit.revwalk.ObjectWalk">
|
||
<filter id="421654647">
|
||
<message_arguments>
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java 2023-10-10 15:45:07.523229821 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java 2023-10-10 16:37:00.354302669 +0200
|
||
@@ -17,21 +17,34 @@
|
||
import java.nio.file.StandardCopyOption;
|
||
import java.text.MessageFormat;
|
||
import java.util.ArrayList;
|
||
+import java.util.Arrays;
|
||
import java.util.Iterator;
|
||
import java.util.List;
|
||
|
||
+import org.eclipse.jgit.annotations.Nullable;
|
||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||
import org.eclipse.jgit.api.errors.PatchApplyException;
|
||
import org.eclipse.jgit.api.errors.PatchFormatException;
|
||
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
|
||
import org.eclipse.jgit.diff.RawText;
|
||
+import org.eclipse.jgit.errors.CorruptObjectException;
|
||
import org.eclipse.jgit.internal.JGitText;
|
||
import org.eclipse.jgit.lib.FileMode;
|
||
+import org.eclipse.jgit.lib.FileModeCache;
|
||
+import org.eclipse.jgit.lib.ObjectId;
|
||
import org.eclipse.jgit.lib.Repository;
|
||
import org.eclipse.jgit.patch.FileHeader;
|
||
import org.eclipse.jgit.patch.HunkHeader;
|
||
import org.eclipse.jgit.patch.Patch;
|
||
import org.eclipse.jgit.util.FileUtils;
|
||
+import org.eclipse.jgit.util.RawParseUtils;
|
||
+import org.eclipse.jgit.util.SystemReader;
|
||
+
|
||
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.ADD;
|
||
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.COPY;
|
||
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE;
|
||
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
|
||
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
|
||
|
||
/**
|
||
* Apply a patch to files and/or to the index.
|
||
@@ -78,6 +91,7 @@
|
||
@Override
|
||
public ApplyResult call() throws GitAPIException, PatchFormatException,
|
||
PatchApplyException {
|
||
+ Result result = new Result();
|
||
checkCallable();
|
||
ApplyResult r = new ApplyResult();
|
||
try {
|
||
@@ -89,29 +103,33 @@
|
||
}
|
||
if (!p.getErrors().isEmpty())
|
||
throw new PatchFormatException(p.getErrors());
|
||
+ FileModeCache directoryCache = new FileModeCache(repo);
|
||
for (FileHeader fh : p.getFiles()) {
|
||
ChangeType type = fh.getChangeType();
|
||
File f = null;
|
||
+ if (!verifyExistence(fh, new File(repo.getWorkTree(), fh.getOldPath()), new File(repo.getWorkTree(), fh.getNewPath()), result)) {
|
||
+ continue;
|
||
+ }
|
||
switch (type) {
|
||
case ADD:
|
||
- f = getFile(fh.getNewPath(), true);
|
||
+ f = getFile(fh.getNewPath(), true, directoryCache);
|
||
apply(f, fh);
|
||
break;
|
||
case MODIFY:
|
||
- f = getFile(fh.getOldPath(), false);
|
||
+ f = getFile(fh.getOldPath(), false, directoryCache);
|
||
apply(f, fh);
|
||
break;
|
||
case DELETE:
|
||
- f = getFile(fh.getOldPath(), false);
|
||
+ f = getFile(fh.getOldPath(), false, directoryCache);
|
||
if (!f.delete())
|
||
throw new PatchApplyException(MessageFormat.format(
|
||
JGitText.get().cannotDeleteFile, f));
|
||
break;
|
||
case RENAME:
|
||
- f = getFile(fh.getOldPath(), false);
|
||
- File dest = getFile(fh.getNewPath(), false);
|
||
+ f = getFile(fh.getOldPath(), false, directoryCache);
|
||
+ File dest = getFile(fh.getNewPath(), false, directoryCache);
|
||
try {
|
||
- FileUtils.mkdirs(dest.getParentFile(), true);
|
||
+ directoryCache.safeCreateParentDirectory(fh.getNewPath(), dest.getParentFile(), false);
|
||
FileUtils.rename(f, dest,
|
||
StandardCopyOption.ATOMIC_MOVE);
|
||
} catch (IOException e) {
|
||
@@ -121,9 +139,9 @@
|
||
apply(dest, fh);
|
||
break;
|
||
case COPY:
|
||
- f = getFile(fh.getOldPath(), false);
|
||
- File target = getFile(fh.getNewPath(), false);
|
||
- FileUtils.mkdirs(target.getParentFile(), true);
|
||
+ f = getFile(fh.getOldPath(), false, directoryCache);
|
||
+ File target = getFile(fh.getNewPath(), false, directoryCache);
|
||
+ directoryCache.safeCreateParentDirectory(fh.getNewPath(), target.getParentFile(), false);
|
||
Files.copy(f.toPath(), target.toPath());
|
||
apply(target, fh);
|
||
}
|
||
@@ -137,13 +155,122 @@
|
||
return r;
|
||
}
|
||
|
||
- private File getFile(String path, boolean create)
|
||
+ /**
|
||
+ * A wrapper for returning both the applied tree ID and the applied files
|
||
+ * list, as well as file specific errors.
|
||
+ *
|
||
+ * @since 6.3
|
||
+ */
|
||
+ public static class Result {
|
||
+
|
||
+ /**
|
||
+ * A wrapper for a patch applying error that affects a given file.
|
||
+ *
|
||
+ * @since 6.6
|
||
+ */
|
||
+ // TODO(ms): rename this class in next major release
|
||
+ @SuppressWarnings("JavaLangClash")
|
||
+ public static class Error {
|
||
+
|
||
+ private String msg;
|
||
+ private String oldFileName;
|
||
+ private @Nullable HunkHeader hh;
|
||
+
|
||
+ private Error(String msg, String oldFileName,
|
||
+ @Nullable HunkHeader hh) {
|
||
+ this.msg = msg;
|
||
+ this.oldFileName = oldFileName;
|
||
+ this.hh = hh;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ if (hh != null) {
|
||
+ return MessageFormat.format(JGitText.get().patchApplyErrorWithHunk,
|
||
+ oldFileName, hh, msg);
|
||
+ }
|
||
+ return MessageFormat.format(JGitText.get().patchApplyErrorWithoutHunk,
|
||
+ oldFileName, msg);
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ private ObjectId treeId;
|
||
+
|
||
+ private List<String> paths;
|
||
+
|
||
+ private List<Error> errors = new ArrayList<>();
|
||
+
|
||
+ /**
|
||
+ * Get modified paths
|
||
+ *
|
||
+ * @return List of modified paths.
|
||
+ */
|
||
+ public List<String> getPaths() {
|
||
+ return paths;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Get tree ID
|
||
+ *
|
||
+ * @return The applied tree ID.
|
||
+ */
|
||
+ public ObjectId getTreeId() {
|
||
+ return treeId;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Get errors
|
||
+ *
|
||
+ * @return Errors occurred while applying the patch.
|
||
+ *
|
||
+ * @since 6.6
|
||
+ */
|
||
+ public List<Error> getErrors() {
|
||
+ return errors;
|
||
+ }
|
||
+
|
||
+ private void addError(String msg,String oldFileName, @Nullable HunkHeader hh) {
|
||
+ errors.add(new Error(msg, oldFileName, hh));
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private boolean verifyExistence(FileHeader fh, File src, File dest,
|
||
+ Result result) throws IOException {
|
||
+ boolean isValid = true;
|
||
+ boolean srcShouldExist = Arrays.asList(new ChangeType[]{MODIFY, DELETE, RENAME, COPY})
|
||
+ .contains(fh.getChangeType());
|
||
+ boolean destShouldNotExist = Arrays.asList(new ChangeType[]{ADD, RENAME, COPY})
|
||
+ .contains(fh.getChangeType());
|
||
+ if (srcShouldExist && !validGitPath(fh.getOldPath())) {
|
||
+ result.addError(JGitText.get().applyPatchSourceInvalid,
|
||
+ fh.getOldPath(), null);
|
||
+ isValid = false;
|
||
+ }
|
||
+ if (destShouldNotExist && !validGitPath(fh.getNewPath())) {
|
||
+ result.addError(JGitText.get().applyPatchDestInvalid,
|
||
+ fh.getNewPath(), null);
|
||
+ isValid = false;
|
||
+ }
|
||
+ return isValid;
|
||
+ }
|
||
+
|
||
+ private boolean validGitPath(String path) {
|
||
+ try {
|
||
+ SystemReader.getInstance().checkPath(path);
|
||
+ return true;
|
||
+ } catch (CorruptObjectException e) {
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private File getFile(String path, boolean create, FileModeCache directoryCache)
|
||
throws PatchApplyException {
|
||
File f = new File(getRepository().getWorkTree(), path);
|
||
if (create)
|
||
try {
|
||
File parent = f.getParentFile();
|
||
- FileUtils.mkdirs(parent, true);
|
||
+ directoryCache.safeCreateParentDirectory(path, parent, false);
|
||
FileUtils.createNewFile(f);
|
||
} catch (IOException e) {
|
||
throw new PatchApplyException(MessageFormat.format(
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java 2023-10-10 15:45:07.523229821 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -28,6 +28,7 @@
|
||
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
|
||
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
||
+import org.eclipse.jgit.dircache.Checkout;
|
||
import org.eclipse.jgit.dircache.DirCache;
|
||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||
@@ -411,6 +412,7 @@
|
||
protected CheckoutCommand checkoutPaths() throws IOException,
|
||
RefNotFoundException {
|
||
actuallyModifiedPaths = new HashSet<>();
|
||
+ Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
|
||
DirCache dc = repo.lockDirCache();
|
||
try (RevWalk revWalk = new RevWalk(repo);
|
||
TreeWalk treeWalk = new TreeWalk(repo,
|
||
@@ -419,10 +421,10 @@
|
||
if (!checkoutAllPaths)
|
||
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
|
||
if (isCheckoutIndex())
|
||
- checkoutPathsFromIndex(treeWalk, dc);
|
||
+ checkoutPathsFromIndex(treeWalk, dc, checkout);
|
||
else {
|
||
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
|
||
- checkoutPathsFromCommit(treeWalk, dc, commit);
|
||
+ checkoutPathsFromCommit(treeWalk, dc, commit, checkout);
|
||
}
|
||
} finally {
|
||
try {
|
||
@@ -439,7 +441,8 @@
|
||
return this;
|
||
}
|
||
|
||
- private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
|
||
+ private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
|
||
+ Checkout checkout)
|
||
throws IOException {
|
||
DirCacheIterator dci = new DirCacheIterator(dc);
|
||
treeWalk.addTree(dci);
|
||
@@ -465,8 +468,9 @@
|
||
if (stage > DirCacheEntry.STAGE_0) {
|
||
if (checkoutStage != null) {
|
||
if (stage == checkoutStage.number) {
|
||
- checkoutPath(ent, r, new CheckoutMetadata(
|
||
- eolStreamType, filterCommand));
|
||
+ checkoutPath(ent, r, checkout, path,
|
||
+ new CheckoutMetadata(eolStreamType,
|
||
+ filterCommand));
|
||
actuallyModifiedPaths.add(path);
|
||
}
|
||
} else {
|
||
@@ -475,7 +479,8 @@
|
||
throw new JGitInternalException(e.getMessage(), e);
|
||
}
|
||
} else {
|
||
- checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
|
||
+ checkoutPath(ent, r, checkout, path,
|
||
+ new CheckoutMetadata(eolStreamType,
|
||
filterCommand));
|
||
actuallyModifiedPaths.add(path);
|
||
}
|
||
@@ -488,7 +493,7 @@
|
||
}
|
||
|
||
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
|
||
- RevCommit commit) throws IOException {
|
||
+ RevCommit commit, Checkout checkout) throws IOException {
|
||
treeWalk.addTree(commit.getTree());
|
||
final ObjectReader r = treeWalk.getObjectReader();
|
||
DirCacheEditor editor = dc.editor();
|
||
@@ -510,7 +515,7 @@
|
||
}
|
||
ent.setObjectId(blobId);
|
||
ent.setFileMode(mode);
|
||
- checkoutPath(ent, r,
|
||
+ checkoutPath(ent, r, checkout, path,
|
||
new CheckoutMetadata(eolStreamType, filterCommand));
|
||
actuallyModifiedPaths.add(path);
|
||
}
|
||
@@ -520,10 +525,9 @@
|
||
}
|
||
|
||
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
|
||
- CheckoutMetadata checkoutMetadata) {
|
||
+ Checkout checkout, String path, CheckoutMetadata checkoutMetadata) {
|
||
try {
|
||
- DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
|
||
- checkoutMetadata);
|
||
+ checkout.checkout(entry, checkoutMetadata, reader, path);
|
||
} catch (IOException e) {
|
||
throw new JGitInternalException(MessageFormat.format(
|
||
JGitText.get().checkoutConflictWithFile,
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java 2023-10-10 15:45:07.526563177 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -23,6 +23,7 @@
|
||
import org.eclipse.jgit.api.errors.NoHeadException;
|
||
import org.eclipse.jgit.api.errors.StashApplyFailureException;
|
||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
||
+import org.eclipse.jgit.dircache.Checkout;
|
||
import org.eclipse.jgit.dircache.DirCache;
|
||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||
@@ -345,6 +346,7 @@
|
||
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
|
||
IOException {
|
||
Set<String> actuallyModifiedPaths = new HashSet<>();
|
||
+ Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
|
||
// TODO maybe NameConflictTreeWalk ?
|
||
try (TreeWalk walk = new TreeWalk(repo)) {
|
||
walk.addTree(tree);
|
||
@@ -368,17 +370,17 @@
|
||
|
||
FileTreeIterator fIter = walk
|
||
.getTree(1, FileTreeIterator.class);
|
||
+ String gitPath = entry.getPathString();
|
||
if (fIter != null) {
|
||
if (fIter.isModified(entry, true, reader)) {
|
||
// file exists and is dirty
|
||
- throw new CheckoutConflictException(
|
||
- entry.getPathString());
|
||
+ throw new CheckoutConflictException(gitPath);
|
||
}
|
||
}
|
||
|
||
- checkoutPath(entry, reader,
|
||
+ checkoutPath(entry, gitPath, reader, checkout,
|
||
new CheckoutMetadata(eolStreamType, null));
|
||
- actuallyModifiedPaths.add(entry.getPathString());
|
||
+ actuallyModifiedPaths.add(gitPath);
|
||
}
|
||
} finally {
|
||
if (!actuallyModifiedPaths.isEmpty()) {
|
||
@@ -388,11 +390,11 @@
|
||
}
|
||
}
|
||
|
||
- private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
|
||
- CheckoutMetadata checkoutMetadata) {
|
||
+ private void checkoutPath(DirCacheEntry entry, String gitPath,
|
||
+ ObjectReader reader,
|
||
+ Checkout checkout, CheckoutMetadata checkoutMetadata) {
|
||
try {
|
||
- DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
|
||
- checkoutMetadata);
|
||
+ checkout.checkout(entry, checkoutMetadata, reader, gitPath);
|
||
} catch (IOException e) {
|
||
throw new JGitInternalException(MessageFormat.format(
|
||
JGitText.get().checkoutConflictWithFile,
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java 2023-10-10 16:04:27.647765655 +0200
|
||
@@ -0,0 +1,238 @@
|
||
+/*
|
||
+ * Copyright (C) 2023, 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
|
||
+ * https://www.eclipse.org/org/documents/edl-v10.php.
|
||
+ *
|
||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||
+ */
|
||
+package org.eclipse.jgit.dircache;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.FileOutputStream;
|
||
+import java.io.IOException;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.LinkOption;
|
||
+import java.nio.file.StandardCopyOption;
|
||
+import java.text.MessageFormat;
|
||
+
|
||
+import org.eclipse.jgit.annotations.NonNull;
|
||
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||
+import org.eclipse.jgit.internal.JGitText;
|
||
+import org.eclipse.jgit.lib.FileMode;
|
||
+import org.eclipse.jgit.lib.FileModeCache;
|
||
+import org.eclipse.jgit.lib.ObjectLoader;
|
||
+import org.eclipse.jgit.lib.ObjectReader;
|
||
+import org.eclipse.jgit.lib.Repository;
|
||
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||
+import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||
+import org.eclipse.jgit.lib.FileModeCache.CacheItem;
|
||
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||
+import org.eclipse.jgit.util.FS;
|
||
+import org.eclipse.jgit.util.FileUtils;
|
||
+import org.eclipse.jgit.util.RawParseUtils;
|
||
+
|
||
+/**
|
||
+ * An object that can be used to check out many files.
|
||
+ *
|
||
+ * @since 6.6.1
|
||
+ */
|
||
+public class Checkout {
|
||
+
|
||
+ private final FileModeCache cache;
|
||
+
|
||
+ private final WorkingTreeOptions options;
|
||
+
|
||
+ private boolean recursiveDelete;
|
||
+
|
||
+ /**
|
||
+ * Creates a new {@link Checkout} for checking out from the given
|
||
+ * repository.
|
||
+ *
|
||
+ * @param repo
|
||
+ * the {@link Repository} to check out from
|
||
+ */
|
||
+ public Checkout(@NonNull Repository repo) {
|
||
+ this(repo, null);
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Creates a new {@link Checkout} for checking out from the given
|
||
+ * repository.
|
||
+ *
|
||
+ * @param repo
|
||
+ * the {@link Repository} to check out from
|
||
+ * @param options
|
||
+ * the {@link WorkingTreeOptions} to use; if {@code null},
|
||
+ * read from the {@code repo} config when this object is
|
||
+ * created
|
||
+ */
|
||
+ public Checkout(@NonNull Repository repo, WorkingTreeOptions options) {
|
||
+ this.cache = new FileModeCache(repo);
|
||
+ this.options = options != null ? options
|
||
+ : repo.getConfig().get(WorkingTreeOptions.KEY);
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Retrieves the {@link WorkingTreeOptions} of the repository that are
|
||
+ * used.
|
||
+ *
|
||
+ * @return the {@link WorkingTreeOptions}
|
||
+ */
|
||
+ public WorkingTreeOptions getWorkingTreeOptions() {
|
||
+ return options;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Defines whether directories that are in the way of the file to be checked
|
||
+ * out shall be deleted recursively.
|
||
+ *
|
||
+ * @param recursive
|
||
+ * whether to delete such directories recursively
|
||
+ * @return {@code this}
|
||
+ */
|
||
+ public Checkout setRecursiveDeletion(boolean recursive) {
|
||
+ this.recursiveDelete = recursive;
|
||
+ return this;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Ensure that the given parent directory exists, and cache the information
|
||
+ * that gitPath refers to a file.
|
||
+ *
|
||
+ * @param gitPath
|
||
+ * of the file to be written
|
||
+ * @param parentDir
|
||
+ * directory in which the file shall be placed, assumed to be the
|
||
+ * parent of the {@code gitPath}
|
||
+ * @param makeSpace
|
||
+ * whether to delete a possibly existing file at
|
||
+ * {@code parentDir}
|
||
+ * @throws IOException
|
||
+ * if the directory cannot be created, if necessary
|
||
+ */
|
||
+ public void safeCreateParentDirectory(String gitPath, File parentDir,
|
||
+ boolean makeSpace) throws IOException {
|
||
+ cache.safeCreateParentDirectory(gitPath, parentDir, makeSpace);
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Checks out the gitlink given by the {@link DirCacheEntry}.
|
||
+ *
|
||
+ * @param entry
|
||
+ * {@link DirCacheEntry} to check out
|
||
+ * @param gitPath
|
||
+ * the git path of the entry, if known already; otherwise
|
||
+ * {@code null} and it's read from the entry itself
|
||
+ * @throws IOException
|
||
+ * if the gitlink cannot be checked out
|
||
+ */
|
||
+ public void checkoutGitlink(DirCacheEntry entry, String gitPath)
|
||
+ throws IOException {
|
||
+ FS fs = cache.getRepository().getFS();
|
||
+ File workingTree = cache.getRepository().getWorkTree();
|
||
+ String path = gitPath != null ? gitPath : entry.getPathString();
|
||
+ File gitlinkDir = new File(workingTree, path);
|
||
+ File parentDir = gitlinkDir.getParentFile();
|
||
+ CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
|
||
+ false);
|
||
+ FileUtils.mkdirs(gitlinkDir, true);
|
||
+ cachedParent.insert(path.substring(path.lastIndexOf('/') + 1),
|
||
+ FileMode.GITLINK);
|
||
+ entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Checks out the file given by the {@link DirCacheEntry}.
|
||
+ *
|
||
+ * @param entry
|
||
+ * {@link DirCacheEntry} to check out
|
||
+ * @param metadata
|
||
+ * {@link CheckoutMetadata} to use for CR/LF handling and
|
||
+ * smudge filtering
|
||
+ * @param reader
|
||
+ * {@link ObjectReader} to use
|
||
+ * @param gitPath
|
||
+ * the git path of the entry, if known already; otherwise
|
||
+ * {@code null} and it's read from the entry itself
|
||
+ * @throws IOException
|
||
+ * if the file cannot be checked out
|
||
+ */
|
||
+ public void checkout(DirCacheEntry entry, CheckoutMetadata metadata,
|
||
+ ObjectReader reader, String gitPath) throws IOException {
|
||
+ if (metadata == null) {
|
||
+ metadata = CheckoutMetadata.EMPTY;
|
||
+ }
|
||
+ FS fs = cache.getRepository().getFS();
|
||
+ ObjectLoader ol = reader.open(entry.getObjectId());
|
||
+ String path = gitPath != null ? gitPath : entry.getPathString();
|
||
+ File f = new File(cache.getRepository().getWorkTree(), path);
|
||
+ File parentDir = f.getParentFile();
|
||
+ CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
|
||
+ true);
|
||
+ if (entry.getFileMode() == FileMode.SYMLINK
|
||
+ && options.getSymLinks() == SymLinks.TRUE) {
|
||
+ byte[] bytes = ol.getBytes();
|
||
+ String target = RawParseUtils.decode(bytes);
|
||
+ if (recursiveDelete && Files.isDirectory(f.toPath(),
|
||
+ LinkOption.NOFOLLOW_LINKS)) {
|
||
+ FileUtils.delete(f, FileUtils.RECURSIVE);
|
||
+ }
|
||
+ fs.createSymLink(f, target);
|
||
+ cachedParent.insert(f.getName(), FileMode.SYMLINK);
|
||
+ 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$
|
||
+
|
||
+ DirCacheCheckout.getContent(cache.getRepository(), path, metadata, ol,
|
||
+ options,
|
||
+ new FileOutputStream(tmpFile));
|
||
+
|
||
+ // The entry needs to correspond to the on-disk file size. If the
|
||
+ // content was filtered (either by autocrlf handling or smudge
|
||
+ // filters) ask the file system again for the length. Otherwise the
|
||
+ // object loader knows the size
|
||
+ if (metadata.eolStreamType == EolStreamType.DIRECT
|
||
+ && metadata.smudgeFilterCommand == null) {
|
||
+ entry.setLength(ol.getSize());
|
||
+ } else {
|
||
+ entry.setLength(tmpFile.length());
|
||
+ }
|
||
+
|
||
+ if (options.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 (recursiveDelete && Files.isDirectory(f.toPath(),
|
||
+ LinkOption.NOFOLLOW_LINKS)) {
|
||
+ FileUtils.delete(f, FileUtils.RECURSIVE);
|
||
+ }
|
||
+ FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
|
||
+ cachedParent.remove(f.getName());
|
||
+ } 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));
|
||
+ }
|
||
+}
|
||
\ No newline at end of file
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java 2023-10-10 15:45:07.529896533 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java 2023-10-10 16:39:28.708642256 +0200
|
||
@@ -18,10 +18,8 @@
|
||
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.OutputStream;
|
||
-import java.nio.file.StandardCopyOption;
|
||
import java.text.MessageFormat;
|
||
import java.time.Instant;
|
||
import java.util.ArrayList;
|
||
@@ -47,7 +45,6 @@
|
||
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;
|
||
@@ -67,7 +64,6 @@
|
||
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;
|
||
@@ -142,6 +138,8 @@
|
||
|
||
private boolean performingCheckout;
|
||
|
||
+ private Checkout checkout;
|
||
+
|
||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
|
||
|
||
/**
|
||
@@ -492,6 +490,7 @@
|
||
CheckoutConflictException, IndexWriteException, CanceledException {
|
||
toBeDeleted.clear();
|
||
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
|
||
+ checkout = new Checkout(repo, null);
|
||
if (headCommitTree != null)
|
||
preScanTwoTrees();
|
||
else
|
||
@@ -558,9 +557,9 @@
|
||
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);
|
||
+ checkout.checkout(entry, meta, objectReader, path);
|
||
}
|
||
e = null;
|
||
|
||
@@ -595,8 +594,8 @@
|
||
break;
|
||
}
|
||
if (entry.getStage() == DirCacheEntry.STAGE_3) {
|
||
- checkoutEntry(repo, entry, objectReader, false,
|
||
- null);
|
||
+ checkout.checkout(entry, null, objectReader,
|
||
+ conflict);
|
||
break;
|
||
}
|
||
++entryIdx;
|
||
@@ -619,14 +618,6 @@
|
||
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();
|
||
@@ -1225,10 +1216,11 @@
|
||
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)));
|
||
+ Constants.ATTR_FILTER_TYPE_SMUDGE)),
|
||
+ walk.getObjectReader(), path);
|
||
}
|
||
}
|
||
}
|
||
@@ -1453,76 +1445,9 @@
|
||
public static void checkoutEntry(Repository repo, DirCacheEntry entry,
|
||
ObjectReader or, boolean deleteRecursive,
|
||
CheckoutMetadata checkoutMetadata) 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 = 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));
|
||
+ Checkout checkout = new Checkout(repo, null)
|
||
+ .setRecursiveDeletion(deleteRecursive);
|
||
+ checkout.checkout(entry, checkoutMetadata, or, null);
|
||
}
|
||
|
||
/**
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java 2023-10-10 15:45:07.533229888 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java 2023-10-10 16:18:44.090214524 +0200
|
||
@@ -41,6 +41,8 @@
|
||
/***/ public String aNewObjectIdIsRequired;
|
||
/***/ public String anExceptionOccurredWhileTryingToAddTheIdOfHEAD;
|
||
/***/ public String anSSHSessionHasBeenAlreadyCreated;
|
||
+ /***/ public String applyPatchDestInvalid;
|
||
+ /***/ public String applyPatchSourceInvalid;
|
||
/***/ public String applyingCommit;
|
||
/***/ public String archiveFormatAlreadyAbsent;
|
||
/***/ public String archiveFormatAlreadyRegistered;
|
||
@@ -550,6 +552,8 @@
|
||
/***/ public String packWriterStatistics;
|
||
/***/ public String panicCantRenameIndexFile;
|
||
/***/ public String patchApplyException;
|
||
+ /***/ public String patchApplyErrorWithHunk;
|
||
+ /***/ public String patchApplyErrorWithoutHunk;
|
||
/***/ public String patchFormatException;
|
||
/***/ public String pathNotConfigured;
|
||
/***/ public String peeledLineBeforeRef;
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java 2023-10-10 16:04:27.647765655 +0200
|
||
@@ -0,0 +1,309 @@
|
||
+/*
|
||
+ * Copyright (C) 2023, 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
|
||
+ * https://www.eclipse.org/org/documents/edl-v10.php.
|
||
+ *
|
||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||
+ */
|
||
+package org.eclipse.jgit.lib;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.IOException;
|
||
+import java.nio.file.InvalidPathException;
|
||
+import java.nio.file.LinkOption;
|
||
+import java.nio.file.Path;
|
||
+import java.nio.file.attribute.BasicFileAttributeView;
|
||
+import java.nio.file.attribute.BasicFileAttributes;
|
||
+import java.util.HashMap;
|
||
+import java.util.Map;
|
||
+
|
||
+import org.eclipse.jgit.annotations.NonNull;
|
||
+import org.eclipse.jgit.util.FS;
|
||
+import org.eclipse.jgit.util.FileUtils;
|
||
+
|
||
+/**
|
||
+ * A hierarchical cache of {@link FileMode}s per git path.
|
||
+ *
|
||
+ * @since 6.6.1
|
||
+ */
|
||
+public class FileModeCache {
|
||
+
|
||
+ @NonNull
|
||
+ private final CacheItem root = new CacheItem(FileMode.TREE);
|
||
+
|
||
+ @NonNull
|
||
+ private final Repository repo;
|
||
+
|
||
+ /**
|
||
+ * Creates a new {@link FileModeCache} for a {@link Repository}.
|
||
+ *
|
||
+ * @param repo
|
||
+ * {@link Repository} this cache is for
|
||
+ */
|
||
+ public FileModeCache(@NonNull Repository repo) {
|
||
+ this.repo = repo;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Retrieves the {@link Repository}.
|
||
+ *
|
||
+ * @return the {@link Repository} this {@link FileModeCache} was created for
|
||
+ */
|
||
+ @NonNull
|
||
+ public Repository getRepository() {
|
||
+ return repo;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Obtains the {@link CacheItem} for the working tree root.
|
||
+ *
|
||
+ * @return the {@link CacheItem}
|
||
+ */
|
||
+ @NonNull
|
||
+ public CacheItem getRoot() {
|
||
+ return root;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Ensure that the given parent directory exists, and cache the information
|
||
+ * that gitPath refers to a file.
|
||
+ *
|
||
+ * @param gitPath
|
||
+ * of the file to be written
|
||
+ * @param parentDir
|
||
+ * directory in which the file shall be placed, assumed to be the
|
||
+ * parent of the {@code gitPath}
|
||
+ * @param makeSpace
|
||
+ * whether to delete a possibly existing file at
|
||
+ * {@code parentDir}
|
||
+ * @throws IOException
|
||
+ * if the directory cannot be created, if necessary
|
||
+ */
|
||
+ public void safeCreateParentDirectory(String gitPath, File parentDir,
|
||
+ boolean makeSpace) throws IOException {
|
||
+ CacheItem cachedParent = safeCreateDirectory(gitPath, parentDir,
|
||
+ makeSpace);
|
||
+ cachedParent.remove(gitPath.substring(gitPath.lastIndexOf('/') + 1));
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Ensures the given directory {@code dir} with the given git path exists.
|
||
+ *
|
||
+ * @param gitPath
|
||
+ * of a file to be written
|
||
+ * @param dir
|
||
+ * directory in which the file shall be placed, assumed to be the
|
||
+ * parent of the {@code gitPath}
|
||
+ * @param makeSpace
|
||
+ * whether to remove a file that already at that name
|
||
+ * @return A {@link CacheItem} describing the directory, which is guaranteed
|
||
+ * to exist
|
||
+ * @throws IOException
|
||
+ * if the directory cannot be made to exist at the given
|
||
+ * location
|
||
+ */
|
||
+ public CacheItem safeCreateDirectory(String gitPath, File dir,
|
||
+ boolean makeSpace) throws IOException {
|
||
+ FS fs = repo.getFS();
|
||
+ int i = gitPath.lastIndexOf('/');
|
||
+ String parentPath = null;
|
||
+ if (i >= 0) {
|
||
+ if ((makeSpace && dir.isFile()) || fs.isSymLink(dir)) {
|
||
+ FileUtils.delete(dir);
|
||
+ }
|
||
+ parentPath = gitPath.substring(0, i);
|
||
+ deleteSymlinkParent(fs, parentPath, repo.getWorkTree());
|
||
+ }
|
||
+ FileUtils.mkdirs(dir, true);
|
||
+ CacheItem cachedParent = getRoot();
|
||
+ if (parentPath != null) {
|
||
+ cachedParent = add(parentPath, FileMode.TREE);
|
||
+ }
|
||
+ return cachedParent;
|
||
+ }
|
||
+
|
||
+ private void deleteSymlinkParent(FS fs, String gitPath, File workingTree)
|
||
+ throws IOException {
|
||
+ if (!fs.supportsSymlinks()) {
|
||
+ return;
|
||
+ }
|
||
+ String[] parts = gitPath.split("/"); //$NON-NLS-1$
|
||
+ int n = parts.length;
|
||
+ CacheItem cached = getRoot();
|
||
+ File p = workingTree;
|
||
+ for (int i = 0; i < n; i++) {
|
||
+ p = new File(p, parts[i]);
|
||
+ CacheItem cachedChild = cached != null ? cached.child(parts[i])
|
||
+ : null;
|
||
+ boolean delete = false;
|
||
+ if (cachedChild != null) {
|
||
+ if (FileMode.SYMLINK.equals(cachedChild.getMode())) {
|
||
+ delete = true;
|
||
+ }
|
||
+ } else {
|
||
+ try {
|
||
+ Path nioPath = FileUtils.toPath(p);
|
||
+ BasicFileAttributes attributes = nioPath.getFileSystem()
|
||
+ .provider()
|
||
+ .getFileAttributeView(nioPath,
|
||
+ BasicFileAttributeView.class,
|
||
+ LinkOption.NOFOLLOW_LINKS)
|
||
+ .readAttributes();
|
||
+ if (attributes.isSymbolicLink()) {
|
||
+ delete = p.isDirectory();
|
||
+ } else if (attributes.isRegularFile()) {
|
||
+ break;
|
||
+ }
|
||
+ } catch (InvalidPathException | IOException e) {
|
||
+ // If we can't get the attributes the path does not exist,
|
||
+ // or if it does a subsequent mkdirs() will also throw an
|
||
+ // exception.
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+ if (delete) {
|
||
+ // Deletes the symlink
|
||
+ FileUtils.delete(p, FileUtils.SKIP_MISSING);
|
||
+ if (cached != null) {
|
||
+ cached.remove(parts[i]);
|
||
+ }
|
||
+ break;
|
||
+ }
|
||
+ cached = cachedChild;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Records the given {@link FileMode} for the given git path in the cache.
|
||
+ * If an entry already exists for the given path, the previously cached file
|
||
+ * mode is overwritten.
|
||
+ *
|
||
+ * @param gitPath
|
||
+ * to cache the {@link FileMode} for
|
||
+ * @param finalMode
|
||
+ * {@link FileMode} to cache
|
||
+ * @return the {@link CacheItem} for the path
|
||
+ */
|
||
+ @NonNull
|
||
+ private CacheItem add(String gitPath, FileMode finalMode) {
|
||
+ if (gitPath.isEmpty()) {
|
||
+ throw new IllegalArgumentException();
|
||
+ }
|
||
+ String[] parts = gitPath.split("/"); //$NON-NLS-1$
|
||
+ int n = parts.length;
|
||
+ int i = 0;
|
||
+ CacheItem curr = getRoot();
|
||
+ while (i < n) {
|
||
+ CacheItem next = curr.child(parts[i]);
|
||
+ if (next == null) {
|
||
+ break;
|
||
+ }
|
||
+ curr = next;
|
||
+ i++;
|
||
+ }
|
||
+ if (i == n) {
|
||
+ curr.setMode(finalMode);
|
||
+ } else {
|
||
+ while (i < n) {
|
||
+ curr = curr.insert(parts[i],
|
||
+ i + 1 == n ? finalMode : FileMode.TREE);
|
||
+ i++;
|
||
+ }
|
||
+ }
|
||
+ return curr;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * An item from a {@link FileModeCache}, recording information about a git
|
||
+ * path (known from context).
|
||
+ */
|
||
+ public static class CacheItem {
|
||
+
|
||
+ @NonNull
|
||
+ private FileMode mode;
|
||
+
|
||
+ private Map<String, CacheItem> children;
|
||
+
|
||
+ /**
|
||
+ * Creates a new {@link CacheItem}.
|
||
+ *
|
||
+ * @param mode
|
||
+ * {@link FileMode} to cache
|
||
+ */
|
||
+ public CacheItem(@NonNull FileMode mode) {
|
||
+ this.mode = mode;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Retrieves the cached {@link FileMode}.
|
||
+ *
|
||
+ * @return the {@link FileMode}
|
||
+ */
|
||
+ @NonNull
|
||
+ public FileMode getMode() {
|
||
+ return mode;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Retrieves an immediate child of this {@link CacheItem} by name.
|
||
+ *
|
||
+ * @param childName
|
||
+ * name of the child to get
|
||
+ * @return the {@link CacheItem}, or {@code null} if no such child is
|
||
+ * known
|
||
+ */
|
||
+ public CacheItem child(String childName) {
|
||
+ if (children == null) {
|
||
+ return null;
|
||
+ }
|
||
+ return children.get(childName);
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Inserts a new cached {@link FileMode} as an immediate child of this
|
||
+ * {@link CacheItem}. If there is already a child with the same name, it
|
||
+ * is overwritten.
|
||
+ *
|
||
+ * @param childName
|
||
+ * name of the child to create
|
||
+ * @param childMode
|
||
+ * {@link FileMode} to cache
|
||
+ * @return the new {@link CacheItem} created for the child
|
||
+ */
|
||
+ public CacheItem insert(String childName, @NonNull FileMode childMode) {
|
||
+ if (!FileMode.TREE.equals(mode)) {
|
||
+ throw new IllegalArgumentException();
|
||
+ }
|
||
+ if (children == null) {
|
||
+ children = new HashMap<>();
|
||
+ }
|
||
+ CacheItem newItem = new CacheItem(childMode);
|
||
+ children.put(childName, newItem);
|
||
+ return newItem;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Removes the immediate child with the given name.
|
||
+ *
|
||
+ * @param childName
|
||
+ * name of the child to remove
|
||
+ * @return the previously cached {@link CacheItem}, if any
|
||
+ */
|
||
+ public CacheItem remove(String childName) {
|
||
+ if (children == null) {
|
||
+ return null;
|
||
+ }
|
||
+ return children.remove(childName);
|
||
+ }
|
||
+
|
||
+ void setMode(@NonNull FileMode mode) {
|
||
+ this.mode = mode;
|
||
+ if (!FileMode.TREE.equals(mode)) {
|
||
+ children = null;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+}
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java 2023-10-10 15:45:07.539896600 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java 2023-10-10 16:04:27.647765655 +0200
|
||
@@ -43,10 +43,10 @@
|
||
import org.eclipse.jgit.diff.RawText;
|
||
import org.eclipse.jgit.diff.RawTextComparator;
|
||
import org.eclipse.jgit.diff.Sequence;
|
||
+import org.eclipse.jgit.dircache.Checkout;
|
||
import org.eclipse.jgit.dircache.DirCache;
|
||
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
|
||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||
-import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||
import org.eclipse.jgit.errors.BinaryBlobException;
|
||
@@ -75,7 +75,6 @@
|
||
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||
-import org.eclipse.jgit.util.FS;
|
||
import org.eclipse.jgit.util.LfsFactory;
|
||
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
||
import org.eclipse.jgit.util.TemporaryBuffer;
|
||
@@ -85,6 +84,13 @@
|
||
* A three-way merger performing a content-merge if necessary
|
||
*/
|
||
public class ResolveMerger extends ThreeWayMerger {
|
||
+
|
||
+ /**
|
||
+ * {@link Checkout} to use for actually checking out files if
|
||
+ * {@link #inCore} is {@code false}.
|
||
+ */
|
||
+ private Checkout checkout;
|
||
+
|
||
/**
|
||
* If the merge fails (means: not stopped because of unresolved conflicts)
|
||
* this enum is used to explain why it failed
|
||
@@ -314,6 +320,7 @@
|
||
implicitDirCache = true;
|
||
workingTreeOptions = local.getConfig().get(WorkingTreeOptions.KEY);
|
||
}
|
||
+ checkout = new Checkout(nonNullRepo(), workingTreeOptions);
|
||
}
|
||
|
||
/**
|
||
@@ -380,12 +387,15 @@
|
||
for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
|
||
.entrySet()) {
|
||
DirCacheEntry cacheEntry = entry.getValue();
|
||
+ String gitPath = entry.getKey();
|
||
if (cacheEntry.getFileMode() == FileMode.GITLINK) {
|
||
new File(nonNullRepo().getWorkTree(), entry.getKey()).mkdirs();
|
||
+ checkout.checkoutGitlink(cacheEntry, gitPath);
|
||
} else {
|
||
- DirCacheCheckout.checkoutEntry(db, cacheEntry, reader, false,
|
||
- checkoutMetadata.get(entry.getKey()));
|
||
- modifiedFiles.add(entry.getKey());
|
||
+ checkout.checkout(cacheEntry,
|
||
+ checkoutMetadata.get(entry.getKey()), reader,
|
||
+ gitPath);
|
||
+ modifiedFiles.add(gitPath);
|
||
}
|
||
}
|
||
}
|
||
@@ -415,8 +425,8 @@
|
||
String mpath = mpathsIt.next();
|
||
DirCacheEntry entry = dc.getEntry(mpath);
|
||
if (entry != null) {
|
||
- DirCacheCheckout.checkoutEntry(db, entry, reader, false,
|
||
- checkoutMetadata.get(mpath));
|
||
+ checkout.checkout(entry, checkoutMetadata.get(mpath),
|
||
+ reader, mpath);
|
||
}
|
||
mpathsIt.remove();
|
||
}
|
||
@@ -1009,15 +1019,12 @@
|
||
Attributes attributes)
|
||
throws FileNotFoundException, IOException {
|
||
File workTree = nonNullRepo().getWorkTree();
|
||
- FS fs = nonNullRepo().getFS();
|
||
- File of = new File(workTree, tw.getPathString());
|
||
- File parentFolder = of.getParentFile();
|
||
- if (!fs.exists(parentFolder)) {
|
||
- parentFolder.mkdirs();
|
||
- }
|
||
+ String gitPath = tw.getPathString();
|
||
+ File of = new File(workTree, gitPath);
|
||
EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
|
||
OperationType.CHECKOUT_OP, workingTreeOptions,
|
||
attributes);
|
||
+ checkout.safeCreateParentDirectory(tw.getPathString(), of.getParentFile(), false);
|
||
try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
|
||
new BufferedOutputStream(new FileOutputStream(of)),
|
||
streamType)) {
|
||
@@ -1295,9 +1302,9 @@
|
||
// go into the new index.
|
||
checkout();
|
||
|
||
- // All content-merges are successfully done. If we can now write the
|
||
- // new index we are on quite safe ground. Even if the checkout of
|
||
- // files coming from "theirs" fails the user can work around such
|
||
+ // All content-merges are successfully done. If we can now write
|
||
+ // the new index we are on quite safe ground. Even if the checkout
|
||
+ // of files coming from "theirs" fails the user can work around such
|
||
// failures by checking out the index again.
|
||
if (!builder.commit()) {
|
||
cleanUp();
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java 2023-10-10 15:45:07.469896126 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java 2023-10-10 16:04:27.641098943 +0200
|
||
@@ -276,6 +276,25 @@
|
||
}
|
||
|
||
/**
|
||
+ * Construct a symlink mode tree entry.
|
||
+ *
|
||
+ * @param path
|
||
+ * path of the symlink.
|
||
+ * @param blob
|
||
+ * a blob, previously constructed in the repository.
|
||
+ * @return the entry.
|
||
+ * @throws Exception
|
||
+ * if an error occurred
|
||
+ * @since 5.13.3
|
||
+ */
|
||
+ public DirCacheEntry link(String path, RevBlob blob) throws Exception {
|
||
+ DirCacheEntry e = new DirCacheEntry(path);
|
||
+ e.setFileMode(FileMode.SYMLINK);
|
||
+ e.setObjectId(blob);
|
||
+ return e;
|
||
+ }
|
||
+
|
||
+ /**
|
||
* Construct a tree from a specific listing of file entries.
|
||
*
|
||
* @param entries
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java 2023-10-10 15:45:07.509896397 +0200
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -46,6 +46,16 @@
|
||
assertFalse(isValidPath("a/"));
|
||
assertFalse(isValidPath("ab/cd/ef/"));
|
||
assertFalse(isValidPath("a\u0000b"));
|
||
+ assertFalse(isValidPath(".git"));
|
||
+ assertFalse(isValidPath(".GIT"));
|
||
+ assertFalse(isValidPath(".Git"));
|
||
+ assertFalse(isValidPath(".git/b"));
|
||
+ assertFalse(isValidPath(".GIT/b"));
|
||
+ assertFalse(isValidPath(".Git/b"));
|
||
+ assertFalse(isValidPath("x/y/.git/z/b"));
|
||
+ assertFalse(isValidPath("x/y/.GIT/z/b"));
|
||
+ assertFalse(isValidPath("x/y/.Git/z/b"));
|
||
+ assertTrue(isValidPath("git/b"));
|
||
}
|
||
|
||
@SuppressWarnings("unused")
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/InvalidPathCheckoutTest.java 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/InvalidPathCheckoutTest.java 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -0,0 +1,61 @@
|
||
+/*
|
||
+ * Copyright (C) 2023 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
|
||
+ * https://www.eclipse.org/org/documents/edl-v10.php.
|
||
+ *
|
||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||
+ */
|
||
+package org.eclipse.jgit.dircache;
|
||
+
|
||
+import static org.junit.Assert.assertFalse;
|
||
+import static org.junit.Assert.assertThrows;
|
||
+
|
||
+import java.io.File;
|
||
+
|
||
+import org.eclipse.jgit.api.Git;
|
||
+import org.eclipse.jgit.api.ResetCommand.ResetType;
|
||
+import org.eclipse.jgit.junit.RepositoryTestCase;
|
||
+import org.eclipse.jgit.junit.TestRepository;
|
||
+import org.eclipse.jgit.lib.FileMode;
|
||
+import org.eclipse.jgit.lib.Repository;
|
||
+import org.eclipse.jgit.revwalk.RevBlob;
|
||
+import org.eclipse.jgit.revwalk.RevCommit;
|
||
+import org.junit.Test;
|
||
+
|
||
+/**
|
||
+ * Tests for checking out with invalid paths.
|
||
+ */
|
||
+public class InvalidPathCheckoutTest extends RepositoryTestCase {
|
||
+
|
||
+ private DirCacheEntry brokenEntry(String fileName, RevBlob blob) {
|
||
+ DirCacheEntry entry = new DirCacheEntry("XXXX/" + fileName);
|
||
+ entry.path[0] = '.';
|
||
+ entry.path[1] = 'g';
|
||
+ entry.path[2] = 'i';
|
||
+ entry.path[3] = 't';
|
||
+ entry.setFileMode(FileMode.REGULAR_FILE);
|
||
+ entry.setObjectId(blob);
|
||
+ return entry;
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testCheckoutIntoDotGit() throws Exception {
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ // DirCacheEntry does not allow any path component to contain
|
||
+ // ".git". C git also forbids this. But what if somebody creates
|
||
+ // such an entry explicitly?
|
||
+ RevCommit base = repo
|
||
+ .commit(repo.tree(brokenEntry("b", repo.blob("test"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ assertThrows(InvalidPathException.class, () -> git.reset()
|
||
+ .setMode(ResetType.HARD).setRef(base.name()).call());
|
||
+ File b = new File(new File(trash, ".git"), "b");
|
||
+ assertFalse(".git/b should not exist", b.exists());
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+}
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/DirectoryTest.java 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/DirectoryTest.java 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -0,0 +1,254 @@
|
||
+/*
|
||
+ * Copyright (C) 2023 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
|
||
+ * https://www.eclipse.org/org/documents/edl-v10.php.
|
||
+ *
|
||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||
+ */
|
||
+package org.eclipse.jgit.symlinks;
|
||
+
|
||
+import static org.junit.Assert.assertFalse;
|
||
+import static org.junit.Assert.assertNotNull;
|
||
+import static org.junit.Assert.assertTrue;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.InputStream;
|
||
+import java.nio.charset.StandardCharsets;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.LinkOption;
|
||
+import java.nio.file.Path;
|
||
+
|
||
+import org.eclipse.jgit.api.ApplyResult;
|
||
+import org.eclipse.jgit.api.Git;
|
||
+import org.eclipse.jgit.api.ResetCommand.ResetType;
|
||
+import org.eclipse.jgit.api.errors.PatchApplyException;
|
||
+import org.eclipse.jgit.junit.RepositoryTestCase;
|
||
+import org.eclipse.jgit.junit.TestRepository;
|
||
+import org.eclipse.jgit.lib.ConfigConstants;
|
||
+import org.eclipse.jgit.lib.Repository;
|
||
+import org.eclipse.jgit.lib.StoredConfig;
|
||
+import org.eclipse.jgit.revwalk.RevCommit;
|
||
+import org.eclipse.jgit.util.FS;
|
||
+import org.eclipse.jgit.util.FileUtils;
|
||
+import org.junit.Assume;
|
||
+import org.junit.BeforeClass;
|
||
+import org.junit.Test;
|
||
+import org.junit.runner.RunWith;
|
||
+import org.junit.runners.Parameterized;
|
||
+import org.junit.runners.Parameterized.Parameter;
|
||
+import org.junit.runners.Parameterized.Parameters;
|
||
+
|
||
+@RunWith(Parameterized.class)
|
||
+public class DirectoryTest extends RepositoryTestCase {
|
||
+
|
||
+ @BeforeClass
|
||
+ public static void checkPrecondition() throws Exception {
|
||
+ Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
|
||
+ Path tempDir = Files.createTempDirectory("jgit");
|
||
+ try {
|
||
+ Path a = tempDir.resolve("a");
|
||
+ Files.write(a, "test".getBytes(StandardCharsets.UTF_8));
|
||
+ Path b = tempDir.resolve("A");
|
||
+ Assume.assumeTrue(Files.exists(b));
|
||
+ } finally {
|
||
+ FileUtils.delete(tempDir.toFile(),
|
||
+ FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Parameters(name = "core.symlinks={0}")
|
||
+ public static Boolean[] parameters() {
|
||
+ return new Boolean[] { Boolean.TRUE, Boolean.FALSE };
|
||
+ }
|
||
+
|
||
+ @Parameter(0)
|
||
+ public boolean useSymlinks;
|
||
+
|
||
+ private void checkFiles() throws Exception {
|
||
+ File a = new File(trash, "a");
|
||
+ assertTrue("a should be a directory",
|
||
+ Files.isDirectory(a.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ File b = new File(a, "b");
|
||
+ assertTrue("a/b should exist", b.isFile());
|
||
+ File x = new File(trash, "x");
|
||
+ assertTrue("x should be a directory",
|
||
+ Files.isDirectory(x.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ File y = new File(x, "y");
|
||
+ assertTrue("x/y should exist", y.isFile());
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testCheckout() throws Exception {
|
||
+ StoredConfig config = db.getConfig();
|
||
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||
+ ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
|
||
+ config.save();
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ // Create links directly in the git repo, then use a hard reset
|
||
+ // to get them into the workspace.
|
||
+ RevCommit base = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.link("A", repo.blob(".git")),
|
||
+ repo.file("a/b", repo.blob("test")),
|
||
+ repo.file("x/y", repo.blob("test2"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
|
||
+ File b = new File(new File(trash, ".git"), "b");
|
||
+ assertFalse(".git/b should not exist", b.exists());
|
||
+ checkFiles();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testCheckout2() throws Exception {
|
||
+ StoredConfig config = db.getConfig();
|
||
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||
+ ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
|
||
+ config.save();
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ RevCommit base = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.link("A/B", repo.blob("../.git")),
|
||
+ repo.file("a/b/a/b", repo.blob("test")),
|
||
+ repo.file("x/y", repo.blob("test2"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ boolean testFiles = true;
|
||
+ try {
|
||
+ git.reset().setMode(ResetType.HARD).setRef(base.name())
|
||
+ .call();
|
||
+ } catch (Exception e) {
|
||
+ if (!useSymlinks) {
|
||
+ // There is a file in the middle of the path where we'd
|
||
+ // expect a directory. This case is not handled
|
||
+ // anywhere. What would be a better reply than an IOE?
|
||
+ testFiles = false;
|
||
+ } else {
|
||
+ throw e;
|
||
+ }
|
||
+ }
|
||
+ File a = new File(new File(trash, ".git"), "a");
|
||
+ assertFalse(".git/a should not exist", a.exists());
|
||
+ if (testFiles) {
|
||
+ a = new File(trash, "a");
|
||
+ assertTrue("a should be a directory", Files.isDirectory(
|
||
+ a.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ File b = new File(a, "b");
|
||
+ assertTrue("a/b should be a directory", Files.isDirectory(
|
||
+ a.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ a = new File(b, "a");
|
||
+ assertTrue("a/b/a should be a directory", Files.isDirectory(
|
||
+ a.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ b = new File(a, "b");
|
||
+ assertTrue("a/b/a/b should exist", b.isFile());
|
||
+ File x = new File(trash, "x");
|
||
+ assertTrue("x should be a directory", Files.isDirectory(
|
||
+ x.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ File y = new File(x, "y");
|
||
+ assertTrue("x/y should exist", y.isFile());
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testMerge() throws Exception {
|
||
+ StoredConfig config = db.getConfig();
|
||
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||
+ ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
|
||
+ config.save();
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ RevCommit base = repo.commit(
|
||
+ repo.tree(repo.file("q", repo.blob("test"))));
|
||
+ RevCommit side = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.link("A", repo.blob(".git")),
|
||
+ repo.file("a/b", repo.blob("test")),
|
||
+ repo.file("x/y", repo.blob("test2"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
|
||
+ git.merge().include(side)
|
||
+ .setMessage("merged").call();
|
||
+ File b = new File(new File(trash, ".git"), "b");
|
||
+ assertFalse(".git/b should not exist", b.exists());
|
||
+ checkFiles();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testMerge2() throws Exception {
|
||
+ StoredConfig config = db.getConfig();
|
||
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||
+ ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
|
||
+ config.save();
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ RevCommit base = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.file("q", repo.blob("test")),
|
||
+ repo.link("A", repo.blob(".git"))));
|
||
+ RevCommit side = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.file("a/b", repo.blob("test")),
|
||
+ repo.file("x/y", repo.blob("test2"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
|
||
+ git.merge().include(side)
|
||
+ .setMessage("merged").call();
|
||
+ File b = new File(new File(trash, ".git"), "b");
|
||
+ assertFalse(".git/b should not exist", b.exists());
|
||
+ checkFiles();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testApply() throws Exception {
|
||
+ StoredConfig config = db.getConfig();
|
||
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||
+ ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
|
||
+ config.save();
|
||
+ // PatchApplier doesn't do symlinks yet.
|
||
+ try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||
+ db.incrementOpen();
|
||
+ RevCommit base = repo.commit(
|
||
+ repo.tree(
|
||
+ repo.file("x", repo.blob("test")),
|
||
+ repo.link("A", repo.blob(".git"))));
|
||
+ try (Git git = new Git(db)) {
|
||
+ boolean testFiles = true;
|
||
+ git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
|
||
+ try (InputStream patchStream = this.getClass()
|
||
+ .getResourceAsStream("dirtest.patch")) {
|
||
+ ApplyResult result = git.apply().setPatch(patchStream).call();
|
||
+ assertNotNull(result);
|
||
+ } catch (PatchApplyException e) {
|
||
+ if (!useSymlinks) {
|
||
+ // There is a file there, so the patch won't apply.
|
||
+ // Unclear whether an IOE is the correct response,
|
||
+ // though. Probably some negative PatchApplier.Result is
|
||
+ // more appropriate.
|
||
+ testFiles = false;
|
||
+ } else {
|
||
+ throw e;
|
||
+ }
|
||
+ }
|
||
+ File b = new File(new File(trash, ".git"), "b");
|
||
+ assertFalse(".git/b should not exist", b.exists());
|
||
+ if (testFiles) {
|
||
+ File a = new File(trash, "a");
|
||
+ assertTrue("a should be a directory",
|
||
+ Files.isDirectory(a.toPath(), LinkOption.NOFOLLOW_LINKS));
|
||
+ b = new File(a, "b");
|
||
+ assertTrue("a/b should exist", b.isFile());
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
\ No newline at end of file
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/dotgit2.patch 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/dotgit2.patch 2023-10-10 16:04:27.641098943 +0200
|
||
@@ -0,0 +1,9 @@
|
||
+diff --git a/.GIT/b b/.GIT/b
|
||
+new file mode 100644
|
||
+index 0000000..de98044
|
||
+--- /dev/null
|
||
++++ b/.git/b
|
||
+@@ -0,0 +1,3 @@
|
||
++a
|
||
++b
|
||
++c
|
||
\ No newline at end of file
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/dotgit.patch 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/dotgit.patch 2023-10-10 16:04:27.641098943 +0200
|
||
@@ -0,0 +1,9 @@
|
||
+diff --git a/.git/b b/.git/b
|
||
+new file mode 100644
|
||
+index 0000000..de98044
|
||
+--- /dev/null
|
||
++++ b/.git/b
|
||
+@@ -0,0 +1,3 @@
|
||
++a
|
||
++b
|
||
++c
|
||
\ No newline at end of file
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/symlinks/dirtest.patch 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/symlinks/dirtest.patch 2023-10-10 16:04:27.644432299 +0200
|
||
@@ -0,0 +1,9 @@
|
||
+diff --git a/a/b b/a/b
|
||
+new file mode 100644
|
||
+index 0000000..de98044
|
||
+--- /dev/null
|
||
++++ b/a/b
|
||
+@@ -0,0 +1,3 @@
|
||
++a
|
||
++b
|
||
++c
|
||
\ No newline at end of file
|
||
--- jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/symlinks/.gitattributes 1970-01-01 01:00:00.000000000 +0100
|
||
+++ jgit-5.11.0.202103091610-r/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/symlinks/.gitattributes 2023-10-10 16:04:27.641098943 +0200
|
||
@@ -0,0 +1 @@
|
||
+*.patch -crlf
|