#include <stdlib.h>
#include <string.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_tables.h>
#include <apr_file_io.h>
#include <apr_strings.h>
#include <apr_general.h>
#include "svn_types.h"
#include "svn_string.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_xml.h"
#include "svn_error.h"
#include "svn_props.h"
#include "svn_io.h"
#include "svn_hash.h"
#include "svn_mergeinfo.h"
#include "svn_wc.h"
#include "svn_utf.h"
#include "svn_diff.h"
#include "private/svn_wc_private.h"
#include "private/svn_mergeinfo_private.h"
#include "wc.h"
#include "log.h"
#include "adm_files.h"
#include "entries.h"
#include "props.h"
#include "translate.h"
#include "questions.h"
#include "lock.h"
#include "svn_private_config.h"
static svn_error_t *
load_props(apr_hash_t **hash,
const char *path,
svn_node_kind_t node_kind,
svn_wc__props_kind_t props_kind,
apr_pool_t *pool)
{
svn_error_t *err;
svn_stream_t *stream;
apr_finfo_t finfo;
const char *prop_path;
SVN_ERR(svn_wc__prop_path(&prop_path, path, node_kind, props_kind, pool));
*hash = apr_hash_make(pool);
err = svn_io_stat(&finfo, prop_path, APR_FINFO_SIZE, pool);
if (err)
{
if (APR_STATUS_IS_ENOENT(err->apr_err)
|| APR_STATUS_IS_ENOTDIR(err->apr_err))
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
else
return err;
}
if (finfo.size == 0)
return SVN_NO_ERROR;
SVN_ERR(svn_stream_open_readonly(&stream, prop_path, pool, pool));
SVN_ERR(svn_hash_read2(*hash, stream, SVN_HASH_TERMINATOR, pool));
return svn_stream_close(stream);
}
static svn_error_t *
save_prop_tmp_file(const char **tmp_file_path,
apr_hash_t *hash,
const char *tmp_base_dir,
svn_boolean_t write_empty,
apr_pool_t *pool)
{
svn_stream_t *stream;
SVN_ERR(svn_stream_open_unique(&stream, tmp_file_path, tmp_base_dir,
svn_io_file_del_none, pool, pool));
if (apr_hash_count(hash) != 0 || write_empty)
SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
return svn_stream_close(stream);
}
static svn_error_t *
open_reject_tmp_stream(svn_stream_t **stream, const char **reject_tmp_path,
const char *full_path,
svn_boolean_t is_dir, apr_pool_t *pool)
{
const char *tmp_base_path;
if (is_dir)
tmp_base_path = svn_wc__adm_child(full_path, SVN_WC__ADM_TMP, pool);
else
tmp_base_path = svn_wc__adm_child(svn_path_dirname(full_path, pool),
SVN_WC__ADM_TMP, pool);
return svn_stream_open_unique(stream, reject_tmp_path, tmp_base_path,
svn_io_file_del_none, pool, pool);
}
static svn_error_t *
append_prop_conflict(svn_stream_t *stream,
const svn_string_t *conflict_description,
apr_pool_t *pool)
{
apr_size_t len;
const char *native_text =
svn_utf_cstring_from_utf8_fuzzy(conflict_description->data, pool);
len = strlen(native_text);
SVN_ERR(svn_stream_write(stream, native_text, &len));
native_text = svn_utf_cstring_from_utf8_fuzzy(APR_EOL_STR, pool);
len = strlen(native_text);
return svn_stream_write(stream, native_text, &len);
}
static svn_error_t *
get_existing_prop_reject_file(const char **reject_file,
svn_wc_adm_access_t *adm_access,
const char *path,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
*reject_file = entry->prejfile
? svn_path_join(svn_wc_adm_access_path(adm_access), entry->prejfile, pool)
: NULL;
return SVN_NO_ERROR;
}
static const char *
build_present_props(apr_hash_t *props, apr_pool_t *pool)
{
svn_stringbuf_t *present_props;
if (apr_hash_count(props) == 0)
return "";
present_props = svn_stringbuf_create("", pool);
if (apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING) != NULL)
svn_stringbuf_appendcstr(present_props, SVN_PROP_SPECIAL " ");
if (apr_hash_get(props, SVN_PROP_EXTERNALS, APR_HASH_KEY_STRING) != NULL)
svn_stringbuf_appendcstr(present_props, SVN_PROP_EXTERNALS " ");
if (apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) != NULL)
svn_stringbuf_appendcstr(present_props, SVN_PROP_NEEDS_LOCK " ");
svn_stringbuf_chop(present_props, 1);
return present_props->data;
}
svn_error_t *
svn_wc__load_props(apr_hash_t **base_props_p,
apr_hash_t **props_p,
apr_hash_t **revert_props_p,
svn_wc_adm_access_t *adm_access,
const char *path,
apr_pool_t *pool)
{
svn_node_kind_t kind;
svn_boolean_t has_propcaching =
svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
const svn_wc_entry_t *entry;
apr_hash_t *base_props = NULL;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
if (base_props_p)
*base_props_p = apr_hash_make(pool);
if (props_p)
*props_p = apr_hash_make(pool);
if (revert_props_p)
*revert_props_p = apr_hash_make(pool);
return SVN_NO_ERROR;
}
kind = entry->kind;
if (base_props_p
|| (has_propcaching && ! entry->has_prop_mods && entry->has_props))
{
SVN_ERR(load_props(&base_props, path, kind, svn_wc__props_base, pool));
if (base_props_p)
*base_props_p = base_props;
}
if (props_p)
{
if (has_propcaching && ! entry->has_prop_mods && entry->has_props)
*props_p = apr_hash_copy(pool, base_props);
else if (! has_propcaching || entry->has_props)
SVN_ERR(load_props(props_p, path, kind, svn_wc__props_working, pool));
else
*props_p = apr_hash_make(pool);
}
if (revert_props_p)
{
if (entry->schedule == svn_wc_schedule_replace)
SVN_ERR(load_props(revert_props_p, path, kind, svn_wc__props_revert,
pool));
else
*revert_props_p = apr_hash_make(pool);
}
return SVN_NO_ERROR;
}
static svn_error_t *
install_props_file(svn_stringbuf_t **log_accum,
svn_wc_adm_access_t *adm_access,
const char *path,
apr_hash_t *props,
svn_wc__props_kind_t wc_prop_kind,
apr_pool_t *pool)
{
svn_node_kind_t node_kind;
const char *propfile_path;
const char *propfile_tmp_path;
if (! svn_path_is_child(svn_wc_adm_access_path(adm_access), path, NULL))
node_kind = svn_node_dir;
else
node_kind = svn_node_file;
SVN_ERR(svn_wc__prop_path(&propfile_path, path,
node_kind, wc_prop_kind, pool));
SVN_ERR(save_prop_tmp_file(&propfile_tmp_path, props,
svn_path_dirname(propfile_path, pool),
FALSE, pool));
SVN_ERR(svn_wc__loggy_move(log_accum, adm_access,
propfile_tmp_path,
propfile_path,
pool));
return svn_wc__loggy_set_readonly(log_accum, adm_access,
propfile_path, pool);
}
svn_error_t *
svn_wc__install_props(svn_stringbuf_t **log_accum,
svn_wc_adm_access_t *adm_access,
const char *path,
apr_hash_t *base_props,
apr_hash_t *working_props,
svn_boolean_t write_base_props,
apr_pool_t *pool)
{
apr_array_header_t *prop_diffs;
const svn_wc_entry_t *entry;
svn_wc_entry_t tmp_entry;
svn_node_kind_t kind;
svn_boolean_t has_propcaching =
svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
if (! svn_path_is_child(svn_wc_adm_access_path(adm_access), path, NULL))
kind = svn_node_dir;
else
kind = svn_node_file;
SVN_ERR(svn_prop_diffs(&prop_diffs, working_props, base_props, pool));
tmp_entry.has_prop_mods = (prop_diffs->nelts > 0);
tmp_entry.has_props = (apr_hash_count(working_props) > 0);
tmp_entry.present_props = build_present_props(working_props, pool);
SVN_ERR(svn_wc__loggy_entry_modify(log_accum, adm_access,
path, &tmp_entry,
SVN_WC__ENTRY_MODIFY_HAS_PROPS
| SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
| SVN_WC__ENTRY_MODIFY_PRESENT_PROPS,
pool));
if (has_propcaching)
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
else
entry = NULL;
if (tmp_entry.has_prop_mods)
{
SVN_ERR(install_props_file(log_accum, adm_access, path, working_props,
svn_wc__props_working, pool));
}
else
{
const char *working_propfile_path;
SVN_ERR(svn_wc__prop_path(&working_propfile_path, path,
kind, svn_wc__props_working, pool));
if (! has_propcaching || (entry && entry->has_prop_mods))
SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access,
working_propfile_path, pool));
}
if (write_base_props)
{
if (apr_hash_count(base_props) > 0)
{
SVN_ERR(install_props_file(log_accum, adm_access, path, base_props,
svn_wc__props_base, pool));
}
else
{
const char *base_propfile_path;
SVN_ERR(svn_wc__prop_path(&base_propfile_path, path,
kind, svn_wc__props_base, pool));
if (! has_propcaching || (entry && entry->has_props))
SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access,
base_propfile_path, pool));
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__working_props_committed(const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t sync_entries,
apr_pool_t *pool)
{
const char *working;
const char *base;
const svn_wc_entry_t *entry;
svn_wc_entry_t mod_entry;
svn_wc_adm_access_t *mod_access;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc__prop_path(&working, path, entry->kind,
svn_wc__props_working, pool));
SVN_ERR(svn_wc__prop_path(&base, path, entry->kind,
svn_wc__props_base, pool));
SVN_ERR(svn_io_file_rename(working, base, pool));
SVN_ERR(svn_wc_adm_probe_retrieve(&mod_access, adm_access, path, pool));
mod_entry.has_prop_mods = FALSE;
return svn_wc__entry_modify(mod_access, entry->name, &mod_entry,
SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS,
sync_entries, pool);
}
svn_error_t *
svn_wc__props_last_modified(apr_time_t *mod_time,
const char *path,
svn_wc__props_kind_t props_kind,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_error_t *err;
const char *props_file;
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
SVN_ERR(svn_wc__prop_path(&props_file, path, entry->kind, props_kind, pool));
err = svn_io_file_affected_time(mod_time, props_file, pool);
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_clear(err);
*mod_time = 0;
}
else
SVN_ERR_W(err,
apr_psprintf(pool,
_("Error getting 'affected time' on '%s'"),
svn_path_local_style(props_file, pool)));
return SVN_NO_ERROR;
}
static svn_error_t *
remove_file_if_present(const char *file, apr_pool_t *pool)
{
svn_error_t *err;
err = svn_io_remove_file(file, pool);
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_clear(err);
err = SVN_NO_ERROR;
}
return err;
}
static svn_error_t *
read_wcprops(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
{
apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
apr_hash_t *all_wcprops;
apr_hash_t *proplist;
svn_stream_t *stream;
svn_error_t *err;
if (svn_wc__adm_wc_format(adm_access) <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
return SVN_NO_ERROR;
all_wcprops = apr_hash_make(cache_pool);
err = svn_wc__open_adm_stream(&stream, svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_ALL_WCPROPS, pool, pool);
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_clear(err);
svn_wc__adm_access_set_wcprops(adm_access, all_wcprops);
return SVN_NO_ERROR;
}
SVN_ERR(err);
proplist = apr_hash_make(cache_pool);
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, cache_pool));
apr_hash_set(all_wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING,
proplist);
while (1729)
{
svn_stringbuf_t *line;
svn_boolean_t eof;
SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, cache_pool));
if (eof)
{
if (line->len > 0)
return svn_error_createf
(SVN_ERR_WC_CORRUPT, NULL,
_("Missing end of line in wcprops file for '%s'"),
svn_path_local_style(svn_wc_adm_access_path(adm_access), pool));
break;
}
proplist = apr_hash_make(cache_pool);
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR,
cache_pool));
apr_hash_set(all_wcprops, line->data, APR_HASH_KEY_STRING, proplist);
}
svn_wc__adm_access_set_wcprops(adm_access, all_wcprops);
return svn_stream_close(stream);
}
static svn_error_t *
write_wcprops(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
{
apr_hash_t *wcprops = svn_wc__adm_access_wcprops(adm_access);
svn_stream_t *stream;
apr_hash_t *proplist;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
svn_boolean_t any_props = FALSE;
const char *temp_file_path;
if (! wcprops)
return SVN_NO_ERROR;
for (hi = apr_hash_first(pool, wcprops); hi && ! any_props;
hi = apr_hash_next(hi))
{
void *val;
apr_hash_this(hi, NULL, NULL, &val);
proplist = val;
if (apr_hash_count(proplist) > 0)
any_props = TRUE;
}
if (! any_props)
{
svn_error_t *err;
err = svn_wc__remove_adm_file(adm_access, SVN_WC__ADM_ALL_WCPROPS,
subpool);
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
else
return err;
}
SVN_ERR(svn_wc__open_adm_writable(&stream, &temp_file_path,
svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_ALL_WCPROPS, pool, subpool));
proplist = apr_hash_get(wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
if (! proplist)
proplist = apr_hash_make(subpool);
SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
for (hi = apr_hash_first(pool, wcprops); hi; hi = apr_hash_next(hi))
{
const void *key;
void *val;
const char *name;
apr_hash_this(hi, &key, NULL, &val);
name = key;
proplist = val;
if (strcmp(SVN_WC_ENTRY_THIS_DIR, name) == 0
|| apr_hash_count(proplist) == 0)
continue;
svn_pool_clear(subpool);
SVN_ERR(svn_stream_printf(stream, subpool, "%s\n", name));
SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
}
return svn_wc__close_adm_stream(stream, temp_file_path,
svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_ALL_WCPROPS, pool);
}
svn_error_t *
svn_wc__wcprops_flush(svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
return write_wcprops(adm_access, scratch_pool);
}
svn_error_t *
svn_wc__loggy_props_delete(svn_stringbuf_t **log_accum,
const char *path,
svn_wc__props_kind_t props_kind,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *props_file;
if (props_kind == svn_wc__props_wcprop)
{
apr_hash_t *props;
apr_pool_t *iterpool = svn_pool_create(pool);
apr_hash_index_t *hi;
SVN_ERR(svn_wc__wcprop_list(&props, path, adm_access, pool));
for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
{
const void *key;
const char *name;
svn_pool_clear(iterpool);
apr_hash_this(hi, &key, NULL, NULL);
name = key;
SVN_ERR(svn_wc__loggy_modify_wcprop(log_accum,
adm_access, path,
name, NULL, iterpool));
}
svn_pool_destroy(iterpool);
}
else
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
SVN_ERR(svn_wc__prop_path(&props_file, path, entry->kind, props_kind,
pool));
SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access, props_file, pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__props_delete(const char *path,
svn_wc__props_kind_t props_kind,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *props_file;
if (props_kind == svn_wc__props_wcprop)
{
svn_wc_adm_access_t *path_access;
const char *filename;
svn_boolean_t write_needed = FALSE;
apr_hash_t *all_wcprops;
SVN_ERR(svn_wc_adm_probe_retrieve(&path_access, adm_access,
path, pool));
all_wcprops = svn_wc__adm_access_wcprops(path_access);
filename = svn_path_is_child(svn_wc_adm_access_path(path_access),
path, NULL);
if (! filename)
{
if (! all_wcprops || apr_hash_count(all_wcprops) > 0)
{
apr_pool_t *adm_pool = svn_wc_adm_access_pool(path_access);
svn_wc__adm_access_set_wcprops(path_access,
apr_hash_make(adm_pool));
write_needed = TRUE;
}
}
else
{
apr_hash_t *wcprops;
if (! all_wcprops)
{
SVN_ERR(read_wcprops(path_access, pool));
all_wcprops = svn_wc__adm_access_wcprops(path_access);
}
if (all_wcprops)
wcprops = apr_hash_get(all_wcprops, filename, APR_HASH_KEY_STRING);
else
wcprops = NULL;
if (wcprops && apr_hash_count(wcprops) > 0)
{
apr_hash_set(all_wcprops, filename, APR_HASH_KEY_STRING, NULL);
write_needed = TRUE;
}
}
if (write_needed)
SVN_ERR(write_wcprops(path_access, pool));
}
else
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, TRUE, pool));
SVN_ERR(svn_wc__prop_path(&props_file, path, entry->kind, props_kind,
pool));
SVN_ERR(remove_file_if_present(props_file, pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__loggy_revert_props_create(svn_stringbuf_t **log_accum,
const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t destroy_baseprops,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
const char *dst_rprop;
const char *dst_bprop;
const char *tmp_rprop;
svn_node_kind_t kind;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc__prop_path(&dst_rprop, path, entry->kind, svn_wc__props_revert,
pool));
if (entry->kind == svn_node_dir)
SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_rprop, path,
svn_io_file_del_none, pool));
else
SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_rprop,
svn_path_dirname(path, pool),
svn_io_file_del_none, pool));
SVN_ERR(svn_wc__prop_path(&dst_bprop, path, entry->kind, svn_wc__props_base,
pool));
SVN_ERR(svn_io_check_path(dst_bprop, &kind, pool));
if (kind == svn_node_file)
{
if (destroy_baseprops)
SVN_ERR(svn_wc__loggy_move(log_accum,
adm_access, dst_bprop, dst_rprop,
pool));
else
{
SVN_ERR(svn_io_copy_file(dst_bprop, tmp_rprop, TRUE, pool));
SVN_ERR(svn_wc__loggy_move(log_accum, adm_access,
tmp_rprop, dst_rprop, pool));
}
}
else if (kind == svn_node_none)
{
SVN_ERR(save_prop_tmp_file(&dst_bprop, apr_hash_make(pool),
svn_path_dirname(dst_bprop, pool),
TRUE, pool));
SVN_ERR(svn_wc__loggy_move(log_accum,
adm_access, dst_bprop, dst_rprop,
pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__loggy_revert_props_restore(svn_stringbuf_t **log_accum,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
const char *revert_file, *base_file;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc__prop_path(&base_file, path, entry->kind, svn_wc__props_base,
pool));
SVN_ERR(svn_wc__prop_path(&revert_file, path, entry->kind,
svn_wc__props_revert, pool));
return svn_wc__loggy_move(log_accum, adm_access,
revert_file, base_file, pool);
}
static svn_error_t *
diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
const svn_string_t *from_prop_val,
const svn_string_t *to_prop_val, apr_pool_t *pool)
{
if (svn_string_compare(from_prop_val, to_prop_val))
{
*deleted = apr_hash_make(pool);
*added = apr_hash_make(pool);
}
else
{
svn_mergeinfo_t from, to;
SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
SVN_ERR(svn_mergeinfo_diff(deleted, added, from, to,
FALSE, pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
combine_mergeinfo_props(const svn_string_t **output,
const svn_string_t *prop_val1,
const svn_string_t *prop_val2,
apr_pool_t *pool)
{
svn_mergeinfo_t mergeinfo1, mergeinfo2;
SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, pool));
SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, pool));
SVN_ERR(svn_mergeinfo_merge(mergeinfo1, mergeinfo2, pool));
return svn_mergeinfo_to_string((svn_string_t **)output, mergeinfo1, pool);
}
static svn_error_t *
combine_forked_mergeinfo_props(const svn_string_t **output,
const svn_string_t *from_prop_val,
const svn_string_t *working_prop_val,
const svn_string_t *to_prop_val,
apr_pool_t *pool)
{
svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
working_prop_val, pool));
SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
to_prop_val, pool));
SVN_ERR(svn_mergeinfo_merge(l_deleted, r_deleted, pool));
SVN_ERR(svn_mergeinfo_merge(l_added, r_added, pool));
SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data, pool));
SVN_ERR(svn_mergeinfo_merge(from_mergeinfo, l_added, pool));
SVN_ERR(svn_mergeinfo_remove(&from_mergeinfo, l_deleted,
from_mergeinfo, pool));
return svn_mergeinfo_to_string((svn_string_t **)output, from_mergeinfo, pool);
}
svn_error_t *
svn_wc_merge_props(svn_wc_notify_state_t *state,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_hash_t *baseprops,
const apr_array_header_t *propchanges,
svn_boolean_t base_merge,
svn_boolean_t dry_run,
apr_pool_t *pool)
{
return svn_wc_merge_props2(state, path, adm_access, baseprops, propchanges,
base_merge, dry_run, NULL, NULL, pool);
}
svn_error_t *
svn_wc_merge_props2(svn_wc_notify_state_t *state,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_hash_t *baseprops,
const apr_array_header_t *propchanges,
svn_boolean_t base_merge,
svn_boolean_t dry_run,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
svn_stringbuf_t *log_accum;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
switch (entry->kind)
{
case svn_node_dir:
case svn_node_file:
break;
default:
return SVN_NO_ERROR;
}
if (! dry_run)
log_accum = svn_stringbuf_create("", pool);
SVN_ERR(svn_wc__merge_props(state, adm_access, path, baseprops, NULL, NULL,
propchanges, base_merge, dry_run,
conflict_func, conflict_baton, pool, &log_accum));
if (! dry_run)
{
SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
}
return SVN_NO_ERROR;
}
static void
set_prop_merge_state(svn_wc_notify_state_t *state,
svn_wc_notify_state_t new_value)
{
static char ordering[] =
{ svn_wc_notify_state_unknown,
svn_wc_notify_state_unchanged,
svn_wc_notify_state_inapplicable,
svn_wc_notify_state_changed,
svn_wc_notify_state_merged,
svn_wc_notify_state_obstructed,
svn_wc_notify_state_conflicted };
int state_pos = 0, i;
if (! state)
return;
for (i = 0; i < sizeof(ordering); i++)
{
if (*state == ordering[i])
{
state_pos = i;
break;
}
}
for (i = 0; i <= state_pos; i++)
{
if (new_value == ordering[i])
return;
}
*state = new_value;
}
static svn_error_t *
maybe_generate_propconflict(svn_boolean_t *conflict_remains,
const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t is_dir,
const char *propname,
apr_hash_t *working_props,
const svn_string_t *old_val,
const svn_string_t *new_val,
const svn_string_t *base_val,
const svn_string_t *working_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
apr_pool_t *pool)
{
svn_wc_conflict_result_t *result = NULL;
svn_string_t *mime_propval = NULL;
apr_pool_t *filepool = svn_pool_create(pool);
svn_wc_conflict_description_t *cdesc;
const char *dirpath = svn_path_dirname(path, filepool);
if (! conflict_func)
{
*conflict_remains = TRUE;
return SVN_NO_ERROR;
}
cdesc = svn_wc_conflict_description_create_prop(
path, adm_access, is_dir ? svn_node_dir : svn_node_file, propname, pool);
if (working_val)
SVN_ERR(svn_io_write_unique(&cdesc->my_file, dirpath, working_val->data,
working_val->len,
svn_io_file_del_on_pool_cleanup, filepool));
if (new_val)
SVN_ERR(svn_io_write_unique(&cdesc->their_file, dirpath, new_val->data,
new_val->len, svn_io_file_del_on_pool_cleanup,
filepool));
if (!base_val && !old_val)
{
}
else if ((base_val && !old_val)
|| (!base_val && old_val))
{
const svn_string_t *the_val = base_val ? base_val : old_val;
SVN_ERR(svn_io_write_unique(&cdesc->base_file, dirpath, the_val->data,
the_val->len, svn_io_file_del_on_pool_cleanup,
filepool));
}
else
{
const svn_string_t *the_val;
if (! svn_string_compare(base_val, old_val))
{
if (working_val && svn_string_compare(base_val, working_val))
the_val = old_val;
else
the_val = base_val;
}
else
{
the_val = base_val;
}
SVN_ERR(svn_io_write_unique(&cdesc->base_file, dirpath, the_val->data,
the_val->len, svn_io_file_del_on_pool_cleanup,
filepool));
if (working_val && new_val)
{
svn_stream_t *mergestream;
svn_diff_t *diff;
svn_diff_file_options_t *options =
svn_diff_file_options_create(filepool);
SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file,
NULL, svn_io_file_del_on_pool_cleanup,
filepool, pool));
SVN_ERR(svn_diff_mem_string_diff3(&diff, the_val, working_val,
new_val, options, filepool));
SVN_ERR(svn_diff_mem_string_output_merge2
(mergestream, diff, the_val, working_val, new_val,
NULL, NULL, NULL, NULL,
svn_diff_conflict_display_modified_latest, filepool));
svn_stream_close(mergestream);
}
}
if (!is_dir && working_props)
mime_propval = apr_hash_get(working_props, SVN_PROP_MIME_TYPE,
APR_HASH_KEY_STRING);
cdesc->mime_type = mime_propval ? mime_propval->data : NULL;
cdesc->is_binary = mime_propval ?
svn_mime_type_is_binary(mime_propval->data) : FALSE;
if (!old_val && new_val)
cdesc->action = svn_wc_conflict_action_add;
else if (old_val && !new_val)
cdesc->action = svn_wc_conflict_action_delete;
else
cdesc->action = svn_wc_conflict_action_edit;
if (base_val && !working_val)
cdesc->reason = svn_wc_conflict_reason_deleted;
else if (!base_val && working_val)
cdesc->reason = svn_wc_conflict_reason_obstructed;
else
cdesc->reason = svn_wc_conflict_reason_edited;
SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
if (result == NULL)
{
*conflict_remains = TRUE;
return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
NULL, _("Conflict callback violated API:"
" returned no results."));
}
switch (result->choice)
{
default:
case svn_wc_conflict_choose_postpone:
{
*conflict_remains = TRUE;
break;
}
case svn_wc_conflict_choose_mine_full:
{
*conflict_remains = FALSE;
break;
}
case svn_wc_conflict_choose_theirs_full:
{
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
*conflict_remains = FALSE;
break;
}
case svn_wc_conflict_choose_base:
{
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, base_val);
*conflict_remains = FALSE;
break;
}
case svn_wc_conflict_choose_merged:
{
if (!cdesc->merged_file && !result->merged_file)
return svn_error_create
(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
NULL, _("Conflict callback violated API:"
" returned no merged file."));
else
{
svn_stringbuf_t *merged_stringbuf;
svn_string_t *merged_string;
SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf,
result->merged_file ?
result->merged_file :
cdesc->merged_file,
pool));
merged_string = svn_string_create_from_buf(merged_stringbuf, pool);
apr_hash_set(working_props, propname,
APR_HASH_KEY_STRING, merged_string);
*conflict_remains = FALSE;
}
break;
}
}
svn_pool_destroy(filepool);
return SVN_NO_ERROR;
}
static svn_error_t *
apply_single_prop_add(svn_wc_notify_state_t *state,
const char *path,
svn_boolean_t is_dir,
apr_hash_t *working_props,
svn_string_t **conflict,
const char *propname,
const svn_string_t *base_val,
const svn_string_t *new_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_boolean_t got_conflict = FALSE;
svn_string_t *working_val
= apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
if (working_val)
{
if (svn_string_compare(working_val, new_val))
set_prop_merge_state(state, svn_wc_notify_state_merged);
else
{
if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
{
SVN_ERR(combine_mergeinfo_props(&new_val, working_val,
new_val, pool));
apr_hash_set(working_props, propname,
APR_HASH_KEY_STRING, new_val);
set_prop_merge_state(state, svn_wc_notify_state_merged);
}
else
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path,
adm_access, is_dir,
propname, working_props,
NULL, new_val,
base_val, working_val,
conflict_func, conflict_baton,
pool));
if (got_conflict)
*conflict = svn_string_createf
(pool,
_("Trying to add new property '%s' with value "
"'%s',\nbut property already exists with value '%s'."),
propname, new_val->data, working_val->data);
}
}
}
else if (base_val)
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
is_dir, propname,
working_props, NULL, new_val,
base_val, NULL,
conflict_func, conflict_baton, pool));
if (got_conflict)
*conflict = svn_string_createf
(pool, _("Trying to create property '%s' with value '%s',\n"
"but it has been locally deleted."),
propname, new_val->data);
}
else
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
return SVN_NO_ERROR;
}
static svn_error_t *
apply_single_prop_delete(svn_wc_notify_state_t *state,
const char *path,
svn_boolean_t is_dir,
apr_hash_t *working_props,
svn_string_t **conflict,
const char *propname,
const svn_string_t *base_val,
const svn_string_t *old_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_boolean_t got_conflict = FALSE;
svn_string_t *working_val
= apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
if (! base_val)
{
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL);
if (old_val)
set_prop_merge_state(state, svn_wc_notify_state_merged);
}
else if (svn_string_compare(base_val, old_val))
{
if (working_val)
{
if (svn_string_compare(working_val, old_val))
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL);
else
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path,
adm_access, is_dir,
propname, working_props,
old_val, NULL,
base_val, working_val,
conflict_func, conflict_baton,
pool));
if (got_conflict)
*conflict = svn_string_createf
(pool,
_("Trying to delete property '%s' with value '%s'\n"
"but it has been modified from '%s' to '%s'."),
propname, old_val->data,
base_val->data, working_val->data);
}
}
else
set_prop_merge_state(state, svn_wc_notify_state_merged);
}
else
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
is_dir, propname,
working_props, old_val, NULL,
base_val, working_val,
conflict_func, conflict_baton, pool));
if (got_conflict)
*conflict = svn_string_createf
(pool,
_("Trying to delete property '%s' with value '%s'\n"
"but the local value is '%s'."),
propname, base_val->data, working_val->data);
}
return SVN_NO_ERROR;
}
static svn_error_t *
apply_single_mergeinfo_prop_change(svn_wc_notify_state_t *state,
const char *path,
svn_boolean_t is_dir,
apr_hash_t *working_props,
svn_string_t **conflict,
const char *propname,
const svn_string_t *base_val,
const svn_string_t *old_val,
const svn_string_t *new_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_boolean_t got_conflict = FALSE;
svn_string_t *working_val
= apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
if ((working_val && ! base_val)
|| (! working_val && base_val)
|| (working_val && base_val
&& !svn_string_compare(working_val, base_val)))
{
if (working_val)
{
if (svn_string_compare(working_val, new_val))
set_prop_merge_state(state, svn_wc_notify_state_merged);
else
{
SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
working_val,
new_val, pool));
apr_hash_set(working_props, propname,
APR_HASH_KEY_STRING, new_val);
set_prop_merge_state(state, svn_wc_notify_state_merged);
}
}
else
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
is_dir, propname, working_props,
old_val, new_val,
base_val, working_val,
conflict_func, conflict_baton,
pool));
if (got_conflict)
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but it has been locally deleted."),
propname, old_val->data, new_val->data);
}
}
else if (! working_val)
{
svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
&added_mergeinfo,
old_val, new_val, pool));
SVN_ERR(svn_mergeinfo_to_string((svn_string_t **)&new_val,
added_mergeinfo, pool));
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
}
else
{
if (svn_string_compare(old_val, base_val))
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
else
{
SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
working_val,
new_val, pool));
apr_hash_set(working_props, propname,
APR_HASH_KEY_STRING, new_val);
set_prop_merge_state(state, svn_wc_notify_state_merged);
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
apply_single_generic_prop_change(svn_wc_notify_state_t *state,
const char *path,
svn_boolean_t is_dir,
apr_hash_t *working_props,
svn_string_t **conflict,
const char *propname,
const svn_string_t *base_val,
const svn_string_t *old_val,
const svn_string_t *new_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_boolean_t got_conflict = FALSE;
svn_string_t *working_val
= apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
if ((!working_val && !old_val)
|| (working_val && old_val
&& svn_string_compare(working_val, old_val)))
{
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
}
else
{
SVN_ERR(maybe_generate_propconflict(&got_conflict, path, adm_access,
is_dir, propname, working_props,
old_val, new_val,
base_val, working_val,
conflict_func, conflict_baton,
pool));
if (got_conflict)
{
if (working_val && base_val
&& svn_string_compare(working_val, base_val))
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but property already exists with value '%s'."),
propname, old_val->data, new_val->data, working_val->data);
else if (working_val && base_val)
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but the property has been locally changed from '%s' to "
"'%s'."),
propname, old_val->data, new_val->data,
base_val->data, working_val->data);
else if (working_val)
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but property has been locally added with value "
"'%s'."),
propname, old_val->data, new_val->data, working_val->data);
else if (base_val)
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but it has been locally deleted."),
propname, old_val->data, new_val->data);
else
*conflict = svn_string_createf
(pool,
_("Trying to change property '%s' from '%s' to '%s',\n"
"but the property does not exist."),
propname, old_val->data, new_val->data);
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
apply_single_prop_change(svn_wc_notify_state_t *state,
const char *path,
svn_boolean_t is_dir,
apr_hash_t *working_props,
svn_string_t **conflict,
const char *propname,
const svn_string_t *base_val,
const svn_string_t *old_val,
const svn_string_t *new_val,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
{
SVN_ERR(apply_single_mergeinfo_prop_change(state, path, is_dir,
working_props, conflict,
propname, base_val, old_val,
new_val, conflict_func,
conflict_baton, adm_access,
pool));
}
else
{
SVN_ERR(apply_single_generic_prop_change(state, path, is_dir,
working_props, conflict,
propname, base_val, old_val,
new_val, conflict_func,
conflict_baton, adm_access,
pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__merge_props(svn_wc_notify_state_t *state,
svn_wc_adm_access_t *adm_access,
const char *path,
apr_hash_t *server_baseprops,
apr_hash_t *base_props,
apr_hash_t *working_props,
const apr_array_header_t *propchanges,
svn_boolean_t base_merge,
svn_boolean_t dry_run,
svn_wc_conflict_resolver_func_t conflict_func,
void *conflict_baton,
apr_pool_t *pool,
svn_stringbuf_t **entry_accum)
{
int i;
svn_boolean_t is_dir;
const char *reject_path = NULL;
svn_stream_t *reject_tmp_stream = NULL;
const char *reject_tmp_path = NULL;
if (! svn_path_is_child(svn_wc_adm_access_path(adm_access), path, NULL))
is_dir = TRUE;
else
is_dir = FALSE;
if (! base_props || ! working_props)
SVN_ERR(svn_wc__load_props(base_props ? NULL : &base_props,
working_props ? NULL : &working_props,
NULL, adm_access, path, pool));
if (!server_baseprops)
server_baseprops = base_props;
if (state)
{
*state = svn_wc_notify_state_unchanged;
}
for (i = 0; i < propchanges->nelts; i++)
{
const char *propname;
svn_string_t *conflict = NULL;
const svn_prop_t *incoming_change;
const svn_string_t *from_val, *to_val, *working_val, *base_val;
svn_boolean_t is_normal;
incoming_change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
propname = incoming_change->name;
is_normal = svn_wc_is_normal_prop(propname);
to_val = incoming_change->value
? svn_string_dup(incoming_change->value, pool) : NULL;
from_val = apr_hash_get(server_baseprops, propname, APR_HASH_KEY_STRING);
working_val = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
base_val = apr_hash_get(base_props, propname, APR_HASH_KEY_STRING);
if (base_merge)
apr_hash_set(base_props, propname, APR_HASH_KEY_STRING, to_val);
if (is_normal)
set_prop_merge_state(state, svn_wc_notify_state_changed);
if (! from_val)
SVN_ERR(apply_single_prop_add(is_normal ? state : NULL, path, is_dir,
working_props, &conflict,
propname, base_val, to_val,
conflict_func, conflict_baton,
adm_access, pool));
else if (! to_val)
SVN_ERR(apply_single_prop_delete(is_normal ? state : NULL, path, is_dir,
working_props, &conflict,
propname, base_val, from_val,
conflict_func, conflict_baton,
adm_access, pool));
else
SVN_ERR(apply_single_prop_change(is_normal ? state : NULL, path, is_dir,
working_props, &conflict,
propname, base_val, from_val, to_val,
conflict_func, conflict_baton,
adm_access, pool));
if (conflict)
{
if (is_normal)
set_prop_merge_state(state, svn_wc_notify_state_conflicted);
if (dry_run)
continue;
if (! reject_tmp_stream)
SVN_ERR(open_reject_tmp_stream(&reject_tmp_stream, &reject_tmp_path,
path, is_dir, pool));
SVN_ERR(append_prop_conflict(reject_tmp_stream, conflict, pool));
}
}
if (dry_run)
return SVN_NO_ERROR;
SVN_ERR(svn_wc__install_props(entry_accum, adm_access, path,
base_props, working_props, base_merge,
pool));
if (reject_tmp_stream)
{
SVN_ERR(svn_stream_close(reject_tmp_stream));
SVN_ERR(get_existing_prop_reject_file(&reject_path,
adm_access, path, pool));
if (! reject_path)
{
const char *reject_dirpath;
const char *reject_filename;
if (is_dir)
{
reject_dirpath = path;
reject_filename = SVN_WC__THIS_DIR_PREJ;
}
else
svn_path_split(path, &reject_dirpath, &reject_filename, pool);
SVN_ERR(svn_io_open_uniquely_named(NULL, &reject_path,
reject_dirpath,
reject_filename,
SVN_WC__PROP_REJ_EXT,
svn_io_file_del_none,
pool, pool));
}
SVN_ERR(svn_wc__loggy_append(entry_accum, adm_access,
reject_tmp_path, reject_path, pool));
SVN_ERR(svn_wc__loggy_remove(entry_accum, adm_access,
reject_tmp_path, pool));
{
svn_wc_entry_t entry;
entry.prejfile = svn_path_is_child(svn_wc_adm_access_path(adm_access),
reject_path, NULL);
SVN_ERR(svn_wc__loggy_entry_modify(entry_accum,
adm_access,
path,
&entry,
SVN_WC__ENTRY_MODIFY_PREJFILE,
pool));
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_merge_prop_diffs(svn_wc_notify_state_t *state,
const char *path,
svn_wc_adm_access_t *adm_access,
const apr_array_header_t *propchanges,
svn_boolean_t base_merge,
svn_boolean_t dry_run,
apr_pool_t *pool)
{
return svn_wc_merge_props2(state, path, adm_access, NULL, propchanges,
base_merge, dry_run, NULL, NULL, pool);
}
svn_error_t *
svn_wc__wcprop_list(apr_hash_t **wcprops,
const char *entryname,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
apr_hash_t *all_wcprops;
apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
const char *path = svn_path_join(svn_wc_adm_access_path(adm_access),
entryname, pool);
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
*wcprops = apr_hash_make(pool);
return SVN_NO_ERROR;
}
all_wcprops = svn_wc__adm_access_wcprops(adm_access);
if (! all_wcprops)
{
SVN_ERR(read_wcprops(adm_access, pool));
all_wcprops = svn_wc__adm_access_wcprops(adm_access);
}
if (all_wcprops)
{
*wcprops = apr_hash_get(all_wcprops, entryname, APR_HASH_KEY_STRING);
if (! *wcprops)
{
*wcprops = apr_hash_make(cache_pool);
entryname = apr_pstrdup(cache_pool, entryname);
apr_hash_set(all_wcprops, entryname, APR_HASH_KEY_STRING, *wcprops);
}
return SVN_NO_ERROR;
}
return load_props(wcprops, path, entry->kind, svn_wc__props_wcprop, pool);
}
static svn_error_t *
wcprop_get(const svn_string_t **value,
const char *name,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
apr_hash_t *prophash;
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
*value = NULL;
return SVN_NO_ERROR;
}
if (entry->kind == svn_node_dir)
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
else
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
svn_path_dirname(path, pool), pool));
SVN_ERR_W(svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool),
_("Failed to load properties from disk"));
*value = apr_hash_get(prophash, name, APR_HASH_KEY_STRING);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__wcprop_set(const char *name,
const svn_string_t *value,
const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t force_write,
apr_pool_t *pool)
{
apr_hash_t *prophash;
apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
if (entry->kind == svn_node_dir)
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
else
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
svn_path_dirname(path, pool), pool));
SVN_ERR_W(svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool),
_("Failed to load properties from disk"));
name = apr_pstrdup(cache_pool, name);
if (value)
value = svn_string_dup(value, cache_pool);
apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value);
if (svn_wc__adm_wc_format(adm_access) > SVN_WC__WCPROPS_MANY_FILES_VERSION)
{
if (force_write)
SVN_ERR(write_wcprops(adm_access, pool));
}
else
{
SVN_ERR(svn_wc__write_old_wcprops(path, prophash, entry->kind, pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_prop_list(apr_hash_t **props,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
*props = apr_hash_make(pool);
return SVN_NO_ERROR;
}
if (entry->kind == svn_node_dir)
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
else
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
svn_path_dirname(path, pool), pool));
return svn_wc__load_props(NULL, props, NULL, adm_access, path, pool);
}
static svn_boolean_t
string_contains_prop(const char *string, const char *propname)
{
const char *place = strstr(string, propname);
int proplen = strlen(propname);
if (!place)
return FALSE;
while (place)
{
if (place[proplen] == ' ' || place[proplen] == 0)
return TRUE;
place = strstr(place + 1, propname);
}
return FALSE;
}
svn_error_t *
svn_wc_prop_get(const svn_string_t **value,
const char *name,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
apr_hash_t *prophash;
enum svn_prop_kind kind = svn_property_kind(NULL, name);
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (entry == NULL)
{
*value = NULL;
return SVN_NO_ERROR;
}
if (entry->cachable_props
&& string_contains_prop(entry->cachable_props, name))
{
if (!entry->present_props
|| !string_contains_prop(entry->present_props, name))
{
*value = NULL;
return SVN_NO_ERROR;
}
if (svn_prop_is_boolean(name))
{
*value = svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool);
SVN_ERR_ASSERT(*value != NULL);
return SVN_NO_ERROR;
}
}
if (kind == svn_prop_wc_kind)
{
return wcprop_get(value, name, path, adm_access, pool);
}
if (kind == svn_prop_entry_kind)
{
return svn_error_createf
(SVN_ERR_BAD_PROP_KIND, NULL,
_("Property '%s' is an entry property"), name);
}
else
{
SVN_ERR_W(svn_wc_prop_list(&prophash, path, adm_access, pool),
_("Failed to load properties from disk"));
*value = apr_hash_get(prophash, name, APR_HASH_KEY_STRING);
return SVN_NO_ERROR;
}
}
static svn_error_t *
validate_prop_against_node_kind(const char *name,
const char *path,
svn_node_kind_t node_kind,
apr_pool_t *pool)
{
const char *file_prohibit[] = { SVN_PROP_IGNORE,
SVN_PROP_EXTERNALS,
NULL };
const char *dir_prohibit[] = { SVN_PROP_EXECUTABLE,
SVN_PROP_KEYWORDS,
SVN_PROP_EOL_STYLE,
SVN_PROP_MIME_TYPE,
SVN_PROP_NEEDS_LOCK,
NULL };
const char **node_kind_prohibit;
const char *path_display
= svn_path_is_url(path) ? path : svn_path_local_style(path, pool);
switch (node_kind)
{
case svn_node_dir:
node_kind_prohibit = dir_prohibit;
while (*node_kind_prohibit)
if (strcmp(name, *node_kind_prohibit++) == 0)
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
_("Cannot set '%s' on a directory ('%s')"),
name, path_display);
break;
case svn_node_file:
node_kind_prohibit = file_prohibit;
while (*node_kind_prohibit)
if (strcmp(name, *node_kind_prohibit++) == 0)
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
_("Cannot set '%s' on a file ('%s')"),
name,
path_display);
break;
default:
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
_("'%s' is not a file or directory"),
path_display);
}
return SVN_NO_ERROR;
}
struct getter_baton {
const char *path;
svn_wc_adm_access_t *adm_access;
};
static svn_error_t *
get_file_for_validation(const svn_string_t **mime_type,
svn_stream_t *stream,
void *baton,
apr_pool_t *pool)
{
struct getter_baton *gb = baton;
if (mime_type)
SVN_ERR(svn_wc_prop_get(mime_type, SVN_PROP_MIME_TYPE,
gb->path, gb->adm_access, pool));
if (stream)
{
svn_stream_t *read_stream;
SVN_ERR(svn_stream_open_readonly(&read_stream, gb->path,
pool, pool));
SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
NULL, NULL, pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
validate_eol_prop_against_file(const char *path,
svn_wc_canonicalize_svn_prop_get_file_t getter,
void *getter_baton,
apr_pool_t *pool)
{
svn_stream_t *translating_stream;
svn_error_t *err;
const svn_string_t *mime_type;
const char *path_display
= svn_path_is_url(path) ? path : svn_path_local_style(path, pool);
SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
if (mime_type && svn_mime_type_is_binary(mime_type->data))
return svn_error_createf
(SVN_ERR_ILLEGAL_TARGET, NULL,
_("File '%s' has binary mime type property"),
path_display);
translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
"", FALSE, NULL, FALSE,
pool);
err = getter(NULL, translating_stream, getter_baton, pool);
if (!err)
err = svn_stream_close(translating_stream);
if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
_("File '%s' has inconsistent newlines"),
path_display);
else if (err)
return err;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_prop_set3(const char *name,
const svn_string_t *value,
const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t skip_checks,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
apr_hash_t *prophash, *base_prophash;
enum svn_prop_kind prop_kind = svn_property_kind(NULL, name);
svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
const svn_wc_entry_t *entry;
svn_wc_notify_action_t notify_action;
if (prop_kind == svn_prop_wc_kind)
return svn_wc__wcprop_set(name, value, path, adm_access, TRUE, pool);
else if (prop_kind == svn_prop_entry_kind)
return svn_error_createf
(SVN_ERR_BAD_PROP_KIND, NULL,
_("Property '%s' is an entry property"), name);
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
if (entry->kind == svn_node_dir)
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
else
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
svn_path_dirname(path, pool), pool));
if (value && svn_prop_is_svn_prop(name))
{
const svn_string_t *new_value;
struct getter_baton *gb = apr_pcalloc(pool, sizeof(*gb));
gb->path = path;
gb->adm_access = adm_access;
SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value, path,
entry->kind, skip_checks,
get_file_for_validation, gb, pool));
value = new_value;
}
if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_EXECUTABLE) == 0)
{
if (value == NULL)
SVN_ERR(svn_io_set_file_executable(path, FALSE, TRUE, pool));
else
SVN_ERR(svn_io_set_file_executable(path, TRUE, TRUE, pool));
}
if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_NEEDS_LOCK) == 0)
{
if (value == NULL)
SVN_ERR(svn_io_set_file_read_write(path, FALSE, pool));
}
SVN_ERR_W(svn_wc__load_props(&base_prophash, &prophash, NULL,
adm_access, path, pool),
_("Failed to load properties from disk"));
if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
{
svn_string_t *old_value = apr_hash_get(prophash, SVN_PROP_KEYWORDS,
APR_HASH_KEY_STRING);
apr_hash_t *old_keywords, *new_keywords;
SVN_ERR(svn_wc__get_keywords(&old_keywords, path, adm_access,
old_value ? old_value->data : "", pool));
SVN_ERR(svn_wc__get_keywords(&new_keywords, path, adm_access,
value ? value->data : "", pool));
if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE, pool))
{
svn_wc_entry_t tmp_entry;
tmp_entry.kind = svn_node_file;
tmp_entry.text_time = 0;
SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
path, &tmp_entry,
SVN_WC__ENTRY_MODIFY_TEXT_TIME,
pool));
}
}
if (apr_hash_get(prophash, name, APR_HASH_KEY_STRING) == NULL)
{
if (value == NULL)
notify_action = svn_wc_notify_property_deleted_nonexistent;
else
notify_action = svn_wc_notify_property_added;
}
else
{
if (value == NULL)
notify_action = svn_wc_notify_property_deleted;
else
notify_action = svn_wc_notify_property_modified;
}
apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value);
SVN_ERR(svn_wc__install_props(&log_accum, adm_access, path,
base_prophash, prophash, FALSE, pool));
SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
if (notify_func)
{
svn_wc_notify_t *notify = svn_wc_create_notify(path, notify_action, pool);
notify->prop_name = name;
(*notify_func)(notify_baton, notify, pool);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
const char *propname,
const svn_string_t *propval,
const char *path,
svn_node_kind_t kind,
svn_boolean_t skip_some_checks,
svn_wc_canonicalize_svn_prop_get_file_t getter,
void *getter_baton,
apr_pool_t *pool)
{
svn_stringbuf_t *new_value = NULL;
static const svn_string_t boolean_value =
{
SVN_PROP_BOOLEAN_TRUE,
sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
};
SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
{
svn_subst_eol_style_t eol_style;
const char *ignored_eol;
new_value = svn_stringbuf_create_from_string(propval, pool);
svn_stringbuf_strip_whitespace(new_value);
svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
if (eol_style == svn_subst_eol_style_unknown)
return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
_("Unrecognized line ending style for '%s'"),
svn_path_local_style(path, pool));
SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
pool));
}
else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
{
new_value = svn_stringbuf_create_from_string(propval, pool);
svn_stringbuf_strip_whitespace(new_value);
SVN_ERR(svn_mime_type_validate(new_value->data, pool));
}
else if (strcmp(propname, SVN_PROP_IGNORE) == 0
|| strcmp(propname, SVN_PROP_EXTERNALS) == 0)
{
if (propval->data[propval->len - 1] != '\n')
{
new_value = svn_stringbuf_create_from_string(propval, pool);
svn_stringbuf_appendbytes(new_value, "\n", 1);
}
if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
{
SVN_ERR(svn_wc_parse_externals_description3
(NULL, path, propval->data, FALSE, pool));
}
}
else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
{
new_value = svn_stringbuf_create_from_string(propval, pool);
svn_stringbuf_strip_whitespace(new_value);
}
else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0
|| strcmp(propname, SVN_PROP_NEEDS_LOCK) == 0)
{
new_value = svn_stringbuf_create_from_string(&boolean_value, pool);
}
else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
{
apr_hash_t *mergeinfo;
svn_string_t *new_value_str;
SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
new_value = svn_stringbuf_create_from_string(new_value_str, pool);
}
if (new_value)
*propval_p = svn_string_create_from_buf(new_value, pool);
else
*propval_p = propval;
return SVN_NO_ERROR;
}
svn_boolean_t
svn_wc_is_normal_prop(const char *name)
{
enum svn_prop_kind kind = svn_property_kind(NULL, name);
return (kind == svn_prop_regular_kind);
}
svn_boolean_t
svn_wc_is_wc_prop(const char *name)
{
enum svn_prop_kind kind = svn_property_kind(NULL, name);
return (kind == svn_prop_wc_kind);
}
svn_boolean_t
svn_wc_is_entry_prop(const char *name)
{
enum svn_prop_kind kind = svn_property_kind(NULL, name);
return (kind == svn_prop_entry_kind);
}
static svn_error_t *
empty_props_p(svn_boolean_t *empty_p,
const char *path,
svn_node_kind_t node_kind,
svn_wc__props_kind_t props_kind,
apr_pool_t *pool)
{
svn_error_t *err;
apr_finfo_t finfo;
const char *prop_path;
SVN_ERR(svn_wc__prop_path(&prop_path, path, node_kind, props_kind, pool));
err = svn_io_stat(&finfo, prop_path, APR_FINFO_MIN | APR_FINFO_TYPE, pool);
if (err)
{
if (! APR_STATUS_IS_ENOENT(err->apr_err)
&& ! APR_STATUS_IS_ENOTDIR(err->apr_err))
return err;
svn_error_clear(err);
*empty_p = TRUE;
return SVN_NO_ERROR;
}
if (finfo.filetype == APR_REG && (finfo.size == 4 || finfo.size == 0))
*empty_p = TRUE;
else
*empty_p = FALSE;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__has_props(svn_boolean_t *has_props,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_boolean_t is_empty;
const svn_wc_entry_t *entry;
svn_boolean_t has_propcaching =
svn_wc__adm_wc_format(adm_access) > SVN_WC__NO_PROPCACHING_VERSION;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
*has_props = FALSE;
return SVN_NO_ERROR;
}
if (has_propcaching)
{
*has_props = entry->has_props;
return SVN_NO_ERROR;
}
SVN_ERR(empty_props_p(&is_empty, path, entry->kind, svn_wc__props_working,
pool));
*has_props = !is_empty;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_props_modified_p(svn_boolean_t *modified_p,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
int wc_format = svn_wc__adm_wc_format(adm_access);
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
*modified_p = FALSE;
return SVN_NO_ERROR;
}
if (wc_format > SVN_WC__NO_PROPCACHING_VERSION)
{
*modified_p = entry->has_prop_mods;
return SVN_NO_ERROR;
}
{
apr_array_header_t *local_propchanges;
apr_hash_t *localprops;
apr_hash_t *baseprops;
SVN_ERR(load_props(&localprops, path, entry->kind, svn_wc__props_working,
pool));
if (entry->schedule == svn_wc_schedule_replace)
{
*modified_p = apr_hash_count(localprops) > 0;
return SVN_NO_ERROR;
}
SVN_ERR(load_props(&baseprops, path, entry->kind, svn_wc__props_base,
pool));
SVN_ERR(svn_prop_diffs(&local_propchanges, localprops, baseprops, pool));
*modified_p = (local_propchanges->nelts > 0);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_prop_diffs(apr_array_header_t **propchanges,
apr_hash_t **original_props,
const char *path,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
apr_hash_t *baseprops, *props;
const char *entryname;
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if (! entry)
{
if (original_props)
*original_props = apr_hash_make(pool);
if (propchanges)
*propchanges = apr_array_make(pool, 0, sizeof(svn_prop_t));
return SVN_NO_ERROR;
}
if (entry->kind == svn_node_dir)
{
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
entryname = SVN_WC_ENTRY_THIS_DIR;
}
else
{
const char *dirname;
svn_path_split(path, &dirname, &entryname, pool);
SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, dirname, pool));
}
SVN_ERR(svn_wc__load_props(&baseprops, propchanges ? &props : NULL, NULL,
adm_access, path, pool));
if (original_props != NULL)
*original_props = baseprops;
if (propchanges != NULL)
SVN_ERR(svn_prop_diffs(propchanges, props, baseprops, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
find_and_remove_externals_revision(int *rev_idx,
const char **line_parts,
int num_line_parts,
svn_wc_external_item2_t *item,
const char *parent_directory_display,
const char *line,
apr_pool_t *pool)
{
int i;
for (i = 0; i < 2; ++i)
{
const char *token = line_parts[i];
if (token[0] == '-' && token[1] == 'r')
{
svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
const char *digits_ptr;
int shift_count;
int j;
*rev_idx = i;
if (token[2] == '\0')
{
if (num_line_parts != 4)
goto parse_error;
shift_count = 2;
digits_ptr = line_parts[i+1];
}
else
{
if (num_line_parts != 3)
goto parse_error;
shift_count = 1;
digits_ptr = token+2;
}
if (svn_opt_parse_revision(&item->revision,
&end_revision,
digits_ptr, pool) != 0)
goto parse_error;
if (end_revision.kind != svn_opt_revision_unspecified)
goto parse_error;
if (item->revision.kind != svn_opt_revision_number
&& item->revision.kind != svn_opt_revision_date)
goto parse_error;
for (j = i; j < num_line_parts-shift_count; ++j)
line_parts[j] = line_parts[j+shift_count];
line_parts[num_line_parts-shift_count] = NULL;
return SVN_NO_ERROR;
}
}
if (num_line_parts == 2)
return SVN_NO_ERROR;
parse_error:
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Error parsing %s property on '%s': '%s'"),
SVN_PROP_EXTERNALS,
parent_directory_display,
line);
}
svn_error_t *
svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
const char *parent_directory,
const char *desc,
svn_boolean_t canonicalize_url,
apr_pool_t *pool)
{
apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
int i;
const char *parent_directory_display = svn_path_is_url(parent_directory) ?
parent_directory : svn_path_local_style(parent_directory, pool);
if (externals_p)
*externals_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
for (i = 0; i < lines->nelts; i++)
{
const char *line = APR_ARRAY_IDX(lines, i, const char *);
apr_status_t status;
char **line_parts;
int num_line_parts;
svn_wc_external_item2_t *item;
const char *token0;
const char *token1;
svn_boolean_t token0_is_url;
svn_boolean_t token1_is_url;
int rev_idx = -1;
if ((! line) || (line[0] == '#'))
continue;
status = apr_tokenize_to_argv(line, &line_parts, pool);
if (status)
return svn_error_wrap_apr(status,
_("Can't split line into components: '%s'"),
line);
for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
;
SVN_ERR(svn_wc_external_item_create
((const svn_wc_external_item2_t **) &item, pool));
item->revision.kind = svn_opt_revision_unspecified;
item->peg_revision.kind = svn_opt_revision_unspecified;
if (num_line_parts < 2 || num_line_parts > 4)
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Error parsing %s property on '%s': '%s'"),
SVN_PROP_EXTERNALS,
parent_directory_display,
line);
SVN_ERR(find_and_remove_externals_revision(&rev_idx,
(const char **)line_parts,
num_line_parts, item,
parent_directory_display,
line, pool));
token0 = line_parts[0];
token1 = line_parts[1];
token0_is_url = svn_path_is_url(token0);
token1_is_url = svn_path_is_url(token1);
if (token0_is_url && token1_is_url)
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Invalid %s property on '%s': "
"cannot use two absolute URLs ('%s' and '%s') in an external; "
"one must be a path where an absolute or relative URL is "
"checked out to"),
SVN_PROP_EXTERNALS, parent_directory_display, token0, token1);
if (0 == rev_idx && token1_is_url)
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Invalid %s property on '%s': "
"cannot use a URL '%s' as the target directory for an external "
"definition"),
SVN_PROP_EXTERNALS, parent_directory_display, token1);
if (1 == rev_idx && token0_is_url)
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Invalid %s property on '%s': "
"cannot use a URL '%s' as the target directory for an external "
"definition"),
SVN_PROP_EXTERNALS, parent_directory_display, token0);
if (0 == rev_idx ||
(-1 == rev_idx && (token0_is_url || ! token1_is_url)))
{
SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
token0, pool));
item->target_dir = token1;
}
else
{
item->target_dir = token0;
item->url = token1;
item->peg_revision = item->revision;
}
SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
&item->revision, TRUE, FALSE,
pool));
item->target_dir = svn_path_canonicalize
(svn_path_internal_style(item->target_dir, pool), pool);
if (item->target_dir[0] == '\0' || item->target_dir[0] == '/'
|| svn_path_is_backpath_present(item->target_dir))
return svn_error_createf
(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
_("Invalid %s property on '%s': "
"target '%s' is an absolute path or involves '..'"),
SVN_PROP_EXTERNALS,
parent_directory_display,
item->target_dir);
if (canonicalize_url)
item->url = svn_path_canonicalize(item->url, pool);
if (externals_p)
APR_ARRAY_PUSH(*externals_p, svn_wc_external_item2_t *) = item;
}
return SVN_NO_ERROR;
}
svn_boolean_t
svn_wc__has_special_property(apr_hash_t *props)
{
return apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING) != NULL;
}
svn_boolean_t
svn_wc__has_magic_property(const apr_array_header_t *properties)
{
int i;
for (i = 0; i < properties->nelts; i++)
{
const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
|| strcmp(property->name, SVN_PROP_KEYWORDS) == 0
|| strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
|| strcmp(property->name, SVN_PROP_SPECIAL) == 0
|| strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
return TRUE;
}
return FALSE;
}