Index: subversion/include/svn_opt.h =================================================================== --- subversion/include/svn_opt.h (revision 15370) +++ subversion/include/svn_opt.h (working copy) @@ -234,7 +234,8 @@ * * Use @a pool for temporary allocations. */ -int svn_opt_parse_revision (svn_opt_revision_t *start_revision, +svn_error_t * + svn_opt_parse_revision (svn_opt_revision_t *start_revision, svn_opt_revision_t *end_revision, const char *arg, apr_pool_t *pool); Index: subversion/libsvn_subr/opt.c =================================================================== --- subversion/libsvn_subr/opt.c (revision 15370) +++ subversion/libsvn_subr/opt.c (working copy) @@ -315,9 +315,13 @@ /* Parse one revision specification. Return pointer to character after revision, or NULL if the revision is invalid. Modifies str, so make sure to pass a copy of anything precious. Uses - POOL for temporary allocation. */ + POOL for temporary allocation. + + @a ret_error allows us to pass up error data without needing to + completely refactor the rest of this function. +*/ static char *parse_one_rev (svn_opt_revision_t *revision, char *str, - apr_pool_t *pool) + apr_pool_t *pool, svn_error_t **err) { char *end, save; @@ -325,7 +329,6 @@ { svn_boolean_t matched; apr_time_t tm; - svn_error_t *err; /* Brackets denote a date. */ str++; @@ -333,12 +336,9 @@ if (!end) return NULL; *end = '\0'; - err = svn_parse_date (&matched, &tm, str, apr_time_now (), pool); - if (err) - { - svn_error_clear (err); + *err = svn_parse_date (&matched, &tm, str, apr_time_now (), pool); + if (*err) return NULL; - } if (!matched) return NULL; revision->kind = svn_opt_revision_date; @@ -355,7 +355,7 @@ *end = '\0'; revision->kind = svn_opt_revision_number; revision->value.number = SVN_STR_TO_REV (str); - *end = save; + *end = save; return end; } else if (apr_isalpha (*str)) @@ -371,33 +371,38 @@ return end; } else - return NULL; + { + *err = svn_error_create(SVN_ERR_INCORRECT_PARAMS,NULL,NULL); + return NULL; + } } -int +svn_error_t * svn_opt_parse_revision (svn_opt_revision_t *start_revision, svn_opt_revision_t *end_revision, const char *arg, apr_pool_t *pool) { char *left_rev, *right_rev, *end; + svn_error_t **err; /* Operate on a copy of the argument. */ left_rev = apr_pstrdup (pool, arg); + right_rev = parse_one_rev (start_revision, left_rev, pool, &err); - right_rev = parse_one_rev (start_revision, left_rev, pool); if (right_rev && *right_rev == ':') { right_rev++; - end = parse_one_rev (end_revision, right_rev, pool); + /* Will this overwrite of the err variable cause a problem? */ + end = parse_one_rev (end_revision, right_rev, pool, &err); if (!end || *end != '\0') - return -1; + return (svn_error_t*) err; // pass the error up } else if (!right_rev || *right_rev != '\0') - return -1; + return (svn_error_t*) err; - return 0; + return SVN_NO_ERROR; } @@ -486,6 +491,7 @@ apr_pool_t *pool) { int i; + svn_error_t *err = '\0'; /* scanning from right to left, just to be friendly to any screwed-up filenames that might *actually* contain @-signs. :-) */ @@ -499,7 +505,6 @@ if (path[i] == '@') { svn_boolean_t is_url; - int ret; svn_opt_revision_t start_revision, end_revision; end_revision.kind = svn_opt_revision_unspecified; @@ -511,23 +516,23 @@ { if (is_url) { - ret = svn_opt_parse_revision (&start_revision, &end_revision, + err = svn_opt_parse_revision (&start_revision, &end_revision, "head", pool); } else { - ret = svn_opt_parse_revision (&start_revision, &end_revision, + err = svn_opt_parse_revision (&start_revision, &end_revision, "base", pool); } } else /* looking at non-empty peg revision */ { - ret = svn_opt_parse_revision (&start_revision, + err = svn_opt_parse_revision (&start_revision, &end_revision, path + i + 1, pool); } - if (ret || end_revision.kind != svn_opt_revision_unspecified) + if (err || end_revision.kind != svn_opt_revision_unspecified) return svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Syntax error parsing revision '%s'"), path + i + 1); Index: subversion/libsvn_subr/date.c =================================================================== --- subversion/libsvn_subr/date.c (revision 15370) +++ subversion/libsvn_subr/date.c (working copy) @@ -203,14 +203,22 @@ if (apr_err != APR_SUCCESS) return svn_error_wrap_apr (apr_err, _("Can't manipulate current date")); + /* The ISO-8601 standard forces the date to be a fully zero padded string + * to aid in the _sorting_ of dates. We are conditioning user input + * instead of sorting therefore allow a relaxation of the standard for + * months, days and hours which use a seperator to omit leading zeros. + * + * But _not_ entire fields. eg) 2005-7-7T9:00Z + */ + if (template_match (&expt, &localtz, /* ISO-8601 extended, date only */ - "YYYY-MM-DD", + "YYYY-M[M]-D[D]", text) || template_match (&expt, &localtz, /* ISO-8601 extended, UTC */ - "YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u][Z]", + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]", text) || template_match (&expt, &localtz, /* ISO-8601 extended, with offset */ - "YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u]+OO[:oo]", + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]", text) || template_match (&expt, &localtz, /* ISO-8601 basic, date only */ "YYYYMMDD", @@ -222,17 +230,17 @@ "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]", text) || template_match (&expt, &localtz, /* "svn log" format */ - "YYYY-MM-DD hh:mm[:ss[.u[u[u[u[u[u][ +OO[oo]", + "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]", text) || template_match (&expt, &localtz, /* GNU date's iso-8601 */ - "YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u]+OO[oo]", + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]", text)) { expt.tm_year -= 1900; expt.tm_mon -= 1; } else if (template_match (&expt, &localtz, /* Just a time */ - "hh:mm[:ss[.u[u[u[u[u[u]", + "h[h]:mm[:ss[.u[u[u[u[u[u]", text)) { expt.tm_year = expnow.tm_year; @@ -240,10 +248,13 @@ expt.tm_mday = expnow.tm_mday; } else - return SVN_NO_ERROR; + // This error message is overridden by the one in the client. + return svn_error_createf (SVN_ERR_BAD_DATE, NULL, + "Date not in ISO-8601 or GNU format"); /* Range validation, allowing for leap seconds */ if (expt.tm_mon < 0 || expt.tm_mon > 11 + || expt.tm_mday < 1 || expt.tm_mday > valid_days_by_month[expt.tm_mon] || expt.tm_hour > 23 || expt.tm_min > 59 Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 15370) +++ subversion/clients/cmdline/main.c (working copy) @@ -803,7 +803,7 @@ int main (int argc, const char * const *argv) { - svn_error_t *err; + svn_error_t *err, *str_err; apr_allocator_t *allocator; apr_pool_t *pool; int opt_id; @@ -910,17 +910,30 @@ "try '-r M:N' instead of '-r M -r N'")); return svn_cmdline_handle_exit_error (err, pool, "svn: "); } - if (svn_opt_parse_revision (&(opt_state.start_revision), - &(opt_state.end_revision), - opt_arg, pool) != 0) + // We execute the function/procedure and check the return value. + err = svn_opt_parse_revision (&(opt_state.start_revision), + &(opt_state.end_revision), + opt_arg, pool); + if (err != SVN_NO_ERROR) { - err = svn_utf_cstring_to_utf8 (&utf8_opt_arg, opt_arg, pool); - if (! err) - err = svn_error_createf - (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("Syntax error in revision argument '%s'"), - utf8_opt_arg); - return svn_cmdline_handle_exit_error (err, pool, "svn: "); + str_err = svn_utf_cstring_to_utf8 (&utf8_opt_arg, opt_arg, pool); + if (! str_err ) + switch (err->apr_err) + { + case SVN_ERR_BAD_DATE: + err = svn_error_createf + (SVN_ERR_BAD_DATE, NULL, + _("'%s' is not in ISO-8061 or GNU format"), + utf8_opt_arg); + break; + default: + err = svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error in revision argument '%s'"), + utf8_opt_arg); + } + if (str_err == SVN_NO_ERROR) + return svn_cmdline_handle_exit_error (err, pool, "svn: "); } break; case 'v': Index: subversion/tests/libsvn_subr/time-test.c =================================================================== --- subversion/tests/libsvn_subr/time-test.c (revision 15370) +++ subversion/tests/libsvn_subr/time-test.c (working copy) @@ -203,6 +203,8 @@ static struct date_test localtz_tests[] = { /* YYYY-MM-DD */ { "2013-01-25", 2013, 1, 25, 0, 0, 0, 0 }, + { "2013-1-25", 2013, 1, 25, 0, 0, 0, 0 }, + { "2013-01-2", 2013, 1, 2, 0, 0, 0, 0 }, /* YYYY-MM-DD[Thh:mm[:ss[.u[u[u[u[u[u] */ { "2015-04-26T00:01:59.652655", 2015, 4, 26, 0, 1, 59, 652655 }, { "2034-07-20T17:03:36.11379", 2034, 7, 20, 17, 3, 36, 113790 }, @@ -263,6 +265,7 @@ "2000-00-02", /* Invalid month */ "2000-13-02", /* Invalid month */ "2000-01-32", /* Invalid day */ + "2000-01-00", "1999-02-29", /* Invalid leap day */ "2000-01-01 24:00:00", /* Invalid hour */ "2000-01-01 00:60:00", /* Invalid minute */