#include <string.h>
#include <stdlib.h>
#include <apr_pools.h>
#include <apr_tables.h>
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_hash.h"
#include "svn_wc.h"
#include "svn_io.h"
#include "wc.h"
#include "adm_files.h"
#include "workqueue.h"
#include "svn_private_config.h"
#include "private/svn_io_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_sorts_private.h"
static svn_error_t *
remove_conflict_file(svn_boolean_t *notify_required,
const char *conflict_abspath,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
if (conflict_abspath)
{
svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE,
scratch_pool);
if (err)
svn_error_clear(err);
else
*notify_required = TRUE;
}
return SVN_NO_ERROR;
}
static int
compare_revert_list_copied_children(const void *a, const void *b)
{
const svn_wc__db_revert_list_copied_child_info_t * const *ca = a;
const svn_wc__db_revert_list_copied_child_info_t * const *cb = b;
int i;
i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath);
return -i;
}
static svn_error_t *
revert_restore_handle_copied_dirs(svn_boolean_t *removed_self,
svn_wc__db_t *db,
const char *local_abspath,
svn_boolean_t remove_self,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
apr_array_header_t *copied_children;
svn_wc__db_revert_list_copied_child_info_t *child_info;
int i;
svn_node_kind_t on_disk;
apr_pool_t *iterpool;
svn_error_t *err;
if (removed_self)
*removed_self = FALSE;
SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children,
db, local_abspath,
scratch_pool,
scratch_pool));
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < copied_children->nelts; i++)
{
child_info = APR_ARRAY_IDX(
copied_children, i,
svn_wc__db_revert_list_copied_child_info_t *);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (child_info->kind != svn_node_file)
continue;
svn_pool_clear(iterpool);
SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool));
if (on_disk != svn_node_file)
continue;
SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool));
}
svn_sort__array(copied_children, compare_revert_list_copied_children);
for (i = 0; i < copied_children->nelts; i++)
{
child_info = APR_ARRAY_IDX(
copied_children, i,
svn_wc__db_revert_list_copied_child_info_t *);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (child_info->kind != svn_node_dir)
continue;
svn_pool_clear(iterpool);
err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool);
if (err)
{
if (APR_STATUS_IS_ENOENT(err->apr_err) ||
SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) ||
APR_STATUS_IS_ENOTEMPTY(err->apr_err))
svn_error_clear(err);
else
return svn_error_trace(err);
}
}
if (remove_self)
{
err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool);
if (err)
{
if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
svn_error_clear(err);
else
return svn_error_trace(err);
}
else if (removed_self)
*removed_self = TRUE;
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
revert_wc_data(svn_boolean_t *run_wq,
svn_boolean_t *notify_required,
svn_wc__db_t *db,
const char *local_abspath,
svn_wc__db_status_t status,
svn_node_kind_t kind,
svn_node_kind_t reverted_kind,
svn_filesize_t recorded_size,
apr_time_t recorded_time,
svn_boolean_t copied_here,
svn_boolean_t use_commit_times,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool);
static svn_error_t *
revert_restore(svn_boolean_t *run_wq,
svn_wc__db_t *db,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t metadata_only,
svn_boolean_t use_commit_times,
svn_boolean_t revert_root,
const struct svn_wc__db_info_t *info,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
svn_wc__db_status_t status;
svn_node_kind_t kind;
svn_boolean_t notify_required;
const apr_array_header_t *conflict_files;
svn_filesize_t recorded_size;
apr_time_t recorded_time;
svn_boolean_t copied_here;
svn_node_kind_t reverted_kind;
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (!revert_root)
{
svn_boolean_t is_wcroot;
SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
if (is_wcroot)
{
if (notify_func)
{
svn_wc_notify_t *notify =
svn_wc_create_notify(
local_abspath,
svn_wc_notify_update_skip_obstruction,
scratch_pool);
notify_func(notify_baton, notify, scratch_pool);
}
return SVN_NO_ERROR;
}
}
SVN_ERR(svn_wc__db_revert_list_read(¬ify_required,
&conflict_files,
&copied_here, &reverted_kind,
db, local_abspath,
scratch_pool, scratch_pool));
if (info)
{
status = info->status;
kind = info->kind;
recorded_size = info->recorded_size;
recorded_time = info->recorded_time;
}
else
{
if (!copied_here)
{
if (notify_func && notify_required)
notify_func(notify_baton,
svn_wc_create_notify(local_abspath,
svn_wc_notify_revert,
scratch_pool),
scratch_pool);
if (notify_func)
SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
db, local_abspath,
scratch_pool));
return SVN_NO_ERROR;
}
else
{
status = svn_wc__db_status_normal;
kind = svn_node_unknown;
recorded_size = SVN_INVALID_FILESIZE;
recorded_time = 0;
}
}
if (!metadata_only)
{
SVN_ERR(revert_wc_data(run_wq,
¬ify_required,
db, local_abspath, status, kind,
reverted_kind, recorded_size, recorded_time,
copied_here, use_commit_times,
cancel_func, cancel_baton, scratch_pool));
}
if (conflict_files)
{
int i;
for (i = 0; i < conflict_files->nelts; i++)
{
SVN_ERR(remove_conflict_file(¬ify_required,
APR_ARRAY_IDX(conflict_files, i,
const char *),
local_abspath, scratch_pool));
}
}
if (notify_func && notify_required)
notify_func(notify_baton,
svn_wc_create_notify(local_abspath, svn_wc_notify_revert,
scratch_pool),
scratch_pool);
if (depth == svn_depth_infinity && kind == svn_node_dir)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_hash_t *children, *conflicts;
apr_hash_index_t *hi;
SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
cancel_func, cancel_baton,
iterpool));
SVN_ERR(svn_wc__db_read_children_info(&children, &conflicts,
db, local_abspath, FALSE,
scratch_pool, iterpool));
for (hi = apr_hash_first(scratch_pool, children);
hi;
hi = apr_hash_next(hi))
{
const char *child_name = apr_hash_this_key(hi);
const char *child_abspath;
svn_pool_clear(iterpool);
child_abspath = svn_dirent_join(local_abspath, child_name, iterpool);
SVN_ERR(revert_restore(run_wq,
db, child_abspath, depth, metadata_only,
use_commit_times, FALSE ,
apr_hash_this_val(hi),
cancel_func, cancel_baton,
notify_func, notify_baton,
iterpool));
}
if (*run_wq)
{
SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
iterpool));
*run_wq = FALSE;
}
svn_pool_destroy(iterpool);
}
if (notify_func && (revert_root || kind == svn_node_dir))
SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
db, local_abspath, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
revert_wc_data(svn_boolean_t *run_wq,
svn_boolean_t *notify_required,
svn_wc__db_t *db,
const char *local_abspath,
svn_wc__db_status_t status,
svn_node_kind_t kind,
svn_node_kind_t reverted_kind,
svn_filesize_t recorded_size,
apr_time_t recorded_time,
svn_boolean_t copied_here,
svn_boolean_t use_commit_times,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
apr_finfo_t finfo;
svn_node_kind_t on_disk;
#ifdef HAVE_SYMLINK
svn_boolean_t special;
#endif
err = svn_io_stat(&finfo, local_abspath,
APR_FINFO_TYPE | APR_FINFO_LINK
| APR_FINFO_SIZE | APR_FINFO_MTIME
| SVN__APR_FINFO_EXECUTABLE
| SVN__APR_FINFO_READONLY,
scratch_pool);
if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
|| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
{
svn_error_clear(err);
on_disk = svn_node_none;
#ifdef HAVE_SYMLINK
special = FALSE;
#endif
}
else if (!err)
{
if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
on_disk = svn_node_file;
else if (finfo.filetype == APR_DIR)
on_disk = svn_node_dir;
else
on_disk = svn_node_unknown;
#ifdef HAVE_SYMLINK
special = (finfo.filetype == APR_LNK);
#endif
}
else
return svn_error_trace(err);
if (copied_here)
{
if (reverted_kind == svn_node_file && on_disk == svn_node_file)
{
SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
on_disk = svn_node_none;
}
else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir)
{
svn_boolean_t removed;
SVN_ERR(revert_restore_handle_copied_dirs(&removed, db,
local_abspath, TRUE,
cancel_func, cancel_baton,
scratch_pool));
if (removed)
on_disk = svn_node_none;
}
}
if (on_disk != svn_node_none
&& status != svn_wc__db_status_server_excluded
&& status != svn_wc__db_status_deleted
&& status != svn_wc__db_status_excluded
&& status != svn_wc__db_status_not_present)
{
if (on_disk == svn_node_dir && kind != svn_node_dir)
{
SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE,
cancel_func, cancel_baton, scratch_pool));
on_disk = svn_node_none;
}
else if (on_disk == svn_node_file && kind != svn_node_file)
{
#ifdef HAVE_SYMLINK
if (!(special && kind == svn_node_dir))
#endif
{
SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
on_disk = svn_node_none;
}
}
else if (on_disk == svn_node_file)
{
svn_boolean_t modified;
apr_hash_t *props;
#ifdef HAVE_SYMLINK
svn_string_t *special_prop;
#endif
SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath,
scratch_pool, scratch_pool));
#ifdef HAVE_SYMLINK
special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL);
if ((special_prop != NULL) != special)
{
SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
on_disk = svn_node_none;
}
else
#endif
{
if (recorded_size != SVN_INVALID_FILESIZE
&& recorded_time != 0
&& recorded_size == finfo.size
&& recorded_time == finfo.mtime)
{
modified = FALSE;
}
else
SVN_ERR(svn_wc__internal_file_modified_p(&modified,
db, local_abspath,
TRUE, scratch_pool));
if (modified)
{
on_disk = svn_node_none;
}
else
{
if (status == svn_wc__db_status_normal)
{
svn_boolean_t read_only;
svn_string_t *needs_lock_prop;
SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo,
scratch_pool));
needs_lock_prop = svn_hash_gets(props,
SVN_PROP_NEEDS_LOCK);
if (needs_lock_prop && !read_only)
{
SVN_ERR(svn_io_set_file_read_only(local_abspath,
FALSE,
scratch_pool));
*notify_required = TRUE;
}
else if (!needs_lock_prop && read_only)
{
SVN_ERR(svn_io_set_file_read_write(local_abspath,
FALSE,
scratch_pool));
*notify_required = TRUE;
}
}
#if !defined(WIN32) && !defined(__OS2__)
#ifdef HAVE_SYMLINK
if (!special)
#endif
{
svn_boolean_t executable;
svn_string_t *executable_prop;
SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo,
scratch_pool));
executable_prop = svn_hash_gets(props,
SVN_PROP_EXECUTABLE);
if (executable_prop && !executable)
{
SVN_ERR(svn_io_set_file_executable(local_abspath,
TRUE, FALSE,
scratch_pool));
*notify_required = TRUE;
}
else if (!executable_prop && executable)
{
SVN_ERR(svn_io_set_file_executable(local_abspath,
FALSE, FALSE,
scratch_pool));
*notify_required = TRUE;
}
}
#endif
}
}
}
}
if (on_disk == svn_node_none
&& status != svn_wc__db_status_server_excluded
&& status != svn_wc__db_status_deleted
&& status != svn_wc__db_status_excluded
&& status != svn_wc__db_status_not_present)
{
if (kind == svn_node_dir)
SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
if (kind == svn_node_file)
{
svn_skel_t *work_item;
SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath,
NULL, use_commit_times, TRUE,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item,
scratch_pool));
*run_wq = TRUE;
}
*notify_required = TRUE;
}
return SVN_NO_ERROR;
}
static svn_error_t *
revert(svn_wc__db_t *db,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t use_commit_times,
svn_boolean_t clear_changelists,
svn_boolean_t metadata_only,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
const struct svn_wc__db_info_t *info = NULL;
svn_boolean_t run_queue = FALSE;
SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity);
{
const char *dir_abspath;
svn_boolean_t is_wcroot;
SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
if (! is_wcroot)
dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
else
dir_abspath = local_abspath;
SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
}
err = svn_error_trace(
svn_wc__db_op_revert(db, local_abspath, depth, clear_changelists,
scratch_pool, scratch_pool));
if (!err)
{
err = svn_error_trace(
svn_wc__db_read_single_info(&info, db, local_abspath, FALSE,
scratch_pool, scratch_pool));
if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
svn_error_clear(err);
err = NULL;
info = NULL;
}
}
if (!err)
err = svn_error_trace(
revert_restore(&run_queue, db, local_abspath, depth, metadata_only,
use_commit_times, TRUE ,
info, cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
if (run_queue)
err = svn_error_compose_create(err,
svn_wc__wq_run(db, local_abspath,
cancel_func, cancel_baton,
scratch_pool));
err = svn_error_compose_create(err,
svn_wc__db_revert_list_done(db,
local_abspath,
scratch_pool));
return err;
}
static svn_error_t *
revert_changelist(svn_wc__db_t *db,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t use_commit_times,
apr_hash_t *changelist_hash,
svn_boolean_t clear_changelists,
svn_boolean_t metadata_only,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool;
const apr_array_header_t *children;
int i;
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash,
scratch_pool))
SVN_ERR(revert(db, local_abspath,
svn_depth_empty, use_commit_times, clear_changelists,
metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
if (depth == svn_depth_empty)
return SVN_NO_ERROR;
iterpool = svn_pool_create(scratch_pool);
if (depth == svn_depth_files || depth == svn_depth_immediates)
depth = svn_depth_empty;
SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
local_abspath,
scratch_pool,
iterpool));
for (i = 0; i < children->nelts; ++i)
{
const char *child_abspath;
svn_pool_clear(iterpool);
child_abspath = svn_dirent_join(local_abspath,
APR_ARRAY_IDX(children, i,
const char *),
iterpool);
SVN_ERR(revert_changelist(db, child_abspath, depth,
use_commit_times, changelist_hash,
clear_changelists, metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
revert_partial(svn_wc__db_t *db,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t use_commit_times,
svn_boolean_t clear_changelists,
svn_boolean_t metadata_only,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool;
const apr_array_header_t *children;
int i;
SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
iterpool = svn_pool_create(scratch_pool);
SVN_ERR(revert(db, local_abspath, svn_depth_empty,
use_commit_times, clear_changelists, metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton, iterpool));
SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
local_abspath,
scratch_pool,
iterpool));
for (i = 0; i < children->nelts; ++i)
{
const char *child_abspath;
svn_pool_clear(iterpool);
child_abspath = svn_dirent_join(local_abspath,
APR_ARRAY_IDX(children, i, const char *),
iterpool);
if (depth == svn_depth_files)
{
svn_node_kind_t kind;
SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath,
FALSE ,
TRUE ,
FALSE ,
iterpool));
if (kind != svn_node_file)
continue;
}
SVN_ERR(revert(db, child_abspath,
svn_depth_empty, use_commit_times, clear_changelists,
metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_revert5(svn_wc_context_t *wc_ctx,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t use_commit_times,
const apr_array_header_t *changelist_filter,
svn_boolean_t clear_changelists,
svn_boolean_t metadata_only,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
if (changelist_filter && changelist_filter->nelts)
{
apr_hash_t *changelist_hash;
SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
scratch_pool));
return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath,
depth, use_commit_times,
changelist_hash,
clear_changelists,
metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
}
if (depth == svn_depth_empty || depth == svn_depth_infinity)
return svn_error_trace(revert(wc_ctx->db, local_abspath,
depth, use_commit_times, clear_changelists,
metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
if (depth == svn_depth_files || depth == svn_depth_immediates)
return svn_error_trace(revert_partial(wc_ctx->db, local_abspath,
depth, use_commit_times,
clear_changelists, metadata_only,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL);
}