Hi,

I have now redone the patch to fix some of the comments given in this
thread and I'm attaching the current working state of it so that we
can have something real to discuss around. This does not include any
changes to svnlook but have a test case to verify that it works as it
should (which it at the moment doesn't, see 10 below).

Further more, I've tried to enumerate all cases that I could think of
were properties are changed and how they should be handled. For some
of the cases I've changed my mind compared to what I've said in the
rest of the thread. Hopefully this is a complete list.

mod_props is the per node hash with properties (see svn_repos_node2_t).

1. Add node w/o props => mod_props is NULL
2. Add node w/ props => mod_props contains all props

Pretty straight-forward; mod_props contains what can be expected and
action is A for all props.

3. Delete node w/o props => mod_props is NULL
4. Delete node w/ props => mod_props is NULL

For case 4 it could be argued that mod_props should contain all props
that the node had prior to being deleted. First I thought it should,
but after thinking some more I now have the opinion that it isn't of
any real use. The node is gone after all, and it doesn't matter if the
node had e.g. svn:eol-style set or not.

5. Change (add, delete or modify) node props => mod_props contains all
changed props

Also straight-forward. Action is A, D or M depending on type of
change. Properties that haven't changed are not included in mod_props.

6. Copy (i.e. add-with-history) node w/o props => mod_props is NULL
7. Copy node w/ props => mod_props is NULL

Case 7 is somewhat similar to case 4 in that one could argue that
mod_props should contain all props that were copied with the node. But
as nothing has been changed and text_mod and prop_mod are both FALSE
in this case I think that mod_props should be NULL.

8. Copy node w/ changed props [1] => mod_props contains all changed props

In this case one or several properties are added, deleted or modified
between coping the node and committing. Unmodified props are not
included in mod_props (case 7).

9. Replace (without history) node (w/ or w/o props) with node w/o
props => mod_props is NULL

We don't include any props from the deleted node (case 3 and 4) and no
props are added for the new node (case 1).

10. Replace node (w/ or w/o props) with node w/ props => mod_props
contains all props

We don't include any props from the deleted node (case 3 and 4) but
new props are added with the new node (case 2). The test for this
fails as new props are marked as M if they existed in the deleted
node. Don't know a good solution for this yet. Suggestions welcome.

11. Replace (with history) node (w/ or w/o props) with copied node w/
or w/o props => mod_props is NULL

We don't include any props from the deleted node (case 3 and 4) nor
any props from the unmodified copy (case 6 and 7).

12. Replace node (w/ or w/o props) with copied node w/ modified props
=> mod_props contains all modified props

We don't include any props from the deleted node (case 3 and 4) but
the modified props from the copied node (case 8).

// Erik

-- 
Erik Johansson
Home Page: http://ejohansson.se/
PGP Key: http://ejohansson.se/erik.asc
Index: subversion/tests/libsvn_repos/repos-test.c
===================================================================
--- subversion/tests/libsvn_repos/repos-test.c	(revision 1055565)
+++ subversion/tests/libsvn_repos/repos-test.c	(working copy)
@@ -367,6 +367,53 @@
 }
 
 
