Author: rhuijben
Date: Wed May 18 13:33:03 2011
New Revision: 1124259

URL: http://svn.apache.org/viewvc?rev=1124259&view=rev
Log:
Resolve issue #3533, "Entry-prop edits resulting in tree conflicts", by making
the update editor determine that a node (or one of its children) is really
edited before installing an edited tree conflict.

* subversion/libsvn_wc/update_editor.c
  (dir_baton,
   file_baton): Add two variables.

  (mark_directory_edited,
   mark_file_edited): New functions.

  (delete_entry): Mark parent directory edited.
  (add_directory): Mark directory edited.
  (open_directory): Don't install tree conflict directly, but delay it until
    the directory is really edited.
  (change_dir_prop): If a not-entry prop is received mark the directory edited.
  (close_directory): Only do an update notification if the directory is edited.
    (Needed because we don't always notify from open_file on new tree
     conflicts)
  (absent_node): Mark the parent directory edited.
  (add_file): Mark the file edited.
  (open_file): Don't install tree conflict directly, but delay it until
    the file is really edited.
  (apply_textdelta): Mark file edited.
  (change_file_prop): If a not-entry prop is received mark the file edited.
  (close_file): Only do an update notification if the file is edited.
    (Needed because we don't always notify from open_file on new tree
     conflicts)

* subversion/tests/cmdline/basic_tests.py
  (delete_child_parent_update): Don't expect a treeconflict in this specific
    case where the only reason we get a change is that we were mixed revision.

* subversion/tests/cmdline/lock_tests.py
  (update_locked_deleted): Extend coverage to include a deleted directory with
    files inside and then try to steal the lock.

Modified:
    subversion/trunk/subversion/libsvn_wc/update_editor.c
    subversion/trunk/subversion/tests/cmdline/basic_tests.py
    subversion/trunk/subversion/tests/cmdline/lock_tests.py

Modified: subversion/trunk/subversion/libsvn_wc/update_editor.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/update_editor.c?rev=1124259&r1=1124258&r2=1124259&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/update_editor.c (original)
+++ subversion/trunk/subversion/libsvn_wc/update_editor.c Wed May 18 13:33:03 
2011
@@ -330,6 +330,14 @@ struct dir_baton
      changes to be applied to this directory. */
   apr_array_header_t *propchanges;
 
+  /* A boolean indicating whether this node or one of its children has
+     received any 'real' changes. Used to avoid tree conflicts for simple
+     entryprop changes, like lock management */
+  svn_boolean_t edited;
+
+  /* The tree conflict to install once the node is really edited */
+  svn_wc_conflict_description2_t *edit_conflict;
+
   /* The bump information for this directory. */
   struct bump_dir_info *bump_info;
 
@@ -731,6 +739,14 @@ struct file_baton
 
   /* Bump information for the directory this file lives in */
   struct bump_dir_info *bump_info;
+
+  /* A boolean indicating whether this node or one of its children has
+     received any 'real' changes. Used to avoid tree conflicts for simple
+     entryprop changes, like lock management */
+  svn_boolean_t edited;
+
+  /* The tree conflict to install once the node is really edited */
+  svn_wc_conflict_description2_t *edit_conflict;
 };
 
 
@@ -787,6 +803,63 @@ make_file_baton(struct file_baton **f_p,
   return SVN_NO_ERROR;
 }
 
