This patch adds --revprop support to svnlook propget and svnlook proplist.
It also adds more basic regression tests for other svnlook subcommands.
--- log-message-worthy content follows ---
* subversion/svnlook/main.c
     (subcommand_plist, subcommand_pget):
         Changed to pass NULL for the path argument to do_plist/do_pget
         when --revprop is specified.
     (do_plist, do_pget):
         When the 'path' argument is NULL, switch from svn_fs_node_proplist/
         svn_fs_node_prop to svn_fs_revision_proplist/svn_fs_revision_prop.
* subversion/tests/clients/cmdline/svnlook_tests.py
     Added the following tests:
         test_propget_revprop (tests svnlook propget --revprop)
         test_propget_revprop_missing (tests svnlook --revprop
              for a revision property that doesn't exist)
         test_proplist_revprop (tests svnlook proplist --revprop)
         test_uuid (tests svnlook uuid)
         test_logmsg (tests svnlook log)
         test_author (tests svnlook author)
* subversion/tests/clients/cmdline/svntest/main.py
     Created function 'run_command_stdin'.  Just like 'run_command',
     except it allows the caller to specify data to be piped into
     the command's stdin (needed by 'test_uuid').
Index: subversion/svnlook/main.c
===================================================================
--- subversion/svnlook/main.c	(revision 12233)
+++ subversion/svnlook/main.c	(working copy)
@@ -72,7 +72,8 @@
   { 
     svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
     svnlook__show_ids,
-    svnlook__no_diff_deleted
+    svnlook__no_diff_deleted,
+    svnlook__revprop_opt
   };
 
 /*
@@ -107,6 +108,9 @@
     {"no-diff-deleted", svnlook__no_diff_deleted, 0,
      N_("do not print differences for deleted files")},
 
+    {"revprop",       svnlook__revprop_opt, 0,
+                      N_("operate on a revision property (use with -r)")},
+
     {0,               0, 0, 0}
   };
 
@@ -170,15 +174,17 @@
      {'r', 't'} },
 
     {"propget", subcommand_pget, {"pget", "pg"},
-     N_("usage: svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n\n"
-        "Print the raw value of a property on a path in the repository.\n"),
-     {'r', 't'} },
+     N_("usage: svnlook propget REPOS_PATH PROPNAME [PATH_IN_REPOS]\n\n"
+        "Print the raw value of a property on a path in the repository.\n"
+        "With --revprop, prints the raw value of a revision property.\n"),
+     {'r', 't', svnlook__revprop_opt} },
 
     {"proplist", subcommand_plist, {"plist", "pl"},
-     N_("usage: svnlook proplist REPOS_PATH PATH_IN_REPOS\n\n"
-        "List the properties of a path in the repository.\n"
+     N_("usage: svnlook proplist REPOS_PATH [PATH_IN_REPOS]\n\n"
+        "List the properties of a path in the repository, or\n"
+        "with the --revprop option, revision properties.\n"
         "With -v, show the property values too.\n"),
-     {'r', 't', 'v'} },
+     {'r', 't', 'v', svnlook__revprop_opt} },
 
     {"tree", subcommand_tree, {0},
      N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
@@ -213,6 +219,7 @@
   svn_boolean_t help;             /* --help */
   svn_boolean_t no_diff_deleted;  /* --no-diff-deleted */
   svn_boolean_t verbose;          /* --verbose */
+  svn_boolean_t revprop;          /* --revprop */
 };
 
 
@@ -1429,13 +1436,25 @@
   apr_size_t len;
   
   SVN_ERR (get_root (&root, c, pool));
-  SVN_ERR (verify_path (&kind, root, path, pool));
-  SVN_ERR (svn_fs_node_prop (&prop, root, path, propname, pool));
+  if (path != NULL)
+    {
+      SVN_ERR (verify_path (&kind, root, path, pool));
+      SVN_ERR (svn_fs_node_prop (&prop, root, path, propname, pool));
+    }
+  else
+      SVN_ERR (svn_fs_revision_prop (&prop, c->fs, c->rev_id, propname, pool));
 
   if (prop == NULL)
