Problems with examples (Managing A Working Copy)

Hi TMate / SVNKit people,

I was glad to find your SVNKit project so I could try using that to manipulate an SVN repo I’m working on. That’s another story. But today I’ve been reading on SVNKit and tried the examples here - Managing_A_Working_Copy - SVNKit Wiki .

I downloaded all the example files at the bottom and it looks like there are some problems there. There is a lot of usage of SVNEvent.getPath() in 3 of the files, but that does not appear to be a valid method. Maybe it was old and replaced. What was getPath() supposed to do?

Also in WorkingCopy.java, I see an error on line 421 -

ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
ourClientManager = SVNClientManager.newInstance(options, name, password);

Incompatible types: ISVNOptions cannot be converted to DefaultSVNOptions

And WorkingCopy.java line 1089 -

return ourClientManager.getCopyClient().doCopy(srcURL,  SVNRevision.HEAD,
                dstURL, isMove, commitMessage);

Incompatible types: SVNURL cannot be converted to SVNCopySource[]

Also, small fix - in WCEventHandler.java, I see both the ADD and the COPY actions use “A”. That should be “C” for COPY I assume.

My goal for now is that I want to replicate both the “svn info” command and the “svnadmin verify” command. I see that “SVNKit supports all standard Subversion operations” so I hope I can do those.

Hello,
sorry for the outdated documentation we have. Since version 1.7 SVNKit now has a newer API based on SvnOperationFactory class and it should be preferred over older SVNClientManager-based API.

For programmatic “svn info” you can write:

        final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
        try {
            final SvnGetInfo getInfo = svnOperationFactory.createGetInfo();
            getInfo.setSingleTarget(SvnTarget.fromFile(workingCopyDirectory));
            final SvnInfo info = getInfo.run();

            if (info != null) {
                System.out.println("revision = " + info.getRevision());
            }
        } finally {
            svnOperationFactory.dispose();
        }

If you want to run “svn info” on the URL, you can use SvnTarget.fromURL(…). If you want to run single “svn info” on several targets, you can use getInfo.addTarget(...); instead of getInfo.setSingleTarget(...), this is rarely needed, though. Finally, you can use getInfo.setReceiver(...); to set a handler that would react on each object returned. This is rarely needed for “svn info” command but could be useful for some other commands like

final SvnList list = svnOperationFactory.createList();
...
list.setReceiver(...);
list.run();

I hope, this API is more intuitive to use.

If you want to use credentials from ~/.subversion directory, you can configure svnOperationFactory:

svnOperationFactory.setAuthenticationManager(SVNWCUtil.createDefaultAuthenticationManager());

There’re also variations for read-only access to ~/.subversion or for using an alternative directory as credentials storage. Alternatively, you can use any other ISVNAuthenticationManager implementation, e.g. BasicAuthenticationManager class that would explicitly enumerate credentials. By default, no authentication manager is used, i.e. SVNKit will only work with open-source repositories, failing for operations requiring authentication.

Finally I would like you to note that SVNKit contains ‘jsvn’ utility which is a Java-based implementation of Subversion. The sources of this utility are located in

package org.tmatesoft.svn.cli.svn;

package. And SVNCommandEnvironment class contains common code for all the commands, in particular SVNCommandEnvironment#createClientAuthenticationManager creates authentication manager for ‘jsvn’ utility. So you can always refer to this class as the example. Or you can run

org.tmatesoft.svn.cli.SVN.main(...)

in the debugger and see how SVNKit internals are used.

And also you can look at SVNXXXClient classes (i.e. the older API) to see how they are implemented via the newer SvnOperationFactory API. This will give you understanding how the old and the new APIs are related.

What about “svnadmin verify”, there’s only one API for “svnadmin” in SVNKit, it is based on SVNAdminClient class.

            final SVNClientManager clientManager = SVNClientManager.newInstance();
            try {
                final SVNAdminClient adminClient = clientManager.getAdminClient();
                adminClient.setEventHandler(new ISVNAdminEventHandler() {
                    @Override
                    public void handleAdminEvent(SVNAdminEvent event, double progress) throws SVNException {
                        //handle the verification event here
                    }
                    @Override public void handleEvent(SVNEvent event, double progress) throws SVNException {}
                    @Override public void checkCancelled() throws SVNCancelException {}
                });
                adminClient.doVerify(repositoryDirectory);
            } finally {
                clientManager.dispose();
            }

Finally you can look at SVNKit tests as the example of usage of some operations.

I hope this information helps.

Dmitry, thank you for your thorough reply. I will work on this more.

I only tried your first block of code. I wonder what I am supposed to specify for the “workingCopyDirectory” in SvnTarget.fromFile(workingCopyDirectory). I tried entering an absolute path to my repository’s directory on my local computer - new File(“D:/blahblah/svn/repo2”) - but I received an error (SVNException). Is this correct? In the error text, I see it added the “file:///” to the beginning, so that looks familiar. I know it works with svn commands like “svn info” when using the same path.

I received the error
“svn: E155007: ‘D:/blahblah/svn/repo2’ is not a working copy”

Thanks for your help

You should distinguish between working copies and SVN repositories. A working copy is a directory created with “svn checkout” command and containing .svn/wc.db file inside. For working copy

