Author: brane
Date: Fri Feb 27 02:19:41 2015
New Revision: 1662620

URL: http://svn.apache.org/r1662620
Log:
Merged the svn-info-detail branch to trunk.

This implements the --show-detail option for the 'svn info' command.

* subversion/svn/cl.h
  (svn_cl__opt_state_t): New option show_item.
  (svn_cl__simcheck_context_t): New forward-declared struct.
  (svn_cl__simcheck_t): New structure.
  (svn_cl__similarity_check): New client-local function.

* subversion/svn/info-cmd.c
  (relative_url): New; constructs a relative URL.
  (info_item_t): New enumeration.
  (info_item_map_t): New struct.
  (info_item_map): Static array of known keywords for --show-item.
  (info_item_map_len): New.
  (print_info_baton_t): New; baton for the various print_info functions.
   Replaces the former use of just the path_prefix string as the baton.
  (find_print_what): New; converts the --show-item argument to its
   equivalent info_item_t enumeration value.
  (print_info_xml, print_info): Call relative_url instead of
   maintaining the same logic in two places.
   Adjust to match the new baton type.
  (print_info_item): New handler for 'svn info --show-item'.
  (svn_cl__info): Initialize the receiver baton and handle the
   --show-item and --no-newline options, including checking valid
   option combinatinos. Change the texts of the parsing-error messages
   to show the complete offending option names.

* subversion/include/private/svn_string_private.h
  (SVN_STRING__SIM_RANGE_MAX): New; parametrized the similarity range.
  (svn_cstring__similarity): Update docstring and adjust return type.
  (svn_string__similarity): Adjust return type.
* subversion/libsvn_subr/string.c
  (svn_cstring__similarity): Adjust return type.
  (svn_string__similarity): Adjust return type. Change the result range.

* subversion/svn/props.c
  (simprop_context_t, simprop_t): Removed in favour of the new
   svn_cl__simcheck_context_t and svn_cl__simcheck_t.
  (simprop_key_diff, simprop_compare): Moved to similarity.c.
  (svn_cl__check_svn_prop_name): Use the new similarity check API.

* subversion/svn/similarity.c: New file.
  (svn_cl__simcheck_context_t): Defined here.
  (simcheck_key_diff): Replaces simprop_key_diff from props.c.
  (simcheck_compare): Replaces simprop_compare from props.c.
  (svn_cl__similarity_check): Implement.

* subversion/tests/libsvn_subr/string-test.c
  (test_string_similarity): Adjust test case to account for the
   changed range of the result of svn_string__similarity.

* subversion/tests/cmdline/info_tests.py
  (info_item_simple,
   info_item_simple_multiple,
   info_item_url,
   info_item_uncommmitted,
   info_item_failures): New test cases.
  (test_list): Updated.

Added:
    subversion/trunk/subversion/svn/similarity.c
      - copied unchanged from r1662618, 
subversion/branches/svn-info-detail/subversion/svn/similarity.c
Modified:
    subversion/trunk/   (props changed)
    subversion/trunk/subversion/include/private/svn_string_private.h
    subversion/trunk/subversion/libsvn_subr/string.c
    subversion/trunk/subversion/svn/cl.h
    subversion/trunk/subversion/svn/info-cmd.c
    subversion/trunk/subversion/svn/props.c
    subversion/trunk/subversion/svn/svn.c
    subversion/trunk/subversion/tests/cmdline/info_tests.py
    subversion/trunk/subversion/tests/libsvn_subr/string-test.c

Propchange: subversion/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Feb 27 02:19:41 2015
@@ -70,7 +70,7 @@
 
/subversion/branches/revprop-packing:1143907,1143971,1143997,1144017,1144499,1144568,1146145
 /subversion/branches/subtree-mergeinfo:876734-878766
 /subversion/branches/svn-auth-x509:1603509-1655900
-/subversion/branches/svn-info-detail:1660035-1660413
+/subversion/branches/svn-info-detail:1660035-1662618
 /subversion/branches/svn-mergeinfo-enhancements:870119-870195,870197-870288
 /subversion/branches/svn-patch-improvements:918519-934609
 /subversion/branches/svn_mutex:1141683-1182099

Modified: subversion/trunk/subversion/include/private/svn_string_private.h
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_string_private.h?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_string_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_string_private.h Fri Feb 27 
02:19:41 2015
@@ -197,10 +197,17 @@ apr_uint64_t
 svn__base36toui64(const char **next, const char *source);
 
 /**
+ * The upper limit of the similarity range returned by
+ * svn_cstring__similarity() and svn_string__similarity().
+ */
+#define SVN_STRING__SIM_RANGE_MAX 1000000
+
+/**
  * Computes the similarity score of STRA and STRB. Returns the ratio
  * of the length of their longest common subsequence and the average
- * length of the strings, normalized to the range [0..1000].
- * The result is equivalent to Python's
+ * length of the strings, normalized to the range
+ * [0..SVN_STRING__SIM_RANGE_MAX]. The result is equivalent to
+ * Python's
  *
  *   difflib.SequenceMatcher.ratio
  *
@@ -225,7 +232,7 @@ svn__base36toui64(const char **next, con
  *    has O(strlen(STRA) * strlen(STRB)) worst-case performance,
  *    so do keep a rein on your enthusiasm.
  */
-unsigned int
+apr_size_t
 svn_cstring__similarity(const char *stra, const char *strb,
                         svn_membuf_t *buffer, apr_size_t *rlcs);
 
@@ -233,7 +240,7 @@ svn_cstring__similarity(const char *stra
  * Like svn_cstring__similarity, but accepts svn_string_t's instead
  * of NUL-terminated character strings.
  */
-unsigned int
+apr_size_t
 svn_string__similarity(const svn_string_t *stringa,
                        const svn_string_t *stringb,
                        svn_membuf_t *buffer, apr_size_t *rlcs);

Modified: subversion/trunk/subversion/libsvn_subr/string.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/string.c?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/string.c (original)
+++ subversion/trunk/subversion/libsvn_subr/string.c Fri Feb 27 02:19:41 2015
@@ -1300,7 +1300,7 @@ svn__base36toui64(const char **next, con
 }
 
 
-unsigned int
+apr_size_t
 svn_cstring__similarity(const char *stra, const char *strb,
                         svn_membuf_t *buffer, apr_size_t *rlcs)
 {
@@ -1312,7 +1312,7 @@ svn_cstring__similarity(const char *stra
   return svn_string__similarity(&stringa, &stringb, buffer, rlcs);
 }
 
-unsigned int
+apr_size_t
 svn_string__similarity(const svn_string_t *stringa,
                        const svn_string_t *stringb,
                        svn_membuf_t *buffer, apr_size_t *rlcs)
@@ -1401,9 +1401,9 @@ svn_string__similarity(const svn_string_
 
   /* Return similarity ratio rounded to 4 significant digits */
   if (total)
-    return(unsigned int)((2000 * lcs + total/2) / total);
+    return ((2 * SVN_STRING__SIM_RANGE_MAX * lcs + total/2) / total);
   else
-    return 1000;
+    return SVN_STRING__SIM_RANGE_MAX;
 }
 
 apr_size_t

Modified: subversion/trunk/subversion/svn/cl.h
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/cl.h?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/cl.h (original)
+++ subversion/trunk/subversion/svn/cl.h Fri Feb 27 02:19:41 2015
@@ -248,6 +248,7 @@ typedef struct svn_cl__opt_state_t
   svn_boolean_t no_newline;        /* do not output the trailing newline */
   svn_boolean_t show_passwords;    /* show cached passwords */
   svn_boolean_t pin_externals;     /* pin externals to last-changed revisions 
*/
+  const char *show_item;           /* print only the given item */
 } svn_cl__opt_state_t;
 
 
@@ -854,6 +855,48 @@ svn_cl__deprecated_merge_reintegrate(con
                                      svn_client_ctx_t *ctx,
                                      apr_pool_t *pool);
 
+
+/* Forward declaration of the similarity check context. */
+typedef struct svn_cl__simcheck_context_t svn_cl__simcheck_context_t;
+
+/* Token definition for the similarity check. */
+typedef struct svn_cl__simcheck_t
+{
+  /* The token we're checking for similarity. */
+  svn_string_t token;
+
+  /* User data associated with this token. */
+  const void *data;
+
+  /*
+   * The following fields are populated by svn_cl__similarity_check.
+   */
+
+  /* Similarity score [0..SVN_STRING__SIM_RANGE_MAX] */
+  apr_size_t score;
+
+  /* Number of characters of difference from the key. */
+  apr_size_t diff;
+
+  /* Similarity check context (private) */
+  svn_cl__simcheck_context_t *context;
+} svn_cl__simcheck_t;
+
+/* Find the entries in TOKENS that are most similar to KEY.
+ * TOKEN_COUNT is the number of entries in the (mutable) TOKENS array.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * On return, the TOKENS array will be sorted according to similarity
+ * to KEY, in descending order. The return value will be zero if the
+ * first token is an exact match; otherwise, it will be one more than
+ * the number of tokens that are at least two-thirds similar to KEY.
+ */
+apr_size_t
+svn_cl__similarity_check(const char *key,
+                         svn_cl__simcheck_t **tokens,
+                         apr_size_t token_count,
+                         apr_pool_t *scratch_pool);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

Modified: subversion/trunk/subversion/svn/info-cmd.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/info-cmd.c?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/info-cmd.c (original)
+++ subversion/trunk/subversion/svn/info-cmd.c Fri Feb 27 02:19:41 2015
@@ -76,6 +76,167 @@ schedule_str(svn_wc_schedule_t schedule)
     }
 }
 
+/* Return a relative URL from information in INFO using POOL for
+   temporary allocation. */
+static const char*
+relative_url(const svn_client_info2_t *info, apr_pool_t *pool)
+{
+  return apr_pstrcat(pool, "^/",
+                     svn_path_uri_encode(
+                         svn_uri_skip_ancestor(info->repos_root_URL,
+                                               info->URL, pool),
+                         pool), SVN_VA_NULL);
+}
+
+
+/* The kinds of items for print_info_item(). */
+typedef enum
+{
+  /* Entry kind */
+  info_item_kind,
+
+  /* Repository location. */
+  info_item_url,
+  info_item_relative_url,
+  info_item_repos_root_url,
+  info_item_repos_uuid,
+
+  /* Working copy revision or repository HEAD revision */
+  info_item_revision,
+
+  /* Commit details. */
+  info_item_last_changed_rev,
+  info_item_last_changed_date,
+  info_item_last_changed_author,
+
+  /* Working copy information */
+  info_item_wc_root
+} info_item_t;
+
+/* Mapping between option keywords and info_item_t. */
+typedef struct info_item_map_t
+{
+  const svn_string_t keyword;
+  const info_item_t print_what;
+} info_item_map_t;
+
+#define MAKE_STRING(x) { x, sizeof(x) - 1 }
+static const info_item_map_t info_item_map[] =
+  {
+    { MAKE_STRING("kind"),                info_item_kind },
+    { MAKE_STRING("url"),                 info_item_url },
+    { MAKE_STRING("relative-url"),        info_item_relative_url },
+    { MAKE_STRING("repos-root-url"),      info_item_repos_root_url },
+    { MAKE_STRING("repos-uuid"),          info_item_repos_uuid },
+    { MAKE_STRING("revision"),            info_item_revision },
+    { MAKE_STRING("last-changed-rev"),    info_item_last_changed_rev },
+    { MAKE_STRING("last-changed-date"),   info_item_last_changed_date },
+    { MAKE_STRING("last-changed-author"), info_item_last_changed_author },
+    { MAKE_STRING("wc-root"),             info_item_wc_root }
+  };
+#undef MAKE_STRING
+
+static const apr_size_t info_item_map_len =
+  (sizeof(info_item_map) / sizeof(info_item_map[0]));
+
+
+/* The baton type used by the info receiver functions. */
+typedef struct print_info_baton_t
+{
+  /* The path prefix that output paths should be normalized to. */
+  const char *path_prefix;
+
+  /*
+   * The following fields are used by print_info_item().
+   */
+
+  /* Which item to print. */
+  info_item_t print_what;
+
+  /* Do we expect to show info for multiple targets? */
+  svn_boolean_t multiple_targets;
+
+  /* TRUE iff the current is a local path. */
+  svn_boolean_t target_is_path;
+
+  /* Did we already print a line of output? */
+  svn_boolean_t start_new_line;
+} print_info_baton_t;
+
+
+/* Find the appropriate info_item_t for KEYWORD and initialize
+ * RECEIVER_BATON for print_info_item(). Use SCRATCH_POOL for
+ * temporary allocation.
+ */
+static svn_error_t *
+find_print_what(const char *keyword,
+                print_info_baton_t *receiver_baton,
+                apr_pool_t *scratch_pool)
+{
+  svn_cl__simcheck_t **keywords = apr_palloc(
+      scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t*));
+  svn_cl__simcheck_t *kwbuf = apr_palloc(
+      scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t));
+  apr_size_t i;
+
+  for (i = 0; i < info_item_map_len; ++i)
+    {
+      keywords[i] = &kwbuf[i];
+      kwbuf[i].token.data = info_item_map[i].keyword.data;
+      kwbuf[i].token.len = info_item_map[i].keyword.len;
+      kwbuf[i].data = &info_item_map[i];
+    }
+
+  switch (svn_cl__similarity_check(keyword, keywords,
+                                   info_item_map_len, scratch_pool))
+    {
+      const info_item_map_t *kw0;
+      const info_item_map_t *kw1;
+      const info_item_map_t *kw2;
+
+    case 0:                     /* Exact match. */
+      kw0 = keywords[0]->data;
+      receiver_baton->print_what = kw0->print_what;
+      return SVN_NO_ERROR;
+
+    case 1:
+      /* The best alternative isn't good enough */
+      return svn_error_createf(
+          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+          _("'%s' is not a valid value for the 'show-item' option."),
+          keyword);
+
+    case 2:
+      /* There is only one good candidate */
+      kw0 = keywords[0]->data;
+      return svn_error_createf(
+          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+          _("'%s' is not a valid value for the 'show-item' option;"
+            " did you mean '%s'?"),
+          keyword, kw0->keyword.data);
+
+    case 3:
+      /* Suggest a list of the most likely candidates */
+      kw0 = keywords[0]->data;
+      kw1 = keywords[1]->data;
+      return svn_error_createf(
+          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+          _("'%s' is not a valid value for the 'show-item' option;"
+            " did you mean '%s' or '%s'?"),
+          keyword, kw0->keyword.data, kw1->keyword.data);
+
+    default:
+      /* Never suggest more than three candidates */
+      kw0 = keywords[0]->data;
+      kw1 = keywords[1]->data;
+      kw2 = keywords[2]->data;
+      return svn_error_createf(
+          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+          _("'%s' is not a valid value for the 'show-item' option;"
+            " did you mean '%s', '%s' or '%s'?"),
+          keyword, kw0->keyword.data, kw1->keyword.data, kw2->keyword.data);
+    }
+}
 
 /* A callback of type svn_client_info_receiver2_t.
    Prints svn info in xml mode to standard out */
@@ -87,7 +248,7 @@ print_info_xml(void *baton,
 {
   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
   const char *rev_str;
-  const char *path_prefix = baton;
+  print_info_baton_t *const receiver_baton = baton;
 
   if (SVN_IS_VALID_REVNUM(info->rev))
     rev_str = apr_psprintf(pool, "%ld", info->rev);
@@ -97,7 +258,7 @@ print_info_xml(void *baton,
   /* "<entry ...>" */
   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
                         "path", svn_cl__local_style_skip_ancestor(
-                                  path_prefix, target, pool),
+                                  receiver_baton->path_prefix, target, pool),
                         "kind", svn_cl__node_kind_str_xml(info->kind),
                         "revision", rev_str,
                         SVN_VA_NULL);
@@ -109,13 +270,7 @@ print_info_xml(void *baton,
     {
       /* "<relative-url> xx </relative-url>" */
       svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
-                               apr_pstrcat(pool, "^/",
-                                           svn_path_uri_encode(
-                                               svn_uri_skip_ancestor(
-                                                   info->repos_root_URL,
-                                                   info->URL, pool),
-                                               pool),
-                                           SVN_VA_NULL));
+                               relative_url(info, pool));
     }
 
   if (info->repos_root_URL || info->repos_UUID)
