[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] Keyword hash and Properties as keyword

From: John Peacock <jpeacock_at_rowman.com>
Date: 2004-03-20 04:58:52 CET

OK, here's my version of plasma's patch to make keywords work using hashes. I
cannot take credit (or blame ;) for most of it, except for the support for
properties as keywords. Includes tests and patches to the book. Both diff and
log message attached.

John

-- 
John Peacock
Director of Information Research and Technology
Rowman & Littlefield Publishing Group
4720 Boston Way
Lanham, MD 20706
301-459-3366 x.5010
fax 301-429-5747

Implement printf-like format characters for keyword expansion. See
Issue #890 for further information.

  * includes/svn_subst.h:
    (struct svn_subst_keywords_t): Redefine svn_subst_keywords_t as an
      alias of apr_hash_t.
    (svn_subst_build_keywords2): Interface change. A new argument
      apr_hash_t *props.
    (svn_subst_build_keywords): API compatibility wrapper for previous
    (svn_subst_keywords_differ2): Interface change. A new argument
      apr_pool_t *pool.
    (svn_subst_keywords_differ): API compatibility wrapper for previous

  * includes/svn_types.h:
    Define keyword format string for Revision, Date, Author, URL, ID

  * libsvn_client/cat.c: Changes because interfaces of
      svn_subst_build_keywords2() and svn_subst_translate_stream() change.

  * libsvn_client/export.c
    (struct file_baton): Add a new apr_hash_t *props field.
    (add_file): Initialize props in struct file_baton.
    (change_file_prop): Store every property in props in struct
      file_baton.
    (close_file): Interface of svn_subst_build_keywords2() changes.

  * libsvn_subr/svn_subst.c:
    (keyword_printf): New private function.
    (svn_subst_build_keywords): API combatibility wrapper
    (svn_subst_build_keywords2): Build keywords by
      svn_subst_keyword_printf().
    (translate_keyword): Interface changes. It now looks up keyword
      passed in buffer, instead of predefined constant string.
    (svn_subst_translate_stream): Loop over all elements of keyword hash
      instead of structure elements.
    (svn_subst_keywords_differ): Compare two hashes instead of comparing
      individual structure elements.

  * libsvn_wc/props.c
    (validate_eol_prop_against_file): Change to use
      svn_subst_keywords_differ2().

  * libsvn_wc/translate.c
    (svn_wc__get_keywords): struct svn_subst_keywords_t changes.
      Change to use svn_subst_build_keywords2().

  * tests/libsvn_wc/translate-test.c:
    (substitute_and_verify): Properly assign values to struct
      svn_subst_keywords_t due to interface changes.

  * tests/clients/cmdline/prop_tests.py
    (keyword_props): new tests for keyword property expansion

  * doc/book/book/ch07.xml: document properties as keyword feature

Index: /svn/trunk/subversion/include/svn_subst.h
==================================================================
--- /svn/trunk/subversion/include/svn_subst.h (/svn/trunk/subversion/include/svn_subst.h) (revision 7672)
+++ /svn/trunk/subversion/include/svn_subst.h (/svn/local/trunk/subversion/include/svn_subst.h) (revision 7672)
@@ -76,26 +76,34 @@
 
 
 /** Values used in keyword expansion. */
-typedef struct svn_subst_keywords_t
-{
- const svn_string_t *revision;
- const svn_string_t *date;
- const svn_string_t *author;
- const svn_string_t *url;
- const svn_string_t *id;
-} svn_subst_keywords_t;
+typedef apr_hash_t svn_subst_keywords_t;
 
-
 /** Fill in an <tt>svn_subst_keywords_t *</tt> @a kw with the appropriate
  * contents given an @a keywords_string (the contents of the svn:keywords
  * property for the file in question), the revision @a rev, the @a url,
  * the @a date the file was committed on, and the @a author of the last
  * commit. Any of these can be @c NULL to indicate that the information is
- * not present, or @c 0 for @a date.
+ * not present, or @c 0 for @a date. If a property is used as a keyword,
+ * that value is stored in the kw hash as well.
  *
  * All memory is allocated out of @a pool.
  */
 svn_error_t *
+svn_subst_build_keywords2 (svn_subst_keywords_t *kw,
+ const char *keywords_string,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_hash_t *props,
+ apr_pool_t *pool);
+
+/** Similar to svn_subst_build_keywords2(); does not handle possible property
+ * keywords.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0.0 API.
+ */
+svn_error_t *
 svn_subst_build_keywords (svn_subst_keywords_t *kw,
                           const char *keywords_string,
                           const char *rev,
@@ -104,7 +112,6 @@
                           const char *author,
                           apr_pool_t *pool);
 
-
 /** Return @c TRUE if @a a and @a b do not hold the same keywords.
  *
  * If @a compare_values is @c TRUE, "same" means that the @a a and @a b
@@ -117,10 +124,22 @@
  * equivalent to holding no keywords.
  */
 svn_boolean_t
+svn_subst_keywords_differ2 (const svn_subst_keywords_t *a,
+ const svn_subst_keywords_t *b,
+ svn_boolean_t compare_values,
+ apr_pool_t *pool);
+
+/** Return @c TRUE if @a a and @a b do not hold the same keywords.
+ *
+ * Similar to svn_subst_keywords_differ2(); provides a pool for performing
+ * the hash comparisons.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0.0 API.
+ */
+svn_boolean_t
 svn_subst_keywords_differ (const svn_subst_keywords_t *a,
                            const svn_subst_keywords_t *b,
                            svn_boolean_t compare_values);