+/* Debug helper */
+static void
+node_tree_print(const svn_repos_node2_t *node,
+                int indent,
+                apr_pool_t *pool)
+{
+  char action;
+
+  if (!node)
+    return;
+
+  /* Make bubble-up nodes have action 'r'. */
+  action = node->action;
+  if (action == 'R' && ! node->text_mod && ! node->prop_mod)
+    action = 'r';
+
+  printf("[%c]%*c%s: (copy %s:%ld) [%s mods text:%d prop:%d]\n",
+         action, indent + 1, ' ', node->name,
+         (node->copyfrom_path ? node->copyfrom_path : "<none>"),
+         node->copyfrom_rev,
+         svn_node_kind_to_word(node->kind),
+         node->text_mod, node->prop_mod);
+
+  if (node->mod_props)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(pool, node->mod_props); hi;
+           hi = apr_hash_next(hi))
+        {
+          const void *key;
+          void *val;
+          svn_repos_node_prop_t *prop;
+
+          apr_hash_this(hi, &key, NULL, &val);
+          prop = val;
+          printf("   %*c[%c] %s (copy %d)\n",
+                 indent + 1 + 2, ' ', prop->action,
+                 (const char *)key,
+                 prop->action == 'M' && node->copyfrom_path);
+        }
+    }
+
+  node_tree_print(node->child, indent + 2, pool);
+  node_tree_print(node->sibling, indent, pool);
+}
+
+
 static svn_error_t *
 node_tree_delete_under_copy(const svn_test_opts_t *opts,
                             apr_pool_t *pool)
@@ -448,6 +495,182 @@
 }
 
 
