<commit_before>/**
 * @copyright
 * ====================================================================
 * Copyright (c) 2003-2007 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 * @endcopyright
 *
 * @file SVNClient.cpp
 * @brief: Implementation of the SVNClient class
 */

#include "SVNClient.h"
#include "JNIUtil.h"
#include "Notify.h"
#include "Notify2.h"
#include "CopySources.h"
#include "DiffSummaryReceiver.h"
#include "ConflictResolverCallback.h"
#include "ProgressListener.h"
#include "Prompter.h"
#include "Pool.h"
#include "Targets.h"
#include "Revision.h"
#include "RevisionRange.h"
#include "BlameCallback.h"
#include "ProplistCallback.h"
#include "LogMessageCallback.h"
#include "InfoCallback.h"
#include "StatusCallback.h"
#include "ListCallback.h"
#include "JNIByteArray.h"
#include "CommitMessage.h"
#include "EnumMapper.h"
#include "svn_types.h"
#include "svn_client.h"
#include "svn_sorts.h"
#include "svn_time.h"
#include "svn_diff.h"
#include "svn_config.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_utf.h"
#include "private/svn_client_private.h"
#include "svn_private_config.h"
#include "../include/org_tigris_subversion_javahl_Revision.h"
#include "../include/org_tigris_subversion_javahl_NodeKind.h"
#include "../include/org_tigris_subversion_javahl_StatusKind.h"
#include "JNIStringHolder.h"
#include <vector>
#include <iostream>
#include <sstream>

struct log_msg_baton
{
    const char *message;
    CommitMessage *messageHandler;
};

SVNClient::SVNClient()
{
    m_notify = NULL;
    m_notify2 = NULL;
    m_progressListener = NULL;
    m_prompter = NULL;
    m_commitMessage = NULL;
    m_conflictResolver = NULL;
}

SVNClient::~SVNClient()
{
    delete m_notify;
    delete m_notify2;
    delete m_progressListener;
    delete m_prompter;
    delete m_conflictResolver;
}

SVNClient *SVNClient::getCppObject(jobject jthis)
{
    static jfieldID fid = 0;
    jlong cppAddr = SVNBase::findCppAddrForJObject(jthis, &fid,
                                                   JAVA_PACKAGE"/SVNClient");
    return (cppAddr == 0 ? NULL : reinterpret_cast<SVNClient *>(cppAddr));
}

void SVNClient::dispose(jobject jthis)
{
    static jfieldID fid = 0;
    SVNBase::dispose(jthis, &fid, JAVA_PACKAGE"/SVNClient");
}

jstring SVNClient::getAdminDirectoryName()
{
    Pool requestPool;
    jstring name =
        JNIUtil::makeJString(svn_wc_get_adm_dir(requestPool.pool()));
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    return name;
}

jboolean SVNClient::isAdminDirectory(const char *name)
{
    Pool requestPool;
    return svn_wc_is_adm_dir(name, requestPool.pool()) ? JNI_TRUE : JNI_FALSE;
}

const char *SVNClient::getLastPath()
{
    return m_lastPath.c_str();
}

/**
 * List directory entries of a URL.
 */
void SVNClient::list(const char *url, Revision &revision,
                     Revision &pegRevision, svn_depth_t depth,
                     int direntFields, bool fetchLocks,
                     ListCallback *callback)
{
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_NULL_PTR_EX(url, "path or url", );

    Path urlPath(url);
    SVN_JNI_ERR(urlPath.error_occured(), );

    SVN_JNI_ERR(svn_client_list2(urlPath.c_str(),
                                 pegRevision.revision(),
                                 revision.revision(),
                                 depth,
                                 direntFields,
                                 fetchLocks,
                                 ListCallback::callback,
                                 callback,
                                 ctx, requestPool.pool()), );
}

void
SVNClient::status(const char *path, svn_depth_t depth,
                  bool onServer, bool getAll, bool noIgnore,
                  bool ignoreExternals, StatusCallback *callback)
{
    Pool requestPool;
    svn_revnum_t youngest = SVN_INVALID_REVNUM;
    svn_opt_revision_t rev;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Path checkedPath(path);
    SVN_JNI_ERR(checkedPath.error_occured(), );

    rev.kind = svn_opt_revision_unspecified;

    SVN_JNI_ERR(svn_client_status3(&youngest, checkedPath.c_str(),
                                   &rev, StatusCallback::callback,
                                   callback,
                                   depth,
                                   getAll, onServer, noIgnore,
                                   ignoreExternals,
                                   ctx, requestPool.pool()), );
}

void SVNClient::username(const char *pi_username)
{
    m_userName = (pi_username == NULL ? "" : pi_username);
}

void SVNClient::password(const char *pi_password)
{
    m_passWord = (pi_password == NULL ? "" : pi_password);
}

void SVNClient::setPrompt(Prompter *prompter)
{
    delete m_prompter;
    m_prompter = prompter;
}

void SVNClient::logMessages(const char *path, Revision &pegRevision,
                            Revision &revisionStart,
                            Revision &revisionEnd, bool stopOnCopy,
                            bool discoverPaths, bool includeMergedRevisions,
                            bool omitLogText, long limit,
                            LogMessageCallback *callback)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Targets target(path);
    const apr_array_header_t *targets = target.array(requestPool);
    SVN_JNI_ERR(target.error_occured(), );

    apr_array_header_t *revprops = apr_array_make(requestPool.pool(), 3,
                                                  sizeof(char *));
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;

    SVN_JNI_ERR(svn_client_log4(targets,
                                pegRevision.revision(),
                                revisionStart.revision(),
                                revisionEnd.revision(),
                                limit,
                                discoverPaths,
                                stopOnCopy,
                                includeMergedRevisions,
                                revprops,
                                LogMessageCallback::callback, callback, ctx,
                                requestPool.pool()), );
}