-    return svn_error_createf
-      (SVN_ERR_PROPERTY_NOT_FOUND, NULL,
-       _("Property '%s' not found on path '%s'"), propname, path);
+    {
+       if (path == NULL)
+         return svn_error_createf
+           (SVN_ERR_PROPERTY_NOT_FOUND, NULL,
+            _("Property '%s' not found on revision %d"), propname, c->rev_id);
+       else
+         return svn_error_createf
+           (SVN_ERR_PROPERTY_NOT_FOUND, NULL,
+            _("Property '%s' not found on path '%s'"), propname, path);
+    }
 
   /* Else. */
 
@@ -1468,10 +1487,15 @@
   apr_hash_index_t *hi;
   svn_node_kind_t kind;
 
+  SVN_ERR (svn_stream_for_stdout (&stdout_stream, pool));
   SVN_ERR (get_root (&root, c, pool));
-  SVN_ERR (verify_path (&kind, root, path, pool));
-  SVN_ERR (svn_fs_node_proplist (&props, root, path, pool));
-  SVN_ERR (svn_stream_for_stdout (&stdout_stream, pool));
+  if (path != NULL)
+    {
+      SVN_ERR (verify_path (&kind, root, path, pool));
+      SVN_ERR (svn_fs_node_proplist (&props, root, path, pool));
+    }
+  else
+      SVN_ERR (svn_fs_revision_proplist (&props, c->fs, c->rev_id, pool));
 
   for (hi = apr_hash_first (pool, props); hi; hi = apr_hash_next (hi))
     {
@@ -1724,7 +1748,7 @@
         (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
          _("Missing propname and repository path arguments"));
     }
-  else if (opt_state->arg2 == NULL)
+  else if (!opt_state->revprop && opt_state->arg2 == NULL)
     {
       return svn_error_createf
         (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
@@ -1732,7 +1756,7 @@
     }
 
   SVN_ERR (get_ctxt_baton (&c, opt_state, pool));
-  SVN_ERR (do_pget (c, opt_state->arg1, opt_state->arg2, pool));
+  SVN_ERR (do_pget (c, opt_state->arg1, opt_state->revprop ? NULL : opt_state->arg2, pool));
   return SVN_NO_ERROR;
 }
 
@@ -1742,14 +1766,23 @@
 {
   struct svnlook_opt_state *opt_state = baton;
   svnlook_ctxt_t *c;
+  
+  /* some might argue that we should error here if --revprop is specified
+   * with no explicit revision, instead of implicitly behaving as -r HEAD.
+   *
+   * Those people might be right, but I'll leave that change to them,
+   * primarily because I don't fully understand how error handling works
+   * in this code.
+   */
 
-  if (opt_state->arg1 == NULL)
+  if (!opt_state->revprop && opt_state->arg1 == NULL)
     return svn_error_createf
       (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
        _("Missing repository path argument"));
 
   SVN_ERR (get_ctxt_baton (&c, opt_state, pool));
-  SVN_ERR (do_plist (c, opt_state->arg1, opt_state->verbose, pool));
+  SVN_ERR (do_plist (c, opt_state->revprop ? NULL : opt_state->arg1, 
+                     opt_state->verbose, pool));
   return SVN_NO_ERROR;
 }
 
@@ -1896,6 +1929,10 @@
         case '?':
           opt_state.help = TRUE;
           break;
+          
+        case svnlook__revprop_opt:
+          opt_state.revprop = TRUE;
+          break;
 
         case svnlook__version:
           opt_state.version = TRUE;
Index: subversion/tests/clients/cmdline/svnlook_tests.py
===================================================================
--- subversion/tests/clients/cmdline/svnlook_tests.py	(revision 12287)
+++ subversion/tests/clients/cmdline/svnlook_tests.py	(working copy)
@@ -38,9 +38,199 @@
 #                        Therefore, we can simply parse transaction headers.
 #
 ######################################################################
