public Pair<List<RepositoryLocationGroup>, List<RepositoryLocation>> groupLocations(
        final List<RepositoryLocation> in) {
      final List<RepositoryLocationGroup> groups = new ArrayList<RepositoryLocationGroup>();
      final List<RepositoryLocation> singles = new ArrayList<RepositoryLocation>();

      final MultiMap<SVNURL, RepositoryLocation> map = new MultiMap<SVNURL, RepositoryLocation>();

      for (RepositoryLocation location : in) {
        final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location;
        final String url = svnLocation.getURL();

        final SVNURL root = SvnUtil.getRepositoryRoot(myVcs, url);
        if (root == null) {
          // should not occur
          LOG.info("repository root not found for location:" + location.toPresentableString());
          singles.add(location);
        } else {
          map.putValue(root, svnLocation);
        }
      }

      final Set<SVNURL> keys = map.keySet();
      for (SVNURL key : keys) {
        final Collection<RepositoryLocation> repositoryLocations = map.get(key);
        if (repositoryLocations.size() == 1) {
          singles.add(repositoryLocations.iterator().next());
        } else {
          final SvnRepositoryLocationGroup group =
              new SvnRepositoryLocationGroup(key, repositoryLocations);
          groups.add(group);
        }
      }
      return new Pair<List<RepositoryLocationGroup>, List<RepositoryLocation>>(groups, singles);
    }
  public void loadCommittedChanges(
      ChangeBrowserSettings settings,
      RepositoryLocation location,
      int maxCount,
      final AsynchConsumer<CommittedChangeList> consumer)
      throws VcsException {
    try {
      final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location;
      final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
      if (progress != null) {
        progress.setText(SvnBundle.message("progress.text.changes.collecting.changes"));
        progress.setText2(
            SvnBundle.message("progress.text2.changes.establishing.connection", location));
      }

      final String repositoryRoot;
      SVNRepository repository = null;
      try {
        repository = myVcs.createRepository(svnLocation.getURL());
        repositoryRoot = repository.getRepositoryRoot(true).toString();
      } catch (SVNException e) {
        throw new VcsException(e);
      } finally {
        if (repository != null) {
          repository.closeSession();
        }
      }

      final ChangeBrowserSettings.Filter filter = settings.createFilter();

      getCommittedChangesImpl(
          settings,
          svnLocation.getURL(),
          new String[] {""},
          maxCount,
          new Consumer<SVNLogEntry>() {
            public void consume(final SVNLogEntry svnLogEntry) {
              final SvnChangeList cl =
                  new SvnChangeList(myVcs, svnLocation, svnLogEntry, repositoryRoot);
              if (filter.accepts(cl)) {
                consumer.consume(cl);
              }
            }
          },
          false,
          true);
    } finally {
      consumer.finished();
    }
  }
  public List<SvnChangeList> getCommittedChanges(
      ChangeBrowserSettings settings, final RepositoryLocation location, final int maxCount)
      throws VcsException {
    final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location;
    final ArrayList<SvnChangeList> result = new ArrayList<SvnChangeList>();
    final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
    if (progress != null) {
      progress.setText(SvnBundle.message("progress.text.changes.collecting.changes"));
      progress.setText2(
          SvnBundle.message("progress.text2.changes.establishing.connection", location));
    }

    final String repositoryRoot;
    SVNRepository repository = null;
    try {
      repository = myVcs.createRepository(svnLocation.getURL());
      repositoryRoot = repository.getRepositoryRoot(true).toString();
      repository.closeSession();
    } catch (SVNException e) {
      throw new VcsException(e);
    } finally {
      if (repository != null) {
        repository.closeSession();
      }
    }

    getCommittedChangesImpl(
        settings,
        svnLocation.getURL(),
        new String[] {""},
        maxCount,
        new Consumer<SVNLogEntry>() {
          public void consume(final SVNLogEntry svnLogEntry) {
            result.add(new SvnChangeList(myVcs, svnLocation, svnLogEntry, repositoryRoot));
          }
        },
        false,
        true);
    settings.filterChanges(result);
    return result;
  }
  @Override
  public Pair<SvnChangeList, FilePath> getOneList(final VirtualFile file, VcsRevisionNumber number)
      throws VcsException {
    final RootUrlInfo rootUrlInfo =
        myVcs.getSvnFileUrlMapping().getWcRootForFilePath(new File(file.getPath()));
    if (rootUrlInfo == null) return null;
    final VirtualFile root = rootUrlInfo.getVirtualFile();
    if (root == null) return null;
    final SvnRepositoryLocation svnRootLocation =
        (SvnRepositoryLocation) getLocationFor(new FilePathImpl(root));
    if (svnRootLocation == null) return null;
    final String url = svnRootLocation.getURL();
    final long revision;
    try {
      revision = Long.parseLong(number.asString());
    } catch (NumberFormatException e) {
      throw new VcsException(e);
    }

    final SvnChangeList[] result = new SvnChangeList[1];
    final SVNLogClient logger;
    final SVNRevision revisionBefore;
    final SVNURL repositoryUrl;
    final SVNURL svnurl;
    final SVNInfo targetInfo;
    try {
      logger = myVcs.createLogClient();
      revisionBefore = SVNRevision.create(revision);

      svnurl = SVNURL.parseURIEncoded(url);
      final SVNWCClient client = myVcs.createWCClient();
      final SVNInfo info = client.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD);
      targetInfo = client.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED);
      if (info == null) {
        throw new VcsException("Can not get repository URL");
      }
      repositoryUrl = info.getRepositoryRootURL();
    } catch (SVNException e) {
      LOG.info(e);
      throw new VcsException(e);
    }

    tryExactHit(svnRootLocation, result, logger, revisionBefore, repositoryUrl, svnurl);
    if (result[0] == null) {
      tryByRoot(result, logger, revisionBefore, repositoryUrl);
      if (result[0] == null) {
        FilePath path =
            tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl);
        path = path == null ? new FilePathImpl(file) : path;
        // and pass & take rename context there
        return new Pair<SvnChangeList, FilePath>(result[0], path);
      }
    }
    if (result[0].getChanges().size() == 1) {
      final Collection<Change> changes = result[0].getChanges();
      final Change change = changes.iterator().next();
      final ContentRevision afterRevision = change.getAfterRevision();
      if (afterRevision != null) {
        return new Pair<SvnChangeList, FilePath>(result[0], afterRevision.getFile());
      } else {
        return new Pair<SvnChangeList, FilePath>(result[0], new FilePathImpl(file));
      }
    }
    String relativePath =
        SVNPathUtil.getRelativePath(
            targetInfo.getRepositoryRootURL().toString(), targetInfo.getURL().toString());
    relativePath = relativePath.startsWith("/") ? relativePath : "/" + relativePath;
    final Change targetChange = result[0].getByPath(relativePath);
    if (targetChange == null) {
      FilePath path =
          tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl);
      path = path == null ? new FilePathImpl(file) : path;
      // and pass & take rename context there
      return new Pair<SvnChangeList, FilePath>(result[0], path);
    }
    return new Pair<SvnChangeList, FilePath>(result[0], new FilePathImpl(file));
  }
  public void getCommittedChangesWithMergedRevisons(
      final ChangeBrowserSettings settings,
      final RepositoryLocation location,
      final int maxCount,
      final PairConsumer<SvnChangeList, TreeStructureNode<SVNLogEntry>> finalConsumer)
      throws VcsException {
    final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location;
    final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
    if (progress != null) {
      progress.setText(SvnBundle.message("progress.text.changes.collecting.changes"));
      progress.setText2(
          SvnBundle.message("progress.text2.changes.establishing.connection", location));
    }

    final String repositoryRoot;
    SVNRepository repository = null;
    try {
      repository = myVcs.createRepository(svnLocation.getURL());
      repositoryRoot = repository.getRepositoryRoot(true).toString();
    } catch (SVNException e) {
      throw new VcsException(e);
    } finally {
      if (repository != null) {
        repository.closeSession();
      }
    }

    final MergeTrackerProxy proxy =
        new MergeTrackerProxy(
            new Consumer<TreeStructureNode<SVNLogEntry>>() {
              public void consume(TreeStructureNode<SVNLogEntry> node) {
                finalConsumer.consume(
                    new SvnChangeList(myVcs, svnLocation, node.getMe(), repositoryRoot), node);
              }
            });
    final SvnMergeSourceTracker mergeSourceTracker =
        new SvnMergeSourceTracker(
            new ThrowableConsumer<Pair<SVNLogEntry, Integer>, SVNException>() {
              public void consume(Pair<SVNLogEntry, Integer> svnLogEntryIntegerPair)
                  throws SVNException {
                proxy.consume(svnLogEntryIntegerPair);
              }
            });

    getCommittedChangesImpl(
        settings,
        svnLocation.getURL(),
        new String[] {""},
        maxCount,
        new Consumer<SVNLogEntry>() {
          public void consume(final SVNLogEntry svnLogEntry) {
            try {
              mergeSourceTracker.consume(svnLogEntry);
            } catch (SVNException e) {
              throw new RuntimeException(e);
              // will not occur actually but anyway never eat them
            }
          }
        },
        true,
        false);

    proxy.finish();
  }