Index: subversion/include/svn_subst.h
===================================================================
--- subversion/include/svn_subst.h (revision 13318)
+++ subversion/include/svn_subst.h (working copy)
@@ -84,6 +84,11 @@
const svn_string_t *url;
const svn_string_t *id;
} svn_subst_keywords_t;
+/** NOTE : The static function svn_canonicalize_keywords() in
+ * props.c contains references to the SVN_KEYWORD_... macros.
+ * This function has to be updated too in case a new keyword
+ * is defined.
+ */
/** Fill in an svn_subst_keywords_t * @a kw with the appropriate
Index: subversion/libsvn_wc/props.c
===================================================================
--- subversion/libsvn_wc/props.c (revision 13317)
+++ subversion/libsvn_wc/props.c (working copy)
@@ -987,6 +987,82 @@
}
+/** Return in an svn_stringbuf_t * with the
+ * canonicalized equivalent of @a value
+ * Repeated instances of the same keyword are ignored.
+ *
+ * All memory is allocated out of @a pool.
+ */
+static svn_stringbuf_t *
+svn_canonicalize_keywords (const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ /* Structure to be used for canonicalization of svn:keywords */
+ typedef struct canon_table_s
+ {
+ const char *prop_value; /* the property value */
+ const int index; /* the index to the corresponding canonical form */
+ const int flag; /* bit pattern to flag that this canonical form
+ ** is already set */
+ } canon_table_t;
+ static canon_table_t canon_kw_table[] =
+ {
+ { SVN_KEYWORD_REVISION_LONG, 2, 0x0001 },
+ { SVN_KEYWORD_REVISION_SHORT, 2, 0x0001 },
+ { SVN_KEYWORD_REVISION_MEDIUM, 2, 0x0001 },
+ { SVN_KEYWORD_DATE_LONG, 4, 0x0002 },
+ { SVN_KEYWORD_DATE_SHORT, 4, 0x0002 },
+ { SVN_KEYWORD_AUTHOR_LONG, 6, 0x0004 },
+ { SVN_KEYWORD_AUTHOR_SHORT, 6, 0x0004 },
+ { SVN_KEYWORD_URL_SHORT, 7, 0x0008 },
+ { SVN_KEYWORD_URL_LONG, 7, 0x0008 },
+ { SVN_KEYWORD_ID, 9, 0x0010 }
+ };
+ const int sizeof_canon_kw_table =
+ sizeof(canon_kw_table)/sizeof(canon_table_t) ;
+
+ apr_array_header_t *keyword_tokens;
+ svn_stringbuf_t *canonicalized_value = NULL ;
+ int flags = 0 ;
+ int i, j;
+
+ /* tokenize the input */
+ keyword_tokens = svn_cstring_split (value->data, " \t\v\n\b\r\f",
+ TRUE /* chop */, pool);
+
+ /* for all the tokens */
+ for (i = 0; i < keyword_tokens->nelts; ++i)
+ {
+ const char *keyword = APR_ARRAY_IDX (keyword_tokens, i, const char *);
+
+ for (j = 0; j < sizeof_canon_kw_table; j++)
+ {
+ /* see if a equivalent standard form exists */
+ if ((! strcasecmp (canon_kw_table[j].prop_value, keyword))
+ && (! (flags & canon_kw_table[j].flag )))
+ {
+ /* If so, canonicalize and prepare output */
+ if (!canonicalized_value)
+ canonicalized_value =
+ svn_stringbuf_create (
+ canon_kw_table[canon_kw_table[j].index].prop_value,
+ pool);
+ else
+ {
+ svn_stringbuf_appendcstr (canonicalized_value, " ");
+ svn_stringbuf_appendcstr (
+ canonicalized_value,
+ canon_kw_table[canon_kw_table[j].index].prop_value);
+ }
+ flags |= canon_kw_table[j].flag ;
+ break; /* goto the next token from the input */
+ }
+ }
+ }
+
+ return canonicalized_value ;
+}
+
svn_error_t *
svn_wc_prop_set2 (const char *name,
const svn_string_t *value,
@@ -1061,7 +1137,7 @@
}
else if (strcmp (name, SVN_PROP_KEYWORDS) == 0)
{
- new_value = svn_stringbuf_create_from_string (value, pool);
+ new_value = svn_canonicalize_keywords (value, pool);
svn_stringbuf_strip_whitespace (new_value);
}
}
Index: subversion/tests/clients/cmdline/prop_tests.py
===================================================================
--- subversion/tests/clients/cmdline/prop_tests.py (revision 13318)
+++ subversion/tests/clients/cmdline/prop_tests.py (working copy)
@@ -857,8 +857,8 @@
['foo http://foo.com/repos'+os.linesep])
# Check svn:keywords
- check_prop('svn:keywords', iota_path, ['Rev Date'])
- check_prop('svn:keywords', mu_path, ['Rev Date'])
+ check_prop('svn:keywords', iota_path, ['Revision Date'])
+ check_prop('svn:keywords', mu_path, ['Revision Date'])
# Check svn:executable
check_prop('svn:executable', iota_path, ['*'])
Index: subversion/tests/clients/cmdline/trans_tests.py
===================================================================
--- subversion/tests/clients/cmdline/trans_tests.py (revision 13318)
+++ subversion/tests/clients/cmdline/trans_tests.py (working copy)
@@ -714,7 +714,184 @@
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+#----------------------------------------------------------------------
+# Testing for canonicalized input to svn:keywords
+# Propset a case-different keyword and check if
+# expansion works
+def canonicalize_keywords_prop(sbox):
+ "test canonocalization of the svn:keywords input"
+
+ given_input = [
+ "lastchangedrevision",
+ "lastcHANgedREviSIoN",
+ "revision",
+ "rev",
+ "lastchangedby",
+ "author",
+ "aUtHoR",
+ "headurl",
+ "url",
+ "lasTChangEDdate",
+ "date",
+ "DaTe",
+ "id",
+ "lastchangedrevision author LastChangedBy"
+ ]
+ expected_output = [
+ ["Revision\n"],
+ ["Revision\n"],
+ ["Revision\n"],
+ ["Revision\n"],
+ ["Author\n"],
+ ["Author\n"],
+ ["Author\n"],
+ ["URL\n"],
+ ["URL\n"],
+ ["Date\n"],
+ ["Date\n"],
+ ["Date\n"],
+ ["Id\n"],
+ ["Revision Author\n"]
+ ]
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ iota_path = os.path.join(wc_dir, 'iota')
+ for ctr in range(len(expected_output)):
+ svntest.actions.run_and_verify_svn(None, None, [], 'propset',
+ "svn:keywords",
+ given_input[ctr],
+ iota_path)
+ svntest.actions.run_and_verify_svn(None, expected_output[ctr], [], 'propget',
+ "svn:keywords",
+ iota_path)
+
+
+#----------------------------------------------------------------------
+# Testing for expansion of keywords in the file
+# in addition to canonicalizable input to svn:keywords
+# Propset a case-different keyword and check if
+# expansion works
+def canonicalized_and_keywords_expanded(sbox):
+ "test keyword expansion after canonicalization"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ Z_path = os.path.join(wc_dir, 'Z')
+ svntest.actions.run_and_verify_svn (None, None, [], 'mkdir', Z_path)
+
+ # Add the file that has the keyword to be expanded
+ url_path = os.path.join(Z_path, 'url')
+ svntest.main.file_append (url_path, """This is the file : url.
+
+LastChangedRevision:$LastChangedRevision$
+lastchangedrevision:$lastchangedrevision$
+Revision:$Revision$
+revision:$revision$
+Rev:$Rev$
+rev:$rev$
+
+LastChangedDate:$LastChangedDate$
+lastchangeddate:$lastchangeddate$
+Date:$Date$
+date:$date$
+
+LastChangedBy:$LastChangedBy$
+lastchangedby:$lastchangedby$
+Author:$Author$
+author:$author$
+
+HeadURL:$HeadURL$
+headurl:$headurl$
+URL:$URL$
+url:$url$
+""")
+ svntest.actions.run_and_verify_svn (None, None, [], 'add', url_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'propset',
+ "svn:keywords",
+ "lastchangedrevision lastchangeddate lastchangedby headurl",
+ url_path)
+
+
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'ci', '-m', 'log msg', wc_dir)
+
+ # Check keyword got expanded (and thus the mkdir, add, ps, commit
+ # etc. worked)
+ fp = open(url_path, 'r')
+ lines = fp.readlines()
+ if (len(lines) == 23):
+ # LastChangedRevision
+ if(re.match("LastChangedRevision:\$LastChangedRevision\$", lines[2])):
+ print "LastChangedRevision expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("lastchangedrevision:\$lastchangedrevision\$", lines[3])):
+ print "lastchangedrevision expansion failed for", url_path
+ raise svntest.Failure
+ #Revision
+ if(re.match("Revision:\$Revision\$", lines[4])):
+ print "Revision expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("revision:\$revision\$", lines[5])):
+ print "revision expansion failed for", url_path
+ raise svntest.Failure
+ #Rev
+ if(re.match("Rev:\$Rev\$", lines[6])):
+ print "Rev expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("rev:\$rev\$", lines[7])):
+ print "rev expansion failed for", url_path
+ raise svntest.Failure
+ #LastChangedDate
+ if(re.match("LastChangedDate:\$LastChangedDate\$", lines[9])):
+ print "LastChangedDate expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("lastchangeddate:\$lastchangeddate\$", lines[10])):
+ print "lastchangeddate expansion failed for", url_path
+ raise svntest.Failure
+ #Date
+ if(re.match("Date:\$Date\$", lines[11])):
+ print "Date expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("date:\$date\$", lines[12])):
+ print "date expansion failed for", url_path
+ raise svntest.Failure
+ #LastChangedBy
+ if(re.match("LastChangedBy:\$LastChangedBy\$", lines[14])):
+ print "LastChangedBy expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("lastchangedby:\$lastchangedby\$", lines[15])):
+ print "lastchangedby expansion failed for", url_path
+ raise svntest.Failure
+ #Author
+ if(re.match("Author:\$Author\$", lines[16])):
+ print "Author expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("author:\$author\$", lines[17])):
+ print "author expansion failed for", url_path
+ raise svntest.Failure
+ #HeadURL
+ if not (re.match("HeadURL:\$HeadURL: (http|file|svn|svn\\+ssh)://", lines[19])):
+ print "HeadURL expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("headurl:\$headurl\$", lines[20])):
+ print "headurl expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("URL:\$URL: (http|file|svn|svn\\+ssh)://", lines[21])):
+ print "URL expansion failed for", url_path
+ raise svntest.Failure
+ if not (re.match("url:\$url\$", lines[22])):
+ print "url expansion failed for", url_path
+ raise svntest.Failure
+ else:
+ print "File is in an inconsistent state"
+ raise svntest.Failure
+ fp.close()
+
########################################################################
# Run the tests
@@ -732,6 +909,8 @@
copy_propset_commit,
propset_commit_checkout_nocrash,
propset_revert_noerror,
+ canonicalize_keywords_prop,
+ canonicalized_and_keywords_expanded,
]
if __name__ == '__main__':