Author: stsp
Date: Mon Nov 13 22:21:50 2017
New Revision: 1815142

URL: http://svn.apache.org/viewvc?rev=1815142&view=rev
Log:
On the addremove branch, add support for moved directories.

This new functionality matches up directories based on the names and
node kinds of children. It does not take file content into account.
This heuristic may be a bit too simplistic, but at least it's a start.

* subversion/libsvn_client/addremove.c
  (match_dirs_recursively, suggest_dir_moves, already_moved): New helpers.
  (suggest_moves): Before matching files, try matching directories.

* subversion/libsvn_wc/wc-queries.sql
  (STMT_SELECT_WORKING_CHILDREN): Actually return deleted children, as
   advertised in the docstring of svn_wc__node_get_children_of_working_node().
   This change could break some things. I haven't run the full test suite yet.

* subversion/tests/cmdline/addremove_tests.py
  (addremove_unversioned_move_dir, test_list): New test.

Modified:
    subversion/branches/addremove/subversion/libsvn_client/addremove.c
    subversion/branches/addremove/subversion/libsvn_wc/wc-queries.sql
    subversion/branches/addremove/subversion/tests/cmdline/addremove_tests.py

Modified: subversion/branches/addremove/subversion/libsvn_client/addremove.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_client/addremove.c?rev=1815142&r1=1815141&r2=1815142&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_client/addremove.c 
(original)
+++ subversion/branches/addremove/subversion/libsvn_client/addremove.c Mon Nov 
13 22:21:50 2017
@@ -39,6 +39,7 @@
 #include "private/svn_client_private.h"
 #include "private/svn_wc_private.h"
 #include "private/svn_magic.h"
+#include "private/svn_sorts_private.h"
 
 #include "svn_private_config.h"
 
@@ -147,6 +148,157 @@ suggest_file_moves(apr_hash_t **moves,
   return SVN_NO_ERROR;
 }
 