+static svn_error_t *
+node_tree_props(const svn_test_opts_t *opts,
+                apr_pool_t *pool)
+{
+  svn_repos_t *repos;
+  svn_fs_t *fs;
+  svn_fs_txn_t *txn;
+  svn_fs_root_t *txn_root, *revision_root, *revision_2_root;
+  svn_revnum_t youngest_rev;
+  void *edit_baton;
+  const svn_delta_editor_t *editor;
+  svn_repos_node2_t *tree, *node;
+  svn_repos_node_prop_t *prop;
+  apr_pool_t *subpool = svn_pool_create(pool);
+
+  /* Create a filesystem and repository. */
+  SVN_ERR(svn_test__create_repos(&repos, "test-repo-props",
+                                 opts, pool));
+  fs = svn_repos_fs(repos);
+
+  /* Prepare a txn to receive the greek tree. */
+  SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
+  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
+
+  /* Create, set some props and commit the greek tree.
+   * Changes to the greek tree are listed below with props inside [].
+   *
+   * /A/B props updated [p1=v1, p2=v2, p3=v3]
+   * /A/C props updated [p1=v1]
+   * /A/D/G props updated [p1=v1]
+   */
+  SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/B", "p1",
+                                  svn_string_create("v1", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/B", "p2",
+                                  svn_string_create("v2", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/B", "p3",
+                                  svn_string_create("v3", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/C", "p1",
+                                  svn_string_create("v1", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/D/G", "p1",
+                                  svn_string_create("v1", pool),
+                                  pool));  
+  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
+  SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+
+  /* Now, commit again, this time with the changes listed below.
+   *
+   * /A/B props updated [p1=v, p2 deleted]
+   * /A/C replaced by /A/B
+   * /A/C props updated [p2=v, p3 deleted, p4=v4]
+   * /A/D/G deleted
+   * /A/D/G created [p1=v]
+   * /X created
+   * /Z created [p1=v1]
+   */
+  SVN_ERR(svn_fs_revision_root(&revision_root, fs, youngest_rev, pool));
+  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
+  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
+
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/B", "p1",
+                                  svn_string_create("v", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/B", "p2", NULL, pool));
+  SVN_ERR(svn_fs_delete(txn_root, "A/C", pool));
+  SVN_ERR(svn_fs_copy(revision_root, "A/B", txn_root, "A/C", pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/C", "p2",
+                                  svn_string_create("v", pool),
+                                  pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/C", "p3", NULL, pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/C", "p4",
+                                  svn_string_create("v4", pool),
+                                  pool));
+  SVN_ERR(svn_fs_delete(txn_root, "A/D/G", pool));
+  SVN_ERR(svn_fs_make_dir(txn_root, "A/D/G", pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "A/D/G", "p1",
+                                  svn_string_create("v", pool),
+                                  pool));
+  SVN_ERR(svn_fs_make_dir(txn_root, "X", pool));
+  SVN_ERR(svn_fs_make_dir(txn_root, "Z", pool));
+  SVN_ERR(svn_fs_change_node_prop(txn_root, "Z", "p1",
+                                  svn_string_create("v1", pool),
+                                  pool));
+
+  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
+  SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+
+  /* Now, we run the node_tree editor code, and see that a) it doesn't
+     bomb out, and b) that our nodes are all good. */
+  SVN_ERR(svn_fs_revision_root(&revision_2_root, fs, youngest_rev, pool));
+  SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
+                                revision_root, revision_2_root,
+                                pool, subpool));
+  SVN_ERR(svn_repos_replay2(revision_2_root, "", SVN_INVALID_REVNUM, TRUE,
+                            editor, edit_baton, NULL, NULL, subpool));
+
+  /* Get the root of the generated tree, and cleanup our mess. */
+  tree = svn_repos_node2_from_baton(edit_baton);
+  svn_pool_destroy(subpool);
+
+  /* See that we got what we expected (fortunately, svn_repos_replay
+     drivers editor paths in a predictable fashion!). */
+
+  node_tree_print(tree, 0, pool);
+
+  /* Verify tree */
+  if (! (tree /* / */
+         && tree->child /* /A */
+         && tree->child->child /* /A/B */
+         && tree->child->child->sibling /* /A/C */
+         && tree->child->child->sibling->sibling /* /A/D */
+         && tree->child->child->sibling->sibling->child /* /A/D/G */
+         && tree->child->sibling /* /X */
+         && tree->child->sibling->sibling /* /Z */
+        ))
+    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                            "Generated node tree is bogus.");
+
+  /* Verify node names */
+  SVN_TEST_STRING_ASSERT(tree->name, "");
+  SVN_TEST_STRING_ASSERT(tree->child->name, "A");
+  SVN_TEST_STRING_ASSERT(tree->child->child->name, "B");
+  SVN_TEST_STRING_ASSERT(tree->child->child->sibling->name, "C");
+  SVN_TEST_STRING_ASSERT(tree->child->child->sibling->sibling->name, "D");
+  SVN_TEST_STRING_ASSERT(tree->child->child->sibling->sibling->child->name, "G");
+  SVN_TEST_STRING_ASSERT(tree->child->sibling->name, "X");
+  SVN_TEST_STRING_ASSERT(tree->child->sibling->sibling->name, "Z");
+
+  /* Verify props for /A/B */
+  node = tree->child->child;
+  SVN_TEST_ASSERT(node->mod_props);
+  SVN_TEST_ASSERT(apr_hash_count(node->mod_props) == 2);
+  prop = apr_hash_get(node->mod_props, "p1", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'M');
+  prop = apr_hash_get(node->mod_props, "p2", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'D');
+  SVN_TEST_ASSERT(! apr_hash_get(node->mod_props, "p3", APR_HASH_KEY_STRING));
+
+  /* Verify props for /A/C */
+  node = tree->child->child->sibling;
+  SVN_TEST_ASSERT(node->mod_props);
+  SVN_TEST_ASSERT(apr_hash_count(node->mod_props) == 3);
+  SVN_TEST_ASSERT(! apr_hash_get(node->mod_props, "p1", APR_HASH_KEY_STRING));
+  prop = apr_hash_get(node->mod_props, "p2", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'M');
+  prop = apr_hash_get(node->mod_props, "p3", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'D');
+  prop = apr_hash_get(node->mod_props, "p4", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'A');
+
+  /* Verify props for /A/D/G */
+  node = tree->child->child->sibling->sibling->child;
+  SVN_TEST_ASSERT(node->mod_props);
+  SVN_TEST_ASSERT(apr_hash_count(node->mod_props) == 1);
+  prop = apr_hash_get(node->mod_props, "p1", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'A');
+
+  /* Verify props for /X */
+  node = tree->child->sibling;
+  SVN_TEST_ASSERT(! node->mod_props);
+
+  /* Verify props for /Z */
+  node = tree->child->sibling->sibling;
+  SVN_TEST_ASSERT(node->mod_props);
+  SVN_TEST_ASSERT(apr_hash_count(node->mod_props) == 1);
+  prop = apr_hash_get(node->mod_props, "p1", APR_HASH_KEY_STRING);
+  SVN_TEST_ASSERT(prop != NULL && prop->action == 'A');
+
+  return SVN_NO_ERROR;
+}
+
+
 /* Helper for revisions_changed(). */
 static const char *
 print_chrevs(const apr_array_header_t *revs_got,
@@ -2497,6 +2720,8 @@
                        "test svn_repos_dir_delta2"),
     SVN_TEST_OPTS_PASS(node_tree_delete_under_copy,
                        "test deletions under copies in node_tree code"),
