Make "svn info" report a tree conflict on the victim, rather than on the
parent directory.

### "svn info" won't work on an unversioned item. A tree conflict can
### presently exist on an unversioned item. For "update", this should not
### happen: a WC item must be schedule-del/mod/add for a tree conflict to
### be raised, and the update should not proceed and make the item
### unversioned. For "merge", it is legitimate to have a conflict on an
### item that is not in the WC. For that, we need either to allow "svn info"
### on an unversioned item, or ensure "merge" always makes a tree conflict
### victim into some kind of versioned item (with schedule=missing or
### something like that) if that's feasible.

### Steve Butler:  I extended the diff so that "svn info" now works on 
### unversioned items.  There's a new error-callback for gathering info in
### the working copy.  If the info-target does not exist, and is a tree
### conflict victim, a minimal set of info is printed.  I finished
### adjusting the tests to act on victims directly.

* subversion/libsvn_wc/tree_conflicts.c
  (svn_wc_get_tree_conflict): Support getting the conflict info from the 
   parent dir of the victim.

* subversion/include/svn_wc.h
  (svn_wc_get_tree_conflict): Add a boolean arg for new parent-dir option.

* subversion/libsvn_wc/status.c
  (assemble_status): track changed svn_wc_get_tree_conflict declaration.

* subversion/include/svn_client.h
  (svn_info_t): Replace the 'tree_conflicts' array with a single
    'tree_conflict' pointer.

* subversion/libsvn_client/info.c
  (build_info_from_dirent): Initialize 'tree_conflict' instead of
    'tree_conflicts'.
  (found_entry_baton): Add an 'adm_access' field.
  (build_info_from_entry): Don't fill in the tree conflict info here, because
    we don't have the required adm_access baton...
  (info_found_entry_callback): ... but fill it in here instead,
  (crawl_entries): Store the adm_access of the root directory of the crawl.
  (info_error_handler): New function.
  (entry_walk_callbacks): Use the new handler function. 
  (build_info_for_unversioned): New helper function.

* subversion/svn/info-cmd.c
  (print_info_xml, print_info): Print just the one tree conflict that is now
    in the info structure, rather than an array of conflicts.  In
    print_info_xml, remove restriction that revnum must be valid.

* subversion/svn/schema/info.rnc
  Remove the 'tree-conflicts' wrapper element, as there can now be only one
  tree conflict reported per node.

* subversion/tests/cmdline/info_tests.py
  (info_with_tree_conflicts): Adjust accordingly.

* subversion/tests/cmdline/merge_tests.py
  (tree_conflicts_and_obstructions): Adjust accordingly.