jlong SVNClient::checkout(const char *moduleName, const char *destPath,
                          Revision &revision, Revision &pegRevision,
                          svn_depth_t depth, bool ignoreExternals,
                          bool allowUnverObstructions)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(moduleName, "moduleName", -1);
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", -1);

    Path url(moduleName);
    Path path(destPath);
    SVN_JNI_ERR(url.error_occured(), -1);
    SVN_JNI_ERR(path.error_occured(), -1);
    svn_revnum_t rev;

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_checkout3(&rev, url.c_str(),
                                     path.c_str(),
                                     pegRevision.revision(),
                                     revision.revision(),
                                     depth,
                                     ignoreExternals,
                                     allowUnverObstructions,
                                     ctx,
                                     requestPool.pool()),
                -1);

    return rev;
}

void SVNClient::notification(Notify *notify)
{
    delete m_notify;
    m_notify = notify;
}

void SVNClient::notification2(Notify2 *notify2)
{
    delete m_notify2;
    m_notify2 = notify2;
}

void SVNClient::setConflictResolver(ConflictResolverCallback *conflictResolver)
{
    delete m_conflictResolver;
    m_conflictResolver = conflictResolver;
}

void SVNClient::setProgressListener(ProgressListener *listener)
{
    delete m_progressListener;
    m_progressListener = listener;
}

void SVNClient::remove(Targets &targets, const char *message, bool force,
                       bool keep_local)
{
    svn_commit_info_t *commit_info = NULL;
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), );

    SVN_JNI_ERR(svn_client_delete3(&commit_info, targets2, force, keep_local,
                                   ctx, requestPool.pool()), );
}

void SVNClient::revert(const char *path, svn_depth_t depth)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Targets target(path);
    const apr_array_header_t *targets = target.array(requestPool);
    SVN_JNI_ERR(target.error_occured(), );
    SVN_JNI_ERR(svn_client_revert2(targets, depth, ctx, requestPool.pool()), );
}

void SVNClient::add(const char *path,
                    svn_depth_t depth, bool force, bool no_ignore,
                    bool add_parents)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_add4(intPath.c_str(), depth, force,
                                no_ignore, add_parents, ctx,
                                requestPool.pool()), );
}

jlongArray SVNClient::update(Targets &targets, Revision &revision,
                             svn_depth_t depth, bool ignoreExternals,
                             bool allowUnverObstructions)
{
    Pool requestPool;

    svn_client_ctx_t *ctx = getContext(NULL);
    apr_array_header_t *revs;
    if (ctx == NULL)
        return NULL;

    const apr_array_header_t *array = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), NULL);
    SVN_JNI_ERR(svn_client_update3(&revs, array,
                                   revision.revision(),
                                   depth,
                                   ignoreExternals,
                                   allowUnverObstructions,
                                   ctx, requestPool.pool()),
                NULL);

    JNIEnv *env = JNIUtil::getEnv();
    jlongArray jrevs = env->NewLongArray(revs->nelts);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;
    jlong *jrevArray = env->GetLongArrayElements(jrevs, NULL);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;
    for (int i = 0; i < revs->nelts; ++i)
    {
        jlong rev = APR_ARRAY_IDX(revs, i, svn_revnum_t);
        jrevArray[i] = rev;
    }
    env->ReleaseLongArrayElements(jrevs, jrevArray, 0);

    return jrevs;

}

jlong SVNClient::commit(Targets &targets, const char *message,
                        svn_depth_t depth, bool noUnlock, bool keepChangelist,
                        const char *changelistName)
{
    Pool requestPool;
    svn_commit_info_t *commit_info = NULL;
    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), -1);
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return SVN_INVALID_REVNUM;

    SVN_JNI_ERR(svn_client_commit4(&commit_info, targets2, depth,
                                   noUnlock, keepChangelist, changelistName,
                                   ctx, requestPool.pool()),
                SVN_INVALID_REVNUM);

    if (commit_info && SVN_IS_VALID_REVNUM(commit_info->revision))
        return commit_info->revision;

    return SVN_INVALID_REVNUM;
}

void SVNClient::copy(CopySources &copySources, const char *destPath,
                     const char *message, bool copyAsChild, bool makeParents,
                     bool withMergeHistory)
{
    Pool requestPool;

    apr_array_header_t *srcs = copySources.array(requestPool);
    if (srcs == NULL)
    {
        JNIUtil::throwNativeException(JAVA_PACKAGE "/ClientException",
                                      "Invalid copy sources");
        return;
    }
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", );
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    svn_commit_info_t *commit_info;
    SVN_JNI_ERR(svn_client_copy4(&commit_info, srcs, destinationPath.c_str(),
                                 copyAsChild, makeParents, withMergeHistory,
                                 ctx, requestPool.pool()),
                );
}

void SVNClient::move(Targets &srcPaths, const char *destPath,
                     const char *message, bool force, bool moveAsChild,
                     bool makeParents, bool withMergeHistory)
{
    Pool requestPool;

    const apr_array_header_t *srcs = srcPaths.array(requestPool);
    SVN_JNI_ERR(srcPaths.error_occured(), );
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", );
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    svn_commit_info_t *commit_info;
    SVN_JNI_ERR(svn_client_move5(&commit_info, (apr_array_header_t *) srcs,
                                 destinationPath.c_str(), force, moveAsChild,
                                 makeParents, withMergeHistory, ctx,
                                 requestPool.pool()), );
}

