SVNConflictChoice.THEIRS_FULL not working when resolving conflicts on repositories 1.7+

By following the wiki, we added a ISVNConflictHandler where we want to auto resolve conflicts by choosing the version from the repository. We therefore always returned SVNConflictChoice.THEIRS_FULL.

This worked well when our working copy was in version 1.6 but when we upgraded it to the 1.8 format, it then stopped working, leaving .mine (and the other revision) files.

When I look into SVNWCContext.resolveTextConflict(...) (which calls ISVNConflictHandler.handleConflict(...)), it seems only SVNConflictChoice.POSTPONE is handled. Am I doing something wrong or is the conflict handling broken ?

For info, we are using version 1.8.14, but it seems the problem should also happen on 1.10.0 (by the way, neither 1.8.14 nor 1.10.0 are selectable as Affected versions, on the right panel).

Hello Joel,
I tried to reproduce the problem but for me it works fine.
I was using the following code: you can put it into ResolveTest and launch.
{code}
@Test
public void testResolveTheirFull() throws Exception {
final TestOptions options = TestOptions.getInstance();

    final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
    final Sandbox sandbox = Sandbox.createWithCleanup(getTestName() + ".testResolveTheirFull", options);
    try {
        final SVNURL url = sandbox.createSvnRepository();

        final CommitBuilder commitBuilder1 = new CommitBuilder(url);
        commitBuilder1.addFile("file");
        commitBuilder1.commit();

        final CommitBuilder commitBuilder2 = new CommitBuilder(url);
        commitBuilder2.changeFile("file", "changed remotely".getBytes());
        commitBuilder2.commit();

        final WorkingCopy workingCopy = sandbox.checkoutNewWorkingCopy(url, 1);
        final File workingCopyDirectory = workingCopy.getWorkingCopyDirectory();
        final File file = workingCopy.getFile("file");
        TestUtil.writeFileContentsString(file, "changed locally");

        final SvnUpdate update = svnOperationFactory.createUpdate();
        update.setSingleTarget(SvnTarget.fromFile(workingCopyDirectory));
        update.run();

        final SvnResolve resolve = svnOperationFactory.createResolve();
        resolve.setConflictChoice(SVNConflictChoice.THEIRS_FULL);
        resolve.setSingleTarget(SvnTarget.fromFile(file));
        resolve.run();

        final Map<File, SvnStatus> statuses = TestUtil.getStatuses(svnOperationFactory, workingCopyDirectory);
        final SvnStatus status = statuses.get(file);
        Assert.assertEquals(SVNStatusType.STATUS_NORMAL, status.getTextStatus());
        Assert.assertEquals(SVNStatusType.STATUS_NORMAL, status.getNodeStatus());
        Assert.assertEquals(SVNStatusType.STATUS_NONE, status.getPropertiesStatus());
    } finally {
        svnOperationFactory.dispose();
        sandbox.dispose();
    }
}

{code}
Could you please update this test according to your scenario so that the problem would be reproducible? I would fix the problem and add the test to the SVNKit repository then.

@dmitry.pavlenko : Thanks for your suggestion, I will try it in our code base and let you know.

I see you are using SvnOperationFactory. I did not know about that workflow, and cannot find any mention of it in the Wiki.

If this is the recommended workflow and ISVNConflictHandler is not meant to be used anymore, I suggest adding a @deprecated and updating the Wiki.

SvnOperationFactory is the recommended API to use, SVNClientManager is the old interface but it still should work. Usually we add new features to SvnOperationFactory and when possible backport them to SVNClientManager API. If it’s not possible for some reasons, the features are only available for SvnOperationFactory API.

Thanks for the clarification, we will consider the switch to that new way.
As you guessed it, we use SVNClientManager (but also SVNUpdateClient and SVNWCClient), and would have liked to continue so.

No problem with using SVNClientManager, for nearly all features it is usable as well. By the way if you have a look at SVNClientManager and SVNXXXClient implementation, under the hood they just call SvnOperationFactory, so if you would like to switch, it should be relatively easy, just inline corresponding methods.

I’ll try to reproduce the problem with SVNClientManager and if it’s not reproducible for me, I’ll ask you to have a look at it and compare with your scenario. If it’s reproducible, I’ll fix it.