-
 
 /** Copy and translate the data in stream @a src into stream @a dst. It is
  * assumed that @a src is a readable stream and @a dst is a writable stream.
Index: /svn/trunk/subversion/include/svn_types.h
==================================================================
--- /svn/trunk/subversion/include/svn_types.h (/svn/trunk/subversion/include/svn_types.h) (revision 7672)
+++ /svn/trunk/subversion/include/svn_types.h (/svn/local/trunk/subversion/include/svn_types.h) (revision 7672)
@@ -219,30 +219,45 @@
 /** Short version of LastChangedRevision */
 #define SVN_KEYWORD_REVISION_SHORT "Rev"
 
+/** Format string for Revision */
+#define SVN_KEYWORD_REVISION_FORMAT "%r"
+
 /** The most recent date (repository time) when this file was changed. */
 #define SVN_KEYWORD_DATE_LONG "LastChangedDate"
 
 /** Short version of LastChangedDate */
 #define SVN_KEYWORD_DATE_SHORT "Date"
 
+/** Format string for Date */
+#define SVN_KEYWORD_DATE_FORMAT "%D"
+
 /** Who most recently committed to this file. */
 #define SVN_KEYWORD_AUTHOR_LONG "LastChangedBy"
 
 /** Short version of LastChangedBy */
 #define SVN_KEYWORD_AUTHOR_SHORT "Author"
 
+/** Format string for Author */
+#define SVN_KEYWORD_AUTHOR_FORMAT "%a"
+
 /** The URL for the head revision of this file. */
 #define SVN_KEYWORD_URL_LONG "HeadURL"
 
 /** Short version of HeadURL */
 #define SVN_KEYWORD_URL_SHORT "URL"
 
+/** Format string for Author */
+#define SVN_KEYWORD_URL_FORMAT "%u"
+
 /** A compressed combination of the other four keywords.
  *
  * (But see comments above about a more general solution to keyword
  * combinations.)
  */
 #define SVN_KEYWORD_ID "Id"
+
+/** Format string for ID */
+#define SVN_KEYWORD_ID_FORMAT "%b %d %a %r"
 
 /** @} */
 