+    SVN_TEST_OPTS_PASS(node_tree_props,
+                       "test property changes in node_tree code"),
     SVN_TEST_OPTS_PASS(revisions_changed,
                        "test svn_repos_history() (partially)"),
     SVN_TEST_OPTS_PASS(node_locations,
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h	(revision 1055565)
+++ subversion/include/svn_repos.h	(working copy)
@@ -2251,7 +2251,86 @@
  * which each node representing a repository node that was changed.
  */
 
-/** A node in the repository. */
+/** A property on a node.
+ *
+ * In order to avoid backwards compatibility problems clients should use
+ * svn_repos_node_prop_create() to allocate and initialize this structure
+ * instead of doing so themselves.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_repos_node_prop_t
+{
+  /** How this property entered the node tree: 'A'dd, 'D'elete, 'M'odify */
+  char action;
+
+  /* NOTE: Add new fields at the end to preserve binary compatibility.  Also,
+     if you add fields here, you have to update svn_repos_node_prop_create(). */
+} svn_repos_node_prop_t;
+
+/**
+ * Allocate a #svn_repos_node_prop_t structure in @a node_pool, initialize and
+ * return it.
+ *
+ * @since New in 1.7.
+ */
+svn_repos_node_prop_t *
+svn_repos_node_prop_create(char action,
+                           apr_pool_t *node_pool);
+
+/** A node in the repository.
+ *
+ * In order to avoid backwards compatibility problems clients should use
+ * svn_repos_node2_create() to allocate and initialize this structure instead
+ * of doing so themselves.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_repos_node2_t
+{
+  /** Node type (file, dir, etc.) */
+  svn_node_kind_t kind;
+
+  /** How this node entered the node tree: 'A'dd, 'D'elete, 'R'eplace */
+  char action;
+
+  /** Were there any textual mods? (files only) */
+  svn_boolean_t text_mod;
+
+  /** Were there any property mods? */
+  svn_boolean_t prop_mod;
+
+  /** The name of this node as it appears in its parent's entries list */
+  const char *name;
+
+  /** The filesystem revision where this was copied from (if any) */
+  svn_revnum_t copyfrom_rev;
+
+  /** The filesystem path where this was copied from (if any) */
+  const char *copyfrom_path;
+
+  /** Pointer to the next sibling of this node */
+  struct svn_repos_node2_t *sibling;
+
+  /** Pointer to the first child of this node */
+  struct svn_repos_node2_t *child;
+
+  /** Pointer to the parent of this node */
+  struct svn_repos_node2_t *parent;
+
+  /** Pointer to a hash (keys <tt>const char *name</tt> and values
+   * <tt>svn_repos_node_prop_t *</tt>) with all changed properties.  Only set
+   * if prop_mod is TRUE and the editor is driven with deltas. */
+  apr_hash_t *mod_props;
+
+  /* NOTE: Add new fields at the end to preserve binary compatibility.  Also,
+     if you add fields here, you have to update svn_repos_node2_create(). */
+} svn_repos_node2_t;
+
+/** A node in the repository.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
 typedef struct svn_repos_node_t
 {
   /** Node type (file, dir, etc.) */