void SVNClient::mkdir(Targets &targets, const char *message, bool makeParents)
{
    Pool requestPool;
    svn_commit_info_t *commit_info = NULL;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), );

    SVN_JNI_ERR(svn_client_mkdir3(&commit_info, targets2, makeParents, ctx,
                                  requestPool.pool()), );
}

void SVNClient::cleanup(const char *path)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_cleanup(intPath.c_str(), ctx, requestPool.pool()),);
}

void SVNClient::resolved(const char *path, bool recurse)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_resolved(intPath.c_str(), recurse,
                                    ctx, requestPool.pool()), );
}

jlong SVNClient::doExport(const char *srcPath, const char *destPath,
                          Revision &revision, Revision &pegRevision,
                          bool force, bool ignoreExternals,
                          svn_depth_t depth, const char *nativeEOL)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(srcPath, "srcPath", -1);
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", -1);
    Path sourcePath(srcPath);
    SVN_JNI_ERR(sourcePath.error_occured(), -1);
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), -1);
    svn_revnum_t rev;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_export4(&rev, sourcePath.c_str(),
                                   destinationPath.c_str(),
                                   pegRevision.revision(),
                                   revision.revision(), force,
                                   ignoreExternals,
                                   depth,
                                   nativeEOL, ctx,
                                   requestPool.pool()),
                -1);

    return rev;

}

jlong SVNClient::doSwitch(const char *path, const char *url,
                          Revision &revision, svn_depth_t depth,
                          bool ignoreExternals, bool allowUnverObstructions)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", -1);
    SVN_JNI_NULL_PTR_EX(url, "url", -1);
    Path intUrl(url);
    SVN_JNI_ERR(intUrl.error_occured(), -1);
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), -1);

    svn_revnum_t rev;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_switch2(&rev, intPath.c_str(),
                                   intUrl.c_str(),
                                   revision.revision(),
                                   depth,
                                   ignoreExternals,
                                   allowUnverObstructions,
                                   ctx,
                                   requestPool.pool()),
                -1);

    return rev;
}

void SVNClient::doImport(const char *path, const char *url,
                         const char *message, svn_depth_t depth,
                         bool noIgnore, bool ignoreUnknownNodeTypes)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    SVN_JNI_NULL_PTR_EX(url, "url", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    Path intUrl(url);
    SVN_JNI_ERR(intUrl.error_occured(), );

    svn_commit_info_t *commit_info = NULL;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_import3(&commit_info, intPath.c_str(),
                                   intUrl.c_str(), depth, noIgnore,
                                   ignoreUnknownNodeTypes, ctx,
                                   requestPool.pool()), );
}

jobjectArray
SVNClient::suggestMergeSources(const char *path, Revision &pegRevision)
{
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_array_header_t *sources;
    SVN_JNI_ERR(svn_client_suggest_merge_sources(&sources, path,
                                                 pegRevision.revision(),
                                                 ctx, requestPool.pool()),
                NULL);

    JNIEnv *env = JNIUtil::getEnv();
    jclass clazz = env->FindClass("java/lang/String");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    jobjectArray jsuggestions = env->NewObjectArray(sources->nelts, clazz,
                                                    NULL);
    for (int i = 0; i < sources->nelts; ++i)
    {
        const char *source = APR_ARRAY_IDX(sources, i, const char *);
        jstring jpath = JNIUtil::makeJString(source);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;

        env->SetObjectArrayElement(jsuggestions, i, jpath);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jsuggestions;
}

void SVNClient::merge(const char *path1, Revision &revision1,
                      const char *path2, Revision &revision2,
                      const char *localPath, bool force, svn_depth_t depth,
                      bool ignoreAncestry, bool dryRun)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path1, "path1", );
    SVN_JNI_NULL_PTR_EX(path2, "path2", );
    SVN_JNI_NULL_PTR_EX(localPath, "localPath", );
    Path intLocalPath(localPath);
    SVN_JNI_ERR(intLocalPath.error_occured(), );

    Path srcPath1(path1);
    SVN_JNI_ERR(srcPath1.error_occured(), );

    Path srcPath2 = path2;
    SVN_JNI_ERR(srcPath2.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_merge3(srcPath1.c_str(), revision1.revision(),
                                  srcPath2.c_str(), revision2.revision(),
                                  intLocalPath.c_str(),
                                  depth,
                                  ignoreAncestry, force, FALSE, dryRun, NULL,
                                  ctx, requestPool.pool()), );
}

void SVNClient::merge(const char *path, Revision &pegRevision,
                      Revision &revision1, Revision &revision2,
                      const char *localPath, bool force, svn_depth_t depth,
                      bool ignoreAncestry, bool dryRun)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    SVN_JNI_NULL_PTR_EX(localPath, "localPath", );
    Path intLocalPath(localPath);
    SVN_JNI_ERR(intLocalPath.error_occured(), );

    Path srcPath(path);
    SVN_JNI_ERR(srcPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_merge_peg3(srcPath.c_str(), revision1.revision(),
                                      revision2.revision(),
                                      pegRevision.revision(),
                                      intLocalPath.c_str(),
                                      depth,
                                      ignoreAncestry, force, FALSE, dryRun,
                                      NULL, ctx, requestPool.pool()), );
}