+/* Check whether the directories at DELETED_DIR_ABSPATH and ADDED_DIR_ABSPATH
+ * can be considered a match.
+ * Requires that all directory entries match up in terms of name
+ * and node kind, recursively. */
+static svn_error_t *
+match_dirs_recursively(svn_boolean_t *found_match,
+                       const char *deleted_dir_abspath,
+                       const char *added_dir_abspath,
+                       svn_client_ctx_t *ctx,
+                       apr_pool_t *scratch_pool)
+{
+  const apr_array_header_t *children1, *children2;
+  int i;
+  svn_boolean_t match = TRUE;
+  apr_pool_t *iterpool;
+
+  *found_match = FALSE;
+
+  SVN_ERR(svn_wc__node_get_children_of_working_node(&children1,
+                                                    ctx->wc_ctx,
+                                                    deleted_dir_abspath,
+                                                    scratch_pool,
+                                                    scratch_pool));
+  svn_sort__array((apr_array_header_t *)children1, svn_sort_compare_paths);
+
+  SVN_ERR(svn_wc__node_get_children_of_working_node(&children2,
+                                                    ctx->wc_ctx,
+                                                    added_dir_abspath,
+                                                    scratch_pool,
+                                                    scratch_pool));
+  if (children1->nelts != children2->nelts)
+    return SVN_NO_ERROR;
+
+  svn_sort__array((apr_array_header_t *)children2, svn_sort_compare_paths);
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < children1->nelts; i++)
+    {
+      const char *child1_abspath = APR_ARRAY_IDX(children1, i, const char *);
+      const char *child2_abspath = APR_ARRAY_IDX(children2, i, const char *);
+      const char *basename1, *basename2;
+      svn_node_kind_t kind1, kind2;
+
+      svn_pool_clear(iterpool);
+
+      basename1 = svn_dirent_basename(child1_abspath, iterpool);
+      basename2 = svn_dirent_basename(child2_abspath, iterpool);
+
+      /* Verify basename. */
+      if (strcmp(basename1, basename2) != 0)
+        {
+          match = FALSE;
+          break;
+        }
+
+      /* Verify node kind. */
+      SVN_ERR(svn_wc_read_kind2(&kind1, ctx->wc_ctx, child1_abspath,
+                                TRUE, FALSE, iterpool));
+      SVN_ERR(svn_wc_read_kind2(&kind2, ctx->wc_ctx, child2_abspath,
+                                FALSE, FALSE, iterpool));
+      if (kind1 != kind2)
+        {
+          match = FALSE;
+          break;
+        }
+
+      if (kind1 == svn_node_dir && kind2 == svn_node_dir)
+        {
+          SVN_ERR(match_dirs_recursively(&match,
+                                         child1_abspath, child2_abspath,
+                                         ctx, iterpool));
+          if (!match)
+            break;
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  *found_match = match;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+suggest_dir_moves(apr_hash_t **moves,
+                  const char *added_abspath,
+                  apr_hash_t *deleted,
+                  svn_client_ctx_t *ctx,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi; 
+  apr_pool_t *iterpool;
+  
+  iterpool = svn_pool_create(scratch_pool);
+  for (hi = apr_hash_first(scratch_pool, deleted); hi;
+       hi = apr_hash_next(hi))
+    {
+      apr_array_header_t *move_targets;
+      const char *deleted_abspath = apr_hash_this_key(hi);
+      const svn_wc_status3_t *deleted_status = apr_hash_this_val(hi);
+      svn_boolean_t match = FALSE;
+
+      if (deleted_status->kind != svn_node_dir)
+        continue;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(match_dirs_recursively(&match, deleted_abspath, added_abspath,
+                                     ctx, iterpool));
+      if (match)
+        {
+          move_targets = svn_hash_gets(*moves, deleted_abspath);
+          if (move_targets == NULL)
+            {
+              move_targets = apr_array_make(result_pool, 1,
+                                            sizeof (const char *));
+              svn_hash_sets(*moves, deleted_abspath, move_targets);
+            }
+
+          APR_ARRAY_PUSH(move_targets, const char *) = 
+            apr_pstrdup(result_pool, added_abspath);
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Indicate whether MOVES already covers an ADDED_ABSPATH. */
+static svn_boolean_t
+already_moved(apr_hash_t *moves, const char *added_abspath,
+              apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(scratch_pool, moves); hi;
+       hi = apr_hash_next(hi))
+    {
+      apr_array_header_t *move_targets = apr_hash_this_val(hi);
+      int i;
+
+      for (i = 0; i < move_targets->nelts; i++) 
+        {
+          const char *dst_abspath = APR_ARRAY_IDX(move_targets, i,
+                                                  const char *);
+          if (svn_dirent_is_child(dst_abspath, added_abspath, NULL) != NULL)
+            return 1;
+        }
+    }
+
+  return 0;
+}
+
 static svn_error_t *
 suggest_moves(apr_hash_t **moves,
               apr_hash_t *deleted,
@@ -169,7 +321,21 @@ suggest_moves(apr_hash_t **moves,
 
       svn_pool_clear(iterpool);
 
-      if (status->actual_kind == svn_node_file)
+      if (status->actual_kind == svn_node_dir)
+        SVN_ERR(suggest_dir_moves(moves, added_abspath, deleted, ctx,
+                                  result_pool, iterpool));
+    }
+
+  for (hi = apr_hash_first(scratch_pool, added); hi;
+       hi = apr_hash_next(hi))
+    {
+      const char *added_abspath = apr_hash_this_key(hi);
+      const svn_wc_status3_t *status = apr_hash_this_val(hi);
+
+      svn_pool_clear(iterpool);
+
+      if (status->actual_kind == svn_node_file &&
+          !already_moved(*moves, added_abspath, iterpool))
         SVN_ERR(suggest_file_moves(moves, added_abspath, deleted, ctx,
                                    result_pool, iterpool));
     }

Modified: subversion/branches/addremove/subversion/libsvn_wc/wc-queries.sql
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_wc/wc-queries.sql?rev=1815142&r1=1815141&r2=1815142&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_wc/wc-queries.sql (original)
+++ subversion/branches/addremove/subversion/libsvn_wc/wc-queries.sql Mon Nov 
13 22:21:50 2017
@@ -415,7 +415,7 @@ WHERE wc_id = ?1 AND parent_relpath = ?2
        OR
        (op_depth = (SELECT MAX(op_depth) FROM nodes
                     WHERE wc_id = ?1 AND local_relpath = ?2)
-        AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)))
+        AND presence IN (MAP_NORMAL, MAP_INCOMPLETE, MAP_BASE_DELETED)))
 ORDER BY local_relpath
 
 -- STMT_SELECT_BASE_NOT_PRESENT_CHILDREN

Modified: 
subversion/branches/addremove/subversion/tests/cmdline/addremove_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/addremove_tests.py?rev=1815142&r1=1815141&r2=1815142&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/addremove_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/addremove_tests.py 
Mon Nov 13 22:21:50 2017
@@ -144,6 +144,30 @@ def addremove_ambiguous_move_file(sbox):
   })
   svntest.actions.run_and_verify_status(wc_dir, expected_status)
 
+def addremove_unversioned_move_dir(sbox):
+  "addremove detects unversioned dir moves"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  # Manually move a versioned directory
+  old_path = sbox.ospath('A/B/E')
+  new_path = sbox.ospath('A/C/E-moved')
+  os.rename(old_path, new_path)
+
+  svntest.actions.run_and_verify_svn(None, [], 'addremove', wc_dir)
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/B/E', status='D ', moved_to='A/C/E-moved')
+  expected_status.tweak('A/B/E/alpha', status='D ')
+  expected_status.tweak('A/B/E/beta', status='D ')
+  expected_status.add({
+    'A/C/E-moved' : Item(status='A ', wc_rev='-', moved_from='A/B/E',
+                         copied='+'),
+    'A/C/E-moved/alpha' : Item(status='A ', wc_rev='-'),
+    'A/C/E-moved/beta'  : Item(status='A ', wc_rev='-'),
+  })
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
 
 ########################################################################
 # Run the tests
@@ -154,6 +178,7 @@ test_list = [ None,
               addremove_ignore,
               addremove_unversioned_move_file,
               addremove_ambiguous_move_file,
+              addremove_unversioned_move_dir,
 ]
 
 if __name__ == '__main__':


Reply via email to