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

import java.io.File;
import java.io.InputStream;
import java.util.*;

import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNHashSet;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.*;
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.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.internal.wc2.ng.SvnNgPropertiesManager;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.wc.DefaultSVNCommitHandler;
import org.tmatesoft.svn.core.wc.DefaultSVNCommitParameters;
import org.tmatesoft.svn.core.wc.ISVNCommitHandler;
import org.tmatesoft.svn.core.wc.ISVNCommitParameters;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.ISVNFileFilter;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
import org.tmatesoft.svn.core.wc.SVNCommitItem;
import org.tmatesoft.svn.core.wc.SVNCommitPacket;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import org.tmatesoft.svn.core.wc2.*;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * The <b>SVNCommitClient</b> class provides methods to perform operations that
 * relate to committing changes to an SVN repository. These operations are
 * similar to respective commands of the native SVN command line client and
 * include ones which operate on working copy items as well as ones that operate
 * only on a repository.
 * <p>
 * Here's a list of the <b>SVNCommitClient</b>'s commit-related methods matched
 * against corresponing commands of the SVN 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>doCommit()</td>
 * <td>'svn commit'</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doImport()</td>
 * <td>'svn import'</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doDelete()</td>
 * <td>'svn delete URL'</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>doMkDir()</td>
 * <td>'svn mkdir URL'</td>
 * </tr>
 * </table>
 *
 * @version 1.3
 * @author TMate Software Ltd.
 * @since 1.2
 * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
 */
public class SVNCommitClient16 extends SVNBasicDelegate {

    private ISVNCommitHandler myCommitHandler;
    private ISVNCommitParameters myCommitParameters;

    /**
     * Constructs and initializes an <b>SVNCommitClient</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>SVNCommitClient</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>SVNCommitClient</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 SVNCommitClient16(ISVNAuthenticationManager authManager, ISVNOptions options) {
        super(authManager, options);
    }

    /**
     * Constructs and initializes an <b>SVNCommitClient</b> object with the
     * specified run-time configuration and repository pool object.
     * <p/>
     * If <code>options</code> is <span class="javakeyword">null</span>, then
     * this <b>SVNCommitClient</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 SVNCommitClient16(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
        super(repositoryPool, options);
    }

    /**
     * @param handler
     * @deprecated use {@link #setCommitHandler(ISVNCommitHandler)} instead
     */
    public void setCommitHander(ISVNCommitHandler handler) {
        myCommitHandler = handler;
    }

    /**
     * Sets an implementation of <b>ISVNCommitHandler</b> to the commit handler
     * that will be used during commit operations to handle commit log messages.
     * The handler will receive a clien's log message and items (represented as
     * <b>SVNCommitItem</b> objects) that will be committed. Depending on
     * implementor's aims the initial log message can be modified (or something
     * else) and returned back.
     * <p>
     * If using <b>SVNCommitClient</b> without specifying any commit handler
     * then a default one will be used - {@link DefaultSVNCommitHandler}.
     *
     * @param handler
     *            an implementor's handler that will be used to handle commit
     *            log messages
     * @see #getCommitHandler()
     * @see ISVNCommitHandler
     */
    public void setCommitHandler(ISVNCommitHandler handler) {
        this.myCommitHandler = handler;
    }

    /**
     * Returns the specified commit handler (if set) being in use or a default
     * one (<b>DefaultSVNCommitHandler</b>) if no special implementations of
     * <b>ISVNCommitHandler</b> were previously provided.
     *
     * @return the commit handler being in use or a default one
     * @see #setCommitHander(ISVNCommitHandler)
     * @see ISVNCommitHandler
     * @see DefaultSVNCommitHandler
     */
    public ISVNCommitHandler getCommitHandler() {
        return this.myCommitHandler;
    }

    /**
     * Sets commit parameters to use.
     * <p>
     * When no parameters are set {@link DefaultSVNCommitParameters default}
     * ones are used.
     *
     * @param parameters
     *            commit parameters
     * @see #getCommitParameters()
     */
    public void setCommitParameters(ISVNCommitParameters parameters) {
        this.myCommitParameters = parameters;
    }

    /**
     * Returns commit parameters.
     * <p>
     * If no user parameters were previously specified, once creates and returns
     * {@link DefaultSVNCommitParameters default} ones.
     *
     * @return commit parameters
     * @see #setCommitParameters(ISVNCommitParameters)
     */
    public ISVNCommitParameters getCommitParameters() {
        return this.myCommitParameters;
    }

    /**
     * Committs removing specified URL-paths from the repository. This call is
     * equivalent to <code>doDelete(urls, commitMessage, null)</code>.
     *
     * @param urls
     *            an array containing URL-strings that represent repository
     *            locations to be removed
     * @param commitMessage
     *            a string to be a commit log message
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li>a URL does not exist
     *             <li>probably some of URLs refer to different repositories
     *             </ul>
     * @see #doDelete(SVNURL[],String,SVNProperties)
     */
    public SVNCommitInfo doDelete(SVNURL[] urls, String commitMessage) throws SVNException {
        return doDelete(urls, commitMessage, null);
    }