{code}
@Test
public void testResolveTheirFull() throws Exception {
final TestOptions options = TestOptions.getInstance();

    final SVNClientManager clientManager = SVNClientManager.newInstance();
    final Sandbox sandbox = Sandbox.createWithCleanup(getTestName() + ".testResolveTheirFull", options);
    try {
        final SVNURL url = sandbox.createSvnRepository();

        final CommitBuilder commitBuilder1 = new CommitBuilder(url);
        commitBuilder1.addFile("file");
        commitBuilder1.commit();

        final CommitBuilder commitBuilder2 = new CommitBuilder(url);
        commitBuilder2.changeFile("file", "changed remotely".getBytes());
        commitBuilder2.commit();

        final WorkingCopy workingCopy = sandbox.checkoutNewWorkingCopy(url, 1);
        final File workingCopyDirectory = workingCopy.getWorkingCopyDirectory();
        final File file = workingCopy.getFile("file");
        TestUtil.writeFileContentsString(file, "changed locally");

        final SVNUpdateClient updateClient = clientManager.getUpdateClient();
        updateClient.doUpdate(workingCopyDirectory, SVNRevision.HEAD, SVNDepth.INFINITY, true, true);

        final SVNWCClient wcClient = clientManager.getWCClient();
        wcClient.doResolve(file, SVNDepth.INFINITY, SVNConflictChoice.THEIRS_FULL);

        //check .mine
    } finally {
        clientManager.dispose();
        sandbox.dispose();
    }
}

{code}

I tried this test and it doesn’t leave anything in the working copy. Is it your scenario?

Not quite, our code base looks more like:

public void doStuff(SVNClientManager manager) throws SVNException
{
    SVNUpdateClient updateClient = manager.getUpdateClient();
    updateClient.setEventHandler(new OurSVNEventHandler(...));
    updateClient.doSwitch(...);
}

class OurSVNEventHandler implements ISVNEventHandler
{
    @Override
    public void handleEvent(final SVNEvent event, double progress) throws SVNException
    {
        // Logging and stuff ...
        return new SVNConflictResult(SVNConflictChoice.THEIRS_FULL, null);
    }
}

We wanted to act directly when a conflict happens and not have to make a 2nd pass. Our repo is quite big (+/- 3GB) and we feared there would be performance impacts if we would do multiple passes. But we never quite tested the impact of it, so it’s only hypothetical at the moment.

Probably you didn’t mean “return new …” from “void handleEvent”, I tried the following test:
{code}
@Test
public void testResolveTheirFull() throws Exception {
final TestOptions options = TestOptions.getInstance();

    final Sandbox sandbox = Sandbox.createWithCleanup(getTestName() + ".testResolveTheirFull", options);
    final SVNClientManager clientManager = SVNClientManager.newInstance();
    try {
        final SVNURL url = sandbox.createSvnRepository();

        final CommitBuilder commitBuilder1 = new CommitBuilder(url);
        commitBuilder1.addFile("file");
        commitBuilder1.commit();

        final CommitBuilder commitBuilder2 = new CommitBuilder(url);
        commitBuilder2.changeFile("file", "changed remotely".getBytes());
        commitBuilder2.commit();

        final WorkingCopy workingCopy = sandbox.checkoutNewWorkingCopy(url, 1);
        final File workingCopyDirectory = workingCopy.getWorkingCopyDirectory();
        final File file = workingCopy.getFile("file");
        TestUtil.writeFileContentsString(file, "changed locally");

        final DefaultSVNOptions svnOptions = new DefaultSVNOptions();
        svnOptions.setConflictHandler(new ISVNConflictHandler() {
            @Override
            public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException {

// return new SVNConflictResult(SVNConflictChoice.POSTPONE, conflictDescription.getPath());
return new SVNConflictResult(SVNConflictChoice.THEIRS_FULL, conflictDescription.getPath());
}
});
clientManager.setOptions(svnOptions);

        final SVNUpdateClient updateClient = clientManager.getUpdateClient();
        updateClient.doUpdate(workingCopyDirectory, SVNRevision.HEAD, SVNDepth.INFINITY, true, true);

        //check no other files (e.g. .mine) are present except the original file
        final File[] files = SVNFileListUtil.listFiles(workingCopyDirectory);
        Assert.assertNotNull(files);
        Assert.assertEquals(2, files.length);
        Arrays.sort(files); // ".svn" first, "file" second
        final File dotSvn = new File(workingCopyDirectory, SVNFileUtil.getAdminDirectoryName());
        Assert.assertEquals(dotSvn, files[0]);
        Assert.assertEquals(file, files[1]);
    } finally {
        clientManager.dispose();
        sandbox.dispose();
    }
}