jobject
SVNClient::getMergeInfo(const char *target, Revision &pegRevision)
{
    Pool requestPool;
    JNIEnv *env = JNIUtil::getEnv();

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_hash_t *mergeinfo;
    Path intLocalTarget(target);
    SVN_JNI_ERR(intLocalTarget.error_occured(), NULL);
    SVN_JNI_ERR(svn_client_mergeinfo_get_merged(&mergeinfo,
                                                intLocalTarget.c_str(),
                                                pegRevision.revision(), ctx,
                                                requestPool.pool()),
                NULL);
    if (mergeinfo == NULL)
        return NULL;

    // Transform mergeinfo into Java MergeInfo object.
    jclass clazz = env->FindClass(JAVA_PACKAGE "/MergeInfo");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    static jmethodID ctor = 0;
    if (ctor == 0)
    {
        ctor = env->GetMethodID(clazz, "<init>", "()V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    static jmethodID addRevisions = 0;
    if (addRevisions == 0)
    {
        addRevisions = env->GetMethodID(clazz, "addRevisions",
                                        "(Ljava/lang/String;"
                                        "Ljava/util/List;)V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    jobject jmergeinfo = env->NewObject(clazz, ctor);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    clazz = env->FindClass("java/util/ArrayList");
    static jmethodID listCtor = 0;
    if (listCtor == 0)
    {
        listCtor = env->GetMethodID(clazz, "<init>", "(I)V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }
    static jmethodID addToList = 0;
    if (addToList == 0)
    {
        addToList = env->GetMethodID(clazz, "add", "(Ljava/lang/Object;)Z");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    apr_hash_index_t *hi;
    for (hi = apr_hash_first(requestPool.pool(), mergeinfo);
         hi;
         hi = apr_hash_next(hi))
    {
        const void *path;
        void *val;
        apr_hash_this(hi, &path, NULL, &val);

        jstring jpath = JNIUtil::makeJString((const char *) path);
        apr_array_header_t *ranges = (apr_array_header_t *) val;
        jobject jranges = env->NewObject(clazz, listCtor, ranges->nelts);
        for (int i = 0; i < ranges->nelts; ++i)
        {
            // Convert svn_merge_range_t *'s to Java RevisionRange objects.
            svn_merge_range_t *range = APR_ARRAY_IDX(ranges, i,
                                                     svn_merge_range_t *);
            jobject jrange = RevisionRange::makeJRevisionRange(range);
            if (jrange == NULL)
                return NULL;

            env->CallBooleanMethod(jranges, addToList, jrange);
            if (JNIUtil::isJavaExceptionThrown())
                return NULL;

            env->DeleteLocalRef(jrange);
            if (JNIUtil::isJavaExceptionThrown())
                return NULL;
        }

        env->CallVoidMethod(jmergeinfo, addRevisions, jpath, jranges);

        env->DeleteLocalRef(jranges);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
        env->DeleteLocalRef(jpath);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jmergeinfo;
}

jobjectArray
SVNClient::getAvailableMerges(const char *target, Revision &pegRevision,
                              const char *mergeSource)
{
    Pool requestPool;
    JNIEnv *env = JNIUtil::getEnv();

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    Path intLocalTarget(target);
    SVN_JNI_ERR(intLocalTarget.error_occured(), NULL);
    Path intMergeSource(mergeSource);
    SVN_JNI_ERR(intMergeSource.error_occured(), NULL);

    apr_array_header_t *ranges;
    SVN_JNI_ERR(svn_client_mergeinfo_get_available(&ranges,
                                                   intLocalTarget.c_str(),
                                                   pegRevision.revision(),
                                                   intMergeSource.c_str(),
                                                   ctx, requestPool.pool()),
                NULL);
    if (ranges == NULL)
        return NULL;

    // Transform ranges into a Java array of RevisionRange objects.
    jclass clazz = env->FindClass(JAVA_PACKAGE "/RevisionRange");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    jobjectArray jranges = env->NewObjectArray(ranges->nelts, clazz, NULL);

    for (int i = 0; i < ranges->nelts; ++i)
    {
        // Convert svn_merge_range_t *'s to Java RevisionRange objects.
        svn_merge_range_t *range =
            APR_ARRAY_IDX(ranges, i, svn_merge_range_t *);
        jobject jrange = RevisionRange::makeJRevisionRange(range);
        if (jrange == NULL)
            return NULL;

        env->SetObjectArrayElement(jranges, i, jrange);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;

        env->DeleteLocalRef(jrange);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jranges;
}

/**
 * Get a property.
 */
jobject SVNClient::propertyGet(jobject jthis, const char *path,
                               const char *name, Revision &revision,
                               Revision &pegRevision)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", NULL);
    SVN_JNI_NULL_PTR_EX(name, "name", NULL);
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), NULL);

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_hash_t *props;
    SVN_JNI_ERR(svn_client_propget2(&props, name,
                                    intPath.c_str(), pegRevision.revision(),
                                    revision.revision(), FALSE,<commit_msg>JavaHL: Update to latest propget API.<commit_after>/**
 * @copyright
 * ====================================================================
 * Copyright (c) 2003-2007 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 * @endcopyright
 *
 * @file SVNClient.cpp
 * @brief: Implementation of the SVNClient class
 */

#include "SVNClient.h"
#include "JNIUtil.h"
#include "Notify.h"
#include "Notify2.h"
#include "CopySources.h"
#include "DiffSummaryReceiver.h"
#include "ConflictResolverCallback.h"
#include "ProgressListener.h"
#include "Prompter.h"
#include "Pool.h"
#include "Targets.h"
#include "Revision.h"
#include "RevisionRange.h"
#include "BlameCallback.h"
#include "ProplistCallback.h"
#include "LogMessageCallback.h"
#include "InfoCallback.h"
#include "StatusCallback.h"
#include "ListCallback.h"
#include "JNIByteArray.h"
#include "CommitMessage.h"
#include "EnumMapper.h"
#include "svn_types.h"
#include "svn_client.h"
#include "svn_sorts.h"
#include "svn_time.h"
#include "svn_diff.h"
#include "svn_config.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_utf.h"
#include "private/svn_client_private.h"
#include "svn_private_config.h"
#include "../include/org_tigris_subversion_javahl_Revision.h"
#include "../include/org_tigris_subversion_javahl_NodeKind.h"
#include "../include/org_tigris_subversion_javahl_StatusKind.h"
#include "JNIStringHolder.h"
#include <vector>
#include <iostream>
#include <sstream>

struct log_msg_baton
{
    const char *message;
    CommitMessage *messageHandler;
};

SVNClient::SVNClient()
{
    m_notify = NULL;
    m_notify2 = NULL;
    m_progressListener = NULL;
    m_prompter = NULL;
    m_commitMessage = NULL;
    m_conflictResolver = NULL;
}

SVNClient::~SVNClient()
{
    delete m_notify;
    delete m_notify2;
    delete m_progressListener;
    delete m_prompter;
    delete m_conflictResolver;
}

SVNClient *SVNClient::getCppObject(jobject jthis)
{
    static jfieldID fid = 0;
    jlong cppAddr = SVNBase::findCppAddrForJObject(jthis, &fid,
                                                   JAVA_PACKAGE"/SVNClient");
    return (cppAddr == 0 ? NULL : reinterpret_cast<SVNClient *>(cppAddr));
}

void SVNClient::dispose(jobject jthis)
{
    static jfieldID fid = 0;
    SVNBase::dispose(jthis, &fid, JAVA_PACKAGE"/SVNClient");
}

jstring SVNClient::getAdminDirectoryName()
{
    Pool requestPool;
    jstring name =
        JNIUtil::makeJString(svn_wc_get_adm_dir(requestPool.pool()));
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    return name;
}

jboolean SVNClient::isAdminDirectory(const char *name)
{
    Pool requestPool;
    return svn_wc_is_adm_dir(name, requestPool.pool()) ? JNI_TRUE : JNI_FALSE;
}

const char *SVNClient::getLastPath()
{
    return m_lastPath.c_str();
}

/**
 * List directory entries of a URL.
 */
void SVNClient::list(const char *url, Revision &revision,
                     Revision &pegRevision, svn_depth_t depth,
                     int direntFields, bool fetchLocks,
                     ListCallback *callback)
{
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_NULL_PTR_EX(url, "path or url", );

    Path urlPath(url);
    SVN_JNI_ERR(urlPath.error_occured(), );

    SVN_JNI_ERR(svn_client_list2(urlPath.c_str(),
                                 pegRevision.revision(),
                                 revision.revision(),
                                 depth,
                                 direntFields,
                                 fetchLocks,
                                 ListCallback::callback,
                                 callback,
                                 ctx, requestPool.pool()), );
}

void
SVNClient::status(const char *path, svn_depth_t depth,
                  bool onServer, bool getAll, bool noIgnore,
                  bool ignoreExternals, StatusCallback *callback)
{
    Pool requestPool;
    svn_revnum_t youngest = SVN_INVALID_REVNUM;
    svn_opt_revision_t rev;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Path checkedPath(path);
    SVN_JNI_ERR(checkedPath.error_occured(), );

    rev.kind = svn_opt_revision_unspecified;

    SVN_JNI_ERR(svn_client_status3(&youngest, checkedPath.c_str(),
                                   &rev, StatusCallback::callback,
                                   callback,
                                   depth,
                                   getAll, onServer, noIgnore,
                                   ignoreExternals,
                                   ctx, requestPool.pool()), );
}

void SVNClient::username(const char *pi_username)
{
    m_userName = (pi_username == NULL ? "" : pi_username);
}

void SVNClient::password(const char *pi_password)
{
    m_passWord = (pi_password == NULL ? "" : pi_password);
}

void SVNClient::setPrompt(Prompter *prompter)
{
    delete m_prompter;
    m_prompter = prompter;
}

void SVNClient::logMessages(const char *path, Revision &pegRevision,
                            Revision &revisionStart,
                            Revision &revisionEnd, bool stopOnCopy,
                            bool discoverPaths, bool includeMergedRevisions,
                            bool omitLogText, long limit,
                            LogMessageCallback *callback)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Targets target(path);
    const apr_array_header_t *targets = target.array(requestPool);
    SVN_JNI_ERR(target.error_occured(), );

    apr_array_header_t *revprops = apr_array_make(requestPool.pool(), 3,
                                                  sizeof(char *));
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
    APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;

    SVN_JNI_ERR(svn_client_log4(targets,
                                pegRevision.revision(),
                                revisionStart.revision(),
                                revisionEnd.revision(),
                                limit,
                                discoverPaths,
                                stopOnCopy,
                                includeMergedRevisions,
                                revprops,
                                LogMessageCallback::callback, callback, ctx,
                                requestPool.pool()), );
}

jlong SVNClient::checkout(const char *moduleName, const char *destPath,
                          Revision &revision, Revision &pegRevision,
                          svn_depth_t depth, bool ignoreExternals,
                          bool allowUnverObstructions)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(moduleName, "moduleName", -1);
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", -1);

    Path url(moduleName);
    Path path(destPath);
    SVN_JNI_ERR(url.error_occured(), -1);
    SVN_JNI_ERR(path.error_occured(), -1);
    svn_revnum_t rev;

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_checkout3(&rev, url.c_str(),
                                     path.c_str(),
                                     pegRevision.revision(),
                                     revision.revision(),
                                     depth,
                                     ignoreExternals,
                                     allowUnverObstructions,
                                     ctx,
                                     requestPool.pool()),
                -1);

    return rev;
}

void SVNClient::notification(Notify *notify)
{
    delete m_notify;
    m_notify = notify;
}

void SVNClient::notification2(Notify2 *notify2)
{
    delete m_notify2;
    m_notify2 = notify2;
}

void SVNClient::setConflictResolver(ConflictResolverCallback *conflictResolver)
{
    delete m_conflictResolver;
    m_conflictResolver = conflictResolver;
}

void SVNClient::setProgressListener(ProgressListener *listener)
{
    delete m_progressListener;
    m_progressListener = listener;
}

void SVNClient::remove(Targets &targets, const char *message, bool force,
                       bool keep_local)
{
    svn_commit_info_t *commit_info = NULL;
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), );

    SVN_JNI_ERR(svn_client_delete3(&commit_info, targets2, force, keep_local,
                                   ctx, requestPool.pool()), );
}

void SVNClient::revert(const char *path, svn_depth_t depth)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    Targets target(path);
    const apr_array_header_t *targets = target.array(requestPool);
    SVN_JNI_ERR(target.error_occured(), );
    SVN_JNI_ERR(svn_client_revert2(targets, depth, ctx, requestPool.pool()), );
}

void SVNClient::add(const char *path,
                    svn_depth_t depth, bool force, bool no_ignore,
                    bool add_parents)
{
    Pool requestPool;

    SVN_JNI_NULL_PTR_EX(path, "path", );

    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_add4(intPath.c_str(), depth, force,
                                no_ignore, add_parents, ctx,
                                requestPool.pool()), );
}

jlongArray SVNClient::update(Targets &targets, Revision &revision,
                             svn_depth_t depth, bool ignoreExternals,
                             bool allowUnverObstructions)
{
    Pool requestPool;

    svn_client_ctx_t *ctx = getContext(NULL);
    apr_array_header_t *revs;
    if (ctx == NULL)
        return NULL;

    const apr_array_header_t *array = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), NULL);
    SVN_JNI_ERR(svn_client_update3(&revs, array,
                                   revision.revision(),
                                   depth,
                                   ignoreExternals,
                                   allowUnverObstructions,
                                   ctx, requestPool.pool()),
                NULL);

    JNIEnv *env = JNIUtil::getEnv();
    jlongArray jrevs = env->NewLongArray(revs->nelts);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;
    jlong *jrevArray = env->GetLongArrayElements(jrevs, NULL);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;
    for (int i = 0; i < revs->nelts; ++i)
    {
        jlong rev = APR_ARRAY_IDX(revs, i, svn_revnum_t);
        jrevArray[i] = rev;
    }
    env->ReleaseLongArrayElements(jrevs, jrevArray, 0);

    return jrevs;

}

