package org.tmatesoft.svn.core.internal.wc16;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.tmatesoft.svn.core.ISVNDirEntryHandler;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNAnnotationGenerator;
import org.tmatesoft.svn.core.SVNAuthenticationException;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepository;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
import org.tmatesoft.svn.core.wc.SVNDiffOptions;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNRevisionRange;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * The <b>SVNLogClient</b> class is intended for such purposes as getting
 * revisions history, browsing repository entries and annotating file contents.
 * <p>
 * Here's a list of the <b>SVNLogClient</b>'s methods matched against
 * corresponing commands of the <b>SVN</b> command line client:
 * <table cellpadding="3" cellspacing="1" border="0" width="40%" bgcolor="#999933">
 * <tr bgcolor="#ADB8D9" align="left">
 * <td><b>SVNKit</b></td>
 * <td><b>Subversion</b></td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doLog()</td>
 * <td>'svn log'</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doList()</td>
 * <td>'svn list'</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doAnnotate()</td>
 * <td>'svn blame'</td>
 * </tr>
 * </table>
 * 
 * @version 1.3
 * @author TMate Software Ltd.
 * @since 1.2
 */
public class SVNLogClient16 extends SVNBasicDelegate {

    private SVNDiffOptions myDiffOptions;

    /**
     * Constructs and initializes an <b>SVNLogClient</b> object with the
     * specified run-time configuration and authentication drivers.
     * <p>
     * If <code>options</code> is <span class="javakeyword">null</span>, then
     * this <b>SVNLogClient</b> will be using a default run-time configuration
     * driver which takes client-side settings from the default SVN's run-time
     * configuration area but is not able to change those settings (read more on
     * {@link ISVNOptions} and {@link SVNWCUtil}).
     * <p>
     * If <code>authManager</code> is <span class="javakeyword">null</span>,
     * then this <b>SVNLogClient</b> will be using a default authentication and
     * network layers driver (see
     * {@link SVNWCUtil#createDefaultAuthenticationManager()}) which uses
     * server-side settings and auth storage from the default SVN's run-time
     * configuration area (or system properties if that area is not found).
     * 
     * @param authManager
     *            an authentication and network layers driver
     * @param options
     *            a run-time configuration options driver
     */
    public SVNLogClient16(ISVNAuthenticationManager authManager, ISVNOptions options) {
        super(authManager, options);
    }

    /**
     * Constructs and initializes an <b>SVNLogClient</b> object with the
     * specified run-time configuration and authentication drivers.
     * <p>
     * If <code>options</code> is <span class="javakeyword">null</span>, then
     * this <b>SVNLogClient</b> will be using a default run-time configuration
     * driver which takes client-side settings from the default SVN's run-time
     * configuration area but is not able to change those settings (read more on
     * {@link ISVNOptions} and {@link SVNWCUtil}).
     * <p/>
     * If <code>repositoryPool</code> is <span class="javakeyword">null</span>,
     * then {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory} will be used
     * to create {@link SVNRepository repository access objects}.
     * 
     * @param repositoryPool
     *            a repository pool object
     * @param options
     *            a run-time configuration options driver
     */
    public SVNLogClient16(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
        super(repositoryPool, options);
    }

    /**
     * Sets diff options for this client to use in annotate operations.
     * 
     * @param diffOptions
     *            diff options object
     */
    public void setDiffOptions(SVNDiffOptions diffOptions) {
        this.myDiffOptions = diffOptions;
    }

    /**
     * Gets the diff options that are used in annotate operations by this
     * client. Creates a new one if none was used before.
     * 
     * @return diff options
     */
    public SVNDiffOptions getDiffOptions() {
        return this.myDiffOptions;
    }

    /**
     * Obtains annotation information for each file text line from a repository
     * (using a Working Copy path to get a corresponding URL) and passes it to a
     * provided annotation handler.
     * <p/>
     * This method is equivalent to a call to
     * <code>doAnnotate(path, pegRevision, startRevision, endRevision, false, false, handler, null)</code>.
     * 
     * @param path
     *            a WC file item to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param handler
     *            a caller's handler to process annotation information
     * @throws SVNException
     *             if <code>startRevision > endRevision</code>
     * @see #doAnnotate(File,SVNRevision,SVNRevision,SVNRevision,boolean,boolean,ISVNAnnotateHandler,String)
     */
    public void doAnnotate(File path, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, ISVNAnnotateHandler handler) throws SVNException {
        doAnnotate(path, pegRevision, startRevision, endRevision, false, false, handler, null);
    }

    /**
     * Obtains annotation information for each file text line from a repository
     * (using a Working Copy path to get a corresponding URL) and passes it to a
     * provided annotation handler.
     * <p/>
     * This method is equivalent to a call to
     * <code>doAnnotate(path, pegRevision, startRevision, endRevision, ignoreMimeType, false, handler, null)</code>.
     * 
     * @param path
     *            a WC file item to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param ignoreMimeType
     *            forces operation to run (all files to be treated as text, no
     *            matter what SVNKit has inferred from the mime-type property)
     * @param handler
     *            a caller's handler to process annotation information
     * @throws SVNException
     * @see #doAnnotate(File,SVNRevision,SVNRevision,SVNRevision,boolean,boolean,ISVNAnnotateHandler,String)
     * @since 1.1
     */
    public void doAnnotate(File path, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean ignoreMimeType, ISVNAnnotateHandler handler) throws SVNException {
        doAnnotate(path, pegRevision, startRevision, endRevision, ignoreMimeType, false, handler, null);
    }

