Browse Source

Process most in-core merges without local temp files

Instead of always writing to disk use TemporaryBuffer.LocalFile to
store up to 10 MiB of merge result in RAM. Most source code will
fit into this limit, avoiding local disk IO for simple merges.

Larger files will automatically spool to a temporary file that
can be cleaned up in the finally, reducing the risk of leaving
them on disk and consuming space in /tmp.

Change-Id: Ieccbd9b354d4dd3d2bc1304857325ae7a9f34ec6
stable-3.5
Shawn Pearce 10 years ago
parent
commit
2badedcbe0
  1. 129
      org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

129
org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

@ -44,6 +44,9 @@
*/ */
package org.eclipse.jgit.merge; package org.eclipse.jgit.merge;
import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -76,7 +79,6 @@ import org.eclipse.jgit.errors.MissingObjectException;
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.ConfigConstants; import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
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.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
@ -89,6 +91,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
/** /**
* A three-way merger performing a content-merge if necessary * A three-way merger performing a content-merge if necessary
@ -756,90 +759,92 @@ public class ResolveMerger extends ThreeWayMerger {
CanonicalTreeParser ours, CanonicalTreeParser theirs, CanonicalTreeParser ours, CanonicalTreeParser theirs,
MergeResult<RawText> result) throws FileNotFoundException, MergeResult<RawText> result) throws FileNotFoundException,
IOException { IOException {
File of = writeMergedFile(result); File mergedFile = !inCore ? writeMergedFile(result) : null;
if (result.containsConflicts()) { if (result.containsConflicts()) {
// a conflict occurred, the file will contain conflict markers // A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and only the // the index will be populated with the three stages and the
// workdir (if used) contains the halfways merged content // workdir (if used) contains the halfway merged content.
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
mergeResults.put(tw.getPathString(), result); mergeResults.put(tw.getPathString(), result);
} else { return;
// no conflict occurred, the file will contain fully merged content. }
// the index will be populated with the new merged version
DirCacheEntry dce = new DirCacheEntry(tw.getPathString()); // No conflict occurred, the file will contain fully merged content.
int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1), // The index will be populated with the new merged version.
tw.getRawMode(2)); DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
// set the mode for the new content. Fall back to REGULAR_FILE if
// you can't merge modes of OURS and THEIRS // Set the mode for the new content. Fall back to REGULAR_FILE if
dce.setFileMode((newMode == FileMode.MISSING.getBits()) ? FileMode.REGULAR_FILE // we can't merge modes of OURS and THEIRS.
: FileMode.fromBits(newMode)); int newMode = mergeFileModes(
dce.setLastModified(of.lastModified()); tw.getRawMode(0),
dce.setLength((int) of.length()); tw.getRawMode(1),
InputStream is = new FileInputStream(of); tw.getRawMode(2));
dce.setFileMode(newMode == FileMode.MISSING.getBits()
? FileMode.REGULAR_FILE
: FileMode.fromBits(newMode));
if (mergedFile != null) {
long len = mergedFile.length();
dce.setLastModified(mergedFile.lastModified());
dce.setLength((int) len);
InputStream is = new FileInputStream(mergedFile);
try { try {
dce.setObjectId(getObjectInserter().insert( dce.setObjectId(getObjectInserter().insert(OBJ_BLOB, len, is));
Constants.OBJ_BLOB, of.length(), is));
} finally { } finally {
is.close(); is.close();
if (inCore)
FileUtils.delete(of);
} }
builder.add(dce); } else
} dce.setObjectId(insertMergeResult(result));
builder.add(dce);
} }
/** /**
* Writes merged file content to the working tree. In case {@link #inCore} * Writes merged file content to the working tree.
* is set and we don't have a working tree the content is written to a
* temporary file
* *
* @param result * @param result
* the result of the content merge * the result of the content merge
* @return the file to which the merged content was written * @return the working tree file to which the merged content was written.
* @throws FileNotFoundException * @throws FileNotFoundException
* @throws IOException * @throws IOException
*/ */
private File writeMergedFile(MergeResult<RawText> result) private File writeMergedFile(MergeResult<RawText> result)
throws FileNotFoundException, IOException { throws FileNotFoundException, IOException {
MergeFormatter fmt = new MergeFormatter(); File workTree = db.getWorkTree();
File of = null; if (workTree == null)
FileOutputStream fos; // TODO: This should be handled by WorkingTreeIterators which
if (!inCore) { // support write operations
File workTree = db.getWorkTree(); throw new UnsupportedOperationException();
if (workTree == null)
// TODO: This should be handled by WorkingTreeIterators which FS fs = db.getFS();
// support write operations File of = new File(workTree, tw.getPathString());
throw new UnsupportedOperationException(); File parentFolder = of.getParentFile();
if (!fs.exists(parentFolder))
FS fs = db.getFS(); parentFolder.mkdirs();
of = new File(workTree, tw.getPathString()); FileOutputStream fos = new FileOutputStream(of);
File parentFolder = of.getParentFile(); try {
if (!fs.exists(parentFolder)) new MergeFormatter().formatMerge(fos, result,
parentFolder.mkdirs(); Arrays.asList(commitNames), CHARACTER_ENCODING);
fos = new FileOutputStream(of); } finally {
try { fos.close();
fmt.formatMerge(fos, result, Arrays.asList(commitNames),
Constants.CHARACTER_ENCODING);
} finally {
fos.close();
}
} else if (!result.containsConflicts()) {
// When working inCore, only trivial merges can be handled,
// so we generate objects only in conflict free cases
of = File.createTempFile("merge_", "_temp", null); //$NON-NLS-1$ //$NON-NLS-2$
fos = new FileOutputStream(of);
try {
fmt.formatMerge(fos, result, Arrays.asList(commitNames),
Constants.CHARACTER_ENCODING);
} finally {
fos.close();
}
} }
return of; return of;
} }
private ObjectId insertMergeResult(MergeResult<RawText> result)
throws IOException {
TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(10 << 20);
try {
new MergeFormatter().formatMerge(buf, result,
Arrays.asList(commitNames), CHARACTER_ENCODING);
buf.close();
return getObjectInserter().insert(OBJ_BLOB, buf.length(),
buf.openInputStream());
} finally {
buf.destroy();
}
}
/** /**
* Try to merge filemodes. If only ours or theirs have changed the mode * Try to merge filemodes. If only ours or theirs have changed the mode
* (compared to base) we choose that one. If ours and theirs have equal * (compared to base) we choose that one. If ours and theirs have equal
@ -872,7 +877,7 @@ public class ResolveMerger extends ThreeWayMerger {
throws IOException { throws IOException {
if (id.equals(ObjectId.zeroId())) if (id.equals(ObjectId.zeroId()))
return new RawText(new byte[] {}); return new RawText(new byte[] {});
return new RawText(db.open(id, Constants.OBJ_BLOB).getCachedBytes()); return new RawText(db.open(id, OBJ_BLOB).getCachedBytes());
} }
private static boolean nonTree(final int mode) { private static boolean nonTree(final int mode) {

Loading…
Cancel
Save