jlong SVNClient::commit(Targets &targets, const char *message,
                        svn_depth_t depth, bool noUnlock, bool keepChangelist,
                        const char *changelistName)
{
    Pool requestPool;
    svn_commit_info_t *commit_info = NULL;
    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), -1);
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return SVN_INVALID_REVNUM;

    SVN_JNI_ERR(svn_client_commit4(&commit_info, targets2, depth,
                                   noUnlock, keepChangelist, changelistName,
                                   ctx, requestPool.pool()),
                SVN_INVALID_REVNUM);

    if (commit_info && SVN_IS_VALID_REVNUM(commit_info->revision))
        return commit_info->revision;

    return SVN_INVALID_REVNUM;
}

void SVNClient::copy(CopySources &copySources, const char *destPath,
                     const char *message, bool copyAsChild, bool makeParents,
                     bool withMergeHistory)
{
    Pool requestPool;

    apr_array_header_t *srcs = copySources.array(requestPool);
    if (srcs == NULL)
    {
        JNIUtil::throwNativeException(JAVA_PACKAGE "/ClientException",
                                      "Invalid copy sources");
        return;
    }
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", );
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    svn_commit_info_t *commit_info;
    SVN_JNI_ERR(svn_client_copy4(&commit_info, srcs, destinationPath.c_str(),
                                 copyAsChild, makeParents, withMergeHistory,
                                 ctx, requestPool.pool()),
                );
}