@@ -2286,9 +2365,19 @@
 
 } svn_repos_node_t;
 
+/**
+ * Allocate a #svn_repos_node2_t structure in @a node_pool, initialize and
+ * return it.
+ *
+ * @since New in 1.7.
+ */
+svn_repos_node2_t *
+svn_repos_node2_create(char action,
+                       const char *name,
+                       apr_pool_t *node_pool);
 
 /** Set @a *editor and @a *edit_baton to an editor that, when driven by
- * a driver such as svn_repos_replay2(), builds an <tt>svn_repos_node_t *</tt>
+ * a driver such as svn_repos_replay2(), builds an <tt>svn_repos_node2_t *</tt>
  * tree representing the delta from @a base_root to @a root in @a
  * repos's filesystem.
  *
@@ -2296,7 +2385,7 @@
  * svn_repos_begin_report2(), but unless you have special needs,
  * svn_repos_replay2() is preferred.
  *
- * Invoke svn_repos_node_from_baton() on @a edit_baton to obtain the root
+ * Invoke svn_repos_node2_from_baton() on @a edit_baton to obtain the root
  * node afterwards.
  *
  * Note that the delta includes "bubbled-up" directories; that is,
@@ -2317,7 +2406,17 @@
 /** Return the root node of the linked-list tree generated by driving the
  * editor (associated with @a edit_baton) created by svn_repos_node_editor().
  * This is only really useful if used *after* the editor drive is completed.
+ *
+ * @since New in 1.7.
  */
+svn_repos_node2_t *
+svn_repos_node2_from_baton(void *edit_baton);
+
+/**
+ * Like svn_repos_node2_from_baton() but returns the old struct.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
 svn_repos_node_t *
 svn_repos_node_from_baton(void *edit_baton);
 
Index: subversion/libsvn_repos/node_tree.c
===================================================================
--- subversion/libsvn_repos/node_tree.c	(revision 1055565)
+++ subversion/libsvn_repos/node_tree.c	(working copy)
@@ -50,26 +50,24 @@
 
 
 /*** Node creation and assembly structures and routines. ***/
-static svn_repos_node_t *
+static svn_repos_node2_t *
 create_node(const char *name,
-            svn_repos_node_t *parent,
+            svn_repos_node2_t *parent,
             apr_pool_t *pool)
 {
-  svn_repos_node_t *node = apr_pcalloc(pool, sizeof(*node));
-  node->action = 'R';
-  node->kind = svn_node_unknown;
-  node->name = apr_pstrdup(pool, name);
+  svn_repos_node2_t *node;
+  node = svn_repos_node2_create('R', name, pool);
   node->parent = parent;
   return node;
 }
 
 
