Make the output of "proplist" and "propget" easier to read, especially with multi-line property values. In "propget", allow the option "--verbose" and make it give an output almost the same as "proplist --verbose". In "proplist", change the property name header line to have just the property name (indented by 2 spaces) without a following colon, regardless whether we're printing the value as well. In both cases, indent every line of the property value by the same amount (4 spaces). * subversion/svn/propget-cmd.c (print_properties): In verbose mode, print exactly like "proplist -v" does with the property value, indented, on subsequent lines. Otherwise keep the old format of "filename - propval". (svn_cl__propget): Check that "--verbose" does not clash with incompatible options. Pass appropriate behaviour flags to print_properties(). * subversion/svn/props.c (svn_cl__print_prop_hash): When printing values, print the property name on a line by itself just like when printing names only, and then print the value, indented, on subsequent lines. * subversion/svn/cl.h (svn_cl__indent_string): New function. * subversion/svn/util.c (next_line, svn_cl__indent_string): New functions. * subversion/svn/main.c (svn_cl__cmd_table): Add 'v' to the options that 'propget' can take. * subversion/tests/cmdline/svntest/tree.py (get_props): Adjust for the new "proplist" output format. * subversion/tests/cmdline/prop_tests.py (recursive_base_wc_ops, url_props_ops, removal_schedule_added_props, depthy_wc_proplist, props_over_time): Adjust for new "proplist" output. Index: subversion/tests/cmdline/prop_tests.py =================================================================== --- subversion/tests/cmdline/prop_tests.py (revision 32463) +++ subversion/tests/cmdline/prop_tests.py (working copy) @@ -1090,13 +1090,15 @@ # Test recursive proplist exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '-R', '-v', wc_dir, '-rBASE') - verify_output([ 'old-del', 'old-keep', 'Properties on ', 'Properties on ' ], + verify_output([ 'old-del', 'old-keep', 'p', 'p', + 'Properties on ', 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '-R', '-v', wc_dir) - verify_output([ 'new-add', 'new-keep', 'Properties on ', 'Properties on ' ], + verify_output([ 'new-add', 'new-keep', 'p', 'p', + 'Properties on ', 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) @@ -1183,13 +1185,13 @@ # Test verbose proplist exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '-v', iota_url) - verify_output([ prop1 + ' : ' + propval1, prop2 + ' : ' + propval2, + verify_output([ propval1, propval2, prop1, prop2, 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '-v', A_url) - verify_output([ prop1 + ' : ' + propval1, prop2 + ' : ' + propval2, + verify_output([ propval1, propval2, prop1, prop2, 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) @@ -1229,7 +1231,8 @@ file_rm_output = ["D " + newfile_path + "\n"] propls_output = [ "Properties on '" + newfile_path + "':\n", - " newprop : newvalue\n", + " newprop\n", + " newvalue\n", ] # create new fs file @@ -1363,7 +1366,7 @@ exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '--depth', 'empty', '-v', wc_dir) - verify_output([ 'prop1', 'Properties on ' ], + verify_output([ 'prop1', 'p', 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) @@ -1371,21 +1374,24 @@ exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '--depth', 'files', '-v', wc_dir) - verify_output([ 'prop1', 'prop2', 'Properties on ', 'Properties on ' ], + verify_output([ 'prop1', 'prop2', 'p', 'p', + 'Properties on ', 'Properties on ' ], output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) # Test depth-immediates proplist. exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '--depth', 'immediates', '-v', wc_dir) - verify_output([ 'prop1', 'prop2', 'prop3' ] + ['Properties on '] * 3, + verify_output([ 'prop1', 'prop2', 'prop3' ] + + ['p'] * 3 + ['Properties on '] * 3, output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) # Test depth-infinity proplist. exit_code, output, errput = svntest.main.run_svn(None, 'proplist', '--depth', 'infinity', '-v', wc_dir) - verify_output([ 'prop1', 'prop2', 'prop3', 'prop4' ] + ['Properties on '] * 4, + verify_output([ 'prop1', 'prop2', 'prop3', 'prop4' ] + + ['p'] * 4 + ['Properties on '] * 4, output, errput) svntest.verify.verify_exit_code(None, exit_code, 0) @@ -1623,7 +1629,8 @@ plist_expected = expected if plist_expected: plist_expected = [ "Properties on '" + path + "':\n", - " revision : " + expected + "\n" ] + " revision\n", + " " + expected + "\n" ] if op_rev != 0: svntest.actions.run_and_verify_svn(None, plist_expected, [], Index: subversion/tests/cmdline/svntest/tree.py =================================================================== --- subversion/tests/cmdline/svntest/tree.py (revision 32463) +++ subversion/tests/cmdline/svntest/tree.py (working copy) @@ -313,7 +313,8 @@ # helper for handle_dir(), which is a helper for build_tree_from_wc() def get_props(path): - "Return a hash of props for PATH, using the svn client." + """Return a hash of props for PATH, using the svn client. Convert each + embedded end-of-line to a single LF character.""" # It's not kosher to look inside .svn/ and try to read the internal # property storage format. Instead, we use 'svn proplist'. After @@ -323,33 +324,28 @@ props = {} exit_code, output, errput = main.run_svn(1, "proplist", path, "--verbose") + # Parse the output for line in output: + line = line.rstrip('\r\n') # ignore stdout's EOL sequence + if line.startswith('Properties on '): continue - if line.startswith(' ') and line.find(' : ') >= 3: - name, value = line.split(' : ') - name = name[2:] - value = value.rstrip("\r\n") - props[name] = value - else: # Multi-line property, so re-use the current name. - # Keep the line endings consistent with what was done to the first - # line by stripping whitespace and then appending a newline. This - # prevents multiline props on Windows that must be stored as UTF8/LF - # in the repository (e.g. svn:mergeinfo), say like this: - # - # "propname : propvalLine1propvalLine2propvalLine3" - # - # but that print to stdout like this: - # - # Properties on 'somepath': - # propname : propvalLine1 - # propvalLine1 - # propvalLine1 - # - # from looking like this in the returned PROPS hash: - # - # "propname" --> "propvalLine1propvalLine2propvalLine3" - props[name] = props[name] + "\n" + line.rstrip("\r\n") + + elif line.startswith(' '): + # It's (part of) the value + props[name] += line[4:] + '\n' # strip the indentation + + elif line.startswith(' '): + # It's the name + name = line[2:] # strip the indentation + props[name] = '' + + else: + raise "Malformed line from proplist: '"+line+"'" + + # Strip, from each property value, the final new-line that we added + for name in props: + props[name] = props[name][:-1] return props Index: subversion/svn/props.c =================================================================== --- subversion/svn/props.c (revision 32463) +++ subversion/svn/props.c (working copy) @@ -103,10 +103,16 @@ /* ### We leave these printfs for now, since if propval wasn't translated * above, we don't know anything about its encoding. In fact, it * might be binary data... */ - if (names_only) - printf(" %s\n", pname_stdout); - else - printf(" %s : %s\n", pname_stdout, propval->data); + printf(" %s\n", pname_stdout); + if (!names_only) + { + /* Add an extra newline to the value before indenting, so that + * every line of output has the indentation whether the value + * already ended in a newline or not. */ + const char *newval = apr_psprintf(pool, "%s\n", propval->data); + + printf("%s", svn_cl__indent_string(newval, " ", pool)); + } } return SVN_NO_ERROR; Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 32463) +++ subversion/svn/cl.h (working copy) @@ -613,6 +613,14 @@ svn_client_ctx_t *ctx, apr_pool_t *pool); +/* Return a string allocated in POOL that is a copy of STR but with each + * line prefixed with INDENT. A line is all characters up to the first + * CR-LF, LF-CR, CR or LF, or the end of STR if sooner. */ +const char * +svn_cl__indent_string(const char *str, + const char *indent, + apr_pool_t *pool); + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/svn/util.c =================================================================== --- subversion/svn/util.c (revision 32463) +++ subversion/svn/util.c (working copy) @@ -1114,3 +1114,53 @@ return SVN_NO_ERROR; } + + +/* Return a copy, allocated in POOL, of the next line of text from *STR + * up to and including a CR and/or an LF. Change *STR to point to the + * remainder of the string after the returned part. If there are no + * characters to be returned, return NULL; never return an empty string. + */ +static const char * +next_line(const char **str, apr_pool_t *pool) +{ + const char *start = *str; + const char *p = *str; + + /* n.b. Throughout this fn, we never read any character after a '\0'. */ + /* Skip over all non-EOL characters, if any. */ + while (*p != '\r' && *p != '\n' && *p != '\0') + p++; + /* Skip over \r\n or \n\r or \r or \n, if any. */ + if (*p == '\r' || *p == '\n') + { + char c = *p++; + + if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) + p++; + } + + /* Now p points after at most one '\n' and/or '\r'. */ + *str = p; + + if (p == start) + return NULL; + + return svn_string_ncreate(start, p - start, pool)->data; +} + +const char * +svn_cl__indent_string(const char *str, + const char *indent, + apr_pool_t *pool) +{ + svn_stringbuf_t *out = svn_stringbuf_create("", pool); + const char *line; + + while ((line = next_line(&str, pool))) + { + svn_stringbuf_appendcstr(out, indent); + svn_stringbuf_appendcstr(out, line); + } + return out->data; +} Index: subversion/svn/propget-cmd.c =================================================================== --- subversion/svn/propget-cmd.c (revision 32463) +++ subversion/svn/propget-cmd.c (working copy) @@ -95,13 +95,23 @@ } +/* Print the properties in PROPS to the stream OUT. PROPS is a hash mapping + * (const char *) path to (svn_string_t) property value. + * If IS_URL is true, all paths are URLs, else all paths are local paths. + * PNAME_UTF8 is the property name of all the properties. + * If PRINT_FILENAMES is true, print the item's path before each property. + * If OMIT_NEWLINE is true, don't add a newline at the end of each property. + * If LIKE_PROPLIST is true, print everything in a more verbose format + * like "svn proplist -v" does. + * */ static svn_error_t * print_properties(svn_stream_t *out, svn_boolean_t is_url, const char *pname_utf8, apr_hash_t *props, svn_boolean_t print_filenames, - svn_cl__opt_state_t *opt_state, + svn_boolean_t omit_newline, + svn_boolean_t like_proplist, apr_pool_t *pool) { apr_hash_index_t *hi; @@ -119,38 +129,46 @@ filename = key; propval = val; - /* If this is a special Subversion property, it is stored as - UTF8, so convert to the native format. */ - if (svn_prop_needs_translation(pname_utf8)) - { - SVN_ERR(svn_subst_detranslate_string(&propval, propval, - TRUE, iterpool)); - } - if (print_filenames) { - const char *filename_stdout; + const char *header; + + /* Print the file name. */ if (! is_url) - { - SVN_ERR(svn_cmdline_path_local_style_from_utf8 - (&filename_stdout, filename, iterpool)); - } - else - { - SVN_ERR(svn_cmdline_cstring_from_utf8 - (&filename_stdout, filename, iterpool)); - } + filename = svn_path_local_style(filename, iterpool); - SVN_ERR(stream_write(out, filename_stdout, - strlen(filename_stdout))); - SVN_ERR(stream_write(out, " - ", 3)); + /* In verbose mode, print exactly same as "proplist" does; + * otherwise, print a brief header. */ + header = apr_psprintf(iterpool, like_proplist + ? _("Properties on '%s':\n") + : "%s - ", filename); + SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, iterpool)); + SVN_ERR(stream_write(out, header, strlen(header))); } - SVN_ERR(stream_write(out, propval->data, propval->len)); - if (! opt_state->strict) - SVN_ERR(stream_write(out, APR_EOL_STR, - strlen(APR_EOL_STR))); + if (like_proplist) + { + /* Print the property name and value just as "proplist -v" does */ + apr_hash_t *hash = apr_hash_make(iterpool); + + apr_hash_set(hash, pname_utf8, APR_HASH_KEY_STRING, propval); + svn_cl__print_prop_hash(hash, FALSE, iterpool); + } + else + { + /* If this is a special Subversion property, it is stored as + UTF8, so convert to the native format. */ + if (svn_prop_needs_translation(pname_utf8)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, iterpool)); + + SVN_ERR(stream_write(out, propval->data, propval->len)); + + if (! omit_newline) + SVN_ERR(stream_write(out, APR_EOL_STR, + strlen(APR_EOL_STR))); + } } svn_pool_destroy(iterpool); @@ -172,6 +190,12 @@ svn_stream_t *out; int i; + if (opt_state->verbose && (opt_state->revprop || opt_state->strict + || opt_state->xml)) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--verbose cannot be used with --revprop or " + "--strict or --xml")); + /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version thereof) */ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); @@ -258,7 +282,9 @@ { const char *target = APR_ARRAY_IDX(targets, i, const char *); apr_hash_t *props; - svn_boolean_t print_filenames = FALSE; + svn_boolean_t print_filenames; + svn_boolean_t omit_newline; + svn_boolean_t like_proplist; const char *truepath; svn_opt_revision_t peg_revision; @@ -281,14 +307,18 @@ not to do so with the --strict option. */ print_filenames = ((opt_state->depth > svn_depth_empty || targets->nelts > 1 - || apr_hash_count(props) > 1) + || apr_hash_count(props) > 1 + || opt_state->verbose) && (! opt_state->strict)); + omit_newline = opt_state->strict; + like_proplist = opt_state->verbose && !opt_state->strict; if (opt_state->xml) print_properties_xml(pname_utf8, props, subpool); else print_properties(out, svn_path_is_url(target), pname_utf8, props, - print_filenames, opt_state, subpool); + print_filenames, omit_newline, like_proplist, + subpool); } if (opt_state->xml) Index: subversion/svn/main.c =================================================================== --- subversion/svn/main.c (revision 32463) +++ subversion/svn/main.c (working copy) @@ -691,7 +691,8 @@ " is prefixed with the path with which it is associated. Use\n" " the --strict option to disable these beautifications (useful,\n" " for example, when redirecting binary property values to a file).\n"), - {'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml, opt_changelist } }, + {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml, + opt_changelist } }, { "proplist", svn_cl__proplist, {"plist", "pl"}, N_ ("List all properties on files, dirs, or revisions.\n"