Index: /svn/trunk/subversion/libsvn_client/cat.c
==================================================================
--- /svn/trunk/subversion/libsvn_client/cat.c (/svn/trunk/subversion/libsvn_client/cat.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_client/cat.c (/svn/local/trunk/subversion/libsvn_client/cat.c) (revision 7672)
@@ -92,7 +92,7 @@
     }
   else
     {
- svn_subst_keywords_t kw = { 0 };
+ svn_subst_keywords_t *kw = apr_hash_make(pool);
       svn_subst_eol_style_t style;
       const char *temp_dir;
       const char *tmp_filename;
@@ -136,16 +136,17 @@
           if (cmt_date)
             SVN_ERR (svn_time_from_cstring (&when, cmt_date->data, pool));
 
- SVN_ERR (svn_subst_build_keywords
- (&kw, keywords->data,
+ SVN_ERR (svn_subst_build_keywords2
+ (kw, keywords->data,
                     cmt_rev->data,
                     url,
                     when,
                     cmt_author ? cmt_author->data : NULL,
+ props,
                     pool));
         }
 
- SVN_ERR (svn_subst_translate_stream (tmp_stream, out, eol, FALSE, &kw,
+ SVN_ERR (svn_subst_translate_stream (tmp_stream, out, eol, FALSE, kw,
                                            TRUE));
 
       SVN_ERR (svn_stream_close (tmp_stream));
Index: /svn/trunk/subversion/libsvn_client/commit.c
==================================================================
--- /svn/trunk/subversion/libsvn_client/commit.c (/svn/trunk/subversion/libsvn_client/commit.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_client/commit.c (/svn/local/trunk/subversion/libsvn_client/commit.c) (revision 7672)
@@ -84,7 +84,7 @@
      Keywords get unexpanded. */
   if (eol_style_val || keywords_val)
     {
- svn_subst_keywords_t keywords = {0};
+ svn_subst_keywords_t *keywords = apr_hash_make(pool);
       const char *temp_dir;
       svn_stream_t *tmp_stream;
       apr_file_t *tmp_f;
@@ -95,9 +95,9 @@
 
       /* Generate a keyword structure. */
       if (keywords_val)
- SVN_ERR (svn_subst_build_keywords (&keywords, keywords_val->data,
- APR_STRINGIFY(SVN_INVALID_REVNUM),
- "", 0, "", pool));
+ SVN_ERR (svn_subst_build_keywords2 (keywords, keywords_val->data,
+ APR_STRINGIFY(SVN_INVALID_REVNUM),
+ "", 0, "", properties, pool));
 
       /* Now create a new tempfile, and open a stream to it. */
       SVN_ERR (svn_io_temp_dir (&temp_dir, pool));
@@ -112,7 +112,7 @@
       if ((err = svn_subst_translate_stream (contents, tmp_stream,
                                              eol_style_val ? "\n" : NULL,
                                              FALSE,
- keywords_val ? &keywords : NULL,
+ keywords_val ? keywords : NULL,
                                              FALSE)))
         goto cleanup;
 
Index: /svn/trunk/subversion/libsvn_client/export.c
==================================================================
--- /svn/trunk/subversion/libsvn_client/export.c (/svn/trunk/subversion/libsvn_client/export.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_client/export.c (/svn/local/trunk/subversion/libsvn_client/export.c) (revision 7672)
@@ -151,7 +151,7 @@
         {
           const char *copy_from = svn_path_join (from, item, iterpool);
           const char *copy_to = svn_path_join (to, item, iterpool);
- svn_subst_keywords_t kw = { 0 };
+ svn_subst_keywords_t *kw = apr_hash_make(pool);
           svn_subst_eol_style_t style;
           apr_hash_t *props;
           const char *base;
@@ -235,14 +235,14 @@
                   author = entry->cmt_author;
                 }
               
- SVN_ERR (svn_subst_build_keywords
- (&kw, keywords->data,
+ SVN_ERR (svn_subst_build_keywords2
+ (kw, keywords->data,
                         apr_psprintf (iterpool, fmt, entry->cmt_rev),
- entry->url, tm, author, iterpool));
+ entry->url, tm, author, props, iterpool));
             }
           
           SVN_ERR (svn_subst_copy_and_translate (base, copy_to, eol, FALSE,
- &kw, TRUE, iterpool));
+ kw, TRUE, iterpool));
           if (executable)
             SVN_ERR (svn_io_set_file_executable (copy_to, TRUE,
                                                  FALSE, iterpool));
@@ -352,6 +352,8 @@
   const char *author;
   apr_time_t date;
 
+ apr_hash_t *props;
+
   /* Pool associated with this baton. */
   apr_pool_t *pool;
 };
@@ -465,6 +467,7 @@
   fb->edit_baton = eb;
   fb->path = full_path;
   fb->url = full_url;
+ fb->props = apr_hash_make (pool);
   fb->pool = pool;
 
   *baton = fb;
@@ -529,6 +532,9 @@
   if (! value)
     return SVN_NO_ERROR;
 
+ /* Store every property */
+ apr_hash_set(fb->props, name, APR_HASH_KEY_STRING, value);
+
   /* Store only the magic three properties. */
   if (strcmp (name, SVN_PROP_EOL_STYLE) == 0)
     fb->eol_style_val = svn_string_dup (value, fb->pool);
@@ -605,21 +611,21 @@
     {
       svn_subst_eol_style_t style;
       const char *eol;
- svn_subst_keywords_t final_kw = {0};
+ svn_subst_keywords_t *final_kw = apr_hash_make(pool);
 
       if (fb->eol_style_val)
         svn_subst_eol_style_from_value (&style, &eol, fb->eol_style_val->data);
 
       if (fb->keywords_val)
- SVN_ERR (svn_subst_build_keywords (&final_kw, fb->keywords_val->data,
- fb->revision, fb->url, fb->date,
- fb->author, pool));
+ SVN_ERR (svn_subst_build_keywords2 (final_kw, fb->keywords_val->data,
+ fb->revision, fb->url, fb->date,
+ fb->author, fb->props, pool));
 
       SVN_ERR (svn_subst_copy_and_translate
                (fb->tmppath, fb->path,
                 fb->eol_style_val ? eol : NULL,
                 fb->eol_style_val ? TRUE : FALSE, /* repair */
- fb->keywords_val ? &final_kw : NULL,
+ fb->keywords_val ? final_kw : NULL,
                 fb->keywords_val ? TRUE : FALSE, /* expand */
                 pool));
 
Index: /svn/trunk/subversion/libsvn_subr/subst.c
==================================================================
--- /svn/trunk/subversion/libsvn_subr/subst.c (/svn/trunk/subversion/libsvn_subr/subst.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_subr/subst.c (/svn/local/trunk/subversion/libsvn_subr/subst.c) (revision 7672)
@@ -114,6 +114,125 @@
   return SVN_NO_ERROR;
 }
 
+
+/* Helper function for svn_subst_build_keywords */
+
+/** Given a printf-like format string, return a string with proper
+ * information filled in.
+ *
+ * The codes of format:
+ *
+ * %a author of this revision
+ * %b basename of the URL of this file
+ * %d short format of date of this revision
+ * %D long format of date of this revision
+ * %p property value
+ * %r number of this revision
+ * %u URL of this file
+ *
+ * All memory is allocated out of @a pool.
+ */
+svn_string_t *
+keyword_printf (const char *fmt,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_hash_t *props,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *value = svn_stringbuf_ncreate ("", 0, pool);
+ const char *cur;
+ char ch;
+ int n;
+
+ for (;;)
+ {
+ for (cur = fmt; (ch = *cur) != '\0' && ch != '%'; cur++)
+ /* void */;
+ if ( (n = cur - fmt) > 0) /* Do we have a as-is string? */
+ svn_stringbuf_appendbytes (value, fmt, n);
+
+ if (ch == '\0')
+ break;
+
+ cur++; /* skip '%' */
+ ch = *cur++;
+ switch (ch)
+ {
+ case 'a': /* author of this revision */
+ if (author)
+ svn_stringbuf_appendcstr (value, author);
+ break;
+ case 'b': /* basename of this file */
+ if (url)
+ {
+ const char *base_name = NULL;
+ base_name = svn_path_basename (url, pool);
+
+ svn_stringbuf_appendcstr (value, base_name);
+ }
+ break;
+ case 'd': /* short format of date of this revision */
+ if (date)
+ {
+ const char *human_date = NULL;
+
+ if (!date_prop_to_human (&human_date, FALSE, date, pool))
+ svn_stringbuf_appendcstr (value, human_date);
+ }
+ break;
+ case 'D': /* long format of date of this revision */
+ if (date)
+ {
+ const char *human_date = NULL;
+
+ if (!date_prop_to_human (&human_date, TRUE, date, pool))
+ svn_stringbuf_appendcstr (value, human_date);
+ }
+ break;
+ case 'p': /* property value */
+ /* Fetch property name */
+ if (*cur != '(') /* Invalid format */
+ break;
+ for (fmt = ++cur; (ch = *cur) != '\0' && ch != ')'; cur++)
+ /* void */;
+ if (ch == '\0') /* Invalid format, rewind. */
+ {
+ cur = fmt - 1;
+ break;
+ }
+ /* Look up in props hash*/
+ if (props)
+ {
+ svn_string_t *prop_val;
+ prop_val = apr_hash_get(props, fmt, cur - fmt);
+ /* do no harm if they didn't include that property */
+ if ( prop_val )
+ svn_stringbuf_appendcstr (value, prop_val->data);
+ }
+ cur++;
+ break;
+ case 'r': /* number of this revision */
+ if (rev)
+ svn_stringbuf_appendcstr (value, rev);
+ break;
+ case 'u': /* URL of this file */
+ if (url)
+ svn_stringbuf_appendcstr (value, url);
+ break;
+ default: /* %?, print ? as is. */
+ svn_stringbuf_appendbytes (value, &ch, 1);
+ break;
+ }
+
+ /* Format code is processed. Get ready for next chunk. */
+ fmt = cur;
+ }
+
+ return svn_string_create_from_buf (value, pool);
+}
+
 svn_error_t *
 svn_subst_build_keywords (svn_subst_keywords_t *kw,
                           const char *keywords_val,
@@ -123,6 +242,20 @@
                           const char *author,
                           apr_pool_t *pool)
 {
+ return svn_subst_build_keywords2(kw, keywords_val, rev,
+ url, date, author, NULL, pool);
+}
+
+svn_error_t *
+svn_subst_build_keywords2 (svn_subst_keywords_t *kw,
+ const char *keywords_val,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_hash_t *props,
+ apr_pool_t *pool)
+{
   apr_array_header_t *keyword_tokens;
   int i;
 
@@ -136,45 +269,141 @@
       if ((! strcmp (keyword, SVN_KEYWORD_REVISION_LONG))
           || (! strcasecmp (keyword, SVN_KEYWORD_REVISION_SHORT)))
         {
- kw->revision = svn_string_create (rev, pool);
- }
+ svn_string_t *revision_val;
+
+ revision_val = keyword_printf (SVN_KEYWORD_REVISION_FORMAT,
+ rev, url, date, author,
+ props, pool);
+ apr_hash_set (kw, SVN_KEYWORD_REVISION_LONG,
+ APR_HASH_KEY_STRING, revision_val);
+ apr_hash_set (kw, SVN_KEYWORD_REVISION_SHORT,
+ APR_HASH_KEY_STRING, revision_val);
+ }
       else if ((! strcmp (keyword, SVN_KEYWORD_DATE_LONG))
                || (! strcasecmp (keyword, SVN_KEYWORD_DATE_SHORT)))
         {
           if (date)
             {
- const char *human_date;
+ svn_string_t *date_val;
 
- SVN_ERR (date_prop_to_human (&human_date, TRUE, date, pool));
-
- kw->date = svn_string_create (human_date, pool);
+ date_val = keyword_printf (SVN_KEYWORD_DATE_FORMAT,
+ rev, url, date, author,
+ props, pool);
+ apr_hash_set (kw, SVN_KEYWORD_DATE_LONG,
+ APR_HASH_KEY_STRING, date_val);
+ apr_hash_set (kw, SVN_KEYWORD_DATE_SHORT,
+ APR_HASH_KEY_STRING, date_val);
             }
           else
- kw->date = svn_string_create ("", pool);
+ {
+ svn_string_t *date_val;
+
+ date_val = svn_string_create ("", pool);
+ apr_hash_set (kw, SVN_KEYWORD_DATE_LONG,
+ APR_HASH_KEY_STRING, date_val);
+ apr_hash_set (kw, SVN_KEYWORD_DATE_SHORT,
+ APR_HASH_KEY_STRING, date_val);
+ }
         }
       else if ((! strcmp (keyword, SVN_KEYWORD_AUTHOR_LONG))
                || (! strcasecmp (keyword, SVN_KEYWORD_AUTHOR_SHORT)))
         {
- kw->author = svn_string_create (author ? author : "", pool);
+ svn_string_t *author_val;
+
+ author_val = keyword_printf (SVN_KEYWORD_AUTHOR_FORMAT,
+ rev, url, date, author,
+ props, pool);
+ apr_hash_set (kw, SVN_KEYWORD_AUTHOR_LONG,
+ APR_HASH_KEY_STRING, author_val);
+ apr_hash_set (kw, SVN_KEYWORD_AUTHOR_SHORT,
+ APR_HASH_KEY_STRING, author_val);
         }
       else if ((! strcmp (keyword, SVN_KEYWORD_URL_LONG))
                || (! strcasecmp (keyword, SVN_KEYWORD_URL_SHORT)))
         {
- kw->url = svn_string_create (url ? url : "", pool);
+ svn_string_t *url_val;
+
+ url_val = keyword_printf (SVN_KEYWORD_URL_FORMAT,
+ rev, url, date, author,
+ props, pool);
+ apr_hash_set (kw, SVN_KEYWORD_URL_LONG,
+ APR_HASH_KEY_STRING, url_val);
+ apr_hash_set (kw, SVN_KEYWORD_URL_SHORT,
+ APR_HASH_KEY_STRING, url_val);
         }
       else if ((! strcasecmp (keyword, SVN_KEYWORD_ID)))
         {
           const char *base_name = url ? svn_path_basename (url, pool) : "";
           const char *human_date = NULL;
+ svn_string_t *id_val;
 
- if (date)
- SVN_ERR (date_prop_to_human (&human_date, FALSE, date, pool));
+ id_val = keyword_printf (SVN_KEYWORD_ID_FORMAT,
+ rev, url, date, author,
+ props, pool);
+ apr_hash_set (kw, SVN_KEYWORD_ID,
+ APR_HASH_KEY_STRING, id_val);
+ }
+ /* Whenever server-side configuration is developed, a test could be
+ * placed here to check and see if one of the repository-defined
+ * keywords matches
+ */
+ else /* a keyword that doesn't match (may be a property) */
+ {
+ if (props)
+ {
+ svn_string_t *prop_val =
+ apr_hash_get(props, keyword, strlen(keyword));
 
- kw->id = svn_string_createf (pool, "%s %s %s %s",
- base_name,
- rev,
- human_date ? human_date : "",
- author ? author : "");
+ /* do no harm if they didn't set that property */
+ if (prop_val)
+ {
+ char *found_nl;
+ svn_error_t *warn = NULL;
+ svn_stringbuf_t *prop_buf =
+ svn_stringbuf_create_from_string (prop_val, pool);
+ apr_size_t last_char = prop_buf->len;
+
+ /* make sure the property is only a single line */
+ if ((found_nl = strchr (prop_buf->data, '\n')) != NULL)
+ {
+ warn = svn_error_createf (SVN_ERR_BAD_PROP_KIND, NULL,
+ "Property used as keyword "
+ "contains newline: '%s'\n",
+ keyword);
+
+ /* shorten the string to the newline character */
+ last_char = (apr_size_t)(found_nl - prop_buf->data);
+ }
+
+ /* cannot have more than SVN_KEYWORD_MAX_LEN characters
+ * in a keyword value
+ */
+ if ( last_char > SVN_KEYWORD_MAX_LEN )
+ {
+ warn = svn_error_createf (SVN_ERR_BAD_PROP_KIND, NULL,
+ "Property used as keyword "
+ "contains too many characters:"
+ "'%s'\n", keyword);
+
+ last_char = SVN_KEYWORD_MAX_LEN;
+ }
+
+ /* emit a warning if one has been created */
+ if (warn)
+ {
+ svn_handle_warning (stderr, warn);
+ }
+
+ /* chop any extra characters off */
+ if (last_char < prop_buf->len)
+ svn_stringbuf_chop (prop_buf,
+ (prop_buf->len - last_char));
+
+ apr_hash_set (kw, keyword,
+ APR_HASH_KEY_STRING,
+ svn_string_create_from_buf(prop_buf,pool));
+ }
+ }
         }
     }
 
@@ -331,6 +560,13 @@
                    svn_boolean_t expand,
                    const svn_subst_keywords_t *keywords)
 {
+ const svn_string_t *value;
+ char key[SVN_KEYWORD_MAX_LEN + 1];
+ svn_subst_keywords_t *lame_keywords;
+ int i;
+
+ lame_keywords = (svn_subst_keywords_t *) keywords;
+
   /* Make sure we gotz good stuffs. */
   assert (*len <= SVN_KEYWORD_MAX_LEN);
   assert ((buf[0] == '$') && (buf[*len - 1] == '$'));
@@ -339,81 +575,20 @@
   if (! keywords)
     return FALSE;
 
- /* Revision */
- if (keywords->revision)
- {
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_REVISION_LONG,
- (sizeof (SVN_KEYWORD_REVISION_LONG)) - 1,
- expand ? keywords->revision : NULL))
- return TRUE;
+ for (i = 0; i < *len - 2 && buf[i + 1] != ':'; i++)
+ key[i] = *(buf + i + 1);
+ key[i] = 0;
 
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_REVISION_SHORT,
- (sizeof (SVN_KEYWORD_REVISION_SHORT)) - 1,
- expand ? keywords->revision : NULL))
- return TRUE;
- }
+ value = apr_hash_get (lame_keywords, key, APR_HASH_KEY_STRING);
 