    /**
     * Invokes <code>handler</code> on each line-blame item associated with
     * revision <code>endRevision</code> of <code>path</code>, using
     * <code>startRevision</code> as the default source of all blame.
     * <p/>
     * <code>pegRevision</code> indicates in which revision <code>path</code> is
     * valid. If <code>pegRevision</code> is {@link SVNRevision#UNDEFINED}, then
     * it defaults to {@link SVNRevision#WORKING}.
     * <p/>
     * If <code>startRevision</code> is <span class="javakeyword">null</span> or
     * {@link SVNRevision#isValid() invalid}, then it defaults to revision 1. If
     * <code>endRevision</code> is <span class="javakeyword">null</span> or
     * {@link SVNRevision#isValid() invalid}, then in defaults to
     * {@link SVNRevision#HEAD}.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param path
     *            a WC file item to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param ignoreMimeType
     *            forces operation to run (all files to be treated as text, no
     *            matter what SVNKit has inferred from the mime-type property)
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, then also returns
     *            data based upon revisions which have been merged to
     *            <code>path</code>
     * @param handler
     *            a caller's handler to process annotation information
     * @param inputEncoding
     *            character set to decode input bytes with
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#CLIENT_BAD_REVISION}
     *             error code - if both <code>startRevision</code> and <code>
     *             endRevision</code> are either <span
     *             class="javakeyword">null</span> or
     *             {@link SVNRevision#isValid() invalid} <li/>exception with
     *             {@link SVNErrorCode#UNSUPPORTED_FEATURE} error code - if
     *             either of <code>startRevision</code> or <code>endRevision
     *             </code> is {@link SVNRevision#WORKING} <li/>exception with
     *             {@link SVNErrorCode#CLIENT_IS_BINARY_FILE} error code - if
     *             any of the revisions of <code>path</code> have a binary
     *             mime-type, unless <code>ignoreMimeType</code> is <span
     *             class="javakeyword">true</span>, in which case blame
     *             information will be generated regardless of the MIME types of
     *             the revisions
     *             </ul>
     * @since 1.2, SVN 1.5
     */
    public void doAnnotate(File path, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean ignoreMimeType, boolean includeMergedRevisions, ISVNAnnotateHandler handler,
            String inputEncoding) throws SVNException {
        if (startRevision == null || !startRevision.isValid()) {
            startRevision = SVNRevision.create(1);
        }
        if (endRevision == null || !endRevision.isValid()) {
            endRevision = pegRevision;
        }
        if (startRevision == SVNRevision.WORKING || endRevision == SVNRevision.WORKING) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Blame of the WORKING revision is not supported");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        SVNRepository repos = createRepository(null, path, null, pegRevision, endRevision, null);
        long endRev = getRevisionNumber(endRevision, repos, path);
        long startRev = getRevisionNumber(startRevision, repos, path);
        if (endRev < startRev) {
            SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Start revision must precede end revision"), SVNLogType.DEFAULT);
        }
        File tmpFile = new File(path.getParentFile(), SVNFileUtil.getAdminDirectoryName());
        tmpFile = new File(tmpFile, "tmp/text-base");
        if (!tmpFile.isDirectory()) {
            tmpFile = SVNFileUtil.createTempDirectory("annotate");
        }
        doAnnotate(path.getAbsolutePath(), startRev, tmpFile, repos, endRev, ignoreMimeType, handler, inputEncoding, includeMergedRevisions);
    }

    /**
     * Obtains annotation information for each file text line from a repository
     * and passes it to a provided annotation handler.
     * <p>
     * This method is equivalent to a call to
     * <code>doAnnotate(url, pegRevision, startRevision, endRevision, false, false, handler, null)</code>.
     * 
     * @param url
     *            a URL of a text file that is to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param handler
     *            a caller's handler to process annotation information
     * @throws SVNException
     *             if <code>startRevision > endRevision</code>
     * @see #doAnnotate(SVNURL,SVNRevision,SVNRevision,SVNRevision,boolean,boolean,ISVNAnnotateHandler,String)
     */
    public void doAnnotate(SVNURL url, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, ISVNAnnotateHandler handler) throws SVNException {
        doAnnotate(url, pegRevision, startRevision, endRevision, false, false, handler, null);
    }

    /**
     * Obtains annotation information for each file text line from a repository
     * and passes it to a provided annotation handler.
     * <p>
     * This method is equivalent to a call to
     * <code>doAnnotate(url, pegRevision, startRevision, endRevision, false, false, handler, inputEncoding)</code>.
     * 
     * @param url
     *            a URL of a text file that is to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param handler
     *            a caller's handler to process annotation information
     * @param inputEncoding
     *            a desired character set (encoding) of text lines
     * @throws SVNException
     * @see #doAnnotate(SVNURL,SVNRevision,SVNRevision,SVNRevision,boolean,boolean,ISVNAnnotateHandler,String)
     */
    public void doAnnotate(SVNURL url, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, ISVNAnnotateHandler handler, String inputEncoding) throws SVNException {
        doAnnotate(url, pegRevision, startRevision, endRevision, false, false, handler, inputEncoding);
    }

    /**
     * Obtains annotation information for each file text line from a repository
     * and passes it to a provided annotation handler.
     * <p>
     * This method is equivalent to a call to
     * 
     * <code>doAnnotate(url, pegRevision, startRevision, endRevision, ignoreMimeType, false, handler, inputEncoding)</code>.
     * 
     * @param url
     *            a URL of a text file that is to be annotated
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param ignoreMimeType
     *            forces operation to run (all files to be treated as text, no
     *            matter what SVNKit has inferred from the mime-type property)
     * @param handler
     *            a caller's handler to process annotation information
     * @param inputEncoding
     *            a desired character set (encoding) of text lines
     * @throws SVNException
     * @see #doAnnotate(SVNURL,SVNRevision,SVNRevision,SVNRevision,boolean,boolean,ISVNAnnotateHandler,String)
     * @since 1.1
     */
    public void doAnnotate(SVNURL url, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean ignoreMimeType, ISVNAnnotateHandler handler, String inputEncoding)
            throws SVNException {
        doAnnotate(url, pegRevision, startRevision, endRevision, ignoreMimeType, false, handler, inputEncoding);
    }

    /**
     * Invokes <code>handler</code> on each line-blame item associated with
     * revision <code>endRevision</code> of <code>url</code>, using
     * <code>startRevision</code> as the default source of all blame.
     * <p/>
     * <code>pegRevision</code> indicates in which revision <code>url</code> is
     * valid. If <code>pegRevision</code> is {@link SVNRevision#UNDEFINED}, then
     * it defaults to {@link SVNRevision#HEAD}.
     * <p/>
     * If <code>startRevision</code> is <span class="javakeyword">null</span> or
     * {@link SVNRevision#isValid() invalid}, then it defaults to revision 1. If
     * <code>endRevision</code> is <span class="javakeyword">null</span> or
     * {@link SVNRevision#isValid() invalid}, then in defaults to
     * {@link SVNRevision#HEAD}.
     * <p/>
     * Note: this routine requires repository access
     * 
     * @param url
     *            a URL of a text file that is to be annotated
     * @param pegRevision
     *            a revision in which <code>url</code> is first looked up in the
     *            repository
     * @param startRevision
     *            a revision for an operation to start from
     * @param endRevision
     *            a revision for an operation to stop at
     * @param ignoreMimeType
     *            forces operation to run (all files to be treated as text, no
     *            matter what SVNKit has inferred from the mime-type property)
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, then also returns
     *            data based upon revisions which have been merged to
     *            <code>url</code>
     * @param handler
     *            a caller's handler to process annotation information
     * @param inputEncoding
     *            character set to decode input bytes with
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#CLIENT_BAD_REVISION}
     *             error code - if both <code>startRevision</code> and <code>
     *             endRevision</code> are either <span
     *             class="javakeyword">null</span> or
     *             {@link SVNRevision#isValid() invalid} <li/>exception with
     *             {@link SVNErrorCode#UNSUPPORTED_FEATURE} error code - if
     *             either of <code>startRevision</code> or <code>endRevision
     *             </code> is {@link SVNRevision#WORKING} <li/>exception with
     *             {@link SVNErrorCode#CLIENT_IS_BINARY_FILE} error code - if
     *             any of the revisions of <code>url</code> have a binary
     *             mime-type, unless <code>ignoreMimeType</code> is <span
     *             class="javakeyword">true</span>, in which case blame
     *             information will be generated regardless of the MIME types of
     *             the revisions
     *             </ul>
     * @since 1.2, SVN 1.5
     */
    public void doAnnotate(SVNURL url, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean ignoreMimeType, boolean includeMergedRevisions,
            ISVNAnnotateHandler handler, String inputEncoding) throws SVNException {
        if (startRevision == null || !startRevision.isValid()) {
            startRevision = SVNRevision.create(1);
        }
        if (endRevision == null || !endRevision.isValid()) {
            endRevision = pegRevision;
        }
        if (startRevision == SVNRevision.WORKING || endRevision == SVNRevision.WORKING) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Blame of the WORKING revision is not supported");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        SVNRepository repos = createRepository(url, null, null, pegRevision, endRevision, null);
        long endRev = getRevisionNumber(endRevision, repos, null);
        long startRev = getRevisionNumber(startRevision, repos, null);
        if (endRev < startRev) {
            SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Start revision must precede end revision"), SVNLogType.DEFAULT);
        }
        File tmpFile = SVNFileUtil.createTempDirectory("annotate");
        doAnnotate(repos.getLocation().toString(), startRev, tmpFile, repos, endRev, ignoreMimeType, handler, inputEncoding, includeMergedRevisions);
    }