void SVNClient::move(Targets &srcPaths, const char *destPath,
                     const char *message, bool force, bool moveAsChild,
                     bool makeParents, bool withMergeHistory)
{
    Pool requestPool;

    const apr_array_header_t *srcs = srcPaths.array(requestPool);
    SVN_JNI_ERR(srcPaths.error_occured(), );
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", );
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    svn_commit_info_t *commit_info;
    SVN_JNI_ERR(svn_client_move5(&commit_info, (apr_array_header_t *) srcs,
                                 destinationPath.c_str(), force, moveAsChild,
                                 makeParents, withMergeHistory, ctx,
                                 requestPool.pool()), );
}

void SVNClient::mkdir(Targets &targets, const char *message, bool makeParents)
{
    Pool requestPool;
    svn_commit_info_t *commit_info = NULL;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    const apr_array_header_t *targets2 = targets.array(requestPool);
    SVN_JNI_ERR(targets.error_occured(), );

    SVN_JNI_ERR(svn_client_mkdir3(&commit_info, targets2, makeParents, ctx,
                                  requestPool.pool()), );
}

void SVNClient::cleanup(const char *path)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_cleanup(intPath.c_str(), ctx, requestPool.pool()),);
}

void SVNClient::resolved(const char *path, bool recurse)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_resolved(intPath.c_str(), recurse,
                                    ctx, requestPool.pool()), );
}