- /* Date */
- if (keywords->date)
+ if (value)
     {
       if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_DATE_LONG,
- (sizeof (SVN_KEYWORD_DATE_LONG)) - 1,
- expand ? keywords->date : NULL))
+ key, strlen(key),
+ expand ? value : NULL))
         return TRUE;
-
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_DATE_SHORT,
- (sizeof (SVN_KEYWORD_DATE_SHORT)) - 1,
- expand ? keywords->date : NULL))
- return TRUE;
     }
 
- /* Author */
- if (keywords->author)
- {
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_AUTHOR_LONG,
- (sizeof (SVN_KEYWORD_AUTHOR_LONG)) - 1,
- expand ? keywords->author : NULL))
- return TRUE;
-
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_AUTHOR_SHORT,
- (sizeof (SVN_KEYWORD_AUTHOR_SHORT)) - 1,
- expand ? keywords->author : NULL))
- return TRUE;
- }
-
- /* URL */
- if (keywords->url)
- {
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_URL_LONG,
- (sizeof (SVN_KEYWORD_URL_LONG)) - 1,
- expand ? keywords->url : NULL))
- return TRUE;
-
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_URL_SHORT,
- (sizeof (SVN_KEYWORD_URL_SHORT)) - 1,
- expand ? keywords->url : NULL))
- return TRUE;
- }
-
- /* Id */
- if (keywords->id)
- {
- if (translate_keyword_subst (buf, len,
- SVN_KEYWORD_ID,
- (sizeof (SVN_KEYWORD_ID)) - 1,
- expand ? keywords->id : NULL))
- return TRUE;
- }
-
- /* No translations were successful. Return FALSE. */
   return FALSE;
 }
 