    /**
     * Deletes items from a repository.
     * <p/>
     * If non-<span class="javakeyword">null</span>,
     * <code>revisionProperties</code> holds additional, custom revision
     * properties (<code>String</code> names mapped to {@link SVNPropertyValue}
     * values) to be set on the new revision. This table cannot contain any
     * standard Subversion properties.
     * <p/>
     * {@link #getCommitHandler() Commit handler} will be asked for a commit log
     * message.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> and if the commit succeeds, the handler
     * will be called with {@link SVNEventAction#COMMIT_COMPLETED} event action.
     *
     * @param urls
     *            repository urls to delete
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @return information about the new committed revision
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#RA_ILLEGAL_URL} error
     *             code - if cannot compute common root url for <code>urls
     *             </code> <li/>exception with {@link SVNErrorCode#FS_NOT_FOUND}
     *             error code - if some of <code>urls</code> does not exist
     *             </ul>
     * @since 1.2.0, SVN 1.5.0
     */
    public SVNCommitInfo doDelete(SVNURL[] urls, String commitMessage, SVNProperties revisionProperties) throws SVNException {
        if (urls == null || urls.length == 0) {
            return SVNCommitInfo.NULL;
        }
        List paths = new ArrayList();
        SVNURL rootURL = SVNURLUtil.condenceURLs(urls, paths, true);
        if (rootURL == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "Can not compute common root URL for specified URLs");
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        if (paths.isEmpty()) {
            paths.add(SVNPathUtil.tail(rootURL.getURIEncodedPath()));
            rootURL = rootURL.removePathTail();
        }
        SVNCommitItem[] commitItems = new SVNCommitItem[paths.size()];
        for (int i = 0; i < commitItems.length; i++) {
            String path = (String) paths.get(i);
            commitItems[i] = new SVNCommitItem(null, rootURL.appendPath(path, true), null, SVNNodeKind.NONE, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED, false, true, false, false, false, false);
        }
        commitMessage = getCommitHandler().getCommitMessage(commitMessage, commitItems);
        if (commitMessage == null) {
            return SVNCommitInfo.NULL;
        }
        commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage);
        List decodedPaths = new ArrayList();
        for (Iterator commitPaths = paths.iterator(); commitPaths.hasNext();) {
            String path = (String) commitPaths.next();
            decodedPaths.add(SVNEncodingUtil.uriDecode(path));
        }
        paths = decodedPaths;
        SVNRepository repos = createRepository(rootURL, null, null, true);
        for (Iterator commitPath = paths.iterator(); commitPath.hasNext();) {
            String path = (String) commitPath.next();
            SVNNodeKind kind = repos.checkPath(path, -1);
            if (kind == SVNNodeKind.NONE) {
                SVNURL url = rootURL.appendPath(path, false);
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "URL ''{0}'' does not exist", url);
                SVNErrorManager.error(err, SVNLogType.DEFAULT);
            }
        }
        commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage);
        SVNPropertiesManager.validateRevisionProperties(revisionProperties);
        ISVNEditor commitEditor = repos.getCommitEditor(commitMessage, null, false, revisionProperties, null);
        ISVNCommitPathHandler deleter = new ISVNCommitPathHandler() {

            public boolean handleCommitPath(String commitPath, ISVNEditor commitEditor) throws SVNException {
                commitEditor.deleteEntry(commitPath, -1);
                return false;
            }
        };
        SVNCommitInfo info;
        try {
            SVNCommitUtil.driveCommitEditor(deleter, paths, commitEditor, -1);
            info = commitEditor.closeEdit();
        } catch (SVNException e) {
            try {
                commitEditor.abortEdit();
            } catch (SVNException inner) {
            }
            throw e;
        }
        if (info != null && info.getNewRevision() >= 0) {
            dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, info.getNewRevision(), SVNEventAction.COMMIT_COMPLETED, null, null, null), ISVNEventHandler.UNKNOWN);
        }
        return info != null ? info : SVNCommitInfo.NULL;
    }

    /**
     * Committs a creation of a new directory/directories in the repository.
     *
     * @param urls
     *            an array containing URL-strings that represent new repository
     *            locations to be created
     * @param commitMessage
     *            a string to be a commit log message
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     *             if some of URLs refer to different repositories
     */
    public SVNCommitInfo doMkDir(SVNURL[] urls, String commitMessage) throws SVNException {
        return doMkDir(urls, commitMessage, null, false);
    }

    /**
     * Creates directory(ies) in a repository.
     * <p/>
     * If <code>makeParents</code> is <span class="javakeyword">true</span>,
     * creates any non-existent parent directories also.
     * <p/>
     * If non-<span class="javakeyword">null</span>,
     * <code>revisionProperties</code> holds additional, custom revision
     * properties (<code>String</code> names mapped to {@link SVNPropertyValue}
     * values) to be set on the new revision. This table cannot contain any
     * standard Subversion properties.
     * <p/>
     * {@link #getCommitHandler() Commit handler} will be asked for a commit log
     * message.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> and if the commit succeeds, the handler
     * will be called with {@link SVNEventAction#COMMIT_COMPLETED} event action.
     *
     * @param urls
     *            repository locations to create
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @param makeParents
     *            if <span class="javakeyword">true</span>, creates all
     *            non-existent parent directories
     * @return information about the new committed revision
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#RA_ILLEGAL_URL} error
     *             code - if cannot compute common root url for <code>urls
     *             </code> <li/>exception with {@link SVNErrorCode#FS_NOT_FOUND}
     *             error code - if some of <code>urls</code> does not exist
     *             </ul>
     * @since 1.2.0, SVN 1.5.0
     */
    public SVNCommitInfo doMkDir(SVNURL[] urls, String commitMessage, SVNProperties revisionProperties, boolean makeParents) throws SVNException {
        if (makeParents) {
            List allURLs = new LinkedList();
            for (int i = 0; i < urls.length; i++) {
                SVNURL url = urls[i];
                addURLParents(allURLs, url);
            }
            urls = (SVNURL[]) allURLs.toArray(new SVNURL[allURLs.size()]);
        }
        if (urls == null || urls.length == 0) {
            return SVNCommitInfo.NULL;
        }
        Collection paths = new SVNHashSet();
        SVNURL rootURL = SVNURLUtil.condenceURLs(urls, paths, false);
        if (rootURL == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "Can not compute common root URL for specified URLs");
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        if (paths.isEmpty()) {
            paths.add(SVNPathUtil.tail(rootURL.getURIEncodedPath()));
            rootURL = rootURL.removePathTail();
        }
        if (paths.contains("")) {
            List convertedPaths = new ArrayList();
            String tail = SVNPathUtil.tail(rootURL.getURIEncodedPath());
            rootURL = rootURL.removePathTail();
            for (Iterator commitPaths = paths.iterator(); commitPaths.hasNext();) {
                String path = (String) commitPaths.next();
                if ("".equals(path)) {
                    convertedPaths.add(tail);
                } else {
                    convertedPaths.add(SVNPathUtil.append(tail, path));
                }
            }
            paths = convertedPaths;
        }
        List sortedPaths = new ArrayList(paths);
        Collections.sort(sortedPaths, SVNPathUtil.PATH_COMPARATOR);
        SVNCommitItem[] commitItems = new SVNCommitItem[sortedPaths.size()];
        for (int i = 0; i < commitItems.length; i++) {
            String path = (String) sortedPaths.get(i);
            commitItems[i] = new SVNCommitItem(null, rootURL.appendPath(path, true), null, SVNNodeKind.DIR, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED, true, false, false, false, false, false);
        }
        commitMessage = getCommitHandler().getCommitMessage(commitMessage, commitItems);
        if (commitMessage == null) {
            return SVNCommitInfo.NULL;
        }
        commitMessage = validateCommitMessage(commitMessage);

        List decodedPaths = new ArrayList();
        for (Iterator commitPaths = sortedPaths.iterator(); commitPaths.hasNext();) {
            String path = (String) commitPaths.next();
            decodedPaths.add(SVNEncodingUtil.uriDecode(path));
        }
        paths = decodedPaths;
        SVNRepository repos = createRepository(rootURL, null, null, true);
        commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage);
        SVNPropertiesManager.validateRevisionProperties(revisionProperties);
        ISVNEditor commitEditor = repos.getCommitEditor(commitMessage, null, false, revisionProperties, null);
        ISVNCommitPathHandler creater = new ISVNCommitPathHandler() {

            public boolean handleCommitPath(String commitPath, ISVNEditor commitEditor) throws SVNException {
                SVNPathUtil.checkPathIsValid(commitPath);
                commitEditor.addDir(commitPath, null, -1);
                return true;
            }
        };
        SVNCommitInfo info;
        try {
            SVNCommitUtil.driveCommitEditor(creater, paths, commitEditor, -1);
            info = commitEditor.closeEdit();
        } catch (SVNException e) {
            try {
                commitEditor.abortEdit();
            } catch (SVNException inner) {
            }
            throw e;
        }
        if (info != null && info.getNewRevision() >= 0) {
            dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, info.getNewRevision(), SVNEventAction.COMMIT_COMPLETED, null, null, null), ISVNEventHandler.UNKNOWN);
        }
        return info != null ? info : SVNCommitInfo.NULL;
    }

    /**
     * Committs an addition of a local unversioned file or directory into the
     * repository.
     * <p/>
     * This method is identical to
     * <code>doImport(path, dstURL, commitMessage, null, true, false, SVNDepth.fromRecurse(recursive))</code>.
     *
     * @param path
     *            a local unversioned file or directory to be imported into the
     *            repository
     * @param dstURL
     *            a URL-string that represents a repository location where the
     *            <code>path</code> will be imported
     * @param commitMessage
     *            a string to be a commit log message
     * @param recursive
     *            this flag is relevant only when the <code>path</code> is a
     *            directory: if <span class="javakeyword">true</span> then the
     *            entire directory tree will be imported including all child
     *            directories, otherwise only items located in the directory
     *            itself
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li><code>dstURL</code> is invalid <li>the path denoted by
     *             <code>dstURL</code> already exists <li><code>path</code>
     *             contains a reserved name - <i>'.svn'</i>
     *             </ul>
     * @deprecated use
     *             {@link #doImport(File,SVNURL,String,SVNProperties,boolean,boolean,SVNDepth)}
     *             instead
     */
    public SVNCommitInfo doImport(File path, SVNURL dstURL, String commitMessage, boolean recursive) throws SVNException {
        return doImport(path, dstURL, commitMessage, null, true, false, SVNDepth.fromRecurse(recursive));
    }

    /**
     * Committs an addition of a local unversioned file or directory into the
     * repository.
     * <p/>
     * This method is identical to
     * <code>doImport(path, dstURL, commitMessage, null, useGlobalIgnores, false, SVNDepth.fromRecurse(recursive))</code>.
     *
     * @param path
     *            a local unversioned file or directory to be imported into the
     *            repository
     * @param dstURL
     *            a URL-string that represents a repository location where the
     *            <code>path</code> will be imported
     * @param commitMessage
     *            a string to be a commit log message
     * @param useGlobalIgnores
     *            if <span class="javakeyword">true</span> then those paths that
     *            match global ignore patterns controlled by a config options
     *            driver (see
     *            {@link org.tmatesoft.svn.core.wc.ISVNOptions#getIgnorePatterns()}
     *            ) will not be imported, otherwise global ignore patterns are
     *            not used
     * @param recursive
     *            this flag is relevant only when the <code>path</code> is a
     *            directory: if <span class="javakeyword">true</span> then the
     *            entire directory tree will be imported including all child
     *            directories, otherwise only items located in the directory
     *            itself
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li><code>dstURL</code> is invalid <li>the path denoted by
     *             <code>dstURL</code> already exists <li><code>path</code>
     *             contains a reserved name - <i>'.svn'</i>
     *             </ul>
     * @deprecated use
     *             {@link #doImport(File,SVNURL,String,SVNProperties,boolean,boolean,SVNDepth)}
     *             instead
     */
    public SVNCommitInfo doImport(File path, SVNURL dstURL, String commitMessage, boolean useGlobalIgnores, boolean recursive) throws SVNException {
        return doImport(path, dstURL, commitMessage, null, useGlobalIgnores, false, SVNDepth.fromRecurse(recursive));
    }

    /**
     * Imports file or directory <code>path</code> into repository directory
     * <code>dstURL</code> at HEAD revision. If some components of
     * <code>dstURL</code> do not exist, then creates parent directories as
     * necessary.
     * <p/>
     * If <code>path</code> is a directory, the contents of that directory are
     * imported directly into the directory identified by <code>dstURL</code>.
     * Note that the directory <code>path</code> itself is not imported -- that
     * is, the base name of <code>path<code> is not part of the import.
     * <p/>
     * If <code>path</code> is a file, then the parent of <code>dstURL</code> is
     * the directory receiving the import. The base name of <code>dstURL</code>
     * is the filename in the repository. In this case if <code>dstURL</code>
     * already exists, throws {@link SVNException}.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> it will be called as the import
     * progresses with {@link SVNEventAction#COMMIT_ADDED} action. If the commit
     * succeeds, the handler will be called with
     * {@link SVNEventAction#COMMIT_COMPLETED} event action.
     * <p/>
     * If non-<span class="javakeyword">null</span>,
     * <code>revisionProperties</code> holds additional, custom revision
     * properties (<code>String</code> names mapped to {@link SVNPropertyValue}
     * values) to be set on the new revision. This table cannot contain any
     * standard Subversion properties.
     * <p/>
     * {@link #getCommitHandler() Commit handler} will be asked for a commit log
     * message.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#EMPTY}, imports just
     * <code>path</code> and nothing below it. If {@link SVNDepth#FILES},
     * imports <code>path</code> and any file children of <code>path</code>. If
     * {@link SVNDepth#IMMEDIATES}, imports <code>path</code>, any file
     * children, and any immediate subdirectories (but nothing underneath those
     * subdirectories). If {@link SVNDepth#INFINITY}, imports <code>path</code>
     * and everything under it fully recursively.
     * <p/>
     * If <code>useGlobalIgnores</code> is <span
     * class="javakeyword">false</span>, doesn't add files or directories that
     * match ignore patterns.
     * <p/>
     * If <code>ignoreUnknownNodeTypes</code> is <span
     * class="javakeyword">false</span>, ignores files of which the node type is
     * unknown, such as device files and pipes.
     *
     * @param path
     *            path to import
     * @param dstURL
     *            import destination url
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @param useGlobalIgnores
     *            whether matching against global ignore patterns should take
     *            place
     * @param ignoreUnknownNodeTypes
     *            whether to ignore files of unknown node types or not
     * @param depth
     *            tree depth to process
     * @return information about the new committed revision
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#ENTRY_NOT_FOUND}
     *             error code - if <code>path</code> does not exist <li/>
     *             exception with {@link SVNErrorCode#ENTRY_EXISTS} error code -
     *             if <code>dstURL</code> already exists and <code>path</code>
     *             is a file <li/>exception with
     *             {@link SVNErrorCode#CL_ADM_DIR_RESERVED} error code - if
     *             trying to import an item with a reserved SVN name (like
     *             <code>'.svn'</code> or <code>'_svn'</code>)
     *             </ul>
     * @since 1.2.0, New in SVN 1.5.0
     */
    public SVNCommitInfo doImport(File path, SVNURL dstURL, String commitMessage, SVNProperties revisionProperties, boolean useGlobalIgnores, boolean ignoreUnknownNodeTypes, SVNDepth depth) throws SVNException {
        return doImport(path, dstURL, commitMessage, revisionProperties, useGlobalIgnores, ignoreUnknownNodeTypes, depth, true);
    }
    /**
     * Imports file or directory <code>path</code> into repository directory
     * <code>dstURL</code> at HEAD revision. If some components of
     * <code>dstURL</code> do not exist, then creates parent directories as
     * necessary.
     * <p/>
     * If <code>path</code> is a directory, the contents of that directory are
     * imported directly into the directory identified by <code>dstURL</code>.
     * Note that the directory <code>path</code> itself is not imported -- that
     * is, the base name of <code>path<code> is not part of the import.
     * <p/>
     * If <code>path</code> is a file, then the parent of <code>dstURL</code> is
     * the directory receiving the import. The base name of <code>dstURL</code>
     * is the filename in the repository. In this case if <code>dstURL</code>
     * already exists, throws {@link SVNException}.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> it will be called as the import
     * progresses with {@link SVNEventAction#COMMIT_ADDED} action. If the commit
     * succeeds, the handler will be called with
     * {@link SVNEventAction#COMMIT_COMPLETED} event action.
     * <p/>
     * If non-<span class="javakeyword">null</span>,
     * <code>revisionProperties</code> holds additional, custom revision
     * properties (<code>String</code> names mapped to {@link SVNPropertyValue}
     * values) to be set on the new revision. This table cannot contain any
     * standard Subversion properties.
     * <p/>
     * {@link #getCommitHandler() Commit handler} will be asked for a commit log
     * message.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#EMPTY}, imports just
     * <code>path</code> and nothing below it. If {@link SVNDepth#FILES},
     * imports <code>path</code> and any file children of <code>path</code>. If
     * {@link SVNDepth#IMMEDIATES}, imports <code>path</code>, any file
     * children, and any immediate subdirectories (but nothing underneath those
     * subdirectories). If {@link SVNDepth#INFINITY}, imports <code>path</code>
     * and everything under it fully recursively.
     * <p/>
     * If <code>useGlobalIgnores</code> is <span
     * class="javakeyword">false</span>, doesn't add files or directories that
     * match ignore patterns.
     * <p/>
     * If <code>ignoreUnknownNodeTypes</code> is <span
     * class="javakeyword">false</span>, ignores files of which the node type is
     * unknown, such as device files and pipes.
     *
     * @param path
     *            path to import
     * @param dstURL
     *            import destination url
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @param useGlobalIgnores
     *            whether matching against global ignore patterns should take
     *            place
     * @param ignoreUnknownNodeTypes
     *            whether to ignore files of unknown node types or not
     * @param depth
     *            tree depth to process
     * @param applyAutoProperties
     *            disable or not auto-properties application
     * @return information about the new committed revision
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#ENTRY_NOT_FOUND}
     *             error code - if <code>path</code> does not exist <li/>
     *             exception with {@link SVNErrorCode#ENTRY_EXISTS} error code -
     *             if <code>dstURL</code> already exists and <code>path</code>
     *             is a file <li/>exception with
     *             {@link SVNErrorCode#CL_ADM_DIR_RESERVED} error code - if
     *             trying to import an item with a reserved SVN name (like
     *             <code>'.svn'</code> or <code>'_svn'</code>)
     *             </ul>
     * @since SVN 1.8
     */
    public SVNCommitInfo doImport(File path, SVNURL dstURL, String commitMessage, SVNProperties revisionProperties, boolean useGlobalIgnores, boolean ignoreUnknownNodeTypes, SVNDepth depth, boolean applyAutoProperties)
            throws SVNException {
        return doImport(path, dstURL, commitMessage, revisionProperties, useGlobalIgnores, ignoreUnknownNodeTypes, depth, applyAutoProperties, null);
    }

    /**
     * Imports file or directory <code>path</code> into repository directory
     * <code>dstURL</code> at HEAD revision. If some components of
     * <code>dstURL</code> do not exist, then creates parent directories as
     * necessary.
     * <p/>
     * If <code>path</code> is a directory, the contents of that directory are
     * imported directly into the directory identified by <code>dstURL</code>.
     * Note that the directory <code>path</code> itself is not imported -- that
     * is, the base name of <code>path<code> is not part of the import.
     * <p/>
     * If <code>path</code> is a file, then the parent of <code>dstURL</code> is
     * the directory receiving the import. The base name of <code>dstURL</code>
     * is the filename in the repository. In this case if <code>dstURL</code>
     * already exists, throws {@link SVNException}.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> it will be called as the import
     * progresses with {@link SVNEventAction#COMMIT_ADDED} action. If the commit
     * succeeds, the handler will be called with
     * {@link SVNEventAction#COMMIT_COMPLETED} event action.
     * <p/>
     * If non-<span class="javakeyword">null</span>,
     * <code>revisionProperties</code> holds additional, custom revision
     * properties (<code>String</code> names mapped to {@link SVNPropertyValue}
     * values) to be set on the new revision. This table cannot contain any
     * standard Subversion properties.
     * <p/>
     * {@link #getCommitHandler() Commit handler} will be asked for a commit log
     * message.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#EMPTY}, imports just
     * <code>path</code> and nothing below it. If {@link SVNDepth#FILES},
     * imports <code>path</code> and any file children of <code>path</code>. If
     * {@link SVNDepth#IMMEDIATES}, imports <code>path</code>, any file
     * children, and any immediate subdirectories (but nothing underneath those
     * subdirectories). If {@link SVNDepth#INFINITY}, imports <code>path</code>
     * and everything under it fully recursively.
     * <p/>
     * If <code>useGlobalIgnores</code> is <span
     * class="javakeyword">false</span>, doesn't add files or directories that
     * match ignore patterns.
     * <p/>
     * If <code>ignoreUnknownNodeTypes</code> is <span
     * class="javakeyword">false</span>, ignores files of which the node type is
     * unknown, such as device files and pipes.
     *
     * @param path
     *            path to import
     * @param dstURL
     *            import destination url
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @param useGlobalIgnores
     *            whether matching against global ignore patterns should take
     *            place
     * @param ignoreUnknownNodeTypes
     *            whether to ignore files of unknown node types or not
     * @param depth
     *            tree depth to process
     * @param applyAutoProperties
     *            disable or not auto-properties application
     * @param fileFilter
     *            filter to exclude or include certain files for the operation
     * @return information about the new committed revision
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#ENTRY_NOT_FOUND}
     *             error code - if <code>path</code> does not exist <li/>
     *             exception with {@link SVNErrorCode#ENTRY_EXISTS} error code -
     *             if <code>dstURL</code> already exists and <code>path</code>
     *             is a file <li/>exception with
     *             {@link SVNErrorCode#CL_ADM_DIR_RESERVED} error code - if
     *             trying to import an item with a reserved SVN name (like
     *             <code>'.svn'</code> or <code>'_svn'</code>)
     *             </ul>
     * @since SVN 1.8
     */
    public SVNCommitInfo doImport(File path, SVNURL dstURL, String commitMessage, SVNProperties revisionProperties, boolean useGlobalIgnores, boolean ignoreUnknownNodeTypes, SVNDepth depth, boolean applyAutoProperties, ISVNFileFilter fileFilter)
            throws SVNException {
        SVNRepository repos = null;
        SVNFileType srcKind = SVNFileType.getType(path);
        if (srcKind == SVNFileType.NONE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "Path ''{0}'' does not exist", path);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        List newPaths = new ArrayList();
        SVNURL rootURL = dstURL;
        repos = createRepository(rootURL, null, null, true);
        SVNURL reposRoot = repos.getRepositoryRoot(true);
        while (!reposRoot.equals(rootURL)) {
            if (repos.checkPath("", -1) == SVNNodeKind.NONE) {
                newPaths.add(SVNPathUtil.tail(rootURL.getPath()));
                rootURL = rootURL.removePathTail();
                repos = createRepository(rootURL, null, null, true);
            } else {
                break;
            }
        }
        if (newPaths.isEmpty() && (srcKind == SVNFileType.FILE || srcKind == SVNFileType.SYMLINK)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Path ''{0}'' already exists", dstURL);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (newPaths.contains(SVNFileUtil.getAdminDirectoryName())) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_ADM_DIR_RESERVED, "''{0}'' is a reserved name and cannot be imported", SVNFileUtil.getAdminDirectoryName());
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        SVNCommitItem[] items = new SVNCommitItem[1];
        items[0] = new SVNCommitItem(path, null, null, SVNNodeKind.NONE, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED, true, false, false,
                false, false, false);
        items[0].setPath(path.getName());
        commitMessage = getCommitHandler().getCommitMessage(commitMessage, items);
        if (commitMessage == null) {
            return SVNCommitInfo.NULL;
        }
        commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage);
        SVNPropertiesManager.validateRevisionProperties(revisionProperties);
        Map<String, Map<String, String>> versionedAutoProperties; //pattern->properties
        if (applyAutoProperties) {
            versionedAutoProperties = getVersionedAutoProperties(dstURL, reposRoot);
            // method above reuses repository and changes its location.
            repos.setLocation(rootURL, false);
        } else {
            versionedAutoProperties = null;
        }
        ISVNEditor commitEditor = repos.getCommitEditor(commitMessage, null, false, revisionProperties, new SVNImportMediator());
        String filePath = "";
        if (srcKind != SVNFileType.DIRECTORY) {
            filePath = (String) newPaths.remove(0);
            for (int i = 0; i < newPaths.size(); i++) {
                String newDir = (String) newPaths.get(i);
                filePath = newDir + "/" + filePath;
            }
        }
        Collection ignores = useGlobalIgnores ? SVNStatusEditor.getGlobalIgnores(getOptions()) : null;
        checkCancelled();
        boolean changed = false;
        SVNCommitInfo info = null;
        try {
            commitEditor.openRoot(-1);
            String newDirPath = null;
            for (int i = newPaths.size() - 1; i >= 0; i--) {
                newDirPath = newDirPath == null ? (String) newPaths.get(i) : SVNPathUtil.append(newDirPath, (String) newPaths.get(i));
                commitEditor.addDir(newDirPath, null, -1);
            }
            changed = newPaths.size() > 0;
            SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
            if (srcKind == SVNFileType.DIRECTORY) {
                changed |= importDir(deltaGenerator, path, newDirPath, useGlobalIgnores, ignoreUnknownNodeTypes, depth, versionedAutoProperties, fileFilter, commitEditor);
            } else if (srcKind == SVNFileType.FILE || srcKind == SVNFileType.SYMLINK) {
                if (!useGlobalIgnores || !SVNStatusEditor.isIgnored(ignores, path, "/" + path.getName())) {
                    changed |= importFile(deltaGenerator, path, srcKind, filePath, versionedAutoProperties, commitEditor);
                }
            } else if (srcKind == SVNFileType.NONE || srcKind == SVNFileType.UNKNOWN) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "''{0}'' does not exist", path);
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            if (!changed) {
                try {
                    commitEditor.abortEdit();
                } catch (SVNException e) {
                }
                return SVNCommitInfo.NULL;
            }
            for (int i = 0; i < newPaths.size(); i++) {
                commitEditor.closeDir();
            }
            info = commitEditor.closeEdit();
        } finally {
            if (!changed || info == null) {
                try {
                    commitEditor.abortEdit();
                } catch (SVNException e) {
                }
            }
        }
        if (info != null && info.getNewRevision() >= 0) {
            dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, info.getNewRevision(), SVNEventAction.COMMIT_COMPLETED, null, null, null), ISVNEventHandler.UNKNOWN);
        }
        return info != null ? info : SVNCommitInfo.NULL;
    }

    /**
     * Committs local changes to the repository.
     * <p/>
     * This method is identical to
     * <code>doCommit(paths, keepLocks, commitMessage, null, null, false, force, SVNDepth.fromRecurse(recursive))</code>.
     *
     * @param paths
     *            an array of local items which should be traversed to commit
     *            changes they have to the repository
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then the commit will left them locked,
     *            otherwise the items will be unlocked after the commit succeeds
     * @param commitMessage
     *            a string to be a commit log message
     * @param force
     *            <span class="javakeyword">true</span> to force a non-recursive
     *            commit; if <code>recursive</code> is set to <span
     *            class="javakeyword">true</span> the <code>force</code> flag is
     *            ignored
     * @param recursive
     *            relevant only for directory items: if <span
     *            class="javakeyword">true</span> then the entire directory tree
     *            will be committed including all child directories, otherwise
     *            only items located in the directory itself
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     * @deprecated use
     *             {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}
     *             instead
     */
    public SVNCommitInfo doCommit(File[] paths, boolean keepLocks, String commitMessage, boolean force, boolean recursive) throws SVNException {
        return doCommit(paths, keepLocks, commitMessage, null, null, false, force, SVNDepth.getInfinityOrEmptyDepth(recursive));
    }

    /**
     * Commits files or directories into repository.
     * <p/>
     * <code>paths</code> need not be canonicalized nor condensed; this method
     * will take care of that. If
     * <code>targets has zero elements, then do nothing and return
     * immediately without error.
     * <p/>
     * If non-<span class="javakeyword">null</span>, <code>revisionProperties</code>
     * holds additional, custom revision properties (<code>String</code> names
     * mapped to {@link SVNPropertyValue} values) to be set on the new revision.
     * This table cannot contain any standard Subversion properties.
     * <p/>
     * If the caller's {@link ISVNEventHandler event handler} is not <span
     * class="javakeyword">null</span> it will be called as the commit
     * progresses with any of the following actions:
     * {@link SVNEventAction#COMMIT_MODIFIED},
     * {@link SVNEventAction#COMMIT_ADDED},
     * {@link SVNEventAction#COMMIT_DELETED},
     * {@link SVNEventAction#COMMIT_REPLACED}. If the commit succeeds, the
     * handler will be called with {@link SVNEventAction#COMMIT_COMPLETED} event
     * action.
     * <p/>
     * If <code>depth</code> is {@link SVNDepth#INFINITY}, commits all changes
     * to and below named targets. If <code>depth</code> is
     * {@link SVNDepth#EMPTY}, commits only named targets (that is, only
     * property changes on named directory targets, and property and content
     * changes for named file targets). If <code>depth</code> is
     * {@link SVNDepth#FILES}, behaves as above for named file targets, and for
     * named directory targets, commits property changes on a named directory
     * and all changes to files directly inside that directory. If
     * {@link SVNDepth#IMMEDIATES}, behaves as for {@link SVNDepth#FILES}, and
     * for subdirectories of any named directory target commits as though for
     * {@link SVNDepth#EMPTY}.
     * <p/>
     * Unlocks paths in the repository, unless <code>keepLocks</code> is <span
     * class="javakeyword">true</span>.
     * <p/>
     * <code>changelists</code> is an array of <code>String</code> changelist
     * names, used as a restrictive filter on items that are committed; that is,
     * doesn't commit anything unless it's a member of one of those changelists.
     * After the commit completes successfully, removes changelist associations
     * from the targets, unless <code>keepChangelist</code> is set. If
     * <code>changelists</code> is empty (or altogether <span
     * class="javakeyword">null</span>), no changelist filtering occurs.
     * <p/>
     * If no exception is thrown and {@link SVNCommitInfo#getNewRevision()} is
     * invalid (<code>&lt;0</code>), then the commit was a no-op; nothing needed
     * to be committed.
     *
     * @param paths
     *            paths to commit
     * @param keepLocks
     *            whether to unlock or not files in the repository
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @param changelists
     *            changelist names array
     * @param keepChangelist
     *            whether to remove <code>changelists</code> or not
     * @param force
     *            <span class="javakeyword">true</span> to force a non-recursive
     *            commit; if <code>depth</code> is {@link SVNDepth#INFINITY} the
     *            <code>force</code> flag is ignored
     * @param depth
     *            tree depth to process
     * @return information about the new committed revision
     * @throws SVNException
     * @since 1.2.0, New in Subversion 1.5.0
     */
    public SVNCommitInfo doCommit(File[] paths, boolean keepLocks, String commitMessage, SVNProperties revisionProperties, String[] changelists, boolean keepChangelist, boolean force, SVNDepth depth)
            throws SVNException {
        SVNCommitPacket packet = doCollectCommitItems(paths, keepLocks, force, depth, changelists);
        try {
            packet = packet.removeSkippedItems();
            return doCommit(packet, keepLocks, keepChangelist, commitMessage, revisionProperties);
        } finally {
            if (packet != null) {
                packet.dispose();
            }
        }
    }

    /**
     * Committs local changes made to the Working Copy items to the repository.
     * <p>
     * This method is identical to
     * <code>doCommit(commitPacket, keepLocks, false, commitMessage, null)</code>.
     * <p>
     * <code>commitPacket</code> contains commit items ({@link SVNCommitItem})
     * which represent local Working Copy items that were changed and are to be
     * committed. Commit items are gathered into a single
     * {@link SVNCommitPacket}by invoking
     * {@link #doCollectCommitItems(File[],boolean,boolean,boolean)
     * doCollectCommitItems()}.
     *
     * @param commitPacket
     *            a single object that contains items to be committed
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then the commit will left them locked,
     *            otherwise the items will be unlocked after the commit succeeds
     * @param commitMessage
     *            a string to be a commit log message
     * @return information on a new revision as the result of the commit
     * @throws SVNException
     * @see SVNCommitItem
     */
    public SVNCommitInfo doCommit(SVNCommitPacket commitPacket, boolean keepLocks, String commitMessage) throws SVNException {
        return doCommit(commitPacket, keepLocks, false, commitMessage, null);
    }

    /**
     * Commits files or directories into repository.
     * <p/>
     * This method is identical to
     * {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}
     * except for it receives a commit packet instead of paths array. The
     * aforementioned method collects commit items into a commit packet given
     * working copy paths. This one accepts already collected commit items
     * provided in <code>commitPacket</code>.
     * <p/>
     * <code>commitPacket</code> contains commit items ({@link SVNCommitItem})
     * which represent local Working Copy items that are to be committed. Commit
     * items are gathered in a single {@link SVNCommitPacket} by invoking either
     * {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,String[])}
     * or
     * {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,boolean,String[])}.
     * <p/>
     * For more details on parameters, please, refer to
     * {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}.
     *
     * @param commitPacket
     *            a single object that contains items to be committed
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then the commit will left them locked,
     *            otherwise the items will be unlocked after the commit succeeds
     * @param keepChangelist
     *            whether to remove changelists or not
     * @param commitMessage
     *            commit log message
     * @param revisionProperties
     *            custom revision properties
     * @return information about the new committed revision
     * @throws SVNException
     * @since 1.2.0, SVN 1.5.0
     */
    public SVNCommitInfo doCommit(SVNCommitPacket commitPacket, boolean keepLocks, boolean keepChangelist, String commitMessage, SVNProperties revisionProperties) throws SVNException {
        SVNCommitInfo[] info = doCommit(new SVNCommitPacket[] {
            commitPacket
        }, keepLocks, keepChangelist, commitMessage, revisionProperties);
        if (info != null && info.length > 0) {
            if (info[0].getErrorMessage() != null && info[0].getErrorMessage().getErrorCode() != SVNErrorCode.REPOS_POST_COMMIT_HOOK_FAILED) {
                SVNErrorManager.error(info[0].getErrorMessage(), SVNLogType.DEFAULT);
            }
            return info[0];
        }
        return SVNCommitInfo.NULL;
    }

    /**
     * Committs local changes, made to the Working Copy items, to the
     * repository.
     * <p>
     * <code>commitPackets</code> is an array of packets that contain commit
     * items (<b>SVNCommitItem</b>) which represent local Working Copy items
     * that were changed and are to be committed. Commit items are gathered in a
     * single <b>SVNCommitPacket</b> by invoking
     * {@link #doCollectCommitItems(File[],boolean,boolean,boolean)
     * doCollectCommitItems()}.
     * <p>
     * This allows to commit separate trees of Working Copies "belonging" to
     * different repositories. One packet per one repository. If repositories
     * are different (it means more than one commit will be done),
     * <code>commitMessage</code> may be replaced by a commit handler to be a
     * specific one for each commit.
     * <p>
     * This method is identical to
     * <code>doCommit(commitPackets, keepLocks, false, commitMessage, null)</code>.
     *
     * @param commitPackets
     *            logically grouped items to be committed
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then the commit will left them locked,
     *            otherwise the items will be unlocked after the commit succeeds
     * @param commitMessage
     *            a string to be a commit log message
     * @return committed information
     * @throws SVNException
     */
    public SVNCommitInfo[] doCommit(SVNCommitPacket[] commitPackets, boolean keepLocks, String commitMessage) throws SVNException {
        return doCommit(commitPackets, keepLocks, false, commitMessage, null);
    }

    /**
     * Commits files or directories into repository.
     * <p>
     * <code>commitPackets</code> is an array of packets that contain commit
     * items ({@link SVNCommitItem}) which represent local Working Copy items
     * that were changed and are to be committed. Commit items are gathered in a
     * single {@link SVNCommitPacket}by invoking
     * {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,String[])}
     * or
     * {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,boolean,String[])}.
     * <p>
     * This allows to commit items from separate Working Copies checked out from
     * the same or different repositories. For each commit packet
     * {@link #getCommitHandler() commit handler} is invoked to produce a commit
     * message given the one <code>commitMessage</code> passed to this method.
     * Each commit packet is committed in a separate transaction.
     * <p/>
     * For more details on parameters, please, refer to
     * {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}.
     *
     * @param commitPackets
     *            commit packets containing commit commit items per one commit
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then the commit will left them locked,
     *            otherwise the items will be unlocked by the commit
     * @param keepChangelist
     *            whether to remove changelists or not
     * @param commitMessage
     *            a string to be a commit log message
     * @param revisionProperties
     *            custom revision properties
     * @return information about the new committed revisions
     * @throws SVNException
     * @since 1.2.0, SVN 1.5.0
     */
    public SVNCommitInfo[] doCommit(SVNCommitPacket[] commitPackets, boolean keepLocks, boolean keepChangelist, String commitMessage, SVNProperties revisionProperties) throws SVNException {
        if (commitPackets == null || commitPackets.length == 0) {
            return new SVNCommitInfo[0];
        }
        Collection tmpFiles = null;
        SVNCommitInfo info = null;
        ISVNEditor commitEditor = null;
        Collection infos = new ArrayList();
        boolean needsSleepForTimeStamp = false;
        for (int p = 0; p < commitPackets.length; p++) {
            SVNCommitPacket commitPacket = commitPackets[p].removeSkippedItems();
            if (commitPacket.getCommitItems().length == 0) {
                continue;
            }
            try {
                commitMessage = getCommitHandler().getCommitMessage(commitMessage, commitPacket.getCommitItems());
                if (commitMessage == null) {
                    infos.add(SVNCommitInfo.NULL);
                    continue;
                }
                commitMessage = SVNCommitUtil.validateCommitMessage(commitMessage);
                Map commitables = new TreeMap();
                SVNURL baseURL = SVNCommitUtil.translateCommitables(commitPacket.getCommitItems(), commitables);
                Map lockTokens = SVNCommitUtil.translateLockTokens(commitPacket.getLockTokens(), baseURL.toString());
                SVNCommitItem firstItem = commitPacket.getCommitItems()[0];
                SVNRepository repository = createRepository(baseURL, firstItem.getFile(), firstItem.getWCAccess(), true);
                SVNCommitMediator mediator = new SVNCommitMediator(commitables);
                tmpFiles = mediator.getTmpFiles();
                SVNURL repositoryRootUrl = repository.getRepositoryRoot(true);
                String repositoryRoot = repositoryRootUrl.getPath();
                SVNPropertiesManager.validateRevisionProperties(revisionProperties);
                commitEditor = repository.getCommitEditor(commitMessage, lockTokens, keepLocks, revisionProperties, mediator);
                for (int i = 0; i < commitPacket.getCommitItems().length; i++) {
                    commitPacket.getCommitItems()[i].getWCAccess().setEventHandler(getEventDispatcher());
                }
                info = SVNCommitter.commit(mediator.getTmpFiles(), commitables, repositoryRootUrl, commitEditor, getEventDispatcher());
                Collection processedItems = new SVNHashSet();
                Collection explicitCommitPaths = new SVNHashSet();
                for (Iterator urls = commitables.keySet().iterator(); urls.hasNext();) {
                    String url = (String) urls.next();
                    SVNCommitItem item = (SVNCommitItem) commitables.get(url);
                    explicitCommitPaths.add(item.getPath());
                }
                for (Iterator urls = commitables.keySet().iterator(); urls.hasNext();) {
                    String url = (String) urls.next();
                    SVNCommitItem item = (SVNCommitItem) commitables.get(url);
                    SVNWCAccess wcAccess = item.getWCAccess();
                    String path = item.getPath();
                    SVNAdminArea dir = null;
                    String target = null;
                    try {
                        if (item.getKind() == SVNNodeKind.DIR) {
                            target = "";
                            dir = wcAccess.retrieve(item.getFile());
                        } else {
                            target = SVNPathUtil.tail(path);
                            dir = wcAccess.retrieve(item.getFile().getParentFile());
                        }
                    } catch (SVNException e) {
                        if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
                            dir = null;
                        }
                    }
                    if (dir == null) {
                        if (hasProcessedParents(processedItems, path)) {
                            processedItems.add(path);
                            continue;
                        }
                        if (item.isDeleted() && item.getKind() == SVNNodeKind.DIR) {
                            File parentPath = "".equals(path) ? null : item.getFile().getParentFile();
                            String nameInParent = "".equals(path) ? null : SVNPathUtil.tail(path);
                            if (parentPath != null) {
                                SVNAdminArea parentDir = wcAccess.retrieve(parentPath);
                                if (parentDir != null) {
                                    SVNEntry entryInParent = parentDir.getEntry(nameInParent, true);
                                    if (entryInParent != null) {
                                        Map attributes = new SVNHashMap();
                                        attributes.put(SVNProperty.SCHEDULE, null);
                                        attributes.put(SVNProperty.DELETED, Boolean.TRUE.toString());
                                        parentDir.modifyEntry(nameInParent, attributes, true, true);
                                    }
                                }
                            }
                            processedItems.add(path);
                            continue;
                        }
                    }
                    SVNEntry entry = dir.getEntry(target, true);
                    if (entry == null && hasProcessedParents(processedItems, path)) {
                        processedItems.add(path);
                        continue;
                    }
                    boolean recurse = false;
                    if (item.isAdded() && item.getCopyFromURL() != null && item.getKind() == SVNNodeKind.DIR) {
                        recurse = true;
                    }
                    boolean removeLock = !keepLocks && item.isLocked();
                    SVNProperties wcPropChanges = mediator.getWCProperties(item);
                    dir.commit(target, info, wcPropChanges, removeLock, recurse, !keepChangelist, explicitCommitPaths, getCommitParameters());
                    processedItems.add(path);
                }
                needsSleepForTimeStamp = true;
                dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, info.getNewRevision(), SVNEventAction.COMMIT_COMPLETED, null, null, null), ISVNEventHandler.UNKNOWN);
            } catch (SVNException e) {
                if (e instanceof SVNCancelException) {
                    throw e;
                }
                SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, e);
                SVNErrorMessage err = e.getErrorMessage().wrap("Commit failed (details follow):");
                infos.add(new SVNCommitInfo(-1, null, null, err));
                dispatchEvent(SVNEventFactory.createErrorEvent(err, SVNEventAction.COMMIT_COMPLETED), ISVNEventHandler.UNKNOWN);
                continue;
            } finally {
                if (info == null && commitEditor != null) {
                    try {
                        commitEditor.abortEdit();
                    } catch (SVNException e) {
                    }
                }
                if (tmpFiles != null) {
                    for (Iterator files = tmpFiles.iterator(); files.hasNext();) {
                        File file = (File) files.next();
                        file.delete();
                    }
                }
                if (commitPacket != null) {
                    commitPacket.dispose();
                }
            }
            infos.add(info != null ? info : SVNCommitInfo.NULL);
        }
        if (needsSleepForTimeStamp) {
            sleepForTimeStamp();
        }
        return (SVNCommitInfo[]) infos.toArray(new SVNCommitInfo[infos.size()]);
    }

    /**
     * Collects commit items (containing detailed information on each Working
     * Copy item that was changed and need to be committed to the repository)
     * into a single {@link SVNCommitPacket}.
     * <p/>
     * This method is equivalent to
     * <code>doCollectCommitItems(paths, keepLocks, force, SVNDepth.fromRecurse(recursive), null)</code>.
     *
     * @param paths
     *            an array of local items which should be traversed to collect
     *            information on every changed item (one <b>SVNCommitItem</b>
     *            per each modified local item)
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then these items will be left locked
     *            after traversing all of them, otherwise the items will be
     *            unlocked
     * @param force
     *            forces collecting commit items for a non-recursive commit
     * @param recursive
     *            relevant only for directory items: if <span
     *            class="javakeyword">true</span> then the entire directory tree
     *            will be traversed including all child directories, otherwise
     *            only items located in the directory itself will be processed
     * @return an <b>SVNCommitPacket</b> containing all Working Copy items
     *         having local modifications and represented as
     *         <b>SVNCommitItem</b> objects; if no modified items were found
     *         then {@link SVNCommitPacket#EMPTY} is returned
     * @throws SVNException
     * @deprecated use
     *             {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,String[])}
     *             instead
     */
    public SVNCommitPacket doCollectCommitItems(File[] paths, boolean keepLocks, boolean force, boolean recursive) throws SVNException {
        SVNDepth depth = recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY;
        return doCollectCommitItems(paths, keepLocks, force, depth, null);
    }

    /**
     * Collects commit items (containing detailed information on each Working
     * Copy item that contains changes and need to be committed to the
     * repository) into a single {@link SVNCommitPacket}. Further this commit
     * packet can be passed to
     * {@link #doCommit(SVNCommitPacket,boolean,boolean,String,SVNProperties)}.
     * <p/>
     * For more details on parameters, please, refer to
     * {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}.
     *
     * @param paths
     *            an array of local items which should be traversed to collect
     *            information on every changed item (one <b>SVNCommitItem</b>
     *            per each modified local item)
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then these items will be left locked
     *            after traversing all of them, otherwise the items will be
     *            unlocked
     * @param force
     *            forces collecting commit items for a non-recursive commit
     * @param depth
     *            tree depth to process
     * @param changelists
     *            changelist names array
     * @return commit packet containing commit items
     * @throws SVNException
     * @since 1.2.0
     */
    public SVNCommitPacket doCollectCommitItems(File[] paths, boolean keepLocks, boolean force, SVNDepth depth, String[] changelists) throws SVNException {
        depth = depth == null ? SVNDepth.UNKNOWN : depth;
        if (depth == SVNDepth.UNKNOWN) {
            depth = SVNDepth.INFINITY;
        }
        if (paths == null || paths.length == 0) {
            return SVNCommitPacket.EMPTY;
        }
        Collection targets = new ArrayList();
        SVNStatusClient16 statusClient = new SVNStatusClient16(getRepositoryPool(), getOptions());
        statusClient.setEventHandler(new ISVNEventHandler() {

            public void handleEvent(SVNEvent event, double progress) throws SVNException {
            }

            public void checkCancelled() throws SVNCancelException {
                SVNCommitClient16.this.checkCancelled();
            }
        });
        SVNWCAccess wcAccess = SVNCommitUtil.createCommitWCAccess(paths, depth, force, targets, statusClient);
        SVNAdminArea[] areas = wcAccess.getAdminAreas();
        for (int i = 0; areas != null && i < areas.length; i++) {
            if (areas[i] != null) {
                areas[i].setCommitParameters(getCommitParameters());
            }
        }
        try {
            Map lockTokens = new SVNHashMap();
            checkCancelled();
            Collection changelistsSet = changelists != null ? new SVNHashSet() : null;
            if (changelists != null) {
                for (int j = 0; j < changelists.length; j++) {
                    changelistsSet.add(changelists[j]);
                }
            }
            SVNCommitItem[] commitItems = SVNCommitUtil.harvestCommitables(wcAccess, targets, lockTokens, !keepLocks, depth, force, changelistsSet, getCommitParameters());
            boolean hasModifications = false;
            checkCancelled();
            for (int i = 0; commitItems != null && i < commitItems.length; i++) {
                SVNCommitItem commitItem = commitItems[i];
                if (commitItem.isAdded() || commitItem.isDeleted() || commitItem.isContentsModified() || commitItem.isPropertiesModified() || commitItem.isCopied()) {
                    hasModifications = true;
                    break;
                }
            }
            if (!hasModifications) {
                wcAccess.close();
                return SVNCommitPacket.EMPTY;
            }
            return new SVNCommitPacket(wcAccess, commitItems, lockTokens);
        } catch (SVNException e) {
            wcAccess.close();
            if (e instanceof SVNCancelException) {
                throw e;
            }
            SVNErrorMessage nestedErr = e.getErrorMessage();
            SVNErrorMessage err = SVNErrorMessage.create(nestedErr.getErrorCode(), "Commit failed (details follow):");
            SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
            return null;
        }
    }

    /**
     * Collects commit items (containing detailed information on each Working
     * Copy item that was changed and need to be committed to the repository)
     * into different <b>SVNCommitPacket</b>s.
     * <p/>
     * This method is identical to
     * <code>doCollectCommitItems(paths, keepLocks, force, SVNDepth.fromRecurse(recursive), combinePackets, null)</code>.
     *
     * @param paths
     *            an array of local items which should be traversed to collect
     *            information on every changed item (one <b>SVNCommitItem</b>
     *            per each modified local item)
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then these items will be left locked
     *            after traversing all of them, otherwise the items will be
     *            unlocked
     * @param force
     *            forces collecting commit items for a non-recursive commit
     * @param recursive
     *            relevant only for directory items: if <span
     *            class="javakeyword">true</span> then the entire directory tree
     *            will be traversed including all child directories, otherwise
     *            only items located in the directory itself will be processed
     * @param combinePackets
     *            if <span class="javakeyword">true</span> then collected commit
     *            packets will be joined into a single one, so that to be
     *            committed in a single transaction
     * @return an array of commit packets
     * @throws SVNException
     * @deprecated use
     *             {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,boolean,String[])}
     *             instead
     */
    public SVNCommitPacket[] doCollectCommitItems(File[] paths, boolean keepLocks, boolean force, boolean recursive, boolean combinePackets) throws SVNException {
        SVNDepth depth = recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY;
        return doCollectCommitItems(paths, keepLocks, force, depth, combinePackets, null);
    }

    /**
     * Collects commit items (containing detailed information on each Working
     * Copy item that was changed and need to be committed to the repository)
     * into different <code>SVNCommitPacket</code>s. This method may be
     * considered as an advanced version of the
     * {@link #doCollectCommitItems(File[],boolean,boolean,SVNDepth,String[])}
     * method. Its main difference from the aforementioned method is that it
     * provides an ability to collect commit items from different working copies
     * checked out from the same repository and combine them into a single
     * commit packet. This is attained via setting <code>combinePackets</code>
     * into <span class="javakeyword">true</span>. However even if
     * <code>combinePackets</code> is set, combining may only occur if (besides
     * that the paths must be from the same repository) URLs of
     * <code>paths</code> are formed of identical components, that is protocol
     * name, host name, port number (if any) must match for all paths. Otherwise
     * combining will not occur.
     * <p/>
     * Combined items will be committed in a single transaction.
     * <p/>
     * For details on other parameters, please, refer to
     * {@link #doCommit(File[],boolean,String,SVNProperties,String[],boolean,boolean,SVNDepth)}.
     *
     * @param paths
     *            an array of local items which should be traversed to collect
     *            information on every changed item (one <b>SVNCommitItem</b>
     *            per each modified local item)
     * @param keepLocks
     *            if <span class="javakeyword">true</span> and there are local
     *            items that were locked then these items will be left locked
     *            after traversing all of them, otherwise the items will be
     *            unlocked
     * @param force
     *            forces collecting commit items for a non-recursive commit
     * @param depth
     *            tree depth to process
     * @param combinePackets
     *            whether combining commit packets into a single commit packet
     *            is allowed or not
     * @param changelists
     *            changelist names array
     * @return array of commit packets
     * @throws SVNException
     *             in the following cases:
     *             <ul>
     *             <li/>exception with {@link SVNErrorCode#ENTRY_MISSING_URL}
     *             error code - if working copy root of either path has no url
     *             </ul>
     * @since 1.2.0
     */
    public SVNCommitPacket[] doCollectCommitItems(File[] paths, boolean keepLocks, boolean force, SVNDepth depth, boolean combinePackets, String[] changelists) throws SVNException {
        depth = depth == null ? SVNDepth.UNKNOWN : depth;
        if (depth == SVNDepth.UNKNOWN) {
            depth = SVNDepth.INFINITY;
        }
        if (paths == null || paths.length == 0) {
            return new SVNCommitPacket[0];
        }
        Collection packets = new ArrayList();
        Map targets = new SVNHashMap();
        SVNStatusClient16 statusClient = new SVNStatusClient16(getRepositoryPool(), getOptions());
        statusClient.setEventHandler(new ISVNEventHandler() {

            public void handleEvent(SVNEvent event, double progress) throws SVNException {
            }

            public void checkCancelled() throws SVNCancelException {
                SVNCommitClient16.this.checkCancelled();
            }
        });
        SVNWCAccess[] wcAccesses = SVNCommitUtil.createCommitWCAccess2(paths, depth, force, targets, statusClient);
        for (int i = 0; i < wcAccesses.length; i++) {
            SVNWCAccess wcAccess = wcAccesses[i];
            SVNAdminArea[] areas = wcAccess.getAdminAreas();
            for (int j = 0; areas != null && j < areas.length; j++) {
                if (areas[j] != null) {
                    areas[j].setCommitParameters(getCommitParameters());
                }
            }
            Collection targetPaths = (Collection) targets.get(wcAccess);
            try {
                checkCancelled();
                Map lockTokens = new SVNHashMap();
                Collection changelistsSet = changelists != null ? new SVNHashSet() : null;
                if (changelists != null) {
                    for (int j = 0; j < changelists.length; j++) {
                        changelistsSet.add(changelists[j]);
                    }
                }
                SVNCommitItem[] commitItems = SVNCommitUtil.harvestCommitables(wcAccess, targetPaths, lockTokens, !keepLocks, depth, force, changelistsSet, getCommitParameters());
                checkCancelled();
                boolean hasModifications = false;
                for (int j = 0; commitItems != null && j < commitItems.length; j++) {
                    SVNCommitItem commitItem = commitItems[j];
                    if (commitItem.isAdded() || commitItem.isDeleted() || commitItem.isContentsModified() || commitItem.isPropertiesModified() || commitItem.isCopied()) {
                        hasModifications = true;
                        break;
                    }
                }
                if (!hasModifications) {
                    wcAccess.close();
                    continue;
                }
                packets.add(new SVNCommitPacket(wcAccess, commitItems, lockTokens));
            } catch (SVNException e) {
                for (int j = 0; j < wcAccesses.length; j++) {
                    wcAccesses[j].close();
                }
                if (e instanceof SVNCancelException) {
                    throw e;
                }
                SVNErrorMessage nestedErr = e.getErrorMessage();
                SVNErrorMessage err = SVNErrorMessage.create(nestedErr.getErrorCode(), "Commit failed (details follow):");
                SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
            }
        }
        SVNCommitPacket[] packetsArray = (SVNCommitPacket[]) packets.toArray(new SVNCommitPacket[packets.size()]);
        if (!combinePackets) {
            return packetsArray;
        }
        Map repoUUIDs = new SVNHashMap();
        Map locktokensMap = new SVNHashMap();
        try {
            for (int i = 0; i < packetsArray.length; i++) {
                checkCancelled();
                SVNCommitPacket packet = packetsArray[i];
                File wcRoot = SVNWCUtil.getWorkingCopyRoot(packet.getCommitItems()[0].getWCAccess().getAnchor(), true);
                SVNWCAccess rootWCAccess = createWCAccess();
                String uuid = null;
                SVNURL url = null;
                try {
                    SVNAdminArea rootDir = rootWCAccess.open(wcRoot, false, 0);
                    uuid = rootDir.getEntry(rootDir.getThisDirName(), false).getUUID();
                    url = rootDir.getEntry(rootDir.getThisDirName(), false).getSVNURL();
                } finally {
                    rootWCAccess.close();
                }
                checkCancelled();
                if (uuid == null) {
                    if (url != null) {
                        SVNRepository repos = createRepository(url, wcRoot, rootWCAccess, true);
                        uuid = repos.getRepositoryUUID(true);
                    } else {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", wcRoot);
                        SVNErrorManager.error(err, SVNLogType.WC);
                    }
                }
                uuid += url.getProtocol() + ":" + url.getHost() + ":" + url.getPort() + ":" + url.getUserInfo();
                if (!repoUUIDs.containsKey(uuid)) {
                    repoUUIDs.put(uuid, new ArrayList());
                    locktokensMap.put(uuid, new SVNHashMap());
                }
                Collection items = (Collection) repoUUIDs.get(uuid);
                Map lockTokens = (Map) locktokensMap.get(uuid);
                for (int j = 0; j < packet.getCommitItems().length; j++) {
                    items.add(packet.getCommitItems()[j]);
                }
                if (packet.getLockTokens() != null) {
                    lockTokens.putAll(packet.getLockTokens());
                }
                checkCancelled();
            }
            packetsArray = new SVNCommitPacket[repoUUIDs.size()];
            int index = 0;
            for (Iterator roots = repoUUIDs.keySet().iterator(); roots.hasNext();) {
                checkCancelled();
                String uuid = (String) roots.next();
                Collection items = (Collection) repoUUIDs.get(uuid);
                Map lockTokens = (Map) locktokensMap.get(uuid);
                SVNCommitItem[] itemsArray = (SVNCommitItem[]) items.toArray(new SVNCommitItem[items.size()]);
                packetsArray[index++] = new SVNCommitPacket((SVNWCAccess)null, itemsArray, lockTokens);
            }
        } catch (SVNException e) {
            for (int j = 0; j < wcAccesses.length; j++) {
                wcAccesses[j].close();
            }
            if (e instanceof SVNCancelException) {
                throw e;
            }
            SVNErrorMessage nestedErr = e.getErrorMessage();
            SVNErrorMessage err = SVNErrorMessage.create(nestedErr.getErrorCode(), "Commit failed (details follow):");
            SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
        }
        return packetsArray;
    }

    private void addURLParents(List targets, SVNURL url) throws SVNException {
        SVNURL parentURL = url.removePathTail();
        SVNRepository repos = createRepository(parentURL, null, null, true);
        SVNNodeKind kind = repos.checkPath("", SVNRepository.INVALID_REVISION);
        if (kind == SVNNodeKind.NONE) {
            addURLParents(targets, parentURL);
        }
        targets.add(url);
    }

    private boolean importDir(SVNDeltaGenerator deltaGenerator, File dir, String importPath, boolean useGlobalIgnores, boolean ignoreUnknownNodeTypes, SVNDepth depth, Map<String, Map<String, String>> versionedAutoProperties, ISVNFileFilter fileFilter, ISVNEditor editor)
            throws SVNException {
        checkCancelled();
        File[] children = SVNFileListUtil.listFiles(dir);
        boolean changed = false;
        ISVNFileFilter commitHandlerFilter = getCommitHandler() instanceof ISVNFileFilter ? (ISVNFileFilter) getCommitHandler() : null;
        Collection ignores = useGlobalIgnores ? SVNStatusEditor.getGlobalIgnores(getOptions()) : null;
        for (int i = 0; children != null && i < children.length; i++) {
            File file = children[i];
            if (SVNFileUtil.getAdminDirectoryName().equals(file.getName())) {
                SVNEvent skippedEvent = SVNEventFactory.createSVNEvent(file, SVNNodeKind.NONE, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.COMMIT_ADDED, null, null);
                handleEvent(skippedEvent, ISVNEventHandler.UNKNOWN);
                continue;
            }
            if (commitHandlerFilter != null && !commitHandlerFilter.accept(file)) {
                continue;
            }
            if (fileFilter != null && !fileFilter.accept(file)) {
                continue;
            }
            String path = importPath == null ? file.getName() : SVNPathUtil.append(importPath, file.getName());
            if (useGlobalIgnores && SVNStatusEditor.isIgnored(ignores, file, "/" + path)) {
                continue;
            }
            SVNFileType fileType = SVNFileType.getType(file);
            if (fileType == SVNFileType.DIRECTORY && depth.compareTo(SVNDepth.IMMEDIATES) >= 0) {
                editor.addDir(path, null, -1);
                changed |= true;
                SVNEvent event = SVNEventFactory.createSVNEvent(file, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.COMMIT_ADDED, null, null, null);
                handleEvent(event, ISVNEventHandler.UNKNOWN);
                SVNDepth depthBelowHere = depth;
                if (depth == SVNDepth.IMMEDIATES) {
                    depthBelowHere = SVNDepth.EMPTY;
                }
                importDir(deltaGenerator, file, path, useGlobalIgnores, ignoreUnknownNodeTypes, depthBelowHere, versionedAutoProperties, fileFilter, editor);
                editor.closeDir();
            } else if ((fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK) && depth.compareTo(SVNDepth.FILES) >= 0) {
                changed |= importFile(deltaGenerator, file, fileType, path, versionedAutoProperties, editor);
            } else if (fileType != SVNFileType.DIRECTORY && fileType != SVNFileType.FILE) {
                if (ignoreUnknownNodeTypes) {
                    SVNEvent skippedEvent = SVNEventFactory.createSVNEvent(file, SVNNodeKind.NONE, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.COMMIT_ADDED, null, null);
                    handleEvent(skippedEvent, ISVNEventHandler.UNKNOWN);
                } else {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Unknown or unversionable type for ''{0}''", file);
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
            }
        }
        return changed;
    }

    private boolean importFile(SVNDeltaGenerator deltaGenerator, File file, SVNFileType fileType, String filePath, Map<String, Map<String, String>> versionedAutoProperties, ISVNEditor editor) throws SVNException {
        if (fileType == null || fileType == SVNFileType.UNKNOWN) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "unknown or unversionable type for ''{0}''", file);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        editor.addFile(filePath, null, -1);
        Map<String, String> matchedAutoProperties = SvnNgPropertiesManager.getMatchedAutoProperties(SVNFileUtil.getFileName(file), versionedAutoProperties);
        Map autoProperties = new SVNHashMap();
        if (fileType != SVNFileType.SYMLINK) {
            autoProperties = SVNPropertiesManager.computeAutoProperties(getOptions(), file, autoProperties);
        } else {
            autoProperties.put(SVNProperty.SPECIAL, "*");
        }
        autoProperties.putAll(matchedAutoProperties);
        String mimeTypeProperty = (String) autoProperties.get(SVNProperty.MIME_TYPE);
        for (Iterator names = autoProperties.keySet().iterator(); names.hasNext();) {
            String name = (String) names.next();
            String value = (String) autoProperties.get(name);
            if (SVNProperty.EOL_STYLE.equals(name) && value != null) {
                if (SVNProperty.isBinaryMimeType(mimeTypeProperty)) {
                    continue;
                } else if (!SVNTranslator.checkNewLines(file)) {
                    continue;
                }
            }
            if (SVNProperty.CHARSET.equals(name) && value != null) {
                if (SVNProperty.isBinaryMimeType(mimeTypeProperty)) {
                    continue;
                }
                try {
                    SVNTranslator.getCharset(value, mimeTypeProperty, filePath, getOptions());
                } catch (SVNException e) {
                    continue;
                }
            }
            editor.changeFileProperty(filePath, name, SVNPropertyValue.create(value));
        }
        SVNEvent addedEvent = SVNEventFactory.createSVNEvent(file, SVNNodeKind.FILE, mimeTypeProperty, SVNRepository.INVALID_REVISION, SVNEventAction.COMMIT_ADDED, null, null, null);
        handleEvent(addedEvent, ISVNEventHandler.UNKNOWN);
        String charset = SVNTranslator.getCharset((String) autoProperties.get(SVNProperty.CHARSET), mimeTypeProperty, file.getPath(), getOptions());
        String eolStyle = (String) autoProperties.get(SVNProperty.EOL_STYLE);
        String keywords = (String) autoProperties.get(SVNProperty.KEYWORDS);
        boolean special = autoProperties.get(SVNProperty.SPECIAL) != null;
        File tmpFile = null;
        if (charset != null || eolStyle != null || keywords != null || special) {
            byte[] eolBytes = SVNTranslator.getBaseEOL(eolStyle);
            Map keywordsMap = keywords != null ? SVNTranslator.computeKeywords(keywords, null, null, null, null, null, getOptions()) : null;
            tmpFile = SVNFileUtil.createTempFile("import", ".tmp");
            SVNTranslator.translate(file, tmpFile, charset, eolBytes, keywordsMap, special, false);
        }
        File importedFile = tmpFile != null ? tmpFile : file;
        InputStream is = null;
        String checksum = null;
        try {
            is = SVNFileUtil.openFileForReading(importedFile, SVNLogType.WC);
            editor.applyTextDelta(filePath, null);
            checksum = deltaGenerator.sendDelta(filePath, is, editor, true);
        } finally {
            SVNFileUtil.closeFile(is);
            SVNFileUtil.deleteFile(tmpFile);
        }
        editor.closeFile(filePath, checksum);
        return true;
    }

    private static boolean hasProcessedParents(Collection paths, String path) throws SVNException {
        path = SVNPathUtil.removeTail(path);
        if (paths.contains(path)) {
            return true;
        }
        if ("".equals(path)) {
            return false;
        }
        return hasProcessedParents(paths, path);
    }

    static String validateCommitMessage(String message) {
        if (message == null) {
            return message;
        }
        message = message.replaceAll("\r\n", "\n");
        message = message.replace('\r', '\n');
        return message;
    }

    private Map<String, Map<String, String>> getVersionedAutoProperties(SVNURL url, SVNURL reposRoot) throws SVNException {
        SVNProperties regularProperties;
        SVNURL parentUrl = url.removePathTail();
        if (url.equals(reposRoot)) {
            return null;
        }

        final List<SvnInheritedProperties>[] inheritedConfigAutoProperties = new List[1];
        final SvnOperationFactory operationFactory = new SvnOperationFactory();
        try {
            operationFactory.setRepositoryPool(getRepositoryPool());
            operationFactory.setOptions(getOptions());
            operationFactory.setEventHandler(getEventDispatcher());
            operationFactory.setAutoDisposeRepositoryPool(false);
            operationFactory.setCanceller(getEventDispatcher());
            do {
                SvnGetProperties getProperties = operationFactory.createGetProperties();
                getProperties.setSingleTarget(SvnTarget.fromURL(parentUrl, SVNRevision.HEAD));
                getProperties.setRevision(SVNRevision.HEAD);
                getProperties.setDepth(SVNDepth.EMPTY);
                getProperties.setTargetInheritedPropertiesReceiver(new ISvnObjectReceiver<List<SvnInheritedProperties>>() {
                    public void receive(SvnTarget target, List<SvnInheritedProperties> inheritedProperties) throws SVNException {
                        inheritedConfigAutoProperties[0] = inheritedProperties;
                    }
                });
                try {
                    regularProperties = getProperties.run();
                    break;
                } catch (SVNException e) {
                    if (e.getErrorMessage().getErrorCode() != SVNErrorCode.RA_LOCAL_REPOS_OPEN_FAILED && e.getErrorMessage().getErrorCode() != SVNErrorCode.ENTRY_NOT_FOUND) {
                        throw e;
                    }
                    if (parentUrl.equals(reposRoot)) {
                        regularProperties = null;
                        break;
                    }
                    parentUrl = parentUrl.removePathTail();
                }
            } while (true);

            Map<String, Map<String ,String>> allAutoProperties = new HashMap<String, Map<String, String>>();

            if (inheritedConfigAutoProperties[0] != null) {
                for (SvnInheritedProperties inheritedConfigAutoProperty : inheritedConfigAutoProperties[0]) {
                    SVNProperties inheritedProperties = inheritedConfigAutoProperty.getProperties();
                    Map<String, SVNPropertyValue> inheritedPropertiesMap = inheritedProperties.asMap();
                    for (Map.Entry<String, SVNPropertyValue> entry : inheritedPropertiesMap.entrySet()) {
                        String propertyName = entry.getKey();
                        if (!SVNProperty.INHERITABLE_AUTO_PROPS.equals(propertyName)) {
                            continue;
                        }
                        SVNPropertyValue propertyValue = entry.getValue();
                        allAutoProperties = SvnNgPropertiesManager.parseAutoProperties(propertyValue, allAutoProperties);
                    }
                }
            }
            if (regularProperties != null) {
                Map<String, SVNPropertyValue> regularPropertiesMap = regularProperties.asMap();
                for (Map.Entry<String, SVNPropertyValue> entry : regularPropertiesMap.entrySet()) {
                    String propertyName = entry.getKey();
                    if (!SVNProperty.INHERITABLE_AUTO_PROPS.equals(propertyName)) {
                        continue;
                    }
                    SVNPropertyValue propertyValue = entry.getValue();
                    allAutoProperties = SvnNgPropertiesManager.parseAutoProperties(propertyValue, allAutoProperties);
                }
            }

            return allAutoProperties;

        } finally {
            operationFactory.dispose();
        }
    }
}