+
+# Convenient functions to make writing more tests easier
+
+# get the value of 'propname' for the given repository
+# on revision HEAD
+
+def svnlook_propget_revprop(rep_dir, propname):
+  output, errput = svntest.main.run_svnlook("propget", "--revprop", rep_dir, propname)
+  
+  if not(output) or errput:
+    raise svntest.Failure
+
+  return output[0]
+
+
 # Tests
 
+def test_author(sbox):
+  "test 'svnlook author'"
 
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  # code stolen from test_youngest() below
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+  svntest.main.file_append (mu_path, 'appended mu text')
+  svntest.main.file_append (rho_path, 'new appended text for rho')
+
+  logmsg = "Frobozz Magic Log Message Company"
+  author = 'lorddimwitflathead'
+
+  # commit with a silly (and uncommon) log message and author
+  svntest.main.run_svn(None, 'ci', '-m', logmsg, '--username', author, wc_dir)
+
+  output, errput = svntest.main.run_svnlook("author", repo_dir)
+
+  if not(output) or errput:
+    raise svntest.Failure
+
+  output[0] = output[0].rstrip("\n")
+
+  if output[0] != author:
+    print "Expected: '", author, "', got '", output[0], "'"
+    raise svntest.main.SVNUnmatchedError
+
+
+def test_logmsg(sbox):
+  "test 'svnlook log'"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  # code stolen from test_youngest() below
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+  svntest.main.file_append (mu_path, 'appended mu text')
+  svntest.main.file_append (rho_path, 'new appended text for rho')
+
+  logmsg = "Frobozz Magic Log Message Company"
+  author = 'lorddimwitflathead'
+
+  # commit with a silly (and uncommon) log message and author
+  svntest.main.run_svn(None, 'ci', '-m', logmsg, '--username', author, wc_dir)
+
+  output, errput = svntest.main.run_svnlook("log", repo_dir)
+
+  if not(output) or errput:
+    raise svntest.Failure
+
+  output[0] = output[0].rstrip("\n")
+
+  if output[0] != logmsg:
+    print "Expected: '", logmsg, "', got '", output[0], "'"
+    raise svntest.main.SVNUnmatchedError
+
+
+def test_uuid(sbox):
+  "test 'svnlook uuid'"
+
+  sbox.build()
+  repo_dir = sbox.repo_dir
+
+  uuid = "01234567-89ab-cdef-89ab-cdef01234567"
+
+  # give the rep a new UUID
+  svntest.main.run_command_stdin(svntest.main.svnadmin_binary, None, 0,
+                           ["SVN-fs-dump-format-version: 2\n",
+                            "\n",
+                            "UUID: ", uuid, "\n",
+                           ],
+                           'load', '--force-uuid', repo_dir)
+
+  # now read the UUID
+  output, errput = svntest.main.run_svnlook('uuid', repo_dir)
+
+  if not(output) or output[0].strip() != uuid:
+    raise svntest.main.SVNUnmatchedError
+
+
+def test_proplist_revprop(sbox):
+  "test 'svnlook proplist --revprop'"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  # code stolen from test_youngest() below
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+  svntest.main.file_append (mu_path, 'appended mu text')
+  svntest.main.file_append (rho_path, 'new appended text for rho')
+
+  logmsg = 'foo bar'
+  author = 'bazquux'
+
+  # commit with a silly (and uncommon) log message and author
+  svntest.main.run_svn(None, 'ci', '-m', logmsg, '--username', author, wc_dir)
+
+  # now here's where we actually do our work.
+  # test both svn:log and svn:author here -- the more, the merrier!
+
+  output, errput = svntest.main.run_svnlook("proplist", "--revprop", repo_dir)
+
+  # clean output
+  output = [prop.strip() for prop in output]
+
+  expected = [ 'svn:author', 'svn:log', 'svn:date' ]
+
+  expected.sort()
+  output.sort()
+
+  if not(output) or output != expected:
+    print "Expected: ", expected
+    print "Got: ", output
+    raise svntest.main.SVNUnmatchedError
+
+
+def test_propget_revprop(sbox):
+  "'svnlook propget' on existing revprops"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  # code stolen from test_youngest() below
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+  svntest.main.file_append (mu_path, 'appended mu text')
+  svntest.main.file_append (rho_path, 'new appended text for rho')
+
+  logmsg = 'foo bar'
+  author = 'bazquux'
+
+  # commit with a silly (and uncommon) log message and author
+  svntest.main.run_svn(None, 'ci', '-m', logmsg, '--username', author, wc_dir)
+
+  # now here's where we actually do our work.
+  # test both svn:log and svn:author here -- the more, the merrier!
+
+  propval = svnlook_propget_revprop(repo_dir, "svn:log")
+
+  if propval != logmsg:
+    print "Mismatch: Expected '", logmsg, "', got '", propval, "'"
+    raise svntest.main.SVNUnmatchedError
+
+  propval = svnlook_propget_revprop(repo_dir, "svn:author")
+
+  if propval != author:
+    print "Mismatch: Expected '", author, "', got '", propval, "'"
+    raise svntest.main.SVNUnmatchedError
+  
+
+def test_propget_revprop_missing(sbox):
+  "'svnlook propget' on nonexistent revprops"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  # don't even need to make any changes here -- sbox.build() already gave us a test commit
+
+  output, errput = svntest.main.run_svnlook("propget", "--revprop", repo_dir, "foo:bar-baz-quux")
+
+  rm = re.compile("Property.*not found")
+  for line in errput:
+    match = rm.search(line)
+    if match:
+      return
+  raise svntest.main.SVNUnmatchedError
+
 #----------------------------------------------------------------------
 
 def test_youngest(sbox):
