#define APR_WANT_STRFUNC
#include <apr_want.h>
#include "svn_private_config.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_string.h"
#include "svn_sorts.h"
#include "svn_props.h"
#include "repos.h"
static svn_error_t *
detect_changed(apr_hash_t **changed,
svn_fs_root_t *root,
svn_fs_t *fs,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
apr_pool_t *pool)
{
apr_hash_t *changes;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
*changed = apr_hash_make(pool);
SVN_ERR(svn_fs_paths_changed(&changes, root, pool));
if (apr_hash_count(changes) == 0)
return SVN_NO_ERROR;
for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
{
const void *key;
void *val;
svn_fs_path_change_t *change;
const char *path;
char action;
svn_log_changed_path_t *item;
svn_pool_clear(subpool);
apr_hash_this(hi, &key, NULL, &val);
path = (const char *) key;
change = val;
if (authz_read_func)
{
svn_boolean_t readable;
SVN_ERR(authz_read_func(&readable,
root, path,
authz_read_baton, subpool));
if (! readable)
{
found_unreadable = TRUE;
continue;
}
}
found_readable = TRUE;
switch (change->change_kind)
{
case svn_fs_path_change_reset:
continue;
case svn_fs_path_change_add:
action = 'A';
break;
case svn_fs_path_change_replace:
action = 'R';
break;
case svn_fs_path_change_delete:
action = 'D';
break;
case svn_fs_path_change_modify:
default:
action = 'M';
break;
}
item = apr_pcalloc(pool, sizeof(*item));
item->action = action;
item->copyfrom_rev = SVN_INVALID_REVNUM;
if ((action == 'A') || (action == 'R'))
{
const char *copyfrom_path;
svn_revnum_t copyfrom_rev;
SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
root, path, subpool));
if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
{
svn_boolean_t readable = TRUE;
if (authz_read_func)
{
svn_fs_root_t *copyfrom_root;
SVN_ERR(svn_fs_revision_root(©from_root, fs,
copyfrom_rev, subpool));
SVN_ERR(authz_read_func(&readable,
copyfrom_root, copyfrom_path,
authz_read_baton, subpool));
if (! readable)
found_unreadable = TRUE;
}
if (readable)
{
item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
item->copyfrom_rev = copyfrom_rev;
}
}
}
apr_hash_set(*changed, apr_pstrdup(pool, path),
APR_HASH_KEY_STRING, item);
}
svn_pool_destroy(subpool);
if (! found_readable)
return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
NULL, NULL);
if (found_unreadable)
return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
NULL, NULL);
return SVN_NO_ERROR;
}
struct path_info
{
svn_stringbuf_t *path;
svn_revnum_t history_rev;
svn_boolean_t done;
svn_boolean_t first_time;
svn_fs_history_t *hist;
apr_pool_t *newpool;
apr_pool_t *oldpool;
};
static svn_error_t *
get_history(struct path_info *info,
svn_fs_t *fs,
svn_boolean_t strict,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_revnum_t start,
apr_pool_t *pool)
{
svn_fs_root_t *history_root = NULL;
svn_fs_history_t *hist;
apr_pool_t *subpool;
const char *path;
if (info->hist)
{
subpool = info->newpool;
SVN_ERR(svn_fs_history_prev(&info->hist, info->hist,
strict ? FALSE : TRUE, subpool));
hist = info->hist;
}
else
{
subpool = svn_pool_create(pool);
SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
subpool));
SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
subpool));
SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
subpool));
if (info->first_time)
info->first_time = FALSE;
else
SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
subpool));
}
if (! hist)
{
svn_pool_destroy(subpool);
if (info->oldpool)
svn_pool_destroy(info->oldpool);
info->done = TRUE;
return SVN_NO_ERROR;
}
SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
hist, subpool));
svn_stringbuf_set(info->path, path);
if (info->history_rev < start)
{
info->done = TRUE;
return SVN_NO_ERROR;
}
if (authz_read_func)
{
svn_boolean_t readable;
SVN_ERR(svn_fs_revision_root(&history_root, fs,
info->history_rev,
subpool));
SVN_ERR(authz_read_func(&readable, history_root,
info->path->data,
authz_read_baton,
subpool));
if (! readable)
info->done = TRUE;
}
if (! info->hist)
{
svn_pool_destroy(subpool);
}
else
{
apr_pool_t *temppool = info->oldpool;
info->oldpool = info->newpool;
svn_pool_clear(temppool);
info->newpool = temppool;
}
return SVN_NO_ERROR;
}
static svn_error_t *
check_history(svn_boolean_t *changed,
struct path_info *info,
svn_fs_t *fs,
svn_revnum_t current,
svn_boolean_t strict,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_revnum_t start,
apr_pool_t *pool)
{
if (info->done)
return SVN_NO_ERROR;
if (info->history_rev < current)
return SVN_NO_ERROR;
*changed = TRUE;
SVN_ERR(get_history(info, fs, strict, authz_read_func,
authz_read_baton, start, pool));
return SVN_NO_ERROR;
}
static svn_revnum_t
next_history_rev(apr_array_header_t *histories)
{
svn_revnum_t next_rev = SVN_INVALID_REVNUM;
int i;
for (i = 0; i < histories->nelts; ++i)
{
struct path_info *info = APR_ARRAY_IDX(histories, i,
struct path_info *);
if (info->done)
continue;
if (info->history_rev > next_rev)
next_rev = info->history_rev;
}
return next_rev;
}
static svn_error_t *
send_change_rev(svn_revnum_t rev,
svn_fs_t *fs,
svn_boolean_t discover_changed_paths,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_log_message_receiver_t receiver,
void *receiver_baton,
apr_pool_t *pool)
{
svn_string_t *author, *date, *message;
apr_hash_t *r_props, *changed_paths = NULL;
SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
APR_HASH_KEY_STRING);
date = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
APR_HASH_KEY_STRING);
message = apr_hash_get(r_props, SVN_PROP_REVISION_LOG,
APR_HASH_KEY_STRING);
if ((rev > 0)
&& (authz_read_func || discover_changed_paths))
{
svn_fs_root_t *newroot;
svn_error_t *patherr;
SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
patherr = detect_changed(&changed_paths,
newroot, fs,
authz_read_func, authz_read_baton,
pool);
if (patherr
&& patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
{
svn_error_clear(patherr);
changed_paths = NULL;
author = NULL;
date = NULL;
message = NULL;
}
else if (patherr
&& patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
{
svn_error_clear(patherr);
message = NULL;
}
else if (patherr)
return patherr;
if (! discover_changed_paths)
changed_paths = NULL;
}
SVN_ERR((*receiver)(receiver_baton,
changed_paths,
rev,
author ? author->data : NULL,
date ? date->data : NULL,
message ? message->data : NULL,
pool));
return SVN_NO_ERROR;
}
#define MAX_OPEN_HISTORIES 32
svn_error_t *
svn_repos_get_logs3(svn_repos_t *repos,
const apr_array_header_t *paths,
svn_revnum_t start,
svn_revnum_t end,
int limit,
svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_log_message_receiver_t receiver,
void *receiver_baton,
apr_pool_t *pool)
{
svn_revnum_t head = SVN_INVALID_REVNUM;
apr_pool_t *subpool = svn_pool_create(pool);
svn_fs_t *fs = repos->fs;
apr_array_header_t *revs = NULL;
svn_revnum_t hist_end = end;
svn_revnum_t hist_start = start;
svn_revnum_t current;
apr_array_header_t *histories;
svn_boolean_t any_histories_left = TRUE;
int send_count = 0;
svn_fs_root_t *root;
int i;
SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
if (! SVN_IS_VALID_REVNUM(start))
start = head;
if (! SVN_IS_VALID_REVNUM(end))
end = head;
if (start > head)
return svn_error_createf
(SVN_ERR_FS_NO_SUCH_REVISION, 0,
_("No such revision %ld"), start);
if (end > head)
return svn_error_createf
(SVN_ERR_FS_NO_SUCH_REVISION, 0,
_("No such revision %ld"), end);
if (start > end)
{
hist_start = end;
hist_end = start;
}
if (! paths ||
(paths->nelts == 1 &&
svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))))
{
send_count = hist_end - hist_start + 1;
if (limit && send_count > limit)
send_count = limit;
for (i = 0; i < send_count; ++i)
{
svn_revnum_t rev = hist_start + i;
svn_pool_clear(subpool);
if (start > end)
rev = hist_end - i;
SVN_ERR(send_change_rev(rev, fs,
discover_changed_paths,
authz_read_func, authz_read_baton,
receiver, receiver_baton, subpool));
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
histories = apr_array_make(pool, paths->nelts,
sizeof(struct path_info *));
SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
for (i = 0; i < paths->nelts; i++)
{
const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
struct path_info *info = apr_palloc(pool,
sizeof(struct path_info));
if (authz_read_func)
{
svn_boolean_t readable;
svn_pool_clear(subpool);
SVN_ERR(authz_read_func(&readable, root, this_path,
authz_read_baton, subpool));
if (! readable)
return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
}
info->path = svn_stringbuf_create(this_path, pool);
info->done = FALSE;
info->history_rev = hist_end;
info->first_time = TRUE;
if (i < MAX_OPEN_HISTORIES)
{
SVN_ERR(svn_fs_node_history(&info->hist, root, this_path, pool));
info->newpool = svn_pool_create(pool);
info->oldpool = svn_pool_create(pool);
}
else
{
info->hist = NULL;
info->oldpool = NULL;
info->newpool = NULL;
}
SVN_ERR(get_history(info, fs,
strict_node_history,
authz_read_func, authz_read_baton,
hist_start, pool));
*((struct path_info **) apr_array_push(histories)) = info;
}
for (current = hist_end;
current >= hist_start && any_histories_left;
current = next_history_rev(histories))
{
svn_boolean_t changed = FALSE;
any_histories_left = FALSE;
svn_pool_clear(subpool);
for (i = 0; i < histories->nelts; i++)
{
struct path_info *info = APR_ARRAY_IDX(histories, i,
struct path_info *);
SVN_ERR(check_history(&changed, info, fs, current,
strict_node_history,
authz_read_func, authz_read_baton,
hist_start, pool));
if (! info->done)
any_histories_left = TRUE;
}
if (changed)
{
if (start > end)
{
SVN_ERR(send_change_rev(current, fs,
discover_changed_paths,
authz_read_func, authz_read_baton,
receiver, receiver_baton,
subpool));
if (limit && ++send_count >= limit)
break;
}
else
{
if (! revs)
revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
*(svn_revnum_t*) apr_array_push(revs) = current;
}
}
}
if (revs)
{
for (i = 0; i < revs->nelts; ++i)
{
svn_pool_clear(subpool);
SVN_ERR(send_change_rev(APR_ARRAY_IDX(revs, revs->nelts - i - 1,
svn_revnum_t),
fs, discover_changed_paths,
authz_read_func, authz_read_baton,
receiver, receiver_baton, subpool));
if (limit && i + 1 >= limit)
break;
}
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_repos_get_logs2(svn_repos_t *repos,
const apr_array_header_t *paths,
svn_revnum_t start,
svn_revnum_t end,
svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
svn_log_message_receiver_t receiver,
void *receiver_baton,
apr_pool_t *pool)
{
return svn_repos_get_logs3(repos, paths, start, end, 0,
discover_changed_paths, strict_node_history,
authz_read_func, authz_read_baton, receiver,
receiver_baton, pool);
}
svn_error_t *
svn_repos_get_logs(svn_repos_t *repos,
const apr_array_header_t *paths,
svn_revnum_t start,
svn_revnum_t end,
svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_log_message_receiver_t receiver,
void *receiver_baton,
apr_pool_t *pool)
{
return svn_repos_get_logs3(repos, paths, start, end, 0,
discover_changed_paths, strict_node_history,
NULL, NULL,
receiver, receiver_baton, pool);
}