{code}

Does it look like your situation? For me the test passes as well

Ha ha, my bad. You are (obviously) right, I provided the wrong part of our code… Here is the relevant (pseudo) code:

public void doStuff(File repoPath, SVNURL branch, SVNRevision rev) throws SVNException
{
    DefaultSVNOptions options = SVNWCUtil.createDefaultOptions(true);
    options.setConflictHandler(new ISVNConflictHandler() {
        @Override
        public SVNConflictResult handleConflict(SVNConflictDescription pDescription) throws SVNException
        {
            // Logging and stuff ...
            return new SVNConflictResult(SVNConflictChoice.THEIRS_FULL, null);
        }
    });

    SVNClientManager manager = SVNClientManager.newInstance(options);

    SVNUpdateClient updateClient = manager.getUpdateClient();
    updateClient.doSwitch(repoPath, branch, rev, rev, SVNDepth.INFINITY, true, false);
}

… which looks more or less like the code you provided.

What I don’t understand is why there is no dangling file.mine after your test. Is it because you use update and I use switch ? Will the Sandbox class provide a SVN repository at the 1.7+ format ? Because when I look into org.tmatesoft.svn.core.internal.wc17.SVNWCContext.resolveTextConflict(...) I don’t see the result much used:

    public TextConflictResolutionInfo resolveTextConflict(...) {
    [...]
        SVNConflictResult result = conflictHandler.handleConflict(conflictDescription.toConflictDescription());
        if (result == null) {
            SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_CONFLICT_RESOLVER_FAILURE, "Conflict callback violated API:" +
                    " returned no results");
            SVNErrorManager.error(errorMessage, SVNLogType.WC);
        }

        if (result.isIsSaveMerged()) {
            saveMergeResult(db, localAbsPath, result.getMergedFile() != null ? result.getMergedFile() : resultTarget);
        }
        if (result.getConflictChoice() == SVNConflictChoice.POSTPONE) { // <--- Here this is what I meant by only POSTPONE seem to be handled
            TextConflictResolutionInfo textConflictResolutionInfo = evalTextConflictFuncResult(localAbsPath, result.getConflictChoice(),
                    leftAbsPath, rightAbsPath, result.getMergedFile() != null ? result.getMergedFile() : resultTarget,
                    detranslatedTarget);
            SVNSkel workItem = textConflictResolutionInfo.workItems;
            resolutionInfo.resolved = textConflictResolutionInfo.resolved;

            resolutionInfo.workItems = SVNWCContext.wqMerge(resolutionInfo.workItems, workItem);
        } else {
            resolutionInfo.resolved = false;
        }
        return resolutionInfo;
    }

(and I think that this problem started to arise when we switched from a repo in 1.6 format to one in 1.8 format)

You’re right, there’s nearly no difference between ‘switch’ and ‘update’ commands.

It’s not the relevant piece of code, look at SVNWCContext.ConflictStatusWalker#receive instead.

I wonder if I could ask you to debug the code with a debugger or even schedule a live debugging session, this would be the easiest way to solve the problem?

The way you’re using SVNClientManager except the fact you don’t dispose the object, leaving connection pool unclosed. See http://vcs.atspace.co.uk/2012/09/22/what-svnkit-resources-should-be-disposed/ for details.

Sandbox class creates a working copy with the latest supported working copy format (1.8 is the latest, as I know; 1.9 and 1.10 didn’t change working copy format).

Sorry, I’m afraid I spent already too much time on that issue.

We found a workaround by registering conflicting files as they are discovered (using that , and then using SVNWCClient.doResolve(...) for each one of them.

Thank you very much for your time :-)