@@ -185,6 +375,12 @@
               test_youngest,
               delete_file_in_moved_dir,
               test_print_property_diffs,
+              test_propget_revprop,
+              test_propget_revprop_missing,
+              test_proplist_revprop,
+              test_uuid,
+              test_logmsg,
+              test_author,
              ]
 
 if __name__ == '__main__':
Index: subversion/tests/clients/cmdline/svntest/main.py
===================================================================
--- subversion/tests/clients/cmdline/svntest/main.py	(revision 12287)
+++ subversion/tests/clients/cmdline/svntest/main.py	(working copy)
@@ -1,4 +1,3 @@
-
 #!/usr/bin/env python
 #
 #  main.py: a shared, automated test suite for Subversion
@@ -199,6 +198,16 @@
   """Run COMMAND with VARARGS; return stdout, stderr as lists of lines.
   If ERROR_EXPECTED is None, any stderr also will be printed."""
 
+  return run_command_stdin(command, error_expected, binary_mode, 
+                           None, *varargs)
+
+# Run any binary, supplying input text, logging the command line
+def run_command_stdin(command, error_expected, binary_mode=0, 
+                      stdin_lines = None, *varargs):
+  """Run COMMAND with VARARGS; input stdin_lines (a list of lines) to
+  program via stdin, return stdout, stderr as lists of lines.
+  If ERROR_EXPECTED is None, any stderr also will be printed."""
+
   args = ''
   for arg in varargs:                   # build the command string
     args = args + ' "' + str(arg) + '"'
@@ -215,11 +224,15 @@
   start = time.time()
   infile, outfile, errfile = os.popen3(command + args, mode)
 
+  if stdin_lines:
+    map(infile.write, stdin_lines)
+
+  infile.close()
+
   stdout_lines = outfile.readlines()
   stderr_lines = errfile.readlines()
 
   outfile.close()
-  infile.close()
   errfile.close()
 
   if verbose_mode:
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Dec 14 03:56:39 2004