filal File workingCopyDirectory = new File(“D:/blahblah/svn/repo2”);
getInfo.setSingleTarget(SvnTarget.fromFile(workingCopyDirectory));

is the correct code.

If you have a local repository on a filesystem, you access to it using file:///, http(s)://, svn://, or svn+ssh:// protocols. In all of these cases the target is not a working copy but is an URL, and the correct code is

getInfo.setSingleTarget(SvnTarget.fromURL(url));

In case of URLs, it should start with “://” prefix, e.g. “file:///” (in case of “file” protocol, a triple slash should be used).

“svn info” and its SVNKit analog can work with both working copies and URLs, but some commands require only working copies or only URLs. For instance, “svn commit” can only run on a working copy.

Excellent, thank you. I have some working output like “svn info” now!

EDIT - wait nevermind on the problem I had with the “svnadmin verify” . I have more work to do. I’ll reply again later.

I have noticed that when I do an “svn info” command in Subversion command line including a single revision, svn shows the “Last Changed Rev” is the same as that specified revision even if there are more recent revisions in the repository. However SVNKit returns the true last changed revision of the repository. (I have used SvnInfo.setRevision(44) to set the revision in SVNKit.)

The Last Changed Date differs in a similar manner. The “svn info” command gives me a 2014 date for example, while SVNKit gives 2020.

Also, SVNKit returns a different Last Changed Rev Author compared to “svn info”. I believe this is the same issue.

I guess SVNKit is making these results from the overall repository while “svn info” is doing it by the specific individual revision. I don’t know which is better and it does not affect me right now. I thought I would just post this info.

Can you reproduce the same behaviour with ‘jsvn’ utility (i.e. so-called “Standalone Version” from https://svnkit.com/download)?

Ok I tried it and “jsvn info” appears to give the exact same output as “svn info”. I hope this is what you meant. Screenshot-

I’m working on “svn verify” now from the code you put in your first post.

I see that adminClient.doVerify() returns void and there is no printed output. Should I rely completely on exceptions? And if there are no exceptions, verification is successful?

How can I verify a single revision? For SvnInfo, I found I could do setRevision(long revision).
(Like svnadmin verify "D:/blahblah/svn/repo2" -r 17)

Is this correct below?

SVNRevision svnRev = SVNRevision.create(17);
adminClient.doVerify(repoDirectory, svnRev, svnRev);

I’m not sure since I don’t yet have any output from doVerify(). But it see the program runs a long time for a larger repository and a short time for a small repository. Actually this is still running on my larger repository. I hope it isn’t stalled. I will see if it has finished tomorrow.

Regarding “svn info”, I’m not sure what’s the correct behaviour is, so I would give you a generic recommendation: open SVNKit project in IDE, set a breakpoint in SVNInfoCommand#run method to these lines (near line 120):

                if (target.isFile()) {
                    client.doInfo(target.getFile(), pegRevision, getSVNEnvironment().getStartRevision(), depth, 
                            getSVNEnvironment().getChangelistsCollection(), this);
                } else {
                    client.doInfo(target.getURL(), pegRevision, getSVNEnvironment().getStartRevision(), depth, this);
                }

then run

org.tmatesoft.svn.cli.SVN

class with your URL and “-r”, “44” as the arguments. You’ll then see with which arguments client.doInfo(...) is called. And if you step inside into the corresponding SVNWCClient#doInfo method, you’ll see, how SvnOperationFactory and SvnGetInfo are configured to produce the same result.

Well, this is an universal advice for whatever command you run, e.g. you can do the same with SVNAdminVerifyCommand and

org.tmatesoft.svn.cli.svnadmin.SVNAdmin

as the launcher (this is how ‘jsvnadmin’ script is implemented).

To my knowledge the code

SVNRevision svnRev = SVNRevision.create(17);
adminClient.doVerify(repoDirectory, svnRev, svnRev);

is correct, indeed. As I wrote, you should implement ISVNAdminEventHandler#handleAdminEvent (don’t confuse it with handleEvent) and set the handler as the event handler to your SVNAdminClient instance. Once a revision is checked, your event handler will be provided with a corresponding SVNAdminEvent instance.

Theoretically it shouldn’t be stuck, as its basic logic doesn’t contain while (true) {...} cycles. Again, I would recommend you to run org.tmatesoft.svn.cli.svnadmin.SVNAdmin in a debugger to see if it’s really stuck or not.

Ooook got it. I see the SVNAdminEvent comes with a message and I can just print that from the handleAdminEvent method. Here we go, single revision verify works.

*** SVNKit svn info ***
  URL: file:///G:/Dataset-3---/svn/repo
  Repository Root: file:///G:/Dataset-3---/svn/repo
  Repository UUID: 1c64d4f6-f0d2-4467-98c6-ed86b9358a8e
  Revision: 44
  Node Kind: dir
  Last Changed Rev Author: Ab****
  Last Changed Rev: 88821
  Last Changed Date: Thu Oct 14 05:58:47 CDT 2021

*** SVNKit svnadmin verify ***
  Youngest Revision: 88821
  * Verified revision 44.
Finished