    /**
     * Gets commit log messages with other revision specific information from a
     * repository (using Working Copy paths to get corresponding URLs) and
     * passes them to a log entry handler for processing. Useful for observing
     * the history of affected paths, author, date and log comments information
     * per revision.
     * <p>
     * Calling this method is equivalent to
     * 
     * <code>doLog(paths, startRevision, endRevision, SVNRevision.UNDEFINED, stopOnCopy, discoverChangedPaths, false, limit, null, handler)</code>.
     * 
     * @param paths
     *            an array of Working Copy paths, should not be <span
     *            class="javakeyword">null</span>
     * @param startRevision
     *            a revision for an operation to start from (including this
     *            revision)
     * @param endRevision
     *            a revision for an operation to stop at (including this
     *            revision)
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param limit
     *            a maximum number of log entries to be processed
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li>a path is not under version control <li>can not obtain a
     *             URL of a WC path - there's no such entry in the Working Copy
     *             <li><code>paths</code> contain entries that belong to
     *             different repositories
     *             </ul>
     * @see #doLog(File[],SVNRevision,SVNRevision,SVNRevision,boolean,boolean,boolean,long,String[],ISVNLogEntryHandler)
     */
    public void doLog(File[] paths, SVNRevision startRevision, SVNRevision endRevision, boolean stopOnCopy, boolean discoverChangedPaths, long limit, final ISVNLogEntryHandler handler)
            throws SVNException {
        doLog(paths, startRevision, endRevision, SVNRevision.UNDEFINED, stopOnCopy, discoverChangedPaths, false, limit, null, handler);
    }

    /**
     * Invokes <code>handler</code> on each log message from
     * <code>startRevision</code> to <code>endRevision</code> in turn, inclusive
     * (but never invokes <code>handler</code> on a given log message more than
     * once).
     * <p/>
     * <code>handler</code> is invoked only on messages whose revisions involved
     * a change to some path in <code>paths</code>. <code>pegRevision</code>
     * indicates in which revision <code>paths</code> are valid. If
     * <code>pegRevision</code> is{@link SVNRevision#isValid() invalid}, it
     * defaults to {@link SVNRevision#WORKING}.
     * <p/>
     * If <code>limit</code> is non-zero, only invokes <code>handler</code> on
     * the first <code>limit</code> logs.
     * <p/>
     * If <code>discoverChangedPaths</code> is set, then the changed paths
     * <code>Map</code> argument will be passed to a constructor of
     * {@link SVNLogEntry} on each invocation of <code>handler</code>.
     * <p/>
     * If <code>stopOnCopy</code> is set, copy history (if any exists) will not
     * be traversed while harvesting revision logs for each target.
     * <p/>
     * If <code>includeMergedRevisions</code> is set, log information for
     * revisions which have been merged to <code>paths</code> will also be
     * returned.
     * <p/>
     * Refer to {@link org.tmatesoft.svn.core.SVNLogEntry#hasChildren()} for
     * additional information on how to handle mergeinfo information during a
     * log operation.
     * <p/>
     * If
     * <code>revisionProperties is <span class="javakeyword">null</span>, retrieves all revision properties; 
     * else, retrieves only the revprops named in the array (i.e. retrieves none if the array is empty).
     * <p/>
     * If <code>startRevision</code> is {@link SVNRevision#isValid() valid} but
     * <code>endRevision</code> is not, then <code>endRevision</code> defaults
     * to <code>startRevision</code>. If both <code>startRevision</code> and
     * <code>endRevision</code> are invalid, then <code>endRevision</code>
     * defaults to revision <code>0</code>, and <code>startRevision</code>
     * defaults either to <code>pegRevision</code> in case the latter one is
     * valid, or to {@link SVNRevision#BASE}, if it is not.
     * <p/>
     * Important: to avoid an exception with the
     * {@link SVNErrorCode#FS_NO_SUCH_REVISION} error code when invoked against
     * an empty repository (i.e. one not containing a revision 1), callers
     * should specify the range {@link SVNRevision#HEAD}:<code>0</code>.
     * <p/>
     * If the caller has provided a non-<span class="javakeyword">null</span>
     * {@link ISVNEventHandler}, it will be called with the
     * {@link SVNEventAction#SKIP} event action on any unversioned paths.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param paths
     *            an array of Working Copy paths, for which log messages are
     *            desired
     * @param startRevision
     *            a revision for an operation to start from (including this
     *            revision)
     * @param endRevision
     *            a revision for an operation to stop at (including this
     *            revision)
     * @param pegRevision
     *            a revision in which <code>paths</code> are first looked up in
     *            the repository
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, merged revisions
     *            will be also reported
     * @param limit
     *            a maximum number of log entries to be processed
     * @param revisionProperties
     *            names of revision properties to retrieve
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li>can not obtain a URL of a WC path - there's no such entry
     *             in the Working Copy <li><code>paths</code> contain entries
     *             that belong to different repositories
     *             </ul>
     * @since 1.2, SVN 1.5
     */
    public void doLog(File[] paths, SVNRevision startRevision, SVNRevision endRevision, SVNRevision pegRevision, boolean stopOnCopy, boolean discoverChangedPaths, boolean includeMergedRevisions,
            long limit, String[] revisionProperties, final ISVNLogEntryHandler handler) throws SVNException {
        Collection revisionRanges = new ArrayList(1);
        revisionRanges.add(new SVNRevisionRange(startRevision, endRevision));
        doLog(paths, revisionRanges, pegRevision, stopOnCopy, discoverChangedPaths, includeMergedRevisions, limit, revisionProperties, handler);
    }