@@ -473,63 +648,52 @@
                            const svn_subst_keywords_t *b,
                            svn_boolean_t compare_values)
 {
+ svn_boolean_t result = FALSE;
+ apr_pool_t *pool = svn_pool_create (NULL);
+
+ result = svn_subst_keywords_differ2 (a,b,compare_values,pool);
+
+ /* Always clean up after ourselves */
+ svn_pool_destroy (pool);
+ return result;
+}
+
+svn_boolean_t
+svn_subst_keywords_differ2 (const svn_subst_keywords_t *a,
+ const svn_subst_keywords_t *b,
+ svn_boolean_t compare_values,
+ apr_pool_t *pool)
+{
+ svn_boolean_t result = FALSE;
+ apr_hash_index_t *hi;
+ svn_subst_keywords_t *lame_a, *lame_b;
+
+ lame_a = (svn_subst_keywords_t *) a;
+ lame_b = (svn_subst_keywords_t *) b;
+
   if (((a == NULL) && (b == NULL)) /* no A or B */
- /* no A, and B has no contents */
- || ((a == NULL)
- && (b->revision == NULL)
- && (b->date == NULL)
- && (b->author == NULL)
- && (b->url == NULL))
- /* no B, and A has no contents */
- || ((b == NULL) && (a->revision == NULL)
- && (a->date == NULL)
- && (a->author == NULL)
- && (a->url == NULL))
- /* neither A nor B has any contents */
- || ((a != NULL) && (b != NULL)
- && (b->revision == NULL)
- && (b->date == NULL)
- && (b->author == NULL)
- && (b->url == NULL)
- && (a->revision == NULL)
- && (a->date == NULL)
- && (a->author == NULL)
- && (a->url == NULL)))
+ /* Unequal number of contents */
+ || (apr_hash_count(lame_a) != apr_hash_count(lame_b)))
+ return TRUE;
+
+ /* If compare_values is FALSE, we can say A and B are the same now. */
+ if (!compare_values)
+ return FALSE;
+
+ /* compare_values is TRUE. Compare value by value */
+ for (hi = apr_hash_first(pool, lame_a);
+ hi && !result;
+ hi = apr_hash_next(hi))
     {
- return FALSE;
+ const char *key;
+
+ apr_hash_this (hi, (const void**) &key, NULL, NULL);
+ if (!svn_string_compare (apr_hash_get (lame_a, key, APR_HASH_KEY_STRING),
+ apr_hash_get (lame_b, key, APR_HASH_KEY_STRING)))
+ result = TRUE;
     }