+/* Called when a directory is really edited, to avoid marking a
+   tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool)
+{
+  if (db->edited)
+    return SVN_NO_ERROR;
+
+  if (db->parent_baton)
+    SVN_ERR(mark_directory_edited(db->parent_baton, scratch_pool));
+
+  db->edited = TRUE;
+
+  if (db->edit_conflict)
+    {
+      /* We have a (delayed) tree conflict to install */
+      SVN_ERR(svn_wc__db_op_set_tree_conflict(db->edit_baton->db,
+                                              db->local_abspath,
+                                              db->edit_conflict,
+                                              scratch_pool));
+
+      do_notification(db->edit_baton, db->local_abspath, svn_node_dir,
+                      svn_wc_notify_tree_conflict, scratch_pool);
+      db->already_notified = TRUE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Called when a file is really edited, to avoid marking a
+   tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool)
+{
+  if (fb->edited)
+    return SVN_NO_ERROR;
+
+  SVN_ERR(mark_directory_edited(fb->dir_baton, scratch_pool));
+
+  fb->edited = TRUE;
+
+  if (fb->edit_conflict)
+    {
+      /* We have a (delayed) tree conflict to install */
+      SVN_ERR(svn_wc__db_op_set_tree_conflict(fb->edit_baton->db,
+                                              fb->local_abspath,
+                                              fb->edit_conflict,
+                                              scratch_pool));
+
+      do_notification(fb->edit_baton, fb->local_abspath, svn_node_file,
+                      svn_wc_notify_tree_conflict, scratch_pool);
+      fb->already_notified = TRUE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 
 /* Handle the next delta window of the file described by BATON.  If it is
  * the end (WINDOW == NULL), then check the checksum, store the text in the
@@ -1594,6 +1667,8 @@ delete_entry(const char *path,
 
   scratch_pool = svn_pool_create(pb->pool);
 
+  SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
   SVN_ERR(check_path_under_root(pb->local_abspath, base, scratch_pool));
 
   local_abspath = svn_dirent_join(pb->local_abspath, base, scratch_pool);
@@ -1822,6 +1897,8 @@ add_directory(const char *path,
   if (db->skip_this)
     return SVN_NO_ERROR;
 
+  SVN_ERR(mark_directory_edited(db, pool));
+
   SVN_ERR(check_path_under_root(pb->local_abspath, db->name, pool));
 
   if (strcmp(eb->target_abspath, db->local_abspath) == 0)
@@ -2238,19 +2315,12 @@ open_directory(const char *path,
     SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath,
                                 status, wc_kind, TRUE,
                                 svn_wc_conflict_action_edit, svn_node_dir,
-                                db->new_relpath, pool, pool));
+                                db->new_relpath, db->pool, pool));
 
   /* Remember the roots of any locally deleted trees. */
   if (tree_conflict != NULL)
     {
-      /* Place a tree conflict into the parent work queue.  */
-      SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db, db->local_abspath,
-                                              tree_conflict, pool));
-
-      do_notification(eb, db->local_abspath, svn_node_dir,
-                      svn_wc_notify_tree_conflict, pool);
-      db->already_notified = TRUE;
-
+      db->edit_conflict = tree_conflict;
       /* Other modifications wouldn't be a tree conflict */
       SVN_ERR_ASSERT(
                 tree_conflict->reason == svn_wc_conflict_reason_deleted ||
@@ -2287,6 +2357,9 @@ change_dir_prop(void *dir_baton,
   propchange->name = apr_pstrdup(db->pool, name);
   propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
 
+  if (!db->edited && svn_property_kind(NULL, name) != svn_prop_entry_kind)
+    SVN_ERR(mark_directory_edited(db, pool));
+
   return SVN_NO_ERROR;
 }
 
@@ -2651,7 +2724,7 @@ close_directory(void *dir_baton,
      happened in that case - unless the add was obstructed by a dir
      scheduled for addition without history, in which case we handle
      notification here). */
-  if (!db->already_notified && eb->notify_func)
+  if (!db->already_notified && eb->notify_func && db->edited)
     {
       svn_wc_notify_t *notify;
       svn_wc_notify_action_t action;
@@ -2699,6 +2772,8 @@ absent_node(const char *path,
   if (pb->skip_this)
     return SVN_NO_ERROR;
 
+  SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
   local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool);
 
   /* If an item by this name is scheduled for addition that's a
@@ -2830,6 +2905,8 @@ add_file(const char *path,
   if (fb->skip_this)
     return SVN_NO_ERROR;
 
+  SVN_ERR(mark_file_edited(fb, pool));
+
   SVN_ERR(check_path_under_root(pb->local_abspath, fb->name, pool));
 
   /* The file_pool can stick around for a *long* time, so we want to
@@ -3216,14 +3293,12 @@ open_file(const char *path,
     SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
                                 status, wc_kind, TRUE,
                                 svn_wc_conflict_action_edit, svn_node_file,
-                                fb->new_relpath, scratch_pool, scratch_pool));
+                                fb->new_relpath, fb->pool, scratch_pool));
 
   /* Is this path the victim of a newly-discovered tree conflict? */
   if (tree_conflict != NULL)
     {
-      SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db,
-                                              fb->local_abspath,
-                                              tree_conflict, scratch_pool));
+      fb->edit_conflict = tree_conflict;
 
       /* Other modifications wouldn't be a tree conflict */
       SVN_ERR_ASSERT(
@@ -3232,10 +3307,6 @@ open_file(const char *path,
 
       /* Continue updating BASE */
       fb->shadowed = TRUE;
-
-      fb->already_notified = TRUE;
-      do_notification(eb, fb->local_abspath, svn_node_unknown,
-                      svn_wc_notify_tree_conflict, scratch_pool);
     }
 
   svn_pool_destroy(scratch_pool);
@@ -3268,6 +3339,8 @@ apply_textdelta(void *file_baton,
       return SVN_NO_ERROR;
     }
 
+  SVN_ERR(mark_file_edited(fb, pool));
+
   /* Parse checksum or sets expected_base_checksum to NULL */
   SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5,
                                  expected_checksum, pool));
@@ -3399,6 +3472,9 @@ change_file_prop(void *file_baton,
   propchange->name = apr_pstrdup(fb->pool, name);
   propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
 
+  if (!fb->edited && svn_property_kind(NULL, name) != svn_prop_entry_kind)
+    SVN_ERR(mark_file_edited(fb, scratch_pool));
+
   return SVN_NO_ERROR;
 }
 
@@ -4182,7 +4258,7 @@ close_file(void *file_baton,
 
   /* Send a notification to the callback function.  (Skip notifications
      about files which were already notified for another reason.) */
-  if (eb->notify_func && !fb->already_notified)
+  if (eb->notify_func && !fb->already_notified && fb->edited)
     {
       const svn_string_t *mime_type;
       svn_wc_notify_t *notify;

Modified: subversion/trunk/subversion/tests/cmdline/basic_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/basic_tests.py?rev=1124259&r1=1124258&r2=1124259&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/basic_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/basic_tests.py Wed May 18 
13:33:03 2011
@@ -2605,9 +2605,8 @@ def delete_child_parent_update(sbox):
   expected_disk = svntest.main.greek_state.copy()
   expected_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/B/E')
 
-  # This produces a tree-conflict
+  # This produced a tree-conflict until we fixed issue #3533
   expected_status.tweak(wc_rev=2)
-  expected_status.tweak('A/B/E', treeconflict='C')
   svntest.actions.run_and_verify_update(wc_dir,
                                         [],
                                         expected_disk,

Modified: subversion/trunk/subversion/tests/cmdline/lock_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/lock_tests.py?rev=1124259&r1=1124258&r2=1124259&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/lock_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/lock_tests.py Wed May 18 13:33:03 
2011
@@ -1657,12 +1657,15 @@ def update_locked_deleted(sbox):
   sbox.build()
   wc_dir = sbox.wc_dir
   
-  iota_path = os.path.join(wc_dir, 'iota')
-  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  iota_path = sbox.ospath('iota')
+  mu_path = sbox.ospath('A/mu')
+  alpha_path = sbox.ospath('A/B/E/alpha')
 
-  svntest.main.run_svn(None, 'lock', '-m', 'locked', mu_path, iota_path)
+  svntest.main.run_svn(None, 'lock', '-m', 'locked', mu_path, iota_path,
+                       alpha_path)
   sbox.simple_rm('iota')
   sbox.simple_rm('A/mu')
+  sbox.simple_rm('A/B/E')
 
   # Create expected output tree for an update.
   expected_output = svntest.wc.State(wc_dir, {
@@ -1670,17 +1673,26 @@ def update_locked_deleted(sbox):
   
   # Create expected status tree for the update.
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
-  expected_status.tweak('iota', 'A/mu', status='D ', writelocked='K')
-    
-  svntest.actions.run_and_verify_update(wc_dir,
-                                        expected_output,
-                                        None,
-                                        expected_status,
-                                        None, None, None,
-                                        None, None)
+  expected_status.tweak('A/B/E', status='D ')
+  expected_status.tweak('iota', 'A/mu', 'A/B/E/alpha',
+                        status='D ', writelocked='K')
+  expected_status.tweak('A/B/E/beta', status='D ')
+
+  svntest.actions.run_and_verify_update(wc_dir, expected_output,
+                                        None, expected_status)
+
+  # Now we steal the lock of iota and A/mu via URL and retry
+  svntest.main.run_svn(None, 'lock', '-m', 'locked', sbox.repo_url + '/iota',
+                       '--force', sbox.repo_url + '/A/mu',
+                       sbox.repo_url + '/A/B/E/alpha')
+
+  expected_status.tweak('iota', 'A/mu', 'A/B/E/alpha',
+                        status='D ', writelocked='O')
+
+  svntest.actions.run_and_verify_update(wc_dir, expected_output,
+                                        None, expected_status)
+
 
-  
-  
 ########################################################################
 # Run the tests
 


Reply via email to