    /**
     * Invokes <code>handler</code> on each log message from the given
     * <code>revisionRanges</code> in turn, inclusive (but never invokes
     * <code>handler</code> on a given log message more than once).
     * <p/>
     * <code>handler</code> is invoked only on messages whose revisions involved
     * a change to some path in <code>paths</code>. <code>pegRevision</code>
     * indicates in which revision <code>paths</code> are valid. If
     * <code>pegRevision</code> is{@link SVNRevision#isValid() invalid}, it
     * defaults to {@link SVNRevision#WORKING}.
     * <p/>
     * If <code>limit</code> is non-zero, only invokes <code>handler</code> on
     * the first <code>limit</code> logs.
     * <p/>
     * If <code>discoverChangedPaths</code> is set, then the changed paths
     * <code>Map</code> argument will be passed to a constructor of
     * {@link SVNLogEntry} on each invocation of <code>handler</code>.
     * <p/>
     * If <code>stopOnCopy</code> is set, copy history (if any exists) will not
     * be traversed while harvesting revision logs for each target.
     * <p/>
     * If <code>includeMergedRevisions</code> is set, log information for
     * revisions which have been merged to <code>paths</code> will also be
     * returned.
     * <p/>
     * Refer to {@link org.tmatesoft.svn.core.SVNLogEntry#hasChildren()} for
     * additional information on how to handle mergeinfo information during a
     * log operation.
     * <p/>
     * If
     * <code>revisionProperties is <span class="javakeyword">null</span>, retrieves all revision properties; 
     * else, retrieves only the revprops named in the array (i.e. retrieves none if the array is empty).
     * <p/>
     * For every {@link SVNRevisionRange} in <code>revisionRanges</code>: <b/>
     * If <code>startRevision</code> is {@link SVNRevision#isValid() valid} but
     * <code>endRevision</code> is not, then <code>endRevision</code> defaults
     * to <code>startRevision</code>. If both <code>startRevision</code> and
     * <code>endRevision</code> are invalid, then <code>endRevision</code>
     * defaults to revision <code>0</code>, and <code>startRevision</code>
     * defaults either to <code>pegRevision</code> in case the latter one is
     * valid, or to {@link SVNRevision#BASE}, if it is not.
     * <p/>
     * Important: to avoid an exception with the
     * {@link SVNErrorCode#FS_NO_SUCH_REVISION} error code when invoked against
     * an empty repository (i.e. one not containing a revision 1), callers
     * should specify the range {@link SVNRevision#HEAD}:<code>0</code>.
     * <p/>
     * If the caller has provided a non-<span class="javakeyword">null</span>
     * {@link ISVNEventHandler}, it will be called with the
     * {@link SVNEventAction#SKIP} event action on any unversioned paths.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param paths
     *            an array of Working Copy paths, for which log messages are
     *            desired
     * @param revisionRanges
     *            collection of {@link SVNRevisionRange} objects
     * @param pegRevision
     *            a revision in which <code>paths</code> are first looked up in
     *            the repository
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, merged revisions
     *            will be also reported
     * @param limit
     *            a maximum number of log entries to be processed
     * @param revisionProperties
     *            names of revision properties to retrieve
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li>can not obtain a URL of a WC path - there's no such entry
     *             in the Working Copy <li><code>paths</code> contain entries
     *             that belong to different repositories
     *             </ul>
     * @since 1.3, SVN 1.6
     */
    public void doLog(File[] paths, Collection revisionRanges, SVNRevision pegRevision, boolean stopOnCopy, boolean discoverChangedPaths, boolean includeMergedRevisions, long limit,
            String[] revisionProperties, final ISVNLogEntryHandler handler) throws SVNException {
        if (paths == null || paths.length == 0 || handler == null) {
            return;
        }
        SVNRevision sessionRevision = SVNRevision.UNDEFINED;
        List editedRevisionRanges = new LinkedList();
        for (Iterator revRangesIter = revisionRanges.iterator(); revRangesIter.hasNext();) {
            SVNRevisionRange revRange = (SVNRevisionRange) revRangesIter.next();
            if (revRange.getStartRevision().isValid() && !revRange.getEndRevision().isValid()) {
                revRange = new SVNRevisionRange(revRange.getStartRevision(), revRange.getStartRevision());
            } else if (!revRange.getStartRevision().isValid()) {
                SVNRevision start = SVNRevision.UNDEFINED;
                SVNRevision end = SVNRevision.UNDEFINED;
                if (!pegRevision.isValid()) {
                    start = SVNRevision.BASE;
                } else {
                    start = pegRevision;
                }
                if (!revRange.getEndRevision().isValid()) {
                    end = SVNRevision.create(0);
                }
                revRange = new SVNRevisionRange(start, end);
            }
            if (!revRange.getStartRevision().isValid() || !revRange.getEndRevision().isValid()) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Missing required revision specification");
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            editedRevisionRanges.add(revRange);
            if (!sessionRevision.isValid()) {
                SVNRevision start = revRange.getStartRevision();
                SVNRevision end = revRange.getEndRevision();
                if (SVNRevision.isValidRevisionNumber(start.getNumber()) && SVNRevision.isValidRevisionNumber(end.getNumber())) {
                    sessionRevision = start.getNumber() > end.getNumber() ? start : end;
                } else if (start.getDate() != null && end.getDate() != null) {
                    sessionRevision = start.getDate().compareTo(end.getDate()) > 0 ? start : end;
                }
            }
        }
        if (limit > Integer.MAX_VALUE) {
            limit = Integer.MAX_VALUE;
        }
        ISVNLogEntryHandler wrappingHandler = new ISVNLogEntryHandler() {

            public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
                checkCancelled();
                handler.handleLogEntry(logEntry);
            }
        };
        SVNURL[] urls = new SVNURL[paths.length];
        SVNWCAccess wcAccess = createWCAccess();
        Collection wcPaths = new ArrayList();
        for (int i = 0; i < paths.length; i++) {
            checkCancelled();
            File path = paths[i];
            wcPaths.add(path.getAbsolutePath().replace(File.separatorChar, '/'));
            SVNAdminArea area = wcAccess.probeOpen(path, false, 0);
            SVNEntry entry = wcAccess.getVersionedEntry(path, false);
            if (entry.getURL() == null) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Entry ''{0}'' has no URL", path);
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            urls[i] = entry.getSVNURL();
            if (area != null) {
                wcAccess.closeAdminArea(area.getRoot());
            }
        }
        if (urls.length == 0) {
            return;
        }
        String[] wcPathsArray = (String[]) wcPaths.toArray(new String[wcPaths.size()]);
        String rootWCPath = SVNPathUtil.condencePaths(wcPathsArray, null, true);
        Collection targets = new TreeSet();
        SVNURL baseURL = SVNURLUtil.condenceURLs(urls, targets, true);
        if (baseURL == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "target log paths belong to different repositories");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (targets.isEmpty()) {
            targets.add("");
        }
        if (!pegRevision.isValid()) {
            pegRevision = SVNRevision.WORKING;
        }
        SVNRepository repos = null;
        if (rootWCPath != null && needsWC(pegRevision)) {
            File root = new File(rootWCPath);
            SVNAdminArea area = wcAccess.probeOpen(root, false, 0);
            repos = createRepository(null, root, area, pegRevision, sessionRevision, null);
            if (area != null) {
                wcAccess.closeAdminArea(area.getRoot());
            }
        } else {
            repos = createRepository(baseURL, null, null, pegRevision, sessionRevision, null);
        }
        String[] targetPaths = (String[]) targets.toArray(new String[targets.size()]);
        for (int i = 0; i < targetPaths.length; i++) {
            targetPaths[i] = SVNEncodingUtil.uriDecode(targetPaths[i]);
        }
        for (Iterator revRangesIter = editedRevisionRanges.iterator(); revRangesIter.hasNext();) {
            checkCancelled();
            SVNRevisionRange revRange = (SVNRevisionRange) revRangesIter.next();
            SVNRevision startRevision = revRange.getStartRevision();
            SVNRevision endRevision = revRange.getEndRevision();
            if (startRevision.isLocal() || endRevision.isLocal()) {
                for (int i = 0; i < paths.length; i++) {
                    checkCancelled();
                    long startRev = getRevisionNumber(startRevision, repos, paths[i]);
                    long endRev = getRevisionNumber(endRevision, repos, paths[i]);
                    repos.log(targetPaths, startRev, endRev, discoverChangedPaths, stopOnCopy, limit, includeMergedRevisions, revisionProperties, wrappingHandler);
                }
            } else {
                long startRev = getRevisionNumber(startRevision, repos, null);
                long endRev = getRevisionNumber(endRevision, repos, null);
                repos.log(targetPaths, startRev, endRev, discoverChangedPaths, stopOnCopy, limit, includeMergedRevisions, revisionProperties, wrappingHandler);
            }
        }
    }

    /**
     * Gets commit log messages with other revision specific information from a
     * repository (using Working Copy paths to get corresponding URLs) and
     * passes them to a log entry handler for processing. Useful for observing
     * the history of affected paths, author, date and log comments information
     * per revision.
     * <p>
     * Calling this method is equivalent to
     * 
     * <code>doLog(paths, startRevision, endRevision, pegRevision, stopOnCopy, discoverChangedPaths, false, limit, null, handler)</code>.
     * 
     * @param paths
     *            an array of Working Copy paths, should not be <span
     *            class="javakeyword">null</span>
     * @param pegRevision
     *            a revision in which <code>path</code> is first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from (including this
     *            revision)
     * @param endRevision
     *            a revision for an operation to stop at (including this
     *            revision)
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param limit
     *            a maximum number of log entries to be processed
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li>a path is not under version control <li>can not obtain a
     *             URL of a WC path - there's no such entry in the Working Copy
     *             <li><code>paths</code> contain entries that belong to
     *             different repositories
     *             </ul>
     * @see #doLog(File[],SVNRevision,SVNRevision,SVNRevision,boolean,boolean,boolean,long,String[],ISVNLogEntryHandler)
     */
    public void doLog(File[] paths, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean stopOnCopy, boolean discoverChangedPaths, long limit,
            final ISVNLogEntryHandler handler) throws SVNException {
        doLog(paths, startRevision, endRevision, pegRevision, stopOnCopy, discoverChangedPaths, false, limit, null, handler);
    }

    /**
     * Gets commit log messages with other revision specific information from a
     * repository and passes them to a log entry handler for processing. Useful
     * for observing the history of affected paths, author, date and log
     * comments information per revision.
     * <p>
     * Calling this method is equivalent to
     * 
     * <code> doLog(url, paths, pegRevision, startRevision, endRevision, stopOnCopy, discoverChangedPaths, false, limit, null, handler)</code>.
     * 
     * @param url
     *            a target URL
     * @param paths
     *            an array of paths relative to the target <code>url</code>
     * @param pegRevision
     *            a revision in which <code>url</code> is first looked up
     * @param startRevision
     *            a revision for an operation to start from (including this
     *            revision)
     * @param endRevision
     *            a revision for an operation to stop at (including this
     *            revision)
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param limit
     *            a maximum number of log entries to be processed
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     * @see #doLog(SVNURL,String[],SVNRevision,SVNRevision,SVNRevision,boolean,boolean,boolean,long,String[],ISVNLogEntryHandler)
     * @since 1.1, new in Subversion 1.4
     */
    public void doLog(SVNURL url, String[] paths, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean stopOnCopy, boolean discoverChangedPaths, long limit,
            final ISVNLogEntryHandler handler) throws SVNException {
        doLog(url, paths, pegRevision, startRevision, endRevision, stopOnCopy, discoverChangedPaths, false, limit, null, handler);
    }

    /**
     * Invokes <code>handler</code> on each log message from
     * <code>startRevision</code> to <code>endRevision</code> in turn, inclusive
     * (but never invokes <code>handler</code> on a given log message more than
     * once).
     * <p/>
     * <code>handler</code> is invoked only on messages whose revisions involved
     * a change to some path in <code>paths</code>. <code>pegRevision</code>
     * indicates in which revision <code>paths</code> are valid. If
     * <code>pegRevision</code> is{@link SVNRevision#isValid() invalid}, it
     * defaults to {@link SVNRevision#HEAD}.
     * <p/>
     * If <code>limit</code> is non-zero, only invokes <code>handler</code> on
     * the first <code>limit</code> logs.
     * <p/>
     * If <code>discoverChangedPaths</code> is set, then the changed paths
     * <code>Map</code> argument will be passed to a constructor of
     * {@link SVNLogEntry} on each invocation of <code>handler</code>.
     * <p/>
     * If <code>stopOnCopy</code> is set, copy history (if any exists) will not
     * be traversed while harvesting revision logs for each target.
     * <p/>
     * If <code>includeMergedRevisions</code> is set, log information for
     * revisions which have been merged to <code>paths</code> will also be
     * returned.
     * <p/>
     * If
     * <code>revisionProperties is <span class="javakeyword">null</span>, retrieves all revision properties; 
     * else, retrieves only the revision properties named in the array (i.e. retrieves none if the array is empty).
     * <p/>
     * Important: to avoid an exception with the {@link SVNErrorCode#FS_NO_SUCH_REVISION} error code  
	 * when invoked against an empty repository (i.e. one not containing a revision 1), callers should specify 
	 * the range {@link SVNRevision#HEAD}:<code>0</code>.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param url
     *            repository URL
     * @param paths
     *            an array of paths relative to <code>url</code>
     * @param pegRevision
     *            a revision in which <code>paths</code> are first looked up in
     *            the repository
     * @param startRevision
     *            a revision for an operation to start from (including this
     *            revision)
     * @param endRevision
     *            a revision for an operation to stop at (including this
     *            revision)
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, merged revisions
     *            will be also reported
     * @param limit
     *            a maximum number of log entries to be processed
     * @param revisionProperties
     *            names of revision properties to retrieve
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     * @since 1.2, SVN 1.5
     */
    public void doLog(SVNURL url, String[] paths, SVNRevision pegRevision, SVNRevision startRevision, SVNRevision endRevision, boolean stopOnCopy, boolean discoverChangedPaths,
            boolean includeMergedRevisions, long limit, String[] revisionProperties, final ISVNLogEntryHandler handler) throws SVNException {
        Collection revisionRanges = new ArrayList(1);
        revisionRanges.add(new SVNRevisionRange(startRevision, endRevision));
        doLog(url, paths, pegRevision, revisionRanges, stopOnCopy, discoverChangedPaths, includeMergedRevisions, limit, revisionProperties, handler);
    }

    /**
     * Invokes <code>handler</code> on each log message from the given
     * <code></code> in turn, inclusive (but never invokes <code>handler</code>
     * on a given log message more than once).
     * <p/>
     * <code>handler</code> is invoked only on messages whose revisions involved
     * a change to some path in <code>paths</code>. <code>pegRevision</code>
     * indicates in which revision <code>paths</code> are valid. If
     * <code>pegRevision</code> is{@link SVNRevision#isValid() invalid}, it
     * defaults to {@link SVNRevision#HEAD}.
     * <p/>
     * If <code>limit</code> is non-zero, only invokes <code>handler</code> on
     * the first <code>limit</code> logs.
     * <p/>
     * If <code>discoverChangedPaths</code> is set, then the changed paths
     * <code>Map</code> argument will be passed to a constructor of
     * {@link SVNLogEntry} on each invocation of <code>handler</code>.
     * <p/>
     * If <code>stopOnCopy</code> is set, copy history (if any exists) will not
     * be traversed while harvesting revision logs for each target.
     * <p/>
     * If <code>includeMergedRevisions</code> is set, log information for
     * revisions which have been merged to <code>paths</code> will also be
     * returned.
     * <p/>
     * If
     * <code>revisionProperties is <span class="javakeyword">null</span>, retrieves all revision properties; 
     * else, retrieves only the revision properties named in the array (i.e. retrieves none if the array is empty).
     * <p/>
     * Important: to avoid an exception with the {@link SVNErrorCode#FS_NO_SUCH_REVISION} error code  
	 * when invoked against an empty repository (i.e. one not containing a revision 1), callers should specify 
	 * the range {@link SVNRevision#HEAD}:<code>0</code>.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param url
     *            repository URL
     * @param paths
     *            an array of paths relative to <code>url</code>
     * @param pegRevision
     *            a revision in which <code>paths</code> are first looked up in
     *            the repository
     * @param revisionRanges
     *            collection of {@link SVNRevisionRange} objects
     * @param stopOnCopy
     *            <span class="javakeyword">true</span> not to cross copies
     *            while traversing history, otherwise copies history will be
     *            also included into processing
     * @param discoverChangedPaths
     *            <span class="javakeyword">true</span> to report of all changed
     *            paths for every revision being processed (those paths will be
     *            available by calling
     *            {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()})
     * @param includeMergedRevisions
     *            if <span class="javakeyword">true</span>, merged revisions
     *            will be also reported
     * @param limit
     *            a maximum number of log entries to be processed
     * @param revisionProperties
     *            names of revision properties to retrieve
     * @param handler
     *            a caller's log entry handler
     * @throws SVNException
     * @since 1.3, SVN 1.6
     */
    public void doLog(SVNURL url, String[] paths, SVNRevision pegRevision, Collection revisionRanges, boolean stopOnCopy, boolean discoverChangedPaths, boolean includeMergedRevisions, long limit,
            String[] revisionProperties, final ISVNLogEntryHandler handler) throws SVNException {
        SVNRevision sessionRevision = SVNRevision.UNDEFINED;
        List editedRevisionRanges = new LinkedList();
        for (Iterator revRangesIter = revisionRanges.iterator(); revRangesIter.hasNext();) {
            SVNRevisionRange revRange = (SVNRevisionRange) revRangesIter.next();
            if (revRange.getStartRevision().isValid() && !revRange.getEndRevision().isValid()) {
                revRange = new SVNRevisionRange(revRange.getStartRevision(), revRange.getStartRevision());
            } else if (!revRange.getStartRevision().isValid()) {
                SVNRevision start = SVNRevision.UNDEFINED;
                SVNRevision end = SVNRevision.UNDEFINED;
                if (!pegRevision.isValid()) {
                    start = SVNRevision.HEAD;
                } else {
                    start = pegRevision;
                }
                if (!revRange.getEndRevision().isValid()) {
                    end = SVNRevision.create(0);
                }
                revRange = new SVNRevisionRange(start, end);
            }
            if (!revRange.getStartRevision().isValid() || !revRange.getEndRevision().isValid()) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Missing required revision specification");
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            if (needsWC(revRange.getStartRevision()) || needsWC(revRange.getEndRevision())) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Revision type requires a working copy path, not a URL");
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            editedRevisionRanges.add(revRange);
            if (!sessionRevision.isValid()) {
                SVNRevision start = revRange.getStartRevision();
                SVNRevision end = revRange.getEndRevision();
                if (SVNRevision.isValidRevisionNumber(start.getNumber()) && SVNRevision.isValidRevisionNumber(end.getNumber())) {
                    sessionRevision = start.getNumber() > end.getNumber() ? start : end;
                } else if (start.getDate() != null && end.getDate() != null) {
                    sessionRevision = start.getDate().compareTo(end.getDate()) > 0 ? start : end;
                }
            }
        }
        if (needsWC(pegRevision)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Revision type requires a working copy path, not a URL");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (limit > Integer.MAX_VALUE) {
            limit = Integer.MAX_VALUE;
        }
        paths = paths == null || paths.length == 0 ? new String[] {
            ""
        } : paths;
        ISVNLogEntryHandler wrappingHandler = new ISVNLogEntryHandler() {

            public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
                checkCancelled();
                handler.handleLogEntry(logEntry);
            }
        };
        SVNRepository repos = sessionRevision.isValid() ? createRepository(url, null, null, pegRevision, sessionRevision, null) : createRepository(url, null, null, true);
        for (Iterator revRangesIter = editedRevisionRanges.iterator(); revRangesIter.hasNext();) {
            checkCancelled();
            SVNRevisionRange revRange = (SVNRevisionRange) revRangesIter.next();
            long startRev = getRevisionNumber(revRange.getStartRevision(), repos, null);
            long endRev = getRevisionNumber(revRange.getEndRevision(), repos, null);
            repos.log(paths, startRev, endRev, discoverChangedPaths, stopOnCopy, limit, includeMergedRevisions, revisionProperties, wrappingHandler);
        }
    }

    /**
     * Browses directory entries from a repository (using Working Copy paths to
     * get corresponding URLs) and uses the provided dir entry handler to
     * process them.
     * <p>
     * On every entry that this method stops it gets some useful entry
     * information which is packed into an
     * {@link org.tmatesoft.svn.core.SVNDirEntry}object and passed to the
     * <code>handler</code>'s
     * {@link org.tmatesoft.svn.core.ISVNDirEntryHandler#handleDirEntry(SVNDirEntry)
     * handleDirEntry()} method.
     * 
     * @param path
     *            a WC item to get its repository location
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param fetchLocks
     *            <span class="javakeyword">true</span> to fetch locks
     *            information from a repository
     * @param recursive
     *            <span class="javakeyword">true</span> to descend recursively
     *            (relevant for directories)
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     * @deprecated use
     *             {@link #doList(File,SVNRevision,SVNRevision,boolean,SVNDepth,int,ISVNDirEntryHandler)}
     *             instead
     */
    public void doList(File path, SVNRevision pegRevision, SVNRevision revision, boolean fetchLocks, boolean recursive, ISVNDirEntryHandler handler) throws SVNException {
        doList(path, pegRevision, revision, fetchLocks, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES, SVNDirEntry.DIRENT_ALL, handler);
    }

    /**
     * Reports the directory entry, and possibly children, for <code>path</code>
     * at <code>revision</code>. The actual node revision selected is determined
     * by the path as it exists in <code>pegRevision</code>. If
     * <code>pegRevision</code> is {@link SVNRevision#isValid() invalid}, then
     * it defaults to {@link SVNRevision#WORKING}.
     * <p/>
     * Reports directory entries by invoking <code>handler</code>,
     * {@link SVNDirEntry#setRelativePath(String) setting} a relative to
     * <code>path</code> path to the {@link SVNDirEntry} object. The directory
     * entry for <code>path</code> is reported using an empty path. If
     * <code>path</code> is a directory, also reports its children.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#EMPTY}, lists just
     * <code>path</code> itself. If <code>depth</code> is {@link SVNDepth#FILES}
     * , lists <code>path</code> and its file entries. If
     * {@link SVNDepth#IMMEDIATES}, lists its immediate file and directory
     * entries. If {@link SVNDepth#INFINITY}, lists file entries and recurses
     * (with {@link SVNDepth#INFINITY}) on directory entries.
     * <p/>
     * <code>entryFields</code> controls which fields in the {@link SVNDirEntry}
     * are filled in. To have them totally filled in use
     * {@link SVNDirEntry#DIRENT_ALL}, otherwise simply bitwise OR together the
     * combination of fields you care about.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param path
     *            a WC item to get its repository location
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param fetchLocks
     *            <span class="javakeyword">true</span>, includes locks when
     *            reporting directory entries
     * @param depth
     *            tree depth to process
     * @param entryFields
     *            entry fields to fill
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#FS_NOT_FOUND} error
     *             code - if <code>path</code> is non-existent in the repository
     *             <ul/>
     * @since 1.2, SVN 1.5
     */
    public void doList(File path, SVNRevision pegRevision, SVNRevision revision, boolean fetchLocks, SVNDepth depth, int entryFields, ISVNDirEntryHandler handler) throws SVNException {
        if (revision == null || !revision.isValid()) {
            revision = SVNRevision.BASE;
        }
        SVNRepository repos = createRepository(null, path, null, pegRevision, revision, null);
        long rev = getRevisionNumber(revision, repos, path);
        doList(repos, rev, handler, fetchLocks, depth, entryFields);
    }

    /**
     * Browses directory entries from a repository (using Working Copy paths to
     * get corresponding URLs) and uses the provided dir entry handler to
     * process them.
     * <p>
     * On every entry that this method stops it gets some useful entry
     * information which is packed into an
     * {@link org.tmatesoft.svn.core.SVNDirEntry}object and passed to the
     * <code>handler</code>'s
     * {@link org.tmatesoft.svn.core.ISVNDirEntryHandler#handleDirEntry(SVNDirEntry)
     * handleDirEntry()} method.
     * 
     * @param path
     *            a WC item to get its repository location
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param recursive
     *            <span class="javakeyword">true</span> to descend recursively
     *            (relevant for directories)
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     * @deprecated use
     *             {@link #doList(File,SVNRevision,SVNRevision,boolean,SVNDepth,int,ISVNDirEntryHandler)}
     *             instead
     */
    public void doList(File path, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNDirEntryHandler handler) throws SVNException {
        doList(path, pegRevision, revision, false, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES, SVNDirEntry.DIRENT_ALL, handler);
    }

    /**
     * Browses directory entries from a repository and uses the provided dir
     * entry handler to process them. This method is especially useful when
     * having no Working Copy.
     * <p>
     * On every entry that this method stops it gets some useful entry
     * information which is packed into an
     * {@link org.tmatesoft.svn.core.SVNDirEntry}object and passed to the
     * <code>handler</code>'s
     * {@link org.tmatesoft.svn.core.ISVNDirEntryHandler#handleDirEntry(SVNDirEntry)
     * handleDirEntry()} method.
     * 
     * @param url
     *            a repository location to be "listed"
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param fetchLocks
     *            <span class="javakeyword">true</span> to fetch locks
     *            information from repository
     * @param recursive
     *            <span class="javakeyword">true</span> to descend recursively
     *            (relevant for directories)
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     * @see #doList(File,SVNRevision,SVNRevision,boolean,ISVNDirEntryHandler)
     */
    public void doList(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean fetchLocks, boolean recursive, ISVNDirEntryHandler handler) throws SVNException {
        doList(url, pegRevision, revision, fetchLocks, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES, SVNDirEntry.DIRENT_ALL, handler);
    }

    /**
     * Reports the directory entry, and possibly children, for <code>url</code>
     * at <code>revision</code>. The actual node revision selected is determined
     * by the path as it exists in <code>pegRevision</code>. If
     * <code>pegRevision</code> is {@link SVNRevision#isValid() invalid}, then
     * it defaults to {@link SVNRevision#HEAD}.
     * <p/>
     * Reports directory entries by invoking <code>handler</code>,
     * {@link SVNDirEntry#setRelativePath(String) setting} a relative to
     * <code>url</code> path to the {@link SVNDirEntry} object. The directory
     * entry for <code>url</code> is reported using an empty path. If
     * <code>url</code> is a directory, also reports its children.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#EMPTY}, lists just
     * <code>url</code> itself. If <code>depth</code> is {@link SVNDepth#FILES},
     * lists <code>url</code> and its file entries. If
     * {@link SVNDepth#IMMEDIATES}, lists its immediate file and directory
     * entries. If {@link SVNDepth#INFINITY}, lists file entries and recurses
     * (with {@link SVNDepth#INFINITY}) on directory entries.
     * <p/>
     * <code>entryFields</code> controls which fields in the {@link SVNDirEntry}
     * are filled in. To have them totally filled in use
     * {@link SVNDirEntry#DIRENT_ALL}, otherwise simply bitwise OR together the
     * combination of fields you care about.
     * <p/>
     * Note: this routine requires repository access.
     * 
     * @param url
     *            a repository url to be "listed"
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param fetchLocks
     *            <span class="javakeyword">true</span>, includes locks when
     *            reporting directory entries
     * @param depth
     *            tree depth to process
     * @param entryFields
     *            entry fields to fill
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#FS_NOT_FOUND} error
     *             code - if <code>url</code> is non-existent in the repository
     *             <ul/>
     * @since 1.2, SVN 1.5
     */
    public void doList(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean fetchLocks, SVNDepth depth, int entryFields, ISVNDirEntryHandler handler) throws SVNException {
        long[] pegRev = new long[] {
            -1
        };
        SVNRepository repos = createRepository(url, null, null, pegRevision, revision, pegRev);
        if (pegRev[0] < 0) {
            pegRev[0] = getRevisionNumber(revision, repos, null);
        }
        doList(repos, pegRev[0], handler, fetchLocks, depth, entryFields);
    }

    /**
     * Browses directory entries from a repository and uses the provided dir
     * entry handler to process them. This method is especially useful when
     * having no Working Copy.
     * <p>
     * On every entry that this method stops it gets some useful entry
     * information which is packed into an
     * {@link org.tmatesoft.svn.core.SVNDirEntry}object and passed to the
     * <code>handler</code>'s
     * {@link org.tmatesoft.svn.core.ISVNDirEntryHandler#handleDirEntry(SVNDirEntry)
     * handleDirEntry()} method.
     * 
     * @param url
     *            a repository location to be "listed"
     * @param pegRevision
     *            a revision in which the item's URL is first looked up
     * @param revision
     *            a target revision
     * @param recursive
     *            <span class="javakeyword">true</span> to descend recursively
     *            (relevant for directories)
     * @param handler
     *            a caller's directory entry handler (to process info on an
     *            entry)
     * @throws SVNException
     * @deprecated use
     *             {@link #doList(SVNURL,SVNRevision,SVNRevision,boolean,SVNDepth,int,ISVNDirEntryHandler)}
     *             instead
     */
    public void doList(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNDirEntryHandler handler) throws SVNException {
        doList(url, pegRevision, revision, false, recursive, handler);
    }

    private boolean needsWC(SVNRevision revision) {
        return revision == SVNRevision.BASE || revision == SVNRevision.COMMITTED || revision == SVNRevision.WORKING || revision == SVNRevision.PREVIOUS;
    }

    private void doAnnotate(String path, long startRev, File tmpFile, SVNRepository repos, long endRev, boolean ignoreMimeType, ISVNAnnotateHandler handler, String inputEncoding,
            boolean includeMergedRevisions) throws SVNException {
        SVNAnnotationGenerator generator = new SVNAnnotationGenerator(path, tmpFile, startRev, ignoreMimeType, includeMergedRevisions, getDiffOptions(), inputEncoding, handler, this);
        boolean useSpool = handler != null && !handler.getClass().getName().startsWith("org.tmatesoft.svn.");
        boolean oldSpool = false;
        if (useSpool && repos instanceof DAVRepository) {
            oldSpool = ((DAVRepository) repos).isSpoolResponse();
            ((DAVRepository) repos).setSpoolResponse(true);
        }
        try {
            repos.getFileRevisions("", startRev > 0 ? startRev - 1 : startRev, endRev, includeMergedRevisions, generator);
            if (!generator.isLastRevisionReported()) {
                generator.reportAnnotations(handler, inputEncoding);
            }
        } finally {
            if (useSpool && repos instanceof DAVRepository) {
                ((DAVRepository) repos).setSpoolResponse(oldSpool);
            }
            generator.dispose();
            SVNFileUtil.deleteAll(tmpFile, !"text-base".equals(tmpFile.getName()), null);
        }
    }

    private void doList(SVNRepository repos, long rev, final ISVNDirEntryHandler handler, boolean fetchLocks, SVNDepth depth, int entryFields) throws SVNException {
        SVNURL url = repos.getLocation();
        SVNURL reposRoot = repos.getRepositoryRoot(false);
        SVNDirEntry entry = null;
        SVNException error = null;
        try {
            entry = repos.info("", rev);
        } catch (SVNException svne) {
            if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                error = svne;
            } else {
                throw svne;
            }
        }
        if (error != null) {
            SVNNodeKind kind = repos.checkPath("", rev);
            if (kind != SVNNodeKind.NONE) {
                if (!url.equals(reposRoot)) {
                    String name = SVNPathUtil.tail(repos.getLocation().getPath());
                    repos.setLocation(repos.getLocation().removePathTail(), false);
                    Collection dirEntries = repos.getDir("", rev, null, entryFields, (Collection) null);
                    repos.setLocation(url, false);
                    for (Iterator ents = dirEntries.iterator(); ents.hasNext();) {
                        SVNDirEntry dirEntry = (SVNDirEntry) ents.next();
                        if (name.equals(dirEntry.getName())) {
                            entry = dirEntry;
                            break;
                        }
                    }
                    if (entry != null) {
                        entry.setRelativePath(kind == SVNNodeKind.FILE ? name : "");
                    }
                } else {
                    SVNProperties props = new SVNProperties();
                    repos.getDir("", rev, props, entryFields, (Collection) null);
                    SVNProperties revProps = repos.getRevisionProperties(rev, null);
                    String author = revProps.getStringValue(SVNRevisionProperty.AUTHOR);
                    String dateStr = revProps.getStringValue(SVNRevisionProperty.DATE);
                    Date datestamp = null;
                    if (dateStr != null) {
                        datestamp = SVNDate.parseDateString(dateStr);
                    }
                    entry = new SVNDirEntry(url, reposRoot, "", kind, 0, !props.isEmpty(), rev, datestamp, author);
                    entry.setRelativePath("");
                }
            }
        } else if (entry != null) {
            entry.setRelativePath(entry.getKind() == SVNNodeKind.DIR ? "" : entry.getName());
        }
        if (entry == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "URL ''{0}'' non-existent in that revision", url);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        final Map locksMap = new SVNHashMap();
        if (fetchLocks) {
            SVNLock[] locks = new SVNLock[0];
            try {
                locks = repos.getLocks("");
            } catch (SVNException e) {
                if (!(e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED)) {
                    throw e;
                }
            }
            if (locks != null && locks.length > 0) {
                SVNURL root = repos.getRepositoryRoot(true);
                for (int i = 0; i < locks.length; i++) {
                    String repositoryPath = locks[i].getPath();
                    locksMap.put(root.appendPath(repositoryPath, false), locks[i]);
                }
            }
        }
        ISVNDirEntryHandler nestedHandler = new ISVNDirEntryHandler() {

            public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {
                dirEntry.setLock((SVNLock) locksMap.get(dirEntry.getURL()));
                handler.handleDirEntry(dirEntry);
            }
        };
        nestedHandler.handleDirEntry(entry);
        if (entry.getKind() == SVNNodeKind.DIR && (depth == SVNDepth.FILES || depth == SVNDepth.IMMEDIATES || depth == SVNDepth.INFINITY)) {
            list(repos, "", rev, depth, entryFields, nestedHandler);
        }
    }

    private static void list(SVNRepository repository, String path, long rev, SVNDepth depth, int entryFields, ISVNDirEntryHandler handler) throws SVNException {
        if (depth == SVNDepth.EMPTY) {
            return;
        }
        Collection entries = new TreeSet();
        try {
            entries = repository.getDir(path, rev, null, entryFields, entries);
        } catch (SVNAuthenticationException e) {
            return;
        } catch (SVNException e) {
            if (e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_AUTHORIZED) {
                return;
            }
            throw e;
        }
        
        for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
            SVNDirEntry entry = (SVNDirEntry) iterator.next();
            String childPath = SVNPathUtil.append(path, entry.getName());
            entry.setRelativePath(childPath);
            if (entry.getKind() == SVNNodeKind.FILE || depth == SVNDepth.IMMEDIATES || depth == SVNDepth.INFINITY) {
                handler.handleDirEntry(entry);
            }
            if (entry.getKind() == SVNNodeKind.DIR && entry.getDate() != null && depth == SVNDepth.INFINITY) {
                list(repository, childPath, rev, depth, entryFields, handler);
            }
        }
    }
}