- else if ((a == NULL) || (b == NULL))
- return TRUE;
-
- /* Else both A and B have some keywords. */
-
- if ((! a->revision) != (! b->revision))
- return TRUE;
- else if ((compare_values && (a->revision != NULL))
- && (strcmp (a->revision->data, b->revision->data) != 0))
- return TRUE;
-
- if ((! a->date) != (! b->date))
- return TRUE;
- else if ((compare_values && (a->date != NULL))
- && (strcmp (a->date->data, b->date->data) != 0))
- return TRUE;
-
- if ((! a->author) != (! b->author))
- return TRUE;
- else if ((compare_values && (a->author != NULL))
- && (strcmp (a->author->data, b->author->data) != 0))
- return TRUE;
-
- if ((! a->url) != (! b->url))
- return TRUE;
- else if ((compare_values && (a->url != NULL))
- && (strcmp (a->url->data, b->url->data) != 0))
- return TRUE;
-
- /* Else we never found a difference, so they must be the same. */
-
- return FALSE;
+
+ return result;
 }
 
 
Index: /svn/trunk/subversion/libsvn_wc/props.c
==================================================================
--- /svn/trunk/subversion/libsvn_wc/props.c (/svn/trunk/subversion/libsvn_wc/props.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_wc/props.c (/svn/local/trunk/subversion/libsvn_wc/props.c) (revision 7672)
@@ -1126,7 +1126,7 @@
       SVN_ERR (svn_wc__get_keywords (&new_keywords, path, adm_access, NULL,
                                      pool));
 
- if (svn_subst_keywords_differ (old_keywords, new_keywords, FALSE))
+ if (svn_subst_keywords_differ2 (old_keywords, new_keywords, FALSE, pool))
         {
           const char *base_name;
           svn_wc_entry_t tmp_entry;
Index: /svn/trunk/subversion/libsvn_wc/translate.c
==================================================================
--- /svn/trunk/subversion/libsvn_wc/translate.c (/svn/trunk/subversion/libsvn_wc/translate.c) (revision 7672)
+++ /svn/trunk/subversion/libsvn_wc/translate.c (/svn/local/trunk/subversion/libsvn_wc/translate.c) (revision 7672)
@@ -177,11 +177,12 @@
                       apr_pool_t *pool)
 {
   const char *list;
- svn_subst_keywords_t tmp_keywords = { 0 };
+ svn_subst_keywords_t *tmp_keywords = apr_hash_make(pool);
   const svn_wc_entry_t *entry = NULL;
+ apr_hash_t *props;
 
   /* Start by assuming no keywords. */
- *keywords = NULL;
+ *keywords = apr_hash_make(pool);
 
   /* Choose a property list to parse: either the one that came into
      this function, or the one attached to PATH. */
@@ -203,16 +204,18 @@
 
   SVN_ERR (svn_wc_entry (&entry, path, adm_access, FALSE, pool));
 
- SVN_ERR (svn_subst_build_keywords (&tmp_keywords,
- list,
- apr_psprintf (pool, "%" SVN_REVNUM_T_FMT,
- entry->cmt_rev),
- entry->url,
- entry->cmt_date,
- entry->cmt_author,
- pool));
+ SVN_ERR (svn_wc_prop_list (&props, path, adm_access, pool));
+ SVN_ERR (svn_subst_build_keywords2 (tmp_keywords,
+ list,
+ apr_psprintf (pool, "%" SVN_REVNUM_T_FMT,
+ entry->cmt_rev),
+ entry->url,
+ entry->cmt_date,
+ entry->cmt_author,
+ props,
+ pool));
 
- *keywords = apr_pmemdup (pool, &tmp_keywords, sizeof (tmp_keywords));
+ *keywords = tmp_keywords;
       
   return SVN_NO_ERROR;
 }
Index: /svn/trunk/subversion/tests/clients/cmdline/prop_tests.py
==================================================================
--- /svn/trunk/subversion/tests/clients/cmdline/prop_tests.py (/svn/trunk/subversion/tests/clients/cmdline/prop_tests.py) (revision 7672)
+++ /svn/trunk/subversion/tests/clients/cmdline/prop_tests.py (/svn/local/trunk/subversion/tests/clients/cmdline/prop_tests.py) (revision 7672)
@@ -1072,7 +1072,171 @@
   verify_output([ prop1 + ' : ' + propval1, prop2 + ' : ' + propval2,
                   'Properties on ' ], output, errput)
 
+#----------------------------------------------------------------------
+
+def keyword_props(sbox):
+ "keyword substitution from properties"
+
+ # Bootstrap
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ prop1 = 'prop1'
+ propval1 = 'propval1'
+ newpropval1 = 'newpropval1'
+ multilineprop = 'line one\nline two\nline three'
+ longprop = '12345' * 100
+ keywords = 'svn:keywords'
+
+ mu_path = os.path.join(sbox.wc_dir, 'A', 'mu')
+ svntest.main.file_append (mu_path, '\n$prop1$\n')
+
+ # Enable the keyword on that property, but don't set the property yet
+ svntest.main.run_svn(None, 'propset', keywords, prop1, mu_path)
+
+ # Create expected output tree.
+ expected_output = svntest.wc.State(wc_dir, {
+ 'A/mu' : Item(verb='Sending'),
+ })
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=2)
+ expected_status.tweak('A/mu', wc_rev=2, status=' ')
+
+ # Commit the one file.
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ None,
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text != "This is the file 'mu'.\n$prop1$\n":
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
+
+ # Now set the property and make sure that it actually expands
+ svntest.main.run_svn(None, 'propset', prop1, propval1, mu_path)
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=3)
+ expected_status.tweak('A/mu', wc_rev=3, status=' ')
+
+ # Commit the file again
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ None,
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's still right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text != "This is the file 'mu'.\n$prop1: propval1 $\n":
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
   