jlong SVNClient::doExport(const char *srcPath, const char *destPath,
                          Revision &revision, Revision &pegRevision,
                          bool force, bool ignoreExternals,
                          svn_depth_t depth, const char *nativeEOL)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(srcPath, "srcPath", -1);
    SVN_JNI_NULL_PTR_EX(destPath, "destPath", -1);
    Path sourcePath(srcPath);
    SVN_JNI_ERR(sourcePath.error_occured(), -1);
    Path destinationPath(destPath);
    SVN_JNI_ERR(destinationPath.error_occured(), -1);
    svn_revnum_t rev;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_export4(&rev, sourcePath.c_str(),
                                   destinationPath.c_str(),
                                   pegRevision.revision(),
                                   revision.revision(), force,
                                   ignoreExternals,
                                   depth,
                                   nativeEOL, ctx,
                                   requestPool.pool()),
                -1);

    return rev;

}

jlong SVNClient::doSwitch(const char *path, const char *url,
                          Revision &revision, svn_depth_t depth,
                          bool ignoreExternals, bool allowUnverObstructions)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", -1);
    SVN_JNI_NULL_PTR_EX(url, "url", -1);
    Path intUrl(url);
    SVN_JNI_ERR(intUrl.error_occured(), -1);
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), -1);

    svn_revnum_t rev;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return -1;

    SVN_JNI_ERR(svn_client_switch2(&rev, intPath.c_str(),
                                   intUrl.c_str(),
                                   revision.revision(),
                                   depth,
                                   ignoreExternals,
                                   allowUnverObstructions,
                                   ctx,
                                   requestPool.pool()),
                -1);

    return rev;
}

void SVNClient::doImport(const char *path, const char *url,
                         const char *message, svn_depth_t depth,
                         bool noIgnore, bool ignoreUnknownNodeTypes)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    SVN_JNI_NULL_PTR_EX(url, "url", );
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), );
    Path intUrl(url);
    SVN_JNI_ERR(intUrl.error_occured(), );

    svn_commit_info_t *commit_info = NULL;
    svn_client_ctx_t *ctx = getContext(message);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_import3(&commit_info, intPath.c_str(),
                                   intUrl.c_str(), depth, noIgnore,
                                   ignoreUnknownNodeTypes, ctx,
                                   requestPool.pool()), );
}

jobjectArray
SVNClient::suggestMergeSources(const char *path, Revision &pegRevision)
{
    Pool requestPool;
    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_array_header_t *sources;
    SVN_JNI_ERR(svn_client_suggest_merge_sources(&sources, path,
                                                 pegRevision.revision(),
                                                 ctx, requestPool.pool()),
                NULL);

    JNIEnv *env = JNIUtil::getEnv();
    jclass clazz = env->FindClass("java/lang/String");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    jobjectArray jsuggestions = env->NewObjectArray(sources->nelts, clazz,
                                                    NULL);
    for (int i = 0; i < sources->nelts; ++i)
    {
        const char *source = APR_ARRAY_IDX(sources, i, const char *);
        jstring jpath = JNIUtil::makeJString(source);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;

        env->SetObjectArrayElement(jsuggestions, i, jpath);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jsuggestions;
}

void SVNClient::merge(const char *path1, Revision &revision1,
                      const char *path2, Revision &revision2,
                      const char *localPath, bool force, svn_depth_t depth,
                      bool ignoreAncestry, bool dryRun)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path1, "path1", );
    SVN_JNI_NULL_PTR_EX(path2, "path2", );
    SVN_JNI_NULL_PTR_EX(localPath, "localPath", );
    Path intLocalPath(localPath);
    SVN_JNI_ERR(intLocalPath.error_occured(), );

    Path srcPath1(path1);
    SVN_JNI_ERR(srcPath1.error_occured(), );

    Path srcPath2 = path2;
    SVN_JNI_ERR(srcPath2.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_merge3(srcPath1.c_str(), revision1.revision(),
                                  srcPath2.c_str(), revision2.revision(),
                                  intLocalPath.c_str(),
                                  depth,
                                  ignoreAncestry, force, FALSE, dryRun, NULL,
                                  ctx, requestPool.pool()), );
}

void SVNClient::merge(const char *path, Revision &pegRevision,
                      Revision &revision1, Revision &revision2,
                      const char *localPath, bool force, svn_depth_t depth,
                      bool ignoreAncestry, bool dryRun)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", );
    SVN_JNI_NULL_PTR_EX(localPath, "localPath", );
    Path intLocalPath(localPath);
    SVN_JNI_ERR(intLocalPath.error_occured(), );

    Path srcPath(path);
    SVN_JNI_ERR(srcPath.error_occured(), );

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return;

    SVN_JNI_ERR(svn_client_merge_peg3(srcPath.c_str(), revision1.revision(),
                                      revision2.revision(),
                                      pegRevision.revision(),
                                      intLocalPath.c_str(),
                                      depth,
                                      ignoreAncestry, force, FALSE, dryRun,
                                      NULL, ctx, requestPool.pool()), );
}