-static svn_repos_node_t *
-create_sibling_node(svn_repos_node_t *elder,
+static svn_repos_node2_t *
+create_sibling_node(svn_repos_node2_t *elder,
                     const char *name,
                     apr_pool_t *pool)
 {
-  svn_repos_node_t *tmp_node;
+  svn_repos_node2_t *tmp_node;
 
   /* No ELDER sibling?  That's just not gonna work out. */
   if (! elder)
@@ -85,8 +83,8 @@
 }
 
 
-static svn_repos_node_t *
-create_child_node(svn_repos_node_t *parent,
+static svn_repos_node2_t *
+create_child_node(svn_repos_node2_t *parent,
                   const char *name,
                   apr_pool_t *pool)
 {
@@ -104,11 +102,11 @@
 }
 
 
-static svn_repos_node_t *
-find_child_by_name(svn_repos_node_t *parent,
+static svn_repos_node2_t *
+find_child_by_name(svn_repos_node2_t *parent,
                    const char *name)
 {
-  svn_repos_node_t *tmp_node;
+  svn_repos_node2_t *tmp_node;
 
   /* No PARENT node, or a barren PARENT?  Nothing to find. */
   if ((! parent) || (! parent->child))
@@ -138,12 +136,12 @@
 static void
 find_real_base_location(const char **path_p,
                         svn_revnum_t *rev_p,
-                        svn_repos_node_t *node,
+                        svn_repos_node2_t *node,
                         apr_pool_t *pool)
 {
-  /* If NODE is an add-with-history, then its real base location is
+  /* If NODE is an add-or-replace-with-history, then its real base location is
      the copy source. */
-  if ((node->action == 'A')
+  if ((node->action == 'A' || node->action == 'R')
       && node->copyfrom_path
       && SVN_IS_VALID_REVNUM(node->copyfrom_rev))
     {
@@ -184,7 +182,7 @@
   svn_fs_root_t *root;
   svn_fs_root_t *base_root;
   apr_pool_t *node_pool;
-  svn_repos_node_t *node;
+  svn_repos_node2_t *node;
 };
 
 
@@ -192,7 +190,7 @@
 {
   struct edit_baton *edit_baton;
   struct node_baton *parent_baton;
-  svn_repos_node_t *node;
+  svn_repos_node2_t *node;
 };
 
 
@@ -202,9 +200,9 @@
              void *parent_baton,
              apr_pool_t *pool)
 {
-  struct node_baton *d = parent_baton;
-  struct edit_baton *eb = d->edit_baton;
-  svn_repos_node_t *node;
+  struct node_baton *pb = parent_baton;
+  struct edit_baton *eb = pb->edit_baton;
+  svn_repos_node2_t *node;
   const char *name;
   const char *base_path;
   svn_revnum_t base_rev;
@@ -213,9 +211,9 @@
 
   /* Get (or create) the change node and update it. */
   name = svn_relpath_basename(path, pool);
-  node = find_child_by_name(d->node, name);
+  node = find_child_by_name(pb->node, name);
   if (! node)
-    node = create_child_node(d->node, name, eb->node_pool);
+    node = create_child_node(pb->node, name, eb->node_pool);
   node->action = 'D';
 
   /* We need to look up this node's parents to see what its original
@@ -261,21 +259,32 @@
   struct node_baton *pb = parent_baton;
   struct edit_baton *eb = pb->edit_baton;
   struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+  svn_repos_node2_t *node;
+  const char *name;
 
   SVN_ERR_ASSERT(parent_baton && path);
 
   nb->edit_baton = eb;
   nb->parent_baton = pb;
 
-  /* Create and populate the node. */
-  nb->node = create_child_node(pb->node, svn_relpath_basename(path, NULL),
-                               eb->node_pool);
-  nb->node->kind = kind;
-  nb->node->action = action;
-  nb->node->copyfrom_rev = copyfrom_rev;
-  nb->node->copyfrom_path =
+  /* Get (or create) the change node. */
+  name = svn_relpath_basename(path, pool);
+  node = find_child_by_name(pb->node, name);
+  if (! node)
+    node = create_child_node(pb->node, name, eb->node_pool);
+
+  /* Check if the node is being 'R'eplaced. */
+  if (node->action == 'D' && action == 'A')
+    action = 'R';
+
+  /* Populate the node. */
+  node->kind = kind;
+  node->action = action;
+  node->copyfrom_rev = copyfrom_rev;
+  node->copyfrom_path =
     copyfrom_path ? apr_pstrdup(eb->node_pool, copyfrom_path) : NULL;
 
+  nb->node = node;
   *child_baton = nb;
   return SVN_NO_ERROR;
 }
@@ -288,14 +297,14 @@
           void **root_baton)
 {
   struct edit_baton *eb = edit_baton;
-  struct node_baton *d = apr_pcalloc(pool, sizeof(*d));
+  struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
 
-  d->edit_baton = eb;
-  d->parent_baton = NULL;
-  d->node = (eb->node = create_node("", NULL, eb->node_pool));
-  d->node->kind = svn_node_dir;
-  d->node->action = 'R';
-  *root_baton = d;
+  nb->edit_baton = eb;
+  nb->parent_baton = NULL;
+  nb->node = (eb->node = create_node("", NULL, eb->node_pool));
+  nb->node->kind = svn_node_dir;
+  nb->node->action = 'R';
+  *root_baton = nb;
 
   return SVN_NO_ERROR;
 }
@@ -379,10 +388,83 @@
 {
   struct node_baton *nb = node_baton;
   nb->node->prop_mod = TRUE;
+
+  /* Ignore dummy prop change callbacks (empty name) that happen if the replay
+     is done without deltas. */
+  if (name[0] != '\0')
+    {
+      struct edit_baton *eb = nb->edit_baton;
+      const char *base_path;
+      svn_revnum_t base_rev;
+      svn_fs_root_t *base_root;
+      svn_string_t *old_value;
+      svn_node_kind_t node_kind;
+      svn_repos_node_prop_t *prop;
+
+      if (! nb->node->mod_props)
+        nb->node->mod_props = apr_hash_make(eb->node_pool);
+
+      prop = svn_repos_node_prop_create('A', eb->node_pool);
+      apr_hash_set(nb->node->mod_props,
+                   apr_pstrdup(eb->node_pool, name),
+                   APR_HASH_KEY_STRING,
+                   prop);
+
+      if (! value)
+        prop->action = 'D';
+      else
+        {
+          /* Get the original filesystem path and base root for the current
+             node so that we can fetch the old property value. */
+          find_real_base_location(&base_path, &base_rev, nb->node, pool);
+
+          if (! SVN_IS_VALID_REVNUM(base_rev))
+            base_root = eb->base_root;
+          else
+            SVN_ERR(svn_fs_revision_root(&base_root, eb->fs, base_rev, pool));
+
+          /* If the node is new there can be no old value... */
+          SVN_ERR(svn_fs_check_path(&node_kind, base_root, base_path, pool));
+
+          if (node_kind != svn_node_none)
+            {
+              /* Fetch old property value */
+              SVN_ERR(svn_fs_node_prop(&old_value, base_root, base_path,
+                                       name, pool));
+              if (old_value)
+                prop->action = 'M';
+            }
+        }
+    }
+
   return SVN_NO_ERROR;
 }
 
 
+svn_repos_node_prop_t *
+svn_repos_node_prop_create(char action,
+                           apr_pool_t *node_pool)
+{
+  svn_repos_node_prop_t *prop = apr_pcalloc(node_pool, sizeof(*prop));
+  prop->action = action;
+  return prop;
+}
+
+
+svn_repos_node2_t *
+svn_repos_node2_create(char action,
+                       const char *name,
+                       apr_pool_t *node_pool)
+{
+  svn_repos_node2_t *node = apr_pcalloc(node_pool, sizeof(*node));
+  node->kind = svn_node_unknown;
+  node->action = action;
+  node->name = apr_pstrdup(node_pool, name);
+  node->copyfrom_rev = SVN_INVALID_REVNUM;
+  return node;
+}
+
+
 svn_error_t *
 svn_repos_node_editor(const svn_delta_editor_t **editor,
                       void **edit_baton,
@@ -421,10 +503,18 @@
 }
 
 
+svn_repos_node2_t *
+svn_repos_node2_from_baton(void *edit_baton)
+{
+  struct edit_baton *eb = edit_baton;
+  return eb->node;
+}
 
+
 svn_repos_node_t *
 svn_repos_node_from_baton(void *edit_baton)
 {
-  struct edit_baton *eb = edit_baton;
-  return eb->node;
+  /* This works because the first part of svn_repos_node2_t looks exactly like
+     svn_repos_node_t. */
+  return (svn_repos_node_t *)svn_repos_node2_from_baton(edit_baton);
 }

Reply via email to