Extend merge support for bare repositories
Optional inCore parameter to Resolver/Strategy will instruct it to perform all the operations in memory and avoid modifying working folder even if there is one. Change-Id: I5b873dead3682f79110f58d7806e43f50bcc5045
This commit is contained in:
parent
a67afbfee1
commit
906887a735
|
@ -143,4 +143,18 @@ public static synchronized MergeStrategy[] get() {
|
||||||
* @return the new merge instance which implements this strategy.
|
* @return the new merge instance which implements this strategy.
|
||||||
*/
|
*/
|
||||||
public abstract Merger newMerger(Repository db);
|
public abstract Merger newMerger(Repository db);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new merge instance.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* repository database the merger will read from, and eventually
|
||||||
|
* write results back to.
|
||||||
|
* @param inCore
|
||||||
|
* the merge will happen in memory, working folder will not be
|
||||||
|
* modified, in case of a non-trivial merge that requires manual
|
||||||
|
* resolution, the merger will fail.
|
||||||
|
* @return the new merge instance which implements this strategy.
|
||||||
|
*/
|
||||||
|
public abstract Merger newMerger(Repository db, boolean inCore);
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,8 @@ public enum MergeFailureReason {
|
||||||
|
|
||||||
private boolean enterSubtree;
|
private boolean enterSubtree;
|
||||||
|
|
||||||
|
private boolean inCore;
|
||||||
|
|
||||||
private DirCache dircache;
|
private DirCache dircache;
|
||||||
|
|
||||||
private WorkingTreeIterator workingTreeIt;
|
private WorkingTreeIterator workingTreeIt;
|
||||||
|
@ -135,11 +137,24 @@ public enum MergeFailureReason {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param local
|
* @param local
|
||||||
|
* @param inCore
|
||||||
*/
|
*/
|
||||||
protected ResolveMerger(Repository local) {
|
protected ResolveMerger(Repository local, boolean inCore) {
|
||||||
super(local);
|
super(local);
|
||||||
commitNames = new String[] { "BASE", "OURS", "THEIRS" };
|
commitNames = new String[] { "BASE", "OURS", "THEIRS" };
|
||||||
oi = getObjectInserter();
|
oi = getObjectInserter();
|
||||||
|
this.inCore = inCore;
|
||||||
|
|
||||||
|
if (inCore) {
|
||||||
|
dircache = DirCache.newInCore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param local
|
||||||
|
*/
|
||||||
|
protected ResolveMerger(Repository local) {
|
||||||
|
this(local, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -178,22 +193,26 @@ protected boolean mergeImpl() throws IOException {
|
||||||
tw.enterSubtree();
|
tw.enterSubtree();
|
||||||
}
|
}
|
||||||
|
|
||||||
// All content-merges are successfully done. If we can now write the
|
if (!inCore) {
|
||||||
// new
|
// All content-merges are successfully done. If we can now write the
|
||||||
// index we are on quite safe ground. Even if the checkout of files
|
// new index we are on quite safe ground. Even if the checkout of
|
||||||
// coming from "theirs" fails the user can work around such failures
|
// files coming from "theirs" fails the user can work around such
|
||||||
// by
|
// failures by checking out the index again.
|
||||||
// checking out the index again.
|
if (!builder.commit()) {
|
||||||
if (!builder.commit()) {
|
cleanUp();
|
||||||
cleanUp();
|
throw new IndexWriteException();
|
||||||
throw new IndexWriteException();
|
}
|
||||||
}
|
builder = null;
|
||||||
builder = null;
|
|
||||||
|
// No problem found. The only thing left to be done is to checkout
|
||||||
|
// all files from "theirs" which have been selected to go into the
|
||||||
|
// new index.
|
||||||
|
checkout();
|
||||||
|
} else {
|
||||||
|
builder.finish();
|
||||||
|
builder = null;
|
||||||
|
}
|
||||||
|
|
||||||
// No problem found. The only thing left to be done is to checkout
|
|
||||||
// all files from "theirs" which have been selected to go into the
|
|
||||||
// new index.
|
|
||||||
checkout();
|
|
||||||
if (getUnmergedPathes().isEmpty()) {
|
if (getUnmergedPathes().isEmpty()) {
|
||||||
resultTree = dircache.writeTree(oi);
|
resultTree = dircache.writeTree(oi);
|
||||||
return true;
|
return true;
|
||||||
|
@ -234,13 +253,19 @@ private void createDir(File f) throws IOException {
|
||||||
/**
|
/**
|
||||||
* Reverts the worktree after an unsuccessful merge. We know that for all
|
* Reverts the worktree after an unsuccessful merge. We know that for all
|
||||||
* modified files the old content was in the old index and the index
|
* modified files the old content was in the old index and the index
|
||||||
* contained only stage 0
|
* contained only stage 0. In case if inCore operation just clear
|
||||||
|
* the history of modified files.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws CorruptObjectException
|
* @throws CorruptObjectException
|
||||||
* @throws NoWorkTreeException
|
* @throws NoWorkTreeException
|
||||||
*/
|
*/
|
||||||
private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException {
|
private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException {
|
||||||
|
if (inCore) {
|
||||||
|
modifiedFiles.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DirCache dc = db.readDirCache();
|
DirCache dc = db.readDirCache();
|
||||||
ObjectReader or = db.getObjectDatabase().newReader();
|
ObjectReader or = db.getObjectDatabase().newReader();
|
||||||
Iterator<String> mpathsIt=modifiedFiles.iterator();
|
Iterator<String> mpathsIt=modifiedFiles.iterator();
|
||||||
|
@ -391,14 +416,17 @@ private boolean processEntry(CanonicalTreeParser base,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonTree(modeO) && nonTree(modeT)) {
|
if (nonTree(modeO) && nonTree(modeT)) {
|
||||||
// We are going to update the worktree. Make sure the worktree is
|
if (!inCore) {
|
||||||
// not modified
|
// We are going to update the worktree. Make sure the worktree
|
||||||
if (work != null
|
// is not modified
|
||||||
&& (!nonTree(work.getEntryRawMode()) || work.isModified(
|
if (work != null
|
||||||
index.getDirCacheEntry(), true, true, db.getFS()))) {
|
&& (!nonTree(work.getEntryRawMode()) || work
|
||||||
failingPathes.put(tw.getPathString(),
|
.isModified(index.getDirCacheEntry(), true,
|
||||||
MergeFailureReason.DIRTY_WORKTREE);
|
true, db.getFS()))) {
|
||||||
return false;
|
failingPathes.put(tw.getPathString(),
|
||||||
|
MergeFailureReason.DIRTY_WORKTREE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contentMerge(base, ours, theirs)) {
|
if (!contentMerge(base, ours, theirs)) {
|
||||||
|
@ -421,24 +449,41 @@ private boolean contentMerge(CanonicalTreeParser base,
|
||||||
getRawText(ours.getEntryObjectId(), db),
|
getRawText(ours.getEntryObjectId(), db),
|
||||||
getRawText(theirs.getEntryObjectId(), db));
|
getRawText(theirs.getEntryObjectId(), db));
|
||||||
|
|
||||||
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
|
||||||
|
// support write operations
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
File of = new File(workTree, tw.getPathString());
|
of = new File(workTree, tw.getPathString());
|
||||||
FileOutputStream fos = new FileOutputStream(of);
|
fos = new FileOutputStream(of);
|
||||||
try {
|
try {
|
||||||
fmt.formatMerge(fos, result, Arrays.asList(commitNames),
|
fmt.formatMerge(fos, result, Arrays.asList(commitNames),
|
||||||
Constants.CHARACTER_ENCODING);
|
Constants.CHARACTER_ENCODING);
|
||||||
} finally {
|
} finally {
|
||||||
fos.close();
|
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);
|
||||||
|
fos = new FileOutputStream(of);
|
||||||
|
try {
|
||||||
|
fmt.formatMerge(fos, result, Arrays.asList(commitNames),
|
||||||
|
Constants.CHARACTER_ENCODING);
|
||||||
|
} finally {
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.containsConflicts()) {
|
if (result.containsConflicts()) {
|
||||||
// a conflict occured, the file will contain conflict markers
|
// a conflict occured, 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 only the
|
||||||
// workdir contains the halfways merged content
|
// workdir (if used) contains the halfways merged content
|
||||||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
|
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
|
||||||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
|
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
|
||||||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
|
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
|
||||||
|
@ -457,6 +502,8 @@ private boolean contentMerge(CanonicalTreeParser base,
|
||||||
is));
|
is));
|
||||||
} finally {
|
} finally {
|
||||||
is.close();
|
is.close();
|
||||||
|
if (inCore)
|
||||||
|
of.delete();
|
||||||
}
|
}
|
||||||
builder.add(dce);
|
builder.add(dce);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -84,6 +84,11 @@ public Merger newMerger(final Repository db) {
|
||||||
return new OneSide(db, treeIndex);
|
return new OneSide(db, treeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Merger newMerger(final Repository db, boolean inCore) {
|
||||||
|
return new OneSide(db, treeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
static class OneSide extends Merger {
|
static class OneSide extends Merger {
|
||||||
private final int treeIndex;
|
private final int treeIndex;
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,15 @@
|
||||||
* A three-way merge strategy performing a content-merge if necessary
|
* A three-way merge strategy performing a content-merge if necessary
|
||||||
*/
|
*/
|
||||||
public class StrategyResolve extends ThreeWayMergeStrategy {
|
public class StrategyResolve extends ThreeWayMergeStrategy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThreeWayMerger newMerger(Repository db) {
|
public ThreeWayMerger newMerger(Repository db) {
|
||||||
return new ResolveMerger(db);
|
return new ResolveMerger(db, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
|
||||||
|
return new ResolveMerger(db, inCore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -83,6 +83,12 @@ public ThreeWayMerger newMerger(final Repository db) {
|
||||||
return new InCoreMerger(db);
|
return new InCoreMerger(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
|
||||||
|
// This class is always inCore, so ignore the parameter
|
||||||
|
return newMerger(db);
|
||||||
|
}
|
||||||
|
|
||||||
private static class InCoreMerger extends ThreeWayMerger {
|
private static class InCoreMerger extends ThreeWayMerger {
|
||||||
private static final int T_BASE = 0;
|
private static final int T_BASE = 0;
|
||||||
|
|
||||||
|
@ -193,4 +199,5 @@ public ObjectId getResultTreeId() {
|
||||||
return resultTree;
|
return resultTree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,4 +49,7 @@
|
||||||
public abstract class ThreeWayMergeStrategy extends MergeStrategy {
|
public abstract class ThreeWayMergeStrategy extends MergeStrategy {
|
||||||
@Override
|
@Override
|
||||||
public abstract ThreeWayMerger newMerger(Repository db);
|
public abstract ThreeWayMerger newMerger(Repository db);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract ThreeWayMerger newMerger(Repository db, boolean inCore);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,18 @@ protected ThreeWayMerger(final Repository local) {
|
||||||
super(local);
|
super(local);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new merge instance for a repository.
|
||||||
|
*
|
||||||
|
* @param local
|
||||||
|
* the repository this merger will read and write data on.
|
||||||
|
* @param inCore
|
||||||
|
* perform the merge in core with no working folder involved
|
||||||
|
*/
|
||||||
|
protected ThreeWayMerger(final Repository local, boolean inCore) {
|
||||||
|
this(local);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the common ancestor tree.
|
* Set the common ancestor tree.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue