Author: brane
Date: Tue Feb 17 19:33:18 2015
New Revision: 1660474

URL: http://svn.apache.org/r1660474
Log:
On the svn-info-detail branch: Increase the range of the string
similarity test result and factor the similar-string search out
of svn_cl__check_prop_name into a separate client-local function.

* 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/cl.h
  (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/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.

Added:
    subversion/branches/svn-info-detail/subversion/svn/similarity.c   (with 
props)
Modified:
    
subversion/branches/svn-info-detail/subversion/include/private/svn_string_private.h
    subversion/branches/svn-info-detail/subversion/libsvn_subr/string.c
    subversion/branches/svn-info-detail/subversion/svn/cl.h
    subversion/branches/svn-info-detail/subversion/svn/props.c
    
subversion/branches/svn-info-detail/subversion/tests/libsvn_subr/string-test.c

Modified: 
subversion/branches/svn-info-detail/subversion/include/private/svn_string_private.h
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/include/private/svn_string_private.h?rev=1660474&r1=1660473&r2=1660474&view=diff
==============================================================================
--- 
subversion/branches/svn-info-detail/subversion/include/private/svn_string_private.h
 (original)
+++ 
subversion/branches/svn-info-detail/subversion/include/private/svn_string_private.h
 Tue Feb 17 19:33:18 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/branches/svn-info-detail/subversion/libsvn_subr/string.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/libsvn_subr/string.c?rev=1660474&r1=1660473&r2=1660474&view=diff
==============================================================================
--- subversion/branches/svn-info-detail/subversion/libsvn_subr/string.c 
(original)
+++ subversion/branches/svn-info-detail/subversion/libsvn_subr/string.c Tue Feb 
17 19:33:18 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/branches/svn-info-detail/subversion/svn/cl.h
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/svn/cl.h?rev=1660474&r1=1660473&r2=1660474&view=diff
==============================================================================
--- subversion/branches/svn-info-detail/subversion/svn/cl.h (original)
+++ subversion/branches/svn-info-detail/subversion/svn/cl.h Tue Feb 17 19:33:18 
2015
@@ -855,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/branches/svn-info-detail/subversion/svn/props.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/svn/props.c?rev=1660474&r1=1660473&r2=1660474&view=diff
==============================================================================
--- subversion/branches/svn-info-detail/subversion/svn/props.c (original)
+++ subversion/branches/svn-info-detail/subversion/svn/props.c Tue Feb 17 
19:33:18 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));
     }
 }

Added: subversion/branches/svn-info-detail/subversion/svn/similarity.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/svn/similarity.c?rev=1660474&view=auto
==============================================================================
--- subversion/branches/svn-info-detail/subversion/svn/similarity.c (added)
+++ subversion/branches/svn-info-detail/subversion/svn/similarity.c Tue Feb 17 
19:33:18 2015
@@ -0,0 +1,124 @@
+/*
+ * similarity.c: Utility functions for finding similar strings in lists
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <stdlib.h>
+
+#include "svn_string.h"
+#include "cl.h"
+
+#include "private/svn_string_private.h"
+
+#include "svn_private_config.h"
+
+
+/* Context for token similarity checking */
+struct svn_cl__simcheck_context_t
+{
+  svn_string_t key;             /* The token we're comparing with */
+  svn_membuf_t buffer;          /* Buffer for similarity testing */
+};
+
+
+/* Similarity test between two property names */
+static APR_INLINE apr_size_t
+simcheck_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 apr_size_t 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 svn_cl__simcheck_t */
+static int
+simcheck_compare(const void *pkeya, const void *pkeyb)
+{
+  svn_cl__simcheck_t *const keya = *(svn_cl__simcheck_t *const *)pkeya;
+  svn_cl__simcheck_t *const keyb = *(svn_cl__simcheck_t *const *)pkeyb;
+  svn_cl__simcheck_context_t *const context = keya->context;
+
+  if (keya->score == -1)
+    keya->score = simcheck_key_diff(&keya->token, &context->key,
+                                    &context->buffer, &keya->diff);
+  if (keyb->score == -1)
+    keyb->score = simcheck_key_diff(&keyb->token, &context->key,
+                                    &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))));
+}
+
+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)
+{
+  apr_size_t result;
+  apr_size_t i;
+
+  svn_cl__simcheck_context_t context;
+  context.key.data = key;
+  context.key.len = strlen(key);
+  svn_membuf__create(&context.buffer, 0, scratch_pool);
+
+  /* Populate the score, diff and context members. */
+  for (i = 0; i < token_count; ++i)
+    {
+      svn_cl__simcheck_t *const token = tokens[i];
+      token->score = -1;
+      token->diff = 0;
+      token->context = &context;
+    }
+
+  /* Sort the tokens by similarity. */
+  qsort(tokens, token_count, sizeof(*tokens), simcheck_compare);
+
+  /* Remove references to the context, since it points to the stack,
+     and calculate the number of results that are at least two-thirds
+     similar to the key. */
+  for (i = 0, result = 1; i < token_count; ++i)
+    {
+      svn_cl__simcheck_t *const token = tokens[i];
+      token->context = NULL;
+      if (token->score >= (2 * SVN_STRING__SIM_RANGE_MAX + 1) / 3)
+        ++result;
+    }
+
+  if (0 == tokens[0]->diff)
+    return 0;                   /* We found an exact match. */
+  return result;
+}

Propchange: subversion/branches/svn-info-detail/subversion/svn/similarity.c
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
subversion/branches/svn-info-detail/subversion/tests/libsvn_subr/string-test.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/svn-info-detail/subversion/tests/libsvn_subr/string-test.c?rev=1660474&r1=1660473&r2=1660474&view=diff
==============================================================================
--- 
subversion/branches/svn-info-detail/subversion/tests/libsvn_subr/string-test.c 
(original)
+++ 
subversion/branches/svn-info-detail/subversion/tests/libsvn_subr/string-test.c 
Tue Feb 17 19:33:18 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