Index: subversion/include/svn_wc.h
===================================================================
--- subversion/include/svn_wc.h	(revision 33607)
+++ subversion/include/svn_wc.h	(working copy)
@@ -5229,10 +5229,14 @@ svn_wc_set_changelist(const char *path,
 
 /** @} */
 
-/** Set @a *tree_conflict to a newly allocated @c svn_wc_conflict_description_t
- * structure describing the tree conflict state of @a victim_path, or to null
- * if @a victim_path is not in a state of tree conflict. @a adm_access is the
- * admin access baton for @a victim_path. Use @a pool for all allocations.
+/** Set @a *tree_conflict to a newly allocated @c
+ * svn_wc_conflict_description_t structure describing the tree
+ * conflict state of @a victim_path, or to @c NULL if @a victim_path
+ * is not in a state of tree conflict. @a adm_access is the admin
+ * access baton for @a victim_path, unless @a adm_access_is_parent is
+ * @c TRUE, in which case @a adm_access is the admin access baton of
+ * the parent dir (which is handy if the victim does not exist in the
+ * working copy). Use @a pool for all allocations.
  *
  * @since New in 1.6.
  */
@@ -5240,6 +5244,7 @@ svn_error_t *
 svn_wc_get_tree_conflict(svn_wc_conflict_description_t **tree_conflict,
                          const char *victim_path,
                          svn_wc_adm_access_t *adm_access,
+                         svn_boolean_t adm_access_is_parent,
                          apr_pool_t *pool);
 
 /**
Index: subversion/libsvn_wc/tree_conflicts.c
===================================================================
--- subversion/libsvn_wc/tree_conflicts.c	(revision 33607)
+++ subversion/libsvn_wc/tree_conflicts.c	(working copy)
@@ -541,6 +541,7 @@ svn_error_t *
 svn_wc_get_tree_conflict(svn_wc_conflict_description_t **tree_conflict,
                          const char *victim_path,
                          svn_wc_adm_access_t *adm_access,
+                         svn_boolean_t adm_access_is_parent,
                          apr_pool_t *pool)
 {
   const char *parent_path = svn_path_dirname(victim_path, pool);
@@ -551,30 +552,39 @@ svn_wc_get_tree_conflict(svn_wc_conflict_descripti
   const svn_wc_entry_t *entry;
   int i;
 
-  /* Try to get the parent's admin access baton from the baton set. */
-  err = svn_wc_adm_retrieve(&parent_adm_access, adm_access, parent_path, pool);
-  if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
+  if (adm_access_is_parent)
     {
-      svn_error_clear(err);
+      parent_adm_access = adm_access;
+    }
+  else
+    {
+      /* Try to get the parent's admin access baton from the baton set. */
+      err = svn_wc_adm_retrieve(&parent_adm_access, adm_access, parent_path,
+                                pool);
+      if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
+        {
+          svn_error_clear(err);
 
-      /* Try to access the parent dir independently. We can't add a parent's
-         access baton to the existing access baton set of its child, because
-         the lifetimes would be wrong according to doc string of
-         svn_wc_adm_open3(), so we get open it temporarily and close it after
-         use. */
-      err = svn_wc_adm_open3(&parent_adm_access, NULL, parent_path,
-                             FALSE, 0, NULL, NULL, pool);
-      parent_adm_access_is_temporary = TRUE;
+          /* Try to access the parent dir independently. We can't add
+             a parent's access baton to the existing access baton set
+             of its child, because the lifetimes would be wrong
+             according to doc string of svn_wc_adm_open3(), so we get
+             open it temporarily and close it after use. */
+          err = svn_wc_adm_open3(&parent_adm_access, NULL, parent_path,
+                                 FALSE, 0, NULL, NULL, pool);
+          parent_adm_access_is_temporary = TRUE;
 
-      /* If the parent isn't a WC dir, the child can't be tree-conflicted. */
-      if (err && (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY))
-        {
-          svn_error_clear(err);
-          *tree_conflict = NULL;
-          return SVN_NO_ERROR;
+          /* If the parent isn't a WC dir, the child can't be
+             tree-conflicted. */
+          if (err && (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY))
+            {
+              svn_error_clear(err);
+              *tree_conflict = NULL;
+              return SVN_NO_ERROR;
+            }
         }
+      SVN_ERR(err);
     }
-  SVN_ERR(err);
 
   conflicts = apr_array_make(pool, 0,
                              sizeof(svn_wc_conflict_description_t *));
Index: subversion/libsvn_wc/status.c
===================================================================
--- subversion/libsvn_wc/status.c	(revision 33607)
+++ subversion/libsvn_wc/status.c	(working copy)
@@ -301,7 +301,7 @@ assemble_status(svn_wc_status2_t **status,
   {
     svn_wc_conflict_description_t *conflict;
 
-    SVN_ERR(svn_wc_get_tree_conflict(&conflict, path, adm_access, pool));
+    SVN_ERR(svn_wc_get_tree_conflict(&conflict, path, adm_access, FALSE, pool));
     tree_conflicted_p = (conflict != NULL);
   }
 
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h	(revision 33607)
+++ subversion/include/svn_client.h	(working copy)
@@ -4361,13 +4361,12 @@ typedef struct svn_info_t
   apr_size_t size;
 
   /**
-   * For a directory only, all tree-conflicted children, stored
-   * in an array of @c svn_wc_conflict_description_t, 
+   * Info on any tree conflict of which this node is a victim. Otherwise NULL.
    * @since New in 1.6.
    * @name Working-copy path fields
    * @{
    */
-  apr_array_header_t *tree_conflicts;
+  svn_wc_conflict_description_t *tree_conflict;
 
   /** @} */
 
Index: subversion/libsvn_client/info.c
===================================================================
--- subversion/libsvn_client/info.c	(revision 33607)
+++ subversion/libsvn_client/info.c	(working copy)
@@ -58,7 +58,7 @@ build_info_from_dirent(svn_info_t **info,
   tmpinfo->depth                = svn_depth_unknown;
   tmpinfo->working_size         = SVN_INFO_SIZE_UNKNOWN;
   tmpinfo->size                 = dirent->size;
-  tmpinfo->tree_conflicts       = NULL;
+  tmpinfo->tree_conflict        = NULL;
 
   *info = tmpinfo;
   return SVN_NO_ERROR;
@@ -102,16 +102,6 @@ build_info_from_entry(svn_info_t **info,
   tmpinfo->working_size         = entry->working_size;
   tmpinfo->size                 = SVN_INFO_SIZE_UNKNOWN;
 
-  /* Check for tree conflicts (only "this-dir" entries have tree conflicts). */
-  if ((strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
-      && entry->tree_conflict_data)
-    {
-      tmpinfo->tree_conflicts = apr_array_make(pool, 1,
-                                     sizeof(svn_wc_conflict_description_t *));
-      SVN_ERR(svn_wc_read_tree_conflicts_from_entry(tmpinfo->tree_conflicts,
-                                                    entry, path, pool));
-    }
-
   /* lock stuff */
   if (entry->lock_token)  /* the token is the critical bit. */
     {
@@ -128,6 +118,34 @@ build_info_from_entry(svn_info_t **info,
 }
 
 
+/* Helper: build an svn_info_t *INFO struct with minimal content, to be
+   used in reporting info for unversioned tree conflict victims. */
+/* ### Some fields we could fill out based on the parent dir's entry
+       or by looking at an obstructing item. */
+static svn_error_t *
+build_info_for_unversioned(svn_info_t **info,
+                           apr_pool_t *pool)
+{
+  svn_info_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
+
+  tmpinfo->URL                  = NULL;
+  tmpinfo->rev                  = SVN_INVALID_REVNUM;
+  tmpinfo->kind                 = svn_node_none;
+  tmpinfo->repos_UUID           = NULL;
+  tmpinfo->repos_root_URL       = NULL;
+  tmpinfo->last_changed_rev     = SVN_INVALID_REVNUM;
+  tmpinfo->last_changed_date    = apr_time_now();
+  tmpinfo->last_changed_author  = NULL;
+  tmpinfo->lock                 = NULL;
+  tmpinfo->working_size         = SVN_INFO_SIZE_UNKNOWN;
+  tmpinfo->size                 = 0;
+  tmpinfo->tree_conflict        = NULL;
+
+  *info = tmpinfo;
+  return SVN_NO_ERROR;
+}
+
+
 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
 #define DIRENT_FIELDS (SVN_DIRENT_KIND        | \
                        SVN_DIRENT_CREATED_REV | \
@@ -222,6 +240,7 @@ struct found_entry_baton
   apr_hash_t *changelist_hash;
   svn_info_receiver_t receiver;
   void *receiver_baton;
+  svn_wc_adm_access_t *adm_access;  /* adm access baton for root of walk */
 };
 
 static svn_error_t *
@@ -242,19 +261,65 @@ info_found_entry_callback(const char *path,
   if (SVN_WC__CL_MATCH(fe_baton->changelist_hash, entry))
     {
       svn_info_t *info;
+      svn_wc_adm_access_t *adm_access;
+
       SVN_ERR(build_info_from_entry(&info, entry, path, pool));
+      SVN_ERR(svn_wc_adm_probe_try3(&adm_access, fe_baton->adm_access, path,
+                               FALSE /* read-only */, 0 /* levels */,
+                               NULL, NULL, pool));
+      SVN_ERR(svn_wc_get_tree_conflict(&info->tree_conflict, path, adm_access, 
+                                       FALSE, pool));
       SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, path, info, pool));
     }
   return SVN_NO_ERROR;
 }
 
+/* If the error is "unversioned resource" and, upon checking the
+   parent dir's tree conflict data, we find that PATH is a tree
+   conflict victim, cancel the error and send a minimal info struct.
+   Otherwise re-raise the error.
+*/
+static svn_error_t *
+info_error_handler(const char *path,
+                   svn_error_t *err,
+                   void *walk_baton,
+                   apr_pool_t *pool)
+{
+  if (err && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE))
+    {
+      struct found_entry_baton *fe_baton = walk_baton;
+      svn_wc_adm_access_t *adm_access;
+      svn_wc_conflict_description_t *tree_conflict;
 
+      SVN_ERR(svn_wc_adm_probe_try3(&adm_access, fe_baton->adm_access, 
+                                    svn_path_dirname(path, pool),
+                                    FALSE, 0, NULL, NULL, pool));
+      SVN_ERR(svn_wc_get_tree_conflict(&tree_conflict, path, adm_access, 
+                                       TRUE, pool));
 
+      if (tree_conflict)
+        {
+          svn_info_t *info;
+
+          svn_error_clear(err);
+         
+          SVN_ERR(build_info_for_unversioned(&info, pool));
+          info->tree_conflict = tree_conflict;
+
+          SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, path, info,
+                                     pool));
+          return SVN_NO_ERROR;
+        }
+    }
+
+  return err;
+}
+
 static const svn_wc_entry_callbacks2_t
 entry_walk_callbacks =
   {
     info_found_entry_callback,
-    svn_client__default_walker_error_handler
+    info_error_handler
   };
 
 
@@ -280,6 +345,7 @@ crawl_entries(const char *wcpath,
   fe_baton.changelist_hash = changelist_hash;
   fe_baton.receiver = receiver;
   fe_baton.receiver_baton = receiver_baton;
+  fe_baton.adm_access = adm_access;
   return svn_wc_walk_entries3(wcpath, adm_access,
                               &entry_walk_callbacks, &fe_baton,
                               depth, FALSE, ctx->cancel_func,
Index: subversion/svn/info-cmd.c
===================================================================
--- subversion/svn/info-cmd.c	(revision 33607)
+++ subversion/svn/info-cmd.c	(working copy)
@@ -86,9 +86,7 @@ print_info_xml(void *baton,
   if (SVN_IS_VALID_REVNUM(info->rev))
     rev_str = apr_psprintf(pool, "%ld", info->rev);
   else
-    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
-                             _("'%s' has invalid revision"),
-                             svn_path_local_style(target, pool));
+    rev_str = apr_pstrdup(pool, "Resource is not under version control.");
 
   /* "<entry ...>" */
   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
@@ -222,25 +222,10 @@ print_info_xml(void *baton,
       svn_xml_make_close_tag(&sb, pool, "lock");
     }
 
-  if (info->tree_conflicts)
-    {
-      svn_wc_conflict_description_t *conflict;
-      int i;
+  if (info->tree_conflict)
+    SVN_ERR(svn_cl__append_tree_conflict_info_xml(sb, info->tree_conflict,
+                                                  pool));
 
-      /* "<tree-conflicts>" */
-      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "tree-conflicts", NULL);
-
-      for (i = 0; i < info->tree_conflicts->nelts; i++)
-        {
-          conflict = APR_ARRAY_IDX(info->tree_conflicts, i, 
-                                   svn_wc_conflict_description_t *);
-          SVN_ERR(svn_cl__append_tree_conflict_info_xml(sb, conflict, pool));
-        }
-
-      /* "</tree-conflicts>" */
-      svn_xml_make_close_tag(&sb, pool, "tree-conflicts");
-    }
-
   /* "</entry>" */
   svn_xml_make_close_tag(&sb, pool, "entry");
 
@@ -447,28 +432,14 @@ print_info(void *baton,
     SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
                                info->changelist));
 
-  if (info->tree_conflicts)
+  if (info->tree_conflict)
     {
-      svn_wc_conflict_description_t *tree_conflict;
-      svn_stringbuf_t *tree_conflict_descs = svn_stringbuf_create("", pool);
-      int i;
+      svn_stringbuf_t *desc = svn_stringbuf_create("", pool);
 
-      for (i = 0; i < info->tree_conflicts->nelts; i++)
-        {
-          svn_stringbuf_appendcstr(tree_conflict_descs, "\n");
-          tree_conflict = APR_ARRAY_IDX(info->tree_conflicts, i,
-                                        svn_wc_conflict_description_t *);
-          SVN_ERR(svn_cl__append_human_readable_tree_conflict_description(
-                                                           tree_conflict_descs,
-                                                           tree_conflict,
-                                                           pool));
-        }
+      SVN_ERR(svn_cl__append_human_readable_tree_conflict_description(
+                desc, info->tree_conflict, pool));
 
-      if (tree_conflict_descs->len > 0)
-        {
-          svn_cmdline_printf(pool, "Tree conflicts:%s",
-                             tree_conflict_descs->data);
-        }
+      svn_cmdline_printf(pool, "Tree conflicts:\n%s", desc->data);
     }
 
   /* Print extra newline separator. */
Index: subversion/svn/schema/info.rnc
===================================================================
--- subversion/svn/schema/info.rnc	(revision 33607)
+++ subversion/svn/schema/info.rnc	(working copy)
@@ -9,8 +9,8 @@ info = element info { entry* }
 
 entry =
   element entry {
-    attlist.entry, url?, repository?, wc-info?, commit?, conflict?, lock?, 
-    tree-conflicts?
+    attlist.entry, url?, repository?, wc-info?, commit?, conflict?, lock?,
+    tree-conflict?
   }
 attlist.entry &=
   ## Local path.
@@ -86,9 +86,6 @@ prop-file = element prop-file { string }
 ## Depth of this directory, always "infinity" for non-directories
 depth = element depth { "infinity" | "immediates" | "files" | "empty" }
 
-tree-conflicts =
-  element tree-conflicts { tree-conflict+ }
-
 tree-conflict =
   element tree-conflict { attlist.tree-conflict }
 
Index: subversion/tests/cmdline/info_tests.py
===================================================================
--- subversion/tests/cmdline/info_tests.py	(revision 33607)
+++ subversion/tests/cmdline/info_tests.py	(working copy)
@@ -180,45 +183,41 @@ def info_with_tree_conflicts(sbox):
   wc_dir = sbox.wc_dir
   G = os.path.join(wc_dir, 'A', 'D', 'G')
 
-  # check info of G
-  exit_code, output, error = svntest.actions.run_and_verify_svn(None, None,
-                                                                [], 'info', G)
+  scenarios = [
+    # (filename, action_verb, action, reason)
+    ('pi',  'edit',   'edited',  'deleted'),
+    ('rho', 'delete', 'deleted', 'edited'),
+    ('tau', 'delete', 'deleted', 'deleted'),
+    ]
 
-  verify_lines(output,
-               ["Tree conflicts:",
-                "The update attempted to edit 'pi'.",
-                "The update attempted to delete 'rho'",
-                "The update attempted to delete 'tau'",
-                ])
+  for fname, action_verb, action, reason in scenarios:
+    path = os.path.join(G, fname)
 
-  # check XML info of G
-  exit_code, output, error = svntest.actions.run_and_verify_svn(None, None,
-                                                                [], 'info', G,
-                                                                '--xml')
+    # check plain info
+    exit_code, output, error = svntest.actions.run_and_verify_svn(None, None,
+                                                                  [], 'info',
+                                                                  path)
+    expected_str1 = "The update attempted to " + action_verb + " '" + fname
+    verify_lines(output, [expected_str1])
 
-  verify_xml_elements(output,
-                      [('tree-conflict', {'victim'   : 'pi',
-                                          'kind'     : 'file',
-                                          'operation': 'update',
-                                          'action'   : 'edited',
-                                          'reason'   : 'deleted'}),
-                       ('tree-conflict', {'victim'   : 'rho',
-                                          'kind'     : 'file',
-                                          'operation': 'update',
-                                          'action'   : 'deleted',
-                                          'reason'   : 'edited'}),
-                       ('tree-conflict', {'victim'   : 'tau',
-                                          'kind'     : 'file',
-                                          'operation': 'update',
-                                          'action'   : 'deleted',
-                                          'reason'   : 'deleted'}),
-                       ])
+    # check XML info
+    exit_code, output, error = svntest.actions.run_and_verify_svn(None, None,
+                                                                  [], 'info',
+                                                                  path,
+                                                                  '--xml')
 
-
+    # In the XML, action and reason are past tense: 'edited' not 'edit'.
+    verify_xml_elements(output,
+                        [('tree-conflict', {'victim'   : fname,
+                                            'kind'     : 'file',
+                                            'operation': 'update',
+                                            'action'   : action,
+                                            'reason'   : reason,
+                                            },
+                          )])
 ########################################################################
 # Run the tests
 
-
 # list all tests here, starting with None:
 test_list = [ None,
               info_with_tree_conflicts,
Index: subversion/tests/cmdline/merge_tests.py
===================================================================
--- subversion/tests/cmdline/merge_tests.py	(revision 33607)
+++ subversion/tests/cmdline/merge_tests.py	(working copy)
@@ -13280,7 +13280,7 @@ def tree_conflicts_and_obstructions(sbox):
                                        expected_skip)
 
   # Make sure all victims have been found.
-  verify_tree_conflict_info(branch_path,
+  verify_tree_conflict_info(br_alpha_moved,
                             [('add', 'alpha-moved')])
 
 