jobject
SVNClient::getMergeInfo(const char *target, Revision &pegRevision)
{
    Pool requestPool;
    JNIEnv *env = JNIUtil::getEnv();

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_hash_t *mergeinfo;
    Path intLocalTarget(target);
    SVN_JNI_ERR(intLocalTarget.error_occured(), NULL);
    SVN_JNI_ERR(svn_client_mergeinfo_get_merged(&mergeinfo,
                                                intLocalTarget.c_str(),
                                                pegRevision.revision(), ctx,
                                                requestPool.pool()),
                NULL);
    if (mergeinfo == NULL)
        return NULL;

    // Transform mergeinfo into Java MergeInfo object.
    jclass clazz = env->FindClass(JAVA_PACKAGE "/MergeInfo");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    static jmethodID ctor = 0;
    if (ctor == 0)
    {
        ctor = env->GetMethodID(clazz, "<init>", "()V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    static jmethodID addRevisions = 0;
    if (addRevisions == 0)
    {
        addRevisions = env->GetMethodID(clazz, "addRevisions",
                                        "(Ljava/lang/String;"
                                        "Ljava/util/List;)V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    jobject jmergeinfo = env->NewObject(clazz, ctor);
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    clazz = env->FindClass("java/util/ArrayList");
    static jmethodID listCtor = 0;
    if (listCtor == 0)
    {
        listCtor = env->GetMethodID(clazz, "<init>", "(I)V");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }
    static jmethodID addToList = 0;
    if (addToList == 0)
    {
        addToList = env->GetMethodID(clazz, "add", "(Ljava/lang/Object;)Z");
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    apr_hash_index_t *hi;
    for (hi = apr_hash_first(requestPool.pool(), mergeinfo);
         hi;
         hi = apr_hash_next(hi))
    {
        const void *path;
        void *val;
        apr_hash_this(hi, &path, NULL, &val);

        jstring jpath = JNIUtil::makeJString((const char *) path);
        apr_array_header_t *ranges = (apr_array_header_t *) val;
        jobject jranges = env->NewObject(clazz, listCtor, ranges->nelts);
        for (int i = 0; i < ranges->nelts; ++i)
        {
            // Convert svn_merge_range_t *'s to Java RevisionRange objects.
            svn_merge_range_t *range = APR_ARRAY_IDX(ranges, i,
                                                     svn_merge_range_t *);
            jobject jrange = RevisionRange::makeJRevisionRange(range);
            if (jrange == NULL)
                return NULL;

            env->CallBooleanMethod(jranges, addToList, jrange);
            if (JNIUtil::isJavaExceptionThrown())
                return NULL;

            env->DeleteLocalRef(jrange);
            if (JNIUtil::isJavaExceptionThrown())
                return NULL;
        }

        env->CallVoidMethod(jmergeinfo, addRevisions, jpath, jranges);

        env->DeleteLocalRef(jranges);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
        env->DeleteLocalRef(jpath);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jmergeinfo;
}

jobjectArray
SVNClient::getAvailableMerges(const char *target, Revision &pegRevision,
                              const char *mergeSource)
{
    Pool requestPool;
    JNIEnv *env = JNIUtil::getEnv();

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    Path intLocalTarget(target);
    SVN_JNI_ERR(intLocalTarget.error_occured(), NULL);
    Path intMergeSource(mergeSource);
    SVN_JNI_ERR(intMergeSource.error_occured(), NULL);

    apr_array_header_t *ranges;
    SVN_JNI_ERR(svn_client_mergeinfo_get_available(&ranges,
                                                   intLocalTarget.c_str(),
                                                   pegRevision.revision(),
                                                   intMergeSource.c_str(),
                                                   ctx, requestPool.pool()),
                NULL);
    if (ranges == NULL)
        return NULL;

    // Transform ranges into a Java array of RevisionRange objects.
    jclass clazz = env->FindClass(JAVA_PACKAGE "/RevisionRange");
    if (JNIUtil::isJavaExceptionThrown())
        return NULL;

    jobjectArray jranges = env->NewObjectArray(ranges->nelts, clazz, NULL);

    for (int i = 0; i < ranges->nelts; ++i)
    {
        // Convert svn_merge_range_t *'s to Java RevisionRange objects.
        svn_merge_range_t *range =
            APR_ARRAY_IDX(ranges, i, svn_merge_range_t *);
        jobject jrange = RevisionRange::makeJRevisionRange(range);
        if (jrange == NULL)
            return NULL;

        env->SetObjectArrayElement(jranges, i, jrange);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;

        env->DeleteLocalRef(jrange);
        if (JNIUtil::isJavaExceptionThrown())
            return NULL;
    }

    return jranges;
}

/**
 * Get a property.
 */
jobject SVNClient::propertyGet(jobject jthis, const char *path,
                               const char *name, Revision &revision,
                               Revision &pegRevision)
{
    Pool requestPool;
    SVN_JNI_NULL_PTR_EX(path, "path", NULL);
    SVN_JNI_NULL_PTR_EX(name, "name", NULL);
    Path intPath(path);
    SVN_JNI_ERR(intPath.error_occured(), NULL);

    svn_client_ctx_t *ctx = getContext(NULL);
    if (ctx == NULL)
        return NULL;

    apr_hash_t *props;
    SVN_JNI_ERR(svn_client_propget4(&props, name,
                                    intPath.c_str(), pegRevision.revision(),
                                    revision.revision(), NULL, svn_depth_empty,
{'fraction_non_alphanumeric': 0.10427893942660056, 'fraction_numerical': 0.0035927283178846018, 'mean_word_length': 2.0553787047200878, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 194, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2228318', 'n_tokens_mistral': 17358, 'n_tokens_neox': 16835, 'n_words': 3249}