+ # Change the property
+ svntest.main.run_svn(None, 'propset', prop1, newpropval1, mu_path)
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=4)
+ expected_status.tweak('A/mu', wc_rev=4, status=' ')
+
+ # Commit the file again
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ None,
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's still right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text != "This is the file 'mu'.\n$prop1: newpropval1 $\n":
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
+
+ # Delete the property, but not the keyword
+ svntest.main.run_svn(None, 'propdel', prop1, mu_path)
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=5)
+ expected_status.tweak('A/mu', wc_rev=5, status=' ')
+
+ # Commit the file again
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ None,
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's still right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text != "This is the file 'mu'.\n$prop1: newpropval1 $\n":
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
+
+ # Re-add the property but with a multiline one
+ svntest.main.run_svn(None, 'propset', prop1, multilineprop, mu_path)
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=6)
+ expected_status.tweak('A/mu', wc_rev=6, status=' ')
+
+ # Commit the file again
+ # output, errput = svntest.actions.run_and_verify_svn(
+ # None, None, SVNAnyOutput,
+ # 'log', 'file:///nonexistent_path')
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ '.*keyword contains newline.*',
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's still right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text != "This is the file 'mu'.\n$prop1: line one $\n":
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
+
+ # Add a property with too many characters
+ svntest.main.run_svn(None, 'propset', prop1, longprop, mu_path)
+
+ # Created expected status tree.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak(repos_rev=7)
+ expected_status.tweak('A/mu', wc_rev=7, status=' ')
+
+ # Commit the file again
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ '.*contains too many characters.*',
+ None, None,
+ None, None,
+ wc_dir)
+
+ # Inspect mu, make sure it's still right.
+ mu_text = svntest.tree.get_text(mu_path)
+ if mu_text.count('12345') != 50 :
+ print "Unexpected text in keyword expanded '" + mu_path + "'"
+ raise svntest.Failure
+
 ########################################################################
 # Run the tests
 
@@ -1096,6 +1260,7 @@
               binary_props,
               recursive_base_wc_ops,
               url_props_ops,
+ keyword_props,
              ]
 
 if __name__ == '__main__':
