Merge branch 'master' into stable-6.3

* master:
  Move WorkTreeUpdater to merge package
  WorkTreeUpdater: use DirCacheCheckout#StreamSupplier
  DirCacheCheckout#getContent: also take InputStream supplier
  WorkTreeUpdater: remove safeWrite option

Change-Id: I8be570dbc4ad0d0b46046b85cbda24c3adcba170
This commit is contained in:
Matthias Sohn 2022-09-06 15:58:47 +02:00
commit 370d1170e8
4 changed files with 110 additions and 186 deletions

View File

@ -58,7 +58,6 @@
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.WorkTreeUpdater;
import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
@ -398,7 +397,7 @@ private void checkOid(ObjectId baseId, ObjectId id, ChangeType type, File f,
} }
private void applyBinary(Repository repository, String path, File f, private void applyBinary(Repository repository, String path, File f,
FileHeader fh, WorkTreeUpdater.StreamSupplier loader, ObjectId id, FileHeader fh, DirCacheCheckout.StreamSupplier loader, ObjectId id,
CheckoutMetadata checkOut) CheckoutMetadata checkOut)
throws PatchApplyException, IOException { throws PatchApplyException, IOException {
if (!fh.getOldId().isComplete() || !fh.getNewId().isComplete()) { if (!fh.getOldId().isComplete() || !fh.getNewId().isComplete()) {
@ -430,9 +429,7 @@ private void applyBinary(Repository repository, String path, File f,
hunk.getBuffer(), start, hunk.getBuffer(), start,
length))))) { length))))) {
DirCacheCheckout.getContent(repository, path, checkOut, DirCacheCheckout.getContent(repository, path, checkOut,
WorkTreeUpdater.createStreamLoader(() -> inflated, () -> inflated, null, out);
hunk.getSize()),
null, out);
if (!fh.getNewId().toObjectId().equals(hash.toObjectId())) { if (!fh.getNewId().toObjectId().equals(hash.toObjectId())) {
throw new PatchApplyException(MessageFormat.format( throw new PatchApplyException(MessageFormat.format(
JGitText.get().applyBinaryResultOidWrong, JGitText.get().applyBinaryResultOidWrong,
@ -463,8 +460,7 @@ private void applyBinary(Repository repository, String path, File f,
SHA1InputStream hashed = new SHA1InputStream(hash, SHA1InputStream hashed = new SHA1InputStream(hash,
input)) { input)) {
DirCacheCheckout.getContent(repository, path, checkOut, DirCacheCheckout.getContent(repository, path, checkOut,
WorkTreeUpdater.createStreamLoader(() -> hashed, finalSize), () -> hashed, null, out);
null, out);
if (!fh.getNewId().toObjectId() if (!fh.getNewId().toObjectId()
.equals(hash.toObjectId())) { .equals(hash.toObjectId())) {
throw new PatchApplyException(MessageFormat.format( throw new PatchApplyException(MessageFormat.format(
@ -632,9 +628,7 @@ && canApplyAt(hunkLines, newLines, 0)) {
} }
try (OutputStream output = new FileOutputStream(f)) { try (OutputStream output = new FileOutputStream(f)) {
DirCacheCheckout.getContent(repository, path, checkOut, DirCacheCheckout.getContent(repository, path, checkOut,
WorkTreeUpdater.createStreamLoader(buffer::openInputStream, buffer::openInputStream, null, output);
buffer.length()),
null, output);
} }
} finally { } finally {
buffer.destroy(); buffer.destroy();

View File

@ -21,6 +21,7 @@
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.text.MessageFormat; import java.text.MessageFormat;
@ -1605,6 +1606,60 @@ public static void getContent(Repository repo, String path,
CheckoutMetadata checkoutMetadata, ObjectLoader ol, CheckoutMetadata checkoutMetadata, ObjectLoader ol,
WorkingTreeOptions opt, OutputStream os) WorkingTreeOptions opt, OutputStream os)
throws IOException { throws IOException {
getContent(repo, path, checkoutMetadata, ol::openStream, opt, os);
}
/**
* Something that can supply an {@link InputStream}.
*
* @since 6.3
*/
public interface StreamSupplier {
/**
* Loads the input stream.
*
* @return the loaded stream
* @throws IOException
* if any reading error occurs
*/
InputStream load() throws IOException;
}
/**
* Return filtered content for blob contents. EOL handling and smudge-filter
* handling are applied in the same way as it would be done during a
* checkout.
*
* @param repo
* the repository
* @param path
* the path used to determine the correct filters for the object
* @param checkoutMetadata
* containing
* <ul>
* <li>smudgeFilterCommand to be run for smudging the object</li>
* <li>eolStreamType used for stream conversion (can be
* null)</li>
* </ul>
* @param inputStream
* A supplier for the raw content of the object. Each call should
* yield a fresh stream of the same object.
* @param opt
* the working tree options where only 'core.autocrlf' is used
* for EOL handling if 'checkoutMetadata.eolStreamType' is not
* valid
* @param os
* the output stream the filtered content is written to. The
* caller is responsible to close the stream.
* @throws IOException
* @since 6.3
*/
public static void getContent(Repository repo, String path,
CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
WorkingTreeOptions opt, OutputStream os)
throws IOException {
EolStreamType nonNullEolStreamType; EolStreamType nonNullEolStreamType;
if (checkoutMetadata.eolStreamType != null) { if (checkoutMetadata.eolStreamType != null) {
nonNullEolStreamType = checkoutMetadata.eolStreamType; nonNullEolStreamType = checkoutMetadata.eolStreamType;
@ -1618,21 +1673,23 @@ public static void getContent(Repository repo, String path,
if (checkoutMetadata.smudgeFilterCommand != null) { if (checkoutMetadata.smudgeFilterCommand != null) {
if (FilterCommandRegistry if (FilterCommandRegistry
.isRegistered(checkoutMetadata.smudgeFilterCommand)) { .isRegistered(checkoutMetadata.smudgeFilterCommand)) {
runBuiltinFilterCommand(repo, checkoutMetadata, ol, runBuiltinFilterCommand(repo, checkoutMetadata, inputStream,
channel); channel);
} else { } else {
runExternalFilterCommand(repo, path, checkoutMetadata, ol, runExternalFilterCommand(repo, path, checkoutMetadata, inputStream,
channel); channel);
} }
} else { } else {
ol.copyTo(channel); try (InputStream in = inputStream.load()) {
in.transferTo(channel);
}
} }
} }
} }
// Run an external filter command // Run an external filter command
private static void runExternalFilterCommand(Repository repo, String path, private static void runExternalFilterCommand(Repository repo, String path,
CheckoutMetadata checkoutMetadata, ObjectLoader ol, CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
OutputStream channel) throws IOException { OutputStream channel) throws IOException {
FS fs = repo.getFS(); FS fs = repo.getFS();
ProcessBuilder filterProcessBuilder = fs.runInShell( ProcessBuilder filterProcessBuilder = fs.runInShell(
@ -1644,7 +1701,9 @@ private static void runExternalFilterCommand(Repository repo, String path,
int rc; int rc;
try { try {
// TODO: wire correctly with AUTOCRLF // TODO: wire correctly with AUTOCRLF
result = fs.execute(filterProcessBuilder, ol.openStream()); try (InputStream in = inputStream.load()) {
result = fs.execute(filterProcessBuilder, in);
}
rc = result.getRc(); rc = result.getRc();
if (rc == 0) { if (rc == 0) {
result.getStdout().writeTo(channel, result.getStdout().writeTo(channel,
@ -1665,31 +1724,35 @@ private static void runExternalFilterCommand(Repository repo, String path,
// Run a builtin filter command // Run a builtin filter command
private static void runBuiltinFilterCommand(Repository repo, private static void runBuiltinFilterCommand(Repository repo,
CheckoutMetadata checkoutMetadata, ObjectLoader ol, CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
OutputStream channel) throws MissingObjectException, IOException { OutputStream channel) throws MissingObjectException, IOException {
boolean isMandatory = repo.getConfig().getBoolean( boolean isMandatory = repo.getConfig().getBoolean(
ConfigConstants.CONFIG_FILTER_SECTION, ConfigConstants.CONFIG_FILTER_SECTION,
ConfigConstants.CONFIG_SECTION_LFS, ConfigConstants.CONFIG_SECTION_LFS,
ConfigConstants.CONFIG_KEY_REQUIRED, false); ConfigConstants.CONFIG_KEY_REQUIRED, false);
FilterCommand command = null; FilterCommand command = null;
try { try (InputStream in = inputStream.load()) {
command = FilterCommandRegistry.createFilterCommand( try {
checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(), command = FilterCommandRegistry.createFilterCommand(
channel); checkoutMetadata.smudgeFilterCommand, repo, in,
} catch (IOException e) { channel);
LOG.error(JGitText.get().failedToDetermineFilterDefinition, e); } catch (IOException e) {
if (!isMandatory) { LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
// In case an IOException occurred during creating of the if (!isMandatory) {
// command then proceed as if there would not have been a // In case an IOException occurred during creating of the
// builtin filter (only if the filter is not mandatory). // command then proceed as if there would not have been a
ol.copyTo(channel); // builtin filter (only if the filter is not mandatory).
} else { try (InputStream again = inputStream.load()) {
throw e; again.transferTo(channel);
}
} else {
throw e;
}
} }
} if (command != null) {
if (command != null) { while (command.run() != -1) {
while (command.run() != -1) { // loop as long as command.run() tells there is work to do
// loop as long as command.run() tells there is work to do }
} }
} }
} }

View File

@ -60,8 +60,6 @@
import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.LfsFactory; import org.eclipse.jgit.util.LfsFactory;
import org.eclipse.jgit.util.WorkTreeUpdater;
import org.eclipse.jgit.util.WorkTreeUpdater.StreamLoader;
import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.TemporaryBuffer;
/** /**
@ -912,10 +910,8 @@ private File writeMergedFile(TemporaryBuffer rawMerged,
if (!fs.exists(parentFolder)) { if (!fs.exists(parentFolder)) {
parentFolder.mkdirs(); parentFolder.mkdirs();
} }
StreamLoader contentLoader = WorkTreeUpdater.createStreamLoader(rawMerged::openInputStream, workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
rawMerged.length()); eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
workTreeUpdater.updateFileWithContent(contentLoader,
eol, tw.getSmudgeCommand(attributes), of.getPath(), of, false);
return of; return of;
} }

View File

@ -7,19 +7,16 @@
* *
* SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: BSD-3-Clause
*/ */
package org.eclipse.jgit.util; package org.eclipse.jgit.merge;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import java.io.BufferedInputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -36,25 +33,23 @@
import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.IndexWriteException; import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.util.LfsFactory;
import org.eclipse.jgit.util.LfsFactory.LfsInputStream; import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil; import org.eclipse.jgit.util.io.EolStreamTypeUtil;
@ -64,10 +59,8 @@
* You should use a single instance for all of your file changes. In case of an * You should use a single instance for all of your file changes. In case of an
* error, make sure your instance is released, and initiate a new one if * error, make sure your instance is released, and initiate a new one if
* necessary. * necessary.
*
* @since 6.3
*/ */
public class WorkTreeUpdater implements Closeable { class WorkTreeUpdater implements Closeable {
/** /**
* The result of writing the index changes. * The result of writing the index changes.
@ -247,77 +240,6 @@ public static WorkTreeUpdater createInCoreWorkTreeUpdater(Repository repo,
return new WorkTreeUpdater(repo, dirCache, oi); return new WorkTreeUpdater(repo, dirCache, oi);
} }
/**
* Something that can supply an {@link InputStream}.
*/
public interface StreamSupplier {
/**
* Loads the input stream.
*
* @return the loaded stream
* @throws IOException
* if any reading error occurs
*/
InputStream load() throws IOException;
}
/**
* We want to use DirCacheCheckout for its CR-LF and smudge filters, but DirCacheCheckout needs an
* ObjectLoader rather than InputStream. This class provides a bridge between the two.
*/
public static class StreamLoader extends ObjectLoader {
private final StreamSupplier data;
private final long size;
private StreamLoader(StreamSupplier data, long length) {
this.data = data;
this.size = length;
}
@Override
public int getType() {
return Constants.OBJ_BLOB;
}
@Override
public long getSize() {
return size;
}
@Override
public boolean isLarge() {
return true;
}
@Override
public byte[] getCachedBytes() throws LargeObjectException {
throw new LargeObjectException();
}
@Override
public ObjectStream openStream() throws IOException {
return new ObjectStream.Filter(getType(), getSize(),
new BufferedInputStream(data.load()));
}
}
/**
* Creates stream loader for the given supplier.
*
* @param supplier
* to wrap
* @param length
* of the supplied content
* @return the result stream loader
*/
public static StreamLoader createStreamLoader(StreamSupplier supplier,
long length) {
return new StreamLoader(supplier, length);
}
private static int getInCoreFileSizeLimit(Config config) { private static int getInCoreFileSizeLimit(Config config) {
return config.getInt(ConfigConstants.CONFIG_MERGE_SECTION, return config.getInt(ConfigConstants.CONFIG_MERGE_SECTION,
ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20); ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20);
@ -601,8 +523,8 @@ public void close() throws IOException {
/** /**
* Updates the file in the checkout with the given content. * Updates the file in the checkout with the given content.
* *
* @param resultStreamLoader * @param inputStream
* with the content to be updated * the content to be updated
* @param streamType * @param streamType
* for parsing the content * for parsing the content
* @param smudgeCommand * @param smudgeCommand
@ -611,40 +533,21 @@ public void close() throws IOException {
* of the file to be updated * of the file to be updated
* @param file * @param file
* to be updated * to be updated
* @param safeWrite
* whether the content should be written to a buffer first
* @throws IOException * @throws IOException
* if the file cannot be updated * if the file cannot be updated
*/ */
public void updateFileWithContent(StreamLoader resultStreamLoader, public void updateFileWithContent(StreamSupplier inputStream,
EolStreamType streamType, String smudgeCommand, String path, EolStreamType streamType, String smudgeCommand, String path,
File file, boolean safeWrite) throws IOException { File file) throws IOException {
if (inCore) { if (inCore) {
return; return;
} }
CheckoutMetadata metadata = new CheckoutMetadata(streamType, CheckoutMetadata metadata = new CheckoutMetadata(streamType,
smudgeCommand); smudgeCommand);
if (safeWrite) {
// Write to a buffer and copy to the file only if everything was
// fine.
TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null);
try {
try (TemporaryBuffer buf = buffer) {
DirCacheCheckout.getContent(repo, path, metadata,
resultStreamLoader, workingTreeOptions, buf);
}
try (InputStream bufIn = buffer.openInputStream()) {
Files.copy(bufIn, file.toPath(),
StandardCopyOption.REPLACE_EXISTING);
}
} finally {
buffer.destroy();
}
return;
}
try (OutputStream outputStream = new FileOutputStream(file)) { try (OutputStream outputStream = new FileOutputStream(file)) {
DirCacheCheckout.getContent(repo, path, metadata, DirCacheCheckout.getContent(repo, path, metadata,
resultStreamLoader, workingTreeOptions, outputStream); inputStream, workingTreeOptions, outputStream);
} }
} }
@ -652,8 +555,8 @@ public void updateFileWithContent(StreamLoader resultStreamLoader,
* Creates a path with the given content, and adds it to the specified stage * Creates a path with the given content, and adds it to the specified stage
* to the index builder. * to the index builder.
* *
* @param inputStream * @param input
* with the content to be updated * the content to be updated
* @param path * @param path
* of the file to be updated * of the file to be updated
* @param fileMode * @param fileMode
@ -670,43 +573,12 @@ public void updateFileWithContent(StreamLoader resultStreamLoader,
* @throws IOException * @throws IOException
* if inserting the content fails * if inserting the content fails
*/ */
public DirCacheEntry insertToIndex(InputStream inputStream, byte[] path, public DirCacheEntry insertToIndex(InputStream input,
FileMode fileMode, int entryStage, Instant lastModified, int len,
Attribute lfsAttribute) throws IOException {
StreamLoader contentLoader = createStreamLoader(() -> inputStream, len);
return insertToIndex(contentLoader, path, fileMode, entryStage,
lastModified, len, lfsAttribute);
}
/**
* Creates a path with the given content, and adds it to the specified stage
* to the index builder.
*
* @param resultStreamLoader
* with the content to be updated
* @param path
* of the file to be updated
* @param fileMode
* of the modified file
* @param entryStage
* of the new entry
* @param lastModified
* instant of the modified file
* @param len
* of the content
* @param lfsAttribute
* for checking for LFS enablement
* @return the entry which was added to the index
* @throws IOException
* if inserting the content fails
*/
public DirCacheEntry insertToIndex(StreamLoader resultStreamLoader,
byte[] path, FileMode fileMode, int entryStage, byte[] path, FileMode fileMode, int entryStage,
Instant lastModified, int len, Attribute lfsAttribute) Instant lastModified, int len, Attribute lfsAttribute)
throws IOException { throws IOException {
return addExistingToIndex( return addExistingToIndex(insertResult(input, lfsAttribute, len), path,
insertResult(resultStreamLoader, lfsAttribute), path, fileMode, fileMode, entryStage, lastModified, len);
entryStage, lastModified, len);
} }
/** /**
@ -734,16 +606,15 @@ public DirCacheEntry addExistingToIndex(ObjectId objectId, byte[] path,
dce.setLastModified(lastModified); dce.setLastModified(lastModified);
} }
dce.setLength(inCore ? 0 : len); dce.setLength(inCore ? 0 : len);
dce.setObjectId(objectId); dce.setObjectId(objectId);
builder.add(dce); builder.add(dce);
return dce; return dce;
} }
private ObjectId insertResult(StreamLoader resultStreamLoader, private ObjectId insertResult(InputStream input,
Attribute lfsAttribute) throws IOException { Attribute lfsAttribute, long length) throws IOException {
try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter(repo, try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter(repo,
resultStreamLoader.data.load(), resultStreamLoader.size, input, length,
lfsAttribute)) { lfsAttribute)) {
return inserter.insert(OBJ_BLOB, is.getLength(), is); return inserter.insert(OBJ_BLOB, is.getLength(), is);
} }