Author: gstein
Date: Fri Apr 23 21:22:52 2010
New Revision: 937524
URL: http://svn.apache.org/viewvc?rev=937524&view=rev
Log:
Begin new infrastructure for generating prop conflict messages. This will
allow us to (re)generate a property reject file at will, given a record of
the property conflicts on a given node.
There are two issues for discussion and fixing in a future revision:
- incoming-delete will remove local-add (it should conflict?)
- incoming-delete will crash on a local-delete
* subversion/libsvn_wc/props.c:
(generate_conflict_message): new function to generate a property
conflict message given the four property values involved in a 4-way
merge.
(apply_single_prop_delete): leave two notes about behavior in here (see
the issues above). fix message generation: use OLD_VAL, not BASE_VAL
(apply_single_generic_prop_change): the OLD_VAL parameter will always be
not-NULL, so we can simplify an if condition.
(svn_wc__merge_props): save away MINE_VAL, and then if we see a conflict
message returned by the property merging functions, then assert that
our new function comes up with the same message
* subversion/tests/cmdline/prop_tests.py:
(prop_reject_grind): new test function to grind thru all the variations
of property conflicts.
(test_list): add new test
* subversion/tests/cmdline/svntest/sandbox.py:
(Sandbox.simple_propset, Sandbox.simple_propdel): new methods
Modified:
subversion/trunk/subversion/libsvn_wc/props.c
subversion/trunk/subversion/tests/cmdline/prop_tests.py
subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py
Modified: subversion/trunk/subversion/libsvn_wc/props.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/props.c?rev=937524&r1=937523&r2=937524&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/props.c (original)
+++ subversion/trunk/subversion/libsvn_wc/props.c Fri Apr 23 21:22:52 2010
@@ -709,6 +709,136 @@ svn_wc_merge_props3(svn_wc_notify_state_
}
+/* Generate a message to describe the property conflict among these four
+ values.
+
+ Note that this function (currently) interprets the property values as
+ strings, but they could actually be binary values. We'll keep the
+ types as svn_string_t in case we fix this in the future. */
+static const svn_string_t *
+generate_conflict_message(const char *propname,
+ const svn_string_t *original,
+ const svn_string_t *mine,
+ const svn_string_t *incoming,
+ const svn_string_t *incoming_base,
+ apr_pool_t *result_pool)
+{
+ if (incoming_base == NULL)
+ {
+ /* Attempting to add the value INCOMING. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
+
+ if (mine)
+ {
+ /* To have a conflict, these must be different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
+
+ /* Note that we don't care whether MINE is locally-added or
+ edited, or just something different that is a copy of the
+ pristine ORIGINAL. */
+ return svn_string_createf(result_pool,
+ _("Trying to add new property '%s' with "
+ "value '%s',\nbut property already "
+ "exists with value '%s'."),
+ propname, incoming->data, mine->data);
+ }
+
+ /* To have a conflict, we must have an ORIGINAL which has been
+ locally-deleted. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+ return svn_string_createf(result_pool,
+ _("Trying to create property '%s' with "
+ "value '%s',\nbut it has been locally "
+ "deleted."),
+ propname, incoming->data);
+ }
+
+ if (incoming == NULL)
+ {
+ /* Attempting to delete the value INCOMING_BASE. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
+
+ /* A conflict can only occur if we originally had the property;
+ otherwise, we would have merged the property-delete into the
+ non-existent property. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+
+ if (mine && svn_string_compare(original, incoming_base))
+ {
+ /* We were trying to delete the correct property, but an edit
+ caused the conflict. */
+ return svn_string_createf(result_pool,
+ _("Trying to delete property '%s' with "
+ "value '%s'\nbut it has been modified "
+ "from '%s' to '%s'."),
+ propname, incoming_base->data,
+ original->data, mine->data);
+ }
+
+ /* We were trying to delete INCOMING_BASE but our ORIGINAL is
+ something else entirely. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+
+ /* ### wait. what if we had a different property and locally
+ ### deleted it? the statement below is gonna blow up.
+ ### we could have: local-add, local-edit, local-del, or just
+ ### something different (and unchanged). */
+ return svn_string_createf(result_pool,
+ _("Trying to delete property '%s' with "
+ "value '%s'\nbut the local value is "
+ "'%s'."),
+ propname, incoming_base->data, mine->data);
+ }
+
+ /* Attempting to change the property from INCOMING_BASE to INCOMING. */
+
+ /* If we have a (current) property value, then it should be different
+ from the INCOMING_BASE; otherwise, the incoming change would have
+ been applied to it. */
+ SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
+
+ if (original && mine && svn_string_compare(original, mine))
+ {
+ /* We have an unchanged property, so the original values must
+ have been different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+ return svn_string_createf(result_pool,
+ _("Trying to change property '%s' from '%s' "
+ "to '%s',\nbut property already exists "
+ "with value '%s'."),
+ propname, incoming_base->data, incoming->data,
+ mine->data);
+ }
+
+ if (original && mine)
+ return svn_string_createf(result_pool,
+ _("Trying to change property '%s' from '%s' "
+ "to '%s',\nbut the property has been locally "
+ "changed from '%s' to '%s'."),
+ propname, incoming_base->data, incoming->data,
+ original->data, mine->data);
+
+ if (original)
+ return svn_string_createf(result_pool,
+ _("Trying to change property '%s' from '%s' "
+ "to '%s',\nbut it has been locally deleted."),
+ propname, incoming_base->data, incoming->data);
+
+ if (mine)
+ return svn_string_createf(result_pool,
+ _("Trying to change property '%s' from '%s' "
+ "to '%s',\nbut property has been locally "
+ "added with value '%s'."),
+ propname, incoming_base->data, incoming->data,
+ mine->data);
+
+ return svn_string_createf(result_pool,
+ _("Trying to change property '%s' from '%s' to "
+ "'%s',\nbut the property does not exist."),
+ propname, incoming_base->data, incoming->data);
+}
+
+
/* Set the value of *STATE to NEW_VALUE if STATE is not NULL
* and NEW_VALUE is a higer order value than *STATE's current value
* using this ordering (lower order first):
@@ -1166,6 +1296,8 @@ apply_single_prop_delete(svn_wc_notify_s
if (! base_val)
{
+ /* ### what about working_val? what if we locally-added? */
+
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, NULL);
if (old_val)
/* This is a merge, merging a delete into non-existent */
@@ -1216,11 +1348,13 @@ apply_single_prop_delete(svn_wc_notify_s
cancel_func, cancel_baton,
dry_run, scratch_pool));
if (got_conflict)
+ /* ### wait. what if we had a different property and locally
+ ### deleted it? the statement below is gonna blow up. */
*conflict = svn_string_createf
(result_pool,
_("Trying to delete property '%s' with value '%s'\n"
"but the local value is '%s'."),
- propname, base_val->data, working_val->data);
+ propname, old_val->data, working_val->data);
}
return SVN_NO_ERROR;
@@ -1373,10 +1507,11 @@ apply_single_generic_prop_change(svn_wc_
svn_string_t *working_val
= apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
+ SVN_ERR_ASSERT(old_val != NULL);
+
/* If working_val is the same as old_val... */
- if ((!working_val && !old_val)
- || (working_val && old_val
- && svn_string_compare(working_val, old_val)))
+ if (working_val && old_val
+ && svn_string_compare(working_val, old_val))
{
/* A trivial update: change it to new_val. */
apr_hash_set(working_props, propname, APR_HASH_KEY_STRING, new_val);
@@ -1580,6 +1715,7 @@ svn_wc__merge_props(svn_wc_notify_state_
svn_string_t *conflict = NULL;
const svn_prop_t *incoming_change;
const svn_string_t *from_val, *to_val, *base_val;
+ const svn_string_t *mine_val;
svn_pool_clear(iterpool);
@@ -1595,6 +1731,9 @@ svn_wc__merge_props(svn_wc_notify_state_
if (base_merge)
apr_hash_set(base_props, propname, APR_HASH_KEY_STRING, to_val);
+ /* Save MINE for later message generation. */
+ mine_val = apr_hash_get(working_props, propname, APR_HASH_KEY_STRING);
+
/* We already know that state is at least `changed', so mark
that, but remember that we may later upgrade to `merged' or
even `conflicted'. */
@@ -1638,6 +1777,20 @@ svn_wc__merge_props(svn_wc_notify_state_
if (conflict)
{
+ /* ### for now, just demonstrate we produce the correct
+ ### messages. we'll be doing more interesting stuff later. */
+ {
+ const svn_string_t *message;
+
+ message = generate_conflict_message(propname,
+ base_val,
+ mine_val,
+ to_val,
+ from_val,
+ iterpool);
+ SVN_ERR_ASSERT(svn_string_compare(conflict, message));
+ }
+
set_prop_merge_state(state, svn_wc_notify_state_conflicted);
if (dry_run)
Modified: subversion/trunk/subversion/tests/cmdline/prop_tests.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/prop_tests.py?rev=937524&r1=937523&r2=937524&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/prop_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/prop_tests.py Fri Apr 23 21:22:52
2010
@@ -1790,6 +1790,83 @@ def rm_of_replaced_file(sbox):
svntest.verify.verify_exit_code(None, exit_code, 0)
+def prop_reject_grind(sbox):
+ """grind through all variants of prop rejects"""
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ iota_path = sbox.ospath('iota')
+ mu_path = sbox.ospath('A/mu')
+
+ # Create r2 with all the properties we intend to use as incoming-change,
+ # and as incoming-delete. Also set up our local-edit and local-delete
+ # properties. We also need some properties that are simply different
+ # from the incoming properties
+ sbox.simple_propset('edit.diff', 'repos', iota_path)
+ sbox.simple_propset('edit.edit', 'repos', iota_path)
+ sbox.simple_propset('edit.del', 'repos', iota_path)
+ sbox.simple_propset('edit.add', 'repos', iota_path)
+ sbox.simple_propset('edit.none', 'repos', iota_path)
+ sbox.simple_propset('del.edit', 'repos', iota_path)
+ sbox.simple_propset('del.edit2', 'repos', iota_path)
+ sbox.simple_propset('del.diff', 'repos', iota_path)
+ sbox.simple_propset('del.del', 'repos', iota_path)
+ sbox.simple_propset('del.add', 'repos', iota_path)
+
+ sbox.simple_propset('edit.edit', 'local', mu_path)
+ sbox.simple_propset('add.edit', 'local', mu_path)
+ sbox.simple_propset('del.edit', 'local', mu_path)
+ sbox.simple_propset('del.edit2', 'repos', mu_path)
+ sbox.simple_propset('add.del', 'local', mu_path)
+ sbox.simple_propset('edit.del', 'local', mu_path)
+ sbox.simple_propset('del.del', 'local', mu_path)
+ sbox.simple_propset('edit.diff', 'local', mu_path)
+ sbox.simple_propset('add.diff', 'local', mu_path)
+ sbox.simple_propset('del.diff', 'local', mu_path)
+
+ sbox.simple_commit()
+
+ # Create r3 with all the properties that we intend to use as incoming-add,
+ # and then perform the incoming-edits and incoming-deletes.
+ sbox.simple_propset('add.add', 'repos', iota_path)
+ sbox.simple_propset('add.edit', 'repos', iota_path)
+ sbox.simple_propset('add.del', 'repos', iota_path)
+ sbox.simple_propset('add.diff', 'repos', iota_path)
+ sbox.simple_propset('edit.diff', 'repos.changed', iota_path)
+ sbox.simple_propset('edit.edit', 'repos.changed', iota_path)
+ sbox.simple_propset('edit.del', 'repos.changed', iota_path)
+ sbox.simple_propset('edit.add', 'repos.changed', iota_path)
+ sbox.simple_propset('edit.none', 'repos.changed', iota_path)
+ sbox.simple_propdel('del.edit', iota_path)
+ sbox.simple_propdel('del.edit2', iota_path)
+ sbox.simple_propdel('del.diff', iota_path)
+ ### don't delete this. causes a segfault :-)
+ #sbox.simple_propdel('del.del', iota_path)
+ sbox.simple_propdel('del.add', iota_path)
+ sbox.simple_commit()
+
+ # Set up our victim for all the right rejects: local-adds, local-edits,
+ # and local-deletes.
+ sbox.simple_propset('edit.add', 'local', mu_path)
+ sbox.simple_propset('add.add', 'local', mu_path)
+ sbox.simple_propset('del.add', 'local', mu_path)
+ sbox.simple_propset('edit.edit', 'local.changed', mu_path)
+ sbox.simple_propset('add.edit', 'local.changed', mu_path)
+ sbox.simple_propset('del.edit', 'local.changed', mu_path)
+ sbox.simple_propset('del.edit2', 'repos.changed', mu_path)
+ sbox.simple_propdel('add.del', mu_path)
+ sbox.simple_propdel('edit.del', mu_path)
+ sbox.simple_propdel('del.del', mu_path)
+
+ # Now merge r2:3 into the victim to create all variants
+ svntest.main.run_svn(False, 'merge', '-r2:3', sbox.repo_url + '/iota',
+ mu_path)
+
+ ### need to verify mu.prej
+ ### note that del.add has been erroneously deleted!
+
+
########################################################################
# Run the tests
@@ -1831,6 +1908,7 @@ test_list = [ None,
delete_nonexistent_property,
XFail(post_revprop_change_hook, svntest.main.is_ra_type_dav),
rm_of_replaced_file,
+ prop_reject_grind,
]
if __name__ == '__main__':
Modified: subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py?rev=937524&r1=937523&r2=937524&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py (original)
+++ subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py Fri Apr 23
21:22:52 2010
@@ -182,6 +182,14 @@ class Sandbox:
assert len(targets) > 0
svntest.main.run_svn(False, 'revert', *targets)
+ def simple_propset(self, name, value, *targets):
+ assert len(targets) > 0
+ svntest.main.run_svn(False, 'propset', name, value, *targets)
+
+ def simple_propdel(self, name, *targets):
+ assert len(targets) > 0
+ svntest.main.run_svn(False, 'propdel', name, *targets)
+
def is_url(target):
return (target.startswith('^/')