@@ -263,11 +418,11 @@ print_info(void *baton,
            const svn_client_info2_t *info,
            apr_pool_t *pool)
 {
-  const char *path_prefix = baton;
+  print_info_baton_t *const receiver_baton = baton;
 
   SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
                              svn_cl__local_style_skip_ancestor(
-                               path_prefix, target, pool)));
+                               receiver_baton->path_prefix, target, pool)));
 
   /* ### remove this someday:  it's only here for cmdline output
      compatibility with svn 1.1 and older.  */
@@ -285,11 +440,8 @@ print_info(void *baton,
     SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
 
   if (info->URL && info->repos_root_URL)
-    SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
-                               svn_path_uri_encode(
-                                   svn_uri_skip_ancestor(info->repos_root_URL,
-                                                         info->URL, pool),
-                                   pool)));
+    SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: %s\n"),
+                               relative_url(info, pool)));
 
   if (info->repos_root_URL)
     SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
@@ -391,14 +543,14 @@ print_info(void *baton,
       if (info->wc_info->moved_from_abspath)
         SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
                                    svn_cl__local_style_skip_ancestor(
-                                      path_prefix,
+                                      receiver_baton->path_prefix,
                                       info->wc_info->moved_from_abspath,
                                       pool)));
 
       if (info->wc_info->moved_to_abspath)
         SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
                                    svn_cl__local_style_skip_ancestor(
-                                      path_prefix,
+                                      receiver_baton->path_prefix,
                                       info->wc_info->moved_to_abspath,
                                       pool)));
     }
@@ -446,21 +598,24 @@ print_info(void *baton,
                       SVN_ERR(svn_cmdline_printf(pool,
                                 _("Conflict Previous Base File: %s\n"),
                                 svn_cl__local_style_skip_ancestor(
-                                        path_prefix, conflict->base_abspath,
+                                        receiver_baton->path_prefix,
+                                        conflict->base_abspath,
                                         pool)));
 
                     if (conflict->my_abspath)
                       SVN_ERR(svn_cmdline_printf(pool,
                                 _("Conflict Previous Working File: %s\n"),
                                 svn_cl__local_style_skip_ancestor(
-                                        path_prefix, conflict->my_abspath,
+                                        receiver_baton->path_prefix,
+                                        conflict->my_abspath,
                                         pool)));
 
                     if (conflict->their_abspath)
                       SVN_ERR(svn_cmdline_printf(pool,
                                 _("Conflict Current Base File: %s\n"),
                                 svn_cl__local_style_skip_ancestor(
-                                        path_prefix, conflict->their_abspath,
+                                        receiver_baton->path_prefix,
+                                        conflict->their_abspath,
                                         pool)));
                   break;
 
@@ -469,7 +624,7 @@ print_info(void *baton,
                       SVN_ERR(svn_cmdline_printf(pool,
                                 _("Conflict Properties File: %s\n"),
                                 svn_cl__local_style_skip_ancestor(
-                                        path_prefix,
+                                        receiver_baton->path_prefix,
                                         conflict->prop_reject_abspath,
                                         pool)));
                     printed_prop_conflict_file = TRUE;
@@ -577,6 +732,123 @@ print_info(void *baton,
 }
 
 
+/* Helper for print_info_item(): Print the value TEXT for TARGET_PATH,
+   either of which may be NULL. Use POOL for temporary allocation. */
+static svn_error_t *
+print_info_item_string(const char *text, const char *target_path,
+                       apr_pool_t *pool)
+{
+  if (text)
+    {
+      if (target_path)
+        SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", text, target_path));
+      else
+        SVN_ERR(svn_cmdline_fputs(text, stdout, pool));
+    }
+  else if (target_path)
+    SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
+
+  return SVN_NO_ERROR;
+}
+
+/* Helper for print_info_item(): Print the revision number REV, which
+   may be SVN_INVALID_REVNUM, for TARGET_PATH, which may be NULL. Use
+   POOL for temporary allocation. */
+static svn_error_t *
+print_info_item_revision(svn_revnum_t rev, const char *target_path,
+                         apr_pool_t *pool)
+{
+  if (SVN_IS_VALID_REVNUM(rev))
+    {
+      if (target_path)
+        SVN_ERR(svn_cmdline_printf(pool, "%-10ld %s", rev, target_path));
+      else
+        SVN_ERR(svn_cmdline_printf(pool, "%-10ld", rev));
+    }
+  else if (target_path)
+    SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
+
+  return SVN_NO_ERROR;
+}
+
+/* A callback of type svn_client_info_receiver2_t. */
+static svn_error_t *
+print_info_item(void *baton,
+                  const char *target,
+                  const svn_client_info2_t *info,
+                  apr_pool_t *pool)
+{
+  print_info_baton_t *const receiver_baton = baton;
+  const char *const target_path =
+    (!receiver_baton->multiple_targets ? NULL
+     : (!receiver_baton->target_is_path ? info->URL
+        : svn_cl__local_style_skip_ancestor(
+            receiver_baton->path_prefix, target, pool)));
+
+  if (receiver_baton->start_new_line)
+    SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+  switch (receiver_baton->print_what)
+    {
+    case info_item_kind:
+      SVN_ERR(print_info_item_string(svn_node_kind_to_word(info->kind),
+                                     target_path, pool));
+      break;
+
+    case info_item_url:
+      SVN_ERR(print_info_item_string(info->URL, target_path, pool));
+      break;
+
+    case info_item_relative_url:
+      SVN_ERR(print_info_item_string(relative_url(info, pool),
+                                     target_path, pool));
+      break;
+
+    case info_item_repos_root_url:
+      SVN_ERR(print_info_item_string(info->repos_root_URL, target_path, pool));
+      break;
+
+    case info_item_repos_uuid:
+      SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool));
+      break;
+
+    case info_item_revision:
+      SVN_ERR(print_info_item_revision(info->rev, target_path, pool));
+      break;
+
+    case info_item_last_changed_rev:
+      SVN_ERR(print_info_item_revision(info->last_changed_rev,
+                                       target_path, pool));
+      break;
+
+    case info_item_last_changed_date:
+      SVN_ERR(print_info_item_string(
+                  (!info->last_changed_date ? NULL
+                   : svn_time_to_cstring(info->last_changed_date, pool)),
+                  target_path, pool));
+      break;
+
+    case info_item_last_changed_author:
+      SVN_ERR(print_info_item_string(info->last_changed_author,
+                                     target_path, pool));
+      break;
+
+    case info_item_wc_root:
+      SVN_ERR(print_info_item_string(
+                  (info->wc_info && info->wc_info->wcroot_abspath
+                   ? info->wc_info->wcroot_abspath : NULL),
+                  target_path, pool));
+      break;
+
+    default:
+      SVN_ERR_MALFUNCTION();
+    }
+
+  receiver_baton->start_new_line = TRUE;
+  return SVN_NO_ERROR;
+}
+
+
 /* This implements the `svn_opt_subcommand_t' interface. */
 svn_error_t *
 svn_cl__info(apr_getopt_t *os,
@@ -592,7 +864,7 @@ svn_cl__info(apr_getopt_t *os,
   svn_boolean_t seen_nonexistent_target = FALSE;
   svn_opt_revision_t peg_revision;
   svn_client_info_receiver2_t receiver;
-  const char *path_prefix;
+  print_info_baton_t receiver_baton = { 0 };
 
   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
                                                       opt_state->targets,
@@ -605,26 +877,59 @@ svn_cl__info(apr_getopt_t *os,
     {
       receiver = print_info_xml;
 
+      if (opt_state->show_item)
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--show-item is not valid in --xml mode"));
+      if (opt_state->no_newline)
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--no-newline' is not valid in --xml mode"));
+
       /* If output is not incremental, output the XML header and wrap
          everything in a top-level element. This makes the output in
          its entirety a well-formed XML document. */
       if (! opt_state->incremental)
         SVN_ERR(svn_cl__xml_print_header("info", pool));
     }
+  else if (opt_state->show_item)
+    {
+      receiver = print_info_item;
+
+      if (opt_state->incremental)
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--incremental is only valid in --xml mode"));
+
+      receiver_baton.multiple_targets = (opt_state->depth > svn_depth_empty
+                                         || targets->nelts > 1);
+      if (receiver_baton.multiple_targets && opt_state->no_newline)
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--no-newline is only available for single-target,"
+              " non-recursive info operations"));
+
+      SVN_ERR(find_print_what(opt_state->show_item, &receiver_baton, pool));
+      receiver_baton.start_new_line = FALSE;
+    }
   else
     {
       receiver = print_info;
 
       if (opt_state->incremental)
-        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                                _("'incremental' option only valid in XML "
-                                  "mode"));
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--incremental is only valid in --xml mode"));
+      if (opt_state->no_newline)
+        return svn_error_create(
+            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+            _("--no-newline' is only valid with --show-item"));
     }
 
   if (opt_state->depth == svn_depth_unknown)
     opt_state->depth = svn_depth_empty;
 