Index: /svn/trunk/subversion/tests/libsvn_wc/translate-test.c
==================================================================
--- /svn/trunk/subversion/tests/libsvn_wc/translate-test.c (/svn/trunk/subversion/tests/libsvn_wc/translate-test.c) (revision 7672)
+++ /svn/trunk/subversion/tests/libsvn_wc/translate-test.c (/svn/local/trunk/subversion/tests/libsvn_wc/translate-test.c) (revision 7672)
@@ -264,26 +264,54 @@
 {
   svn_error_t *err;
   svn_stringbuf_t *contents;
- svn_subst_keywords_t keywords;
+ svn_subst_keywords_t *keywords = apr_hash_make(pool);
   apr_size_t idx = 0;
   apr_size_t i;
   const char *expect[(sizeof (lines) / sizeof (*lines))];
   const char *src_fname = apr_pstrcat (pool, test_name, ".src", NULL);
   const char *dst_fname = apr_pstrcat (pool, test_name, ".dst", NULL);
+ svn_string_t *val;
 
   /** Clean up from previous tests, set up src data, and convert. **/
   SVN_ERR (remove_file (src_fname, pool));
   SVN_ERR (remove_file (dst_fname, pool));
   SVN_ERR (create_file (src_fname, src_eol, pool));
 
- keywords.revision = rev ? svn_string_create (rev, pool) : NULL;
- keywords.date = date ? svn_string_create (date, pool) : NULL;
- keywords.author = author ? svn_string_create (author, pool) : NULL;
- keywords.url = url ? svn_string_create (url, pool) : NULL;
- keywords.id = NULL;
+ if (rev)
+ {
+ val = svn_string_create (rev, pool);
+ apr_hash_set(keywords, SVN_KEYWORD_REVISION_LONG,
+ APR_HASH_KEY_STRING, val);
+ apr_hash_set(keywords, SVN_KEYWORD_REVISION_SHORT,
+ APR_HASH_KEY_STRING, val);
+ }
+ if (date)
+ {
+ val = svn_string_create (date, pool);
+ apr_hash_set(keywords, SVN_KEYWORD_DATE_LONG,
+ APR_HASH_KEY_STRING, val);
+ apr_hash_set(keywords, SVN_KEYWORD_DATE_SHORT,
+ APR_HASH_KEY_STRING, val);
+ }
+ if (author)
+ {
+ val = svn_string_create (author, pool);
+ apr_hash_set(keywords, SVN_KEYWORD_AUTHOR_LONG,
+ APR_HASH_KEY_STRING, val);
+ apr_hash_set(keywords, SVN_KEYWORD_AUTHOR_SHORT,
+ APR_HASH_KEY_STRING, val);
+ }
+ if (url)
+ {
+ val = svn_string_create (url, pool);
+ apr_hash_set(keywords, SVN_KEYWORD_URL_LONG,
+ APR_HASH_KEY_STRING, val);
+ apr_hash_set(keywords, SVN_KEYWORD_URL_SHORT,
+ APR_HASH_KEY_STRING, val);
+ }
 
   err = svn_subst_copy_and_translate (src_fname, dst_fname, dst_eol, repair,
- &keywords, expand, pool);
+ keywords, expand, pool);
 
 
   /* Conversion should have failed, if src has mixed eol, and the
Index: /svn/trunk/doc/book/book/ch07.xml
==================================================================
--- /svn/trunk/doc/book/book/ch07.xml (/svn/trunk/doc/book/book/ch07.xml) (revision 7672)
+++ /svn/trunk/doc/book/book/ch07.xml (/svn/local/trunk/doc/book/book/ch07.xml) (revision 7672)
@@ -1396,6 +1396,17 @@
                 <literal>sally</literal>.</para>
             </listitem>
           </varlistentry>
+ <varlistentry>
+ <term><literal>[property]</literal></term>
+ <listitem>
+ <para>In addition to the predefined keywords, any file property
+ that meets certain basic characteristics can be used as a
+ keyword. As long as a property contains only a single line of
+ text and is less than 250 characters long, it can be used as a
+ keyword. Both the <literal>svn:keywords</literal> and the
+ actual property have to be set in order to substitute in the
+ file. See below for more details.</para>
+ </listitem>
         </variablelist>
 
         <para>Simply adding keyword anchor text to your file does
@@ -1495,6 +1506,55 @@
           file will be re-substituted with information that
           reflects the most recent known commit to that file.</para>
 
+ <title><literal>Properties as keywords</literal></title>
+
+ <para>If you wish to use a generic property as a keyword in a file,
+ you must do two things: set the property on the file, and include
+ the property name in the <literal>svn:keywords</literal> list. For
+ example, if you have a file <filename>myproj.pl</filename>like
+ this:</para>
+
+ <programlisting>
+# this is a simple Perl script
+use 5.006;
+$VERSION = qw$Version$[1];
+print $VERSION;
+</programlisting>
+
+ <para>you could then set the following properties:</para>
+
+ <screen>
+$ svn propset Version "1.2.3" myproj.pl
+$ svn propset svn:keywords "Version" myproj.pl
+</screen>
+
+ <para>and once the file has been committed to the repository, the
+ contents of the keyword will be be substituted by the
+ property. This feature will by much more useful when inherited
+ properties become functional, so that a single property set at the
+ top of a directory tree would be applied to all files contained
+ within.</para>
+
+ <sidebar>
+ <title>Important limitations of properties used as keywords</title>
+
+ <para>Properties that are used as a keyword cannot exceed 250
+ characters or contain more than a single line. If the property
+ violates those contraints, a warning will be issued and the
+ keyword expansion will be limited to meet the limitations. The
+ property itself will not be altered, but the keyword expansion
+ will be limited to 250 characters or a single line, whichever is
+ applicable.</para>
+
+ <para>In addition, since the property as keyword expansion requires
+ both the property to be set, as well as the property name be
+ included in the <literal>svn:keywords</literal>, if either one of
+ those is unset after being set, the keyword expansion will not be
+ undone. In essence, if either element is removed, the keyword
+ expansion becomes permanent (although the entire block could be
+ deleted).</para>
+ </sidebar>
+
       </sect3>
 
       <sect3 id="svn-ch-7-sect-2.3.5">

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Mar 20 04:58:52 2004

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.