1
\$\begingroup\$

My app lists commits from a specific branch and tag on a SVN repo. The commit list is afterwards transferred to the front end which is a web app.

The method for retrieving the commits takes a rather long time, around half a minute when there are around 100 commits associated with a tag.

I am using SVNKit to communicate with the SVN repository.

<dependency>
    <groupId>org.tmatesoft.svnkit</groupId>
    <artifactId>svnkit</artifactId>
    <version>1.2.3.5521</version>
</dependency>

Here are the 2 methods responsible for getting the commit list:

/**
 * get a list of all commits associated with a branch and a specific tag
 * @param branch
 * @param tag
 * @return list<Commit>
 * @throws SVNException
 */
public List<Commit> getBranchCommits(String branch, Tag tag) throws SVNException{

    List<Commit> commitList=new ArrayList<Commit>();
    @SuppressWarnings("unchecked")
    Collection<SVNLogEntry> logEntries = repository.log(new String[] {"/Assurance/branches/"+ branch }, null, -1, tag.getRevision(), true, true);

    for (Iterator<SVNLogEntry> entries = logEntries.iterator(); entries.hasNext();) {
        SVNLogEntry logEntry = (SVNLogEntry) entries.next();
        if(logEntry.getAuthor() == null || "SVN_LOGON".equals(logEntry.getAuthor().toUpperCase()))
            continue;
        Commit commit=constructCommit(logEntry);
        commitList.add(commit);
    }
    return commitList;
}

/**
 * construct the object Commit from a svn log entry
 * 
 * @param logEntry
 *            a svn log entry
 * @return a constructed object Commit
 * @throws SVNException 
 */
private Commit constructCommit(SVNLogEntry logEntry) throws SVNException {

    Commit commit = new Commit(logEntry.getRevision(), logEntry.getDate(), logEntry.getAuthor(), logEntry.getMessage());

    @SuppressWarnings("unchecked")
    Iterator<SVNLogEntryPath> changedPaths = logEntry.getChangedPaths().values().iterator();
    while (changedPaths.hasNext()) {
        SVNLogEntryPath entryPath = changedPaths.next();
        SVNNodeKind nodeKind=repository.checkPath(entryPath.getPath(), -1);
        if ( nodeKind != SVNNodeKind.DIR) {
            commit.getChangedPathList().add(new ChangedPath(entryPath.getPath(), entryPath.getType(), entryPath.getCopyPath()));
        }
    }
    commit.getChangedPathList().sort( new Comparator<ChangedPath>() {
        public int compare(ChangedPath o1, ChangedPath o2) {
            return o1.getPath().compareTo(o2.getPath());
        }
    });

    return commit;
}

Waiting for half a minute or even more is rather unpleasant. Do you have any suggestions for improving this code to make it work faster?

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

First of all, please check whether it is possible to use a newer version of SVNKit. According to mvnrepository, the used version 1.2.3.5521 is dated of March 2009, which is quite dinosaurian in our days.

The obsolete version might explain the long delays you are experiencing (I underline might explain, because there might have been a lot of optimisations compared with the most recent version, but I don't have any precise idea about it).

Another explanation may be the network communication lags with the SVN server the app is connecting to. It hugely depends on your network conditions and it is possible that there is no remedy for that.

To prove that the problem is related with networks, roughly and manually, the calls of methods on repository field can be surrounded with time measurements, as well as method entries and exit points, for example:

public List<Commit> getBranchCommits(String branch, Tag tag) throws SVNException {
  final long methodStart = System.nanoTime();
  // other code

  final long repoStart = System.nanoTime();
  Collection<SVNLogEntry> logEntries = repository.log(...);
  final long repoEnd = System.nanoTime();

  // other code

  final long methodEnd = System.nanoTime();

  System.out.println(String.format("Total time: %1$s ms.", (methodEnd - methodStart) / 1_000_000));
  System.out.println(String.format("Repo call time: %1$s ms.", (repoEnd - repoStart) / 1_000_000));
  return commitList;
}

If repository call times take most part of total execution times, the problem is definitely related with network communications.

As for the code you are trying to improve, I can say that there should not be problems with the code itself. Although the style is a bit oldish, it does not look problematic. Here are some suggestions, using Java 8 and functional style:

public List<Commit> getBranchCommits(String branch, Tag tag) throws SVNException {
  @SuppressWarnings("unchecked")
  Collection<SVNLogEntry> logEntries = repository.log(/* no changes in args */);
  return logEntries.stream()
      .filter(this::shouldSkipEntryByAuthor)
      .map(this::constructCommit)
      .collect(Collectors.toList());
}

private boolean shouldSkipEntryByAuthor(SVNLogEntry logEntry) {
  return !(logEntry.getAuthor() == null || "SVN_LOGON".equalsIgnoreCase(logEntry.getAuthor()));
}

private Commit constructCommit(SVNLogEntry logEntry) {

  final Commit commit = new Commit(logEntry.getRevision(), logEntry.getDate(), logEntry.getAuthor(), logEntry.getMessage());

  // the problem with this obsolete API is that getChangedPaths() returns an untyped Map, we need to cast it properly.
  final List<ChangedPath> changedPaths = ((Map<Object, SVNLogEntryPath>) logEntry.getChangedPaths()).values().stream()
                                                .map(entry -> extractChangedPath(entry))
                                                .filter(Optional::isPresent)
                                                .map(Optional::get)
                                                .sorted(Comparator.comparing(ChangedPath::getPath))
                                                .collect(Collectors.toList());
  commit.setChangedPathList(changedPaths);
  return commit;
}

private Optional<ChangedPath> extractChangedPath(SVNLogEntryPath entry) {
  // we need to wrap SVNException into an unchecked one in order to make it compile in lambdas
  try {
    SVNNodeKind nodeKind = repository.checkPath(entry.getPath(), -1);
    if (!SVNNodeKind.DIR.equals(nodeKind)) {
      return Optional.of(new ChangedPath(entry.getPath(), entry.getType(), entry.getCopyPath()));
    }
    return Optional.empty();
  } catch (SVNException ex) {
    throw new IllegalStateException(ex);
  }
}
\$\endgroup\$
2
  • \$\begingroup\$ Thank you! I have tested and the connection takes only a small part of the time, around 300ms. I have never used functional programming before, I will research more into it, your code does improve a tad on the performance, by around 1/6-1/7 of the time. \$\endgroup\$
    – dannemp
    Jun 28, 2017 at 8:15
  • \$\begingroup\$ I finally found the culprit of the long time! it is the SVNNodeKind nodeKind = repository.checkPath(entry.getPath(), -1); which in fact is useless and takes too much time, the SVNLogEntryPath already has the kind atribute, so I now just use: !SVNNodeKind.DIR.equals(entry.getKind()) \$\endgroup\$
    – dannemp
    Jul 13, 2017 at 13:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.