-  SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+  SVN_ERR(svn_dirent_get_absolute(&receiver_baton.path_prefix, "", pool));
 
   for (i = 0; i < targets->nelts; i++)
     {
@@ -642,10 +947,12 @@ svn_cl__info(apr_getopt_t *os,
         {
           if (peg_revision.kind == svn_opt_revision_unspecified)
             peg_revision.kind = svn_opt_revision_head;
+          receiver_baton.target_is_path = FALSE;
         }
       else
         {
           SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+          receiver_baton.target_is_path = TRUE;
         }
 
       err = svn_client_info4(truepath,
@@ -655,7 +962,7 @@ svn_cl__info(apr_getopt_t *os,
                              TRUE /* fetch_actual_only */,
                              opt_state->include_externals,
                              opt_state->changelists,
-                             receiver, (void *) path_prefix,
+                             receiver, &receiver_baton,
                              ctx, subpool);
 
       if (err)
@@ -682,6 +989,9 @@ svn_cl__info(apr_getopt_t *os,
 
   if (opt_state->xml && (! opt_state->incremental))
     SVN_ERR(svn_cl__xml_print_footer("info", pool));
+  else if (opt_state->show_item && !opt_state->no_newline
+           && receiver_baton.start_new_line)
+    SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
 
   if (seen_nonexistent_target)
     return svn_error_create(

Modified: subversion/trunk/subversion/svn/props.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/props.c?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/props.c (original)
+++ subversion/trunk/subversion/svn/props.c Fri Feb 27 02:19:41 2015
@@ -112,59 +112,6 @@ svn_cl__check_boolean_prop_val(const cha
     }
 }
 
-
-/* Context for sorting property names */
-struct simprop_context_t
-{
-  svn_string_t name;    /* The name of the property we're comparing with */
-  svn_membuf_t buffer;  /* Buffer for similarity testing */
-};
-
-struct simprop_t
-{
-  const char *propname; /* The original svn: property name */
-  svn_string_t name;    /* The property name without the svn: prefix */
-  unsigned int score;   /* The similarity score */
-  apr_size_t diff;      /* Number of chars different from context.name */
-  struct simprop_context_t *context; /* Sorting context for qsort() */
-};
-
-/* Similarity test between two property names */
-static APR_INLINE unsigned int
-simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
-                 svn_membuf_t *buffer, apr_size_t *diff)
-{
-  apr_size_t lcs;
-  const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
-  if (key->len > ctx->len)
-    *diff = key->len - lcs;
-  else
-    *diff = ctx->len - lcs;
-  return score;
-}
-
-/* Key comparator for qsort for simprop_t */
-static int
-simprop_compare(const void *pkeya, const void *pkeyb)
-{
-  struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
-  struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
-  struct simprop_context_t *const context = keya->context;
-
-  if (keya->score == -1)
-    keya->score = simprop_key_diff(&keya->name, &context->name,
-                                   &context->buffer, &keya->diff);
-  if (keyb->score == -1)
-    keyb->score = simprop_key_diff(&keyb->name, &context->name,
-                                   &context->buffer, &keyb->diff);
-
-  return (keya->score < keyb->score ? 1
-          : (keya->score > keyb->score ? -1
-             : (keya->diff > keyb->diff ? 1
-                : (keya->diff < keyb->diff ? -1 : 0))));
-}
-
-
 static const char*
 force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
                           apr_pool_t *scratch_pool)
@@ -239,33 +186,34 @@ svn_cl__check_svn_prop_name(const char *
   const char *const *const proplist = (revprop ? revprops : nodeprops);
   const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
 
-  struct simprop_t **propkeys;
-  struct simprop_t *propbuf;
+  svn_cl__simcheck_t **propkeys;
+  svn_cl__simcheck_t *propbuf;
   apr_size_t i;
 
-  struct simprop_context_t context;
+  svn_string_t propstring;
   svn_string_t prefix;
+  svn_membuf_t buffer;
 
-  context.name.data = propname;
-  context.name.len = strlen(propname);
+  propstring.data = propname;
+  propstring.len = strlen(propname);
   prefix.data = SVN_PROP_PREFIX;
   prefix.len = strlen(SVN_PROP_PREFIX);
 
-  svn_membuf__create(&context.buffer, 0, scratch_pool);
+  svn_membuf__create(&buffer, 0, scratch_pool);
 
   /* First, check if the name is even close to being in the svn: namespace.
      It must contain a colon in the right place, and we only allow
      one-char typos or a single transposition. */
-  if (context.name.len < prefix.len
-      || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
+  if (propstring.len < prefix.len
+      || propstring.data[prefix.len - 1] != prefix.data[prefix.len - 1])
     return SVN_NO_ERROR;        /* Wrong prefix, ignore */
   else
     {
       apr_size_t lcs;
-      const apr_size_t name_len = context.name.len;
-      context.name.len = prefix.len; /* Only check up to the prefix length */
-      svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
-      context.name.len = name_len; /* Restore the original propname length */
+      const apr_size_t name_len = propstring.len;
+      propstring.len = prefix.len; /* Only check up to the prefix length */
+      svn_string__similarity(&propstring, &prefix, &buffer, &lcs);
+      propstring.len = name_len; /* Restore the original propname length */
       if (lcs < prefix.len - 1)
         return SVN_NO_ERROR;    /* Wrong prefix, ignore */
 
@@ -292,55 +240,47 @@ svn_cl__check_svn_prop_name(const char *
      we already know that it's the same and looking at it would only
      skew the results. */
   propkeys = apr_palloc(scratch_pool,
-                        numprops * sizeof(struct simprop_t*));
+                        numprops * sizeof(svn_cl__simcheck_t*));
   propbuf = apr_palloc(scratch_pool,
-                       numprops * sizeof(struct simprop_t));
-  context.name.data += prefix.len;
-  context.name.len -= prefix.len;
+                       numprops * sizeof(svn_cl__simcheck_t));
+  propstring.data += prefix.len;
+  propstring.len -= prefix.len;
   for (i = 0; i < numprops; ++i)
     {
       propkeys[i] = &propbuf[i];
-      propbuf[i].propname = proplist[i];
-      propbuf[i].name.data = proplist[i] + prefix.len;
-      propbuf[i].name.len = strlen(propbuf[i].name.data);
-      propbuf[i].score = (unsigned int)-1;
-      propbuf[i].context = &context;
+      propbuf[i].token.data = proplist[i] + prefix.len;
+      propbuf[i].token.len = strlen(propbuf[i].token.data);
+      propbuf[i].data = proplist[i];
     }
 
-  qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
-
-  if (0 == propkeys[0]->diff)
-    return SVN_NO_ERROR;        /* We found an exact match. */
-
-  /* See if we can suggest a sane alternative spelling */
-  for (i = 0; i < numprops; ++i)
-    if (propkeys[i]->score < 666) /* 2/3 similarity required */
-      break;
-
-  switch (i)
+  switch (svn_cl__similarity_check(
+              propstring.data, propkeys, numprops, scratch_pool))
     {
     case 0:
+      return SVN_NO_ERROR;      /* We found an exact match. */
+
+    case 1:
       /* The best alternative isn't good enough */
       return svn_error_create(
         SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
         wrong_prop_error_message(prop_use, propname, scratch_pool));
 
-    case 1:
+    case 2:
       /* There is only one good candidate */
       return svn_error_createf(
         SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
         _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
-        propname, SVN_PROP_PREFIX, propkeys[0]->propname,
+        propname, SVN_PROP_PREFIX, propkeys[0]->data,
         force_prop_option_message(prop_use, propname, scratch_pool));
 
-    case 2:
+    case 3:
       /* Suggest a list of the most likely candidates */
       return svn_error_createf(
         SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
         _("'%s' is not a valid %s property name\n"
           "Did you mean '%s' or '%s'?\n%s"),
         propname, SVN_PROP_PREFIX,
-        propkeys[0]->propname, propkeys[1]->propname,
+        propkeys[0]->data, propkeys[1]->data,
         force_prop_option_message(prop_use, propname, scratch_pool));
 
     default:
@@ -350,7 +290,7 @@ svn_cl__check_svn_prop_name(const char *
         _("'%s' is not a valid %s property name\n"
           "Did you mean '%s', '%s' or '%s'?\n%s"),
         propname, SVN_PROP_PREFIX,
-        propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
+        propkeys[0]->data, propkeys[1]->data, propkeys[2]->data,
         force_prop_option_message(prop_use, propname, scratch_pool));
     }
 }

Modified: subversion/trunk/subversion/svn/svn.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/svn.c?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/svn.c (original)
+++ subversion/trunk/subversion/svn/svn.c Fri Feb 27 02:19:41 2015
@@ -146,6 +146,7 @@ typedef enum svn_cl__longopt_t {
   opt_no_newline,
   opt_show_passwords,
   opt_pin_externals,
+  opt_show_item,
 } svn_cl__longopt_t;
 
 
@@ -423,6 +424,8 @@ const apr_getopt_option_t svn_cl__option
                        N_("pin externals with no explicit revision to their\n"
                           "                             "
                           "current revision (recommended when tagging)")},
+  {"show-item", opt_show_item, 1,
+                       N_("print only the item identified by ARG")},
 
   /* Long-opt Aliases
    *
@@ -736,9 +739,24 @@ const svn_opt_subcommand_desc2_t svn_cl_
      "\n"
      "  Print information about each TARGET (default: '.').\n"
      "  TARGET may be either a working-copy path or URL.  If specified, REV\n"
-     "  determines in which revision the target is first looked up.\n"),
+     "  determines in which revision the target is first looked up.\n"
+     "\n"
+     "  With --show-item, print only the value of one item of information\n"
+     "  about TARGET. One of the following items can be selected:\n"
+     "     kind                  the kind of TARGET\n"
+     "     url                   the URL of TARGET in the repository\n"
+     "     relative-url          the repository-relative URL\n"
+     "     repos-root-url        the repository root URL\n"
+     "     repos-uuid            the repository UUID\n"
+     "     revision              the revision of TARGET (defaults to BASE\n"
+     "                           for working copy paths and HEAD for URLs)\n"
+     "     last-changed-rev      the most recent revision in which TARGET\n"
+     "                           was changed\n"
+     "     last-changed-date     the date of the last-changed revision\n"
+     "     last-changed-author   the author of the last-changed revision\n"
+     "     wc-root               the root of TARGET's working copy"),
     {'r', 'R', opt_depth, opt_targets, opt_incremental, opt_xml,
-     opt_changelist, opt_include_externals}
+     opt_changelist, opt_include_externals, opt_show_item, opt_no_newline}
   },
 
   { "list", svn_cl__list, {"ls"}, N_
@@ -2404,6 +2422,10 @@ sub_main(int *exit_code, int argc, const
       case opt_pin_externals:
         opt_state.pin_externals = TRUE;
         break;
+      case opt_show_item:
+        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+        opt_state.show_item = utf8_opt_arg;
+        break;
       default:
         /* Hmmm. Perhaps this would be a good place to squirrel away
            opts that commands like svn diff might need. Hmmm indeed. */

Modified: subversion/trunk/subversion/tests/cmdline/info_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/info_tests.py?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/info_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/info_tests.py Fri Feb 27 02:19:41 
2015
@@ -636,6 +636,117 @@ def node_hidden_info(sbox):
                                       sbox.ospath('E/beta'))
 
 
+def info_item_simple(sbox):
+  "show one info item"
+
+  sbox.build(read_only=True)
+  svntest.actions.run_and_verify_svn(
+    '1', [],
+    'info', '--show-item=revision', '--no-newline',
+    sbox.ospath(''))
+
+
+def info_item_simple_multiple(sbox):
+  "show one info item with multiple targets"
+
+  sbox.build(read_only=True)
+
+  svntest.actions.run_and_verify_svn(
+    r'^jrandom\s+\S+/info_tests-\d+(/[^/]+)?$', [],
+    'info', '--show-item=last-changed-author',
+    '--depth=immediates', sbox.ospath(''))
+
+  svntest.actions.run_and_verify_svn(
+    r'^1\s+\S+/info_tests-\d+/[^/]+$', [],
+    'info', '--show-item=last-changed-rev',
+    sbox.ospath('A'), sbox.ospath('iota'))
+
+
+def info_item_url(sbox):
+  "show one info item with URL targets"
+
+  sbox.build(create_wc=False, read_only=True)
+
+  svntest.actions.run_and_verify_svn(
+    '1', [],
+    'info', '--show-item=last-changed-rev',
+    sbox.repo_url)
+
+
+  svntest.actions.run_and_verify_svn(
+    r'^1\s+[^/:]+://.+/repos/[^/]+$', [],
+    'info', '--show-item=last-changed-rev',
+    sbox.repo_url + '/A', sbox.repo_url + '/iota')
+
+
+  # Empty working copy root on URL targets
+  svntest.actions.run_and_verify_svn(
+    '', [],
+    'info', '--show-item=wc-root',
+    sbox.repo_url)
+
+
+def info_item_uncommmitted(sbox):
+  "show one info item on uncommitted targets"
+
+  sbox.build()
+
+  svntest.main.file_write(sbox.ospath('newfile'), 'newfile')
+  sbox.simple_add('newfile')
+  sbox.simple_mkdir('newdir')
+
+  svntest.actions.run_and_verify_svn(
+    '', [],
+    'info', '--show-item=last-changed-rev',
+    sbox.ospath('newfile'))
+
+  svntest.actions.run_and_verify_svn(
+    '', [],
+    'info', '--show-item=last-changed-author',
+    sbox.ospath('newdir'))
+
+  svntest.actions.run_and_verify_svn(
+    r'\s+\S+/new(file|dir)', [],
+    'info', '--show-item=last-changed-date',
+    sbox.ospath('newfile'), sbox.ospath('newdir'))
+
+  svntest.actions.run_and_verify_svn(
+    r'\^/new(file|dir)\s+\S+/new(file|dir)', [],
+    'info', '--show-item=relative-url',
+    sbox.ospath('newfile'), sbox.ospath('newdir'))
+
+
+def info_item_failures(sbox):
+  "failure modes of 'svn info --show-item'"
+
+  sbox.build(read_only=True)
+
+  svntest.actions.run_and_verify_svn(
+    None, r'.*E200009:.*',
+    'info', '--show-item=revision',
+    sbox.ospath('not-there'))
+
+  svntest.actions.run_and_verify_svn(
+    None, r".*E205000: .*; did you mean 'wc-root'\?",
+    'info', '--show-item=root',
+    sbox.ospath(''))
+
+  svntest.actions.run_and_verify_svn(
+    None, (r".*E205000: --show-item is not valid in --xml mode"),
+    'info', '--show-item=revision', '--xml',
+    sbox.ospath(''))
+
+  svntest.actions.run_and_verify_svn(
+    None, (r".*E205000: --incremental is only valid in --xml mode"),
+    'info', '--show-item=revision', '--incremental',
+    sbox.ospath(''))
+
+  svntest.actions.run_and_verify_svn(
+    None, (r".*E205000: --no-newline is only available.*"),
+    'info', '--show-item=revision', '--no-newline',
+    sbox.ospath('A'), sbox.ospath('iota'))
+
+
 ########################################################################
 # Run the tests
 
@@ -652,6 +763,11 @@ test_list = [ None,
               binary_tree_conflict,
               relpath_escaping,
               node_hidden_info,
+              info_item_simple,
+              info_item_simple_multiple,
+              info_item_url,
+              info_item_uncommmitted,
+              info_item_failures,
              ]
 
 if __name__ == '__main__':

Modified: subversion/trunk/subversion/tests/libsvn_subr/string-test.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_subr/string-test.c?rev=1662620&r1=1662619&r2=1662620&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_subr/string-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_subr/string-test.c Fri Feb 27 
02:19:41 2015
@@ -686,10 +686,11 @@ test_string_similarity(apr_pool_t *pool)
     unsigned int score;
   } tests[] =
       {
-#define SCORE(lcs, len) ((2000 * (lcs) + (len)/2) / (len))
+#define SCORE(lcs, len) \
+   ((2 * SVN_STRING__SIM_RANGE_MAX * (lcs) + (len)/2) / (len))
 
         /* Equality */
-        {"",       "",          0, 1000},
+        {"",       "",          0, SVN_STRING__SIM_RANGE_MAX},
         {"quoth",  "quoth",     5, SCORE(5, 5+5)},
 
         /* Deletion at start */
@@ -743,17 +744,20 @@ test_string_similarity(apr_pool_t *pool)
   for (t = tests; t->stra; ++t)
     {
       apr_size_t lcs;
-      const unsigned int score =
+      const apr_size_t score =
         svn_cstring__similarity(t->stra, t->strb, &buffer, &lcs);
       /*
       fprintf(stderr,
-              "lcs %s ~ %s score %.3f (%"APR_SIZE_T_FMT
-              ") expected %.3f (%"APR_SIZE_T_FMT"))\n",
-              t->stra, t->strb, score/1000.0, lcs, t->score/1000.0, t->lcs);
+              "lcs %s ~ %s score %.6f (%"APR_SIZE_T_FMT
+              ") expected %.6f (%"APR_SIZE_T_FMT"))\n",
+              t->stra, t->strb, score/1.0/SVN_STRING__SIM_RANGE_MAX,
+              lcs, t->score/1.0/SVN_STRING__SIM_RANGE_MAX, t->lcs);
       */
       if (score != t->score)
-        return fail(pool, "%s ~ %s score %.3f <> expected %.3f",
-                    t->stra, t->strb, score/1000.0, t->score/1000.0);
+        return fail(pool, "%s ~ %s score %.6f <> expected %.6f",
+                    t->stra, t->strb,
+                    score/1.0/SVN_STRING__SIM_RANGE_MAX,
+                    t->score/1.0/SVN_STRING__SIM_RANGE_MAX);
 
       if (lcs != t->lcs)
         return fail(pool,
@@ -766,7 +770,8 @@ test_string_similarity(apr_pool_t *pool)
   {
     const svn_string_t foo = {"svn:foo", 4};
     const svn_string_t bar = {"svn:bar", 4};
-    if (1000 != svn_string__similarity(&foo, &bar, &buffer, NULL))
+    if (SVN_STRING__SIM_RANGE_MAX
+        != svn_string__similarity(&foo, &bar, &buffer, NULL))
       return fail(pool, "'%s'[:4] ~ '%s'[:4] found different",
                   foo.data, bar.data);
   }


Reply via email to