/*
* conflicts.c: Tree conflicts.
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include "cl-conflicts.h"
#include "svn_hash.h"
#include "svn_xml.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "private/svn_token.h"
#include "cl.h"
#include "svn_private_config.h"
/* A map for svn_wc_conflict_action_t values to XML strings */
static const svn_token_map_t map_conflict_action_xml[] =
{
{ "edit", svn_wc_conflict_action_edit },
{ "delete", svn_wc_conflict_action_delete },
{ "add", svn_wc_conflict_action_add },
{ "replace", svn_wc_conflict_action_replace },
{ NULL, 0 }
};
/* A map for svn_wc_conflict_reason_t values to XML strings */
static const svn_token_map_t map_conflict_reason_xml[] =
{
{ "edit", svn_wc_conflict_reason_edited },
{ "delete", svn_wc_conflict_reason_deleted },
{ "missing", svn_wc_conflict_reason_missing },
{ "obstruction", svn_wc_conflict_reason_obstructed },
{ "add", svn_wc_conflict_reason_added },
{ "replace", svn_wc_conflict_reason_replaced },
{ "unversioned", svn_wc_conflict_reason_unversioned },
{ "moved-away", svn_wc_conflict_reason_moved_away },
{ "moved-here", svn_wc_conflict_reason_moved_here },
{ NULL, 0 }
};
/* Return a localised string representation of the local part of a conflict;
NULL for non-localised odd cases. */
static const char *
local_reason_str(svn_node_kind_t kind, svn_wc_conflict_reason_t reason,
svn_wc_operation_t operation)
{
switch (kind)
{
case svn_node_file:
case svn_node_symlink:
switch (reason)
{
case svn_wc_conflict_reason_edited:
return _("local file edit");
case svn_wc_conflict_reason_obstructed:
return _("local file obstruction");
case svn_wc_conflict_reason_deleted:
return _("local file delete");
case svn_wc_conflict_reason_missing:
if (operation == svn_wc_operation_merge)
return _("local file missing or deleted or moved away");
else
return _("local file missing");
case svn_wc_conflict_reason_unversioned:
return _("local file unversioned");
case svn_wc_conflict_reason_added:
return _("local file add");
case svn_wc_conflict_reason_replaced:
return _("local file replace");
case svn_wc_conflict_reason_moved_away:
return _("local file moved away");
case svn_wc_conflict_reason_moved_here:
return _("local file moved here");
}
break;
case svn_node_dir:
switch (reason)
{
case svn_wc_conflict_reason_edited:
return _("local dir edit");
case svn_wc_conflict_reason_obstructed:
return _("local dir obstruction");
case svn_wc_conflict_reason_deleted:
return _("local dir delete");
case svn_wc_conflict_reason_missing:
if (operation == svn_wc_operation_merge)
return _("local dir missing or deleted or moved away");
else
return _("local dir missing");
case svn_wc_conflict_reason_unversioned:
return _("local dir unversioned");
case svn_wc_conflict_reason_added:
return _("local dir add");
case svn_wc_conflict_reason_replaced:
return _("local dir replace");
case svn_wc_conflict_reason_moved_away:
return _("local dir moved away");
case svn_wc_conflict_reason_moved_here:
return _("local dir moved here");
}
break;
case svn_node_none:
case svn_node_unknown:
switch (reason)
{
case svn_wc_conflict_reason_edited:
return _("local edit");
case svn_wc_conflict_reason_obstructed:
return _("local obstruction");
case svn_wc_conflict_reason_deleted:
return _("local delete");
case svn_wc_conflict_reason_missing:
if (operation == svn_wc_operation_merge)
return _("local missing or deleted or moved away");
else
return _("local missing");
case svn_wc_conflict_reason_unversioned:
return _("local unversioned");
case svn_wc_conflict_reason_added:
return _("local add");
case svn_wc_conflict_reason_replaced:
return _("local replace");
case svn_wc_conflict_reason_moved_away:
return _("local moved away");
case svn_wc_conflict_reason_moved_here:
return _("local moved here");
}
break;
}
return NULL;
}
/* Return a localised string representation of the incoming part of a
conflict; NULL for non-localised odd cases. */
static const char *
incoming_action_str(svn_node_kind_t kind, svn_wc_conflict_action_t action)
{
switch (kind)
{
case svn_node_file:
case svn_node_symlink:
switch (action)
{
case svn_wc_conflict_action_edit:
return _("incoming file edit");
case svn_wc_conflict_action_add:
return _("incoming file add");
case svn_wc_conflict_action_delete:
return _("incoming file delete or move");
case svn_wc_conflict_action_replace:
return _("incoming replace with file");
}
break;
case svn_node_dir:
switch (action)
{
case svn_wc_conflict_action_edit:
return _("incoming dir edit");
case svn_wc_conflict_action_add:
return _("incoming dir add");
case svn_wc_conflict_action_delete:
return _("incoming dir delete or move");
case svn_wc_conflict_action_replace:
return _("incoming replace with dir");
}
break;
case svn_node_none:
case svn_node_unknown:
switch (action)
{
case svn_wc_conflict_action_edit:
return _("incoming edit");
case svn_wc_conflict_action_add:
return _("incoming add");
case svn_wc_conflict_action_delete:
return _("incoming delete or move");
case svn_wc_conflict_action_replace:
return _("incoming replace");
}
break;
}
return NULL;
}
/* Return a localised string representation of the operation part of a
conflict. */
static const char *
operation_str(svn_wc_operation_t operation)
{
switch (operation)
{
case svn_wc_operation_update: return _("upon update");
case svn_wc_operation_switch: return _("upon switch");
case svn_wc_operation_merge: return _("upon merge");
case svn_wc_operation_none: return _("upon none");
}
SVN_ERR_MALFUNCTION_NO_RETURN();
return NULL;
}
svn_error_t *
svn_cl__get_human_readable_prop_conflict_description(
const char **desc,
svn_client_conflict_t *conflict,
apr_pool_t *pool)
{
const char *reason_str, *action_str;
/* We provide separately translatable strings for the values that we
* know about, and a fall-back in case any other values occur. */
switch (svn_client_conflict_get_local_change(conflict))
{
case svn_wc_conflict_reason_edited:
reason_str = _("local edit");
break;
case svn_wc_conflict_reason_added:
reason_str = _("local add");
break;
case svn_wc_conflict_reason_deleted:
reason_str = _("local delete");
break;
case svn_wc_conflict_reason_obstructed:
reason_str = _("local obstruction");
break;
default:
reason_str = apr_psprintf(
pool, _("local %s"),
svn_token__to_word(
map_conflict_reason_xml,
svn_client_conflict_get_local_change(conflict)));
break;
}
switch (svn_client_conflict_get_incoming_change(conflict))
{
case svn_wc_conflict_action_edit:
action_str = _("incoming edit");
break;
case svn_wc_conflict_action_add:
action_str = _("incoming add");
break;
case svn_wc_conflict_action_delete:
action_str = _("incoming delete");
break;
default:
action_str = apr_psprintf(
pool, _("incoming %s"),
svn_token__to_word(
map_conflict_action_xml,
svn_client_conflict_get_incoming_change(conflict)));
break;
}
SVN_ERR_ASSERT(reason_str && action_str);
*desc = apr_psprintf(pool, _("%s, %s %s"),
reason_str, action_str,
operation_str(
svn_client_conflict_get_operation(conflict)));
return SVN_NO_ERROR;
}
svn_error_t *
svn_cl__get_human_readable_tree_conflict_description(
const char **desc,
svn_client_conflict_t *conflict,
apr_pool_t *pool)
{
const char *action, *reason, *operation;
svn_node_kind_t incoming_kind;
svn_wc_conflict_action_t conflict_action;
svn_wc_conflict_reason_t conflict_reason;
svn_wc_operation_t conflict_operation;
svn_node_kind_t conflict_node_kind;
conflict_action = svn_client_conflict_get_incoming_change(conflict);
conflict_reason = svn_client_conflict_get_local_change(conflict);
conflict_operation = svn_client_conflict_get_operation(conflict);
conflict_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
/* Determine the node kind of the incoming change. */
incoming_kind = svn_node_unknown;
if (conflict_action == svn_wc_conflict_action_edit ||
conflict_action == svn_wc_conflict_action_delete)
{
/* Change is acting on 'src_left' version of the node. */
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
NULL, NULL, &incoming_kind, conflict, pool, pool));
}
else if (conflict_action == svn_wc_conflict_action_add ||
conflict_action == svn_wc_conflict_action_replace)
{
/* Change is acting on 'src_right' version of the node.
*
* ### For 'replace', the node kind is ambiguous. However, src_left
* ### is NULL for replace, so we must use src_right. */
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
NULL, NULL, &incoming_kind, conflict, pool, pool));
}
reason = local_reason_str(conflict_node_kind, conflict_reason,
conflict_operation);
action = incoming_action_str(incoming_kind, conflict_action);
operation = operation_str(conflict_operation);
SVN_ERR_ASSERT(operation);
if (action && reason)
{
*desc = apr_psprintf(pool, _("%s, %s %s"),
reason, action, operation);
}
else
{
/* A catch-all message for very rare or nominally impossible cases.
It will not be pretty, but is closer to an internal error than
an ordinary user-facing string. */
*desc = apr_psprintf(pool, _("local: %s %s incoming: %s %s %s"),
svn_node_kind_to_word(conflict_node_kind),
svn_token__to_word(map_conflict_reason_xml,
conflict_reason),
svn_node_kind_to_word(incoming_kind),
svn_token__to_word(map_conflict_action_xml,
conflict_action),
operation);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_cl__get_human_readable_action_description(
const char **desc,
svn_wc_conflict_action_t action,
svn_wc_operation_t operation,
svn_node_kind_t kind,
apr_pool_t *pool)
{
const char *action_s, *operation_s;
action_s = incoming_action_str(kind, action);
operation_s = operation_str(operation);
SVN_ERR_ASSERT(operation_s);
*desc = apr_psprintf(pool, _("%s %s"),
action_s, operation_s);
return SVN_NO_ERROR;
}
/* Helper for svn_cl__append_tree_conflict_info_xml().
* Appends the repository location of a conflicted node to ATT_HASH.
* SIDE is the content of the version tag's side="..." attribute,
* currently one of "source-left" or "source-right".*/
static svn_error_t *
add_conflict_version_xml(svn_stringbuf_t **pstr,
const char *side,
const char *repos_root_url,
const char *repos_relpath,
svn_revnum_t peg_rev,
svn_node_kind_t node_kind,
apr_pool_t *pool)
{
apr_hash_t *att_hash = apr_hash_make(pool);
svn_hash_sets(att_hash, "side", side);
if (repos_root_url)
svn_hash_sets(att_hash, "repos-url", repos_root_url);
if (repos_relpath)
svn_hash_sets(att_hash, "path-in-repos", repos_relpath);
if (SVN_IS_VALID_REVNUM(peg_rev))
svn_hash_sets(att_hash, "revision", apr_ltoa(pool, peg_rev));
if (node_kind != svn_node_unknown)
svn_hash_sets(att_hash, "kind", svn_cl__node_kind_str_xml(node_kind));
svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing,
"version", att_hash);
return SVN_NO_ERROR;
}
static svn_error_t *
append_tree_conflict_info_xml(svn_stringbuf_t *str,
svn_client_conflict_t *conflict,
apr_pool_t *pool)
{
apr_hash_t *att_hash = apr_hash_make(pool);
const char *tmp;
const char *repos_root_url;
const char *repos_relpath;
svn_revnum_t peg_rev;
svn_node_kind_t node_kind;
svn_hash_sets(att_hash, "victim",
svn_dirent_basename(
svn_client_conflict_get_local_abspath(conflict), pool));
svn_hash_sets(att_hash, "kind",
svn_cl__node_kind_str_xml(
svn_client_conflict_tree_get_victim_node_kind(conflict)));
svn_hash_sets(att_hash, "operation",
svn_cl__operation_str_xml(
svn_client_conflict_get_operation(conflict), pool));
tmp = svn_token__to_word(map_conflict_action_xml,
svn_client_conflict_get_incoming_change(conflict));
svn_hash_sets(att_hash, "action", tmp);
tmp = svn_token__to_word(map_conflict_reason_xml,
svn_client_conflict_get_local_change(conflict));
svn_hash_sets(att_hash, "reason", tmp);
/* Open the tree-conflict tag. */
svn_xml_make_open_tag_hash(&str, pool, svn_xml_normal,
"tree-conflict", att_hash);
/* Add child tags for OLDER_VERSION and THEIR_VERSION. */
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict,
pool, pool));
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(&repos_relpath,
&peg_rev,
&node_kind,
conflict,
pool,
pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str, "source-left",
repos_root_url, repos_relpath, peg_rev,
node_kind, pool));
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(&repos_relpath,
&peg_rev,
&node_kind,
conflict,
pool,
pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str,
"source-right",
repos_root_url, repos_relpath, peg_rev,
node_kind, pool));
svn_xml_make_close_tag(&str, pool, "tree-conflict");
return SVN_NO_ERROR;
}
svn_error_t *
svn_cl__append_conflict_info_xml(svn_stringbuf_t *str,
svn_client_conflict_t *conflict,
apr_pool_t *scratch_pool)
{
apr_hash_t *att_hash;
svn_boolean_t text_conflicted;
apr_array_header_t *props_conflicted;
svn_boolean_t tree_conflicted;
svn_wc_operation_t conflict_operation;
const char *repos_root_url;
const char *repos_relpath;
svn_revnum_t peg_rev;
svn_node_kind_t node_kind;
conflict_operation = svn_client_conflict_get_operation(conflict);
SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
&props_conflicted,
&tree_conflicted,
conflict,
scratch_pool, scratch_pool));
if (tree_conflicted)
{
/* Uses other element type */
return svn_error_trace(
append_tree_conflict_info_xml(str, conflict, scratch_pool));
}
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
conflict,
scratch_pool, scratch_pool));
att_hash = apr_hash_make(scratch_pool);
svn_hash_sets(att_hash, "operation",
svn_cl__operation_str_xml(conflict_operation, scratch_pool));
svn_hash_sets(att_hash, "operation",
svn_cl__operation_str_xml(conflict_operation, scratch_pool));
if (text_conflicted)
{
const char *base_abspath;
const char *my_abspath;
const char *their_abspath;
svn_hash_sets(att_hash, "type", "text");
/* "<conflict>" */
svn_xml_make_open_tag_hash(&str, scratch_pool,
svn_xml_normal, "conflict", att_hash);
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
scratch_pool, scratch_pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str, "source-left",
repos_root_url, repos_relpath, peg_rev,
node_kind, scratch_pool));
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
scratch_pool, scratch_pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str, "source-right",
repos_root_url, repos_relpath, peg_rev,
node_kind, scratch_pool));
SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
&base_abspath,
&their_abspath,
conflict, scratch_pool,
scratch_pool));
/* "<prev-base-file> xx </prev-base-file>" */
svn_cl__xml_tagged_cdata(
&str, scratch_pool, "prev-base-file", base_abspath);
/* "<prev-wc-file> xx </prev-wc-file>" */
svn_cl__xml_tagged_cdata(
&str, scratch_pool, "prev-wc-file", my_abspath);
/* "<cur-base-file> xx </cur-base-file>" */
svn_cl__xml_tagged_cdata(
&str, scratch_pool, "cur-base-file", their_abspath);
/* "</conflict>" */
svn_xml_make_close_tag(&str, scratch_pool, "conflict");
}
if (props_conflicted->nelts > 0)
{
const char *reject_abspath;
svn_hash_sets(att_hash, "type", "property");
/* "<conflict>" */
svn_xml_make_open_tag_hash(&str, scratch_pool,
svn_xml_normal, "conflict", att_hash);
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
scratch_pool, scratch_pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str, "source-left",
repos_root_url, repos_relpath, peg_rev,
node_kind, scratch_pool));
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&repos_relpath, &peg_rev, &node_kind, conflict,
scratch_pool, scratch_pool));
if (repos_root_url && repos_relpath)
SVN_ERR(add_conflict_version_xml(&str, "source-right",
repos_root_url, repos_relpath, peg_rev,
node_kind, scratch_pool));
/* "<prop-file> xx </prop-file>" */
reject_abspath =
svn_client_conflict_prop_get_reject_abspath(conflict);
svn_cl__xml_tagged_cdata(
&str, scratch_pool, "prop-file", reject_abspath);
/* "</conflict>" */
svn_xml_make_close_tag(&str, scratch_pool, "conflict");
}
return SVN_NO_ERROR;
}