svn commit: r1932856 - subversion/trunk/subversion/svnbrowse

[email protected]
Newsgroups gmane.comp.version-control.subversion.svn
Message-ID <177539336415.3618969.2086458960046960073@svn03-he-fi>
Author: rinrab
Date: Sun Apr  5 12:49:23 2026
New Revision: 1932856

Log:
svnbrowse: Use the Subversion framework for command-line argument
parsing and help.

* subversion/svnbrowse/svnbrowse.c
  (includes): Add svn_cmdline.h, private/svn_cmdline_private.h, and
   svn_private_config.
  (opt_): New enum.
  (svn_browse__opt_state_t): New struct.
  (svn_browse__options): List all options.
  (show_usage, show_help, show_version): New functions.
  (sub_main, main): Mark argv as const.
  (sub_main): Parse command line using getopt, and add some boilerplate-ish
   stolen initialization code; the majority of options are no-op.

Modified:
   subversion/trunk/subversion/svnbrowse/svnbrowse.c

Modified: subversion/trunk/subversion/svnbrowse/svnbrowse.c
==============================================================================
--- subversion/trunk/subversion/svnbrowse/svnbrowse.c	Sun Apr  5 09:44:54 2026	(r1932855)
+++ subversion/trunk/subversion/svnbrowse/svnbrowse.c	Sun Apr  5 12:49:23 2026	(r1932856)
@@ -24,6 +24,7 @@
 
 #include <apr.h>
 
+#include "svn_cmdline.h"
 #include "svn_client.h"
 #include "svn_opt.h"
 #include "svn_ra.h"
@@ -32,8 +33,112 @@
 #include "svn_cmdline.h"
 #include "svn_error.h"
 
+#include "private/svn_cmdline_private.h"
+
 #include <ncurses.h>
 
+#include "svn_private_config.h"
+
+enum {
+  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
+  opt_auth_password_from_stdin,
+  opt_auth_username,
+  opt_config_dir,
+  opt_config_option,
+  opt_no_auth_cache,
+  opt_version,
+  opt_trust_server_cert,
+  opt_trust_server_cert_failures,
+  opt_password_from_stdin,
+};
+
+typedef struct svn_browse__opt_state_t {
+  svn_boolean_t version;         /* print version information */
+  svn_boolean_t verbose;         /* for svnbrowse --version */
+  svn_boolean_t quiet;           /* for svnbrowse --version */
+  svn_boolean_t help;            /* print usage message */
+
+  const char *auth_username;     /* auth username */
+  const char *auth_password;     /* auth password */
+  apr_array_header_t *targets;   /* target list from file */
+  svn_boolean_t no_auth_cache;   /* do not cache authentication information */
+  const char *config_dir;        /* over-riding configuration directory */
+  apr_array_header_t *config_options; /* over-riding configuration options */
+  svn_opt_revision_t revision;
+
+  /* trust server SSL certs that would otherwise be rejected as "untrusted" */
+  svn_boolean_t trust_server_cert_unknown_ca;
+  svn_boolean_t trust_server_cert_cn_mismatch;
+  svn_boolean_t trust_server_cert_expired;
+  svn_boolean_t trust_server_cert_not_yet_valid;
+  svn_boolean_t trust_server_cert_other_failure;
+} svn_browse__opt_state_t;
+
+/* Option codes and descriptions for the command line client.
+ * The entire list must be terminated with an entry of nulls. */
+const apr_getopt_option_t svn_browse__options[] =
+{
+  {"username",      opt_auth_username, 1, N_("specify a username ARG")},
+  {"password",      opt_auth_password, 1,
+                    N_("specify a password ARG (caution: on many operating\n"
+                       "                             "
+                       "systems, other users will be able to see this)")},
+  {"password-from-stdin",
+                    opt_auth_password_from_stdin, 0,
+                    N_("read password from stdin")},
+  {"revision",      'r', 1,
+                    N_("ARG\n"
+                       "                             "
+                       "A revision argument can be one of:\n"
+                       "                             "
+                       "   NUMBER       revision number\n"
+                       "                             "
+                       "   '{' DATE '}' revision at start of the date\n"
+                       "                             "
+                       "   'HEAD'       latest in repository\n"
+                       "                             "
+                       "   'BASE'       base rev of item's working copy\n"
+                       "                             "
+                       "   'COMMITTED'  last commit at or before BASE\n"
+                       "                             "
+                       "   'PREV'       revision just before COMMITTED")},
+  {"help",          'h', 0, N_("show help on a subcommand")},
+  {NULL,            '?', 0, N_("show help on a subcommand")},
+  {"trust-server-cert", opt_trust_server_cert, 0,
+                    N_("deprecated; same as\n"
+                       "                             "
+                       "--trust-server-cert-failures=unknown-ca")},
+  {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
+                    N_("with --non-interactive, accept SSL server\n"
+                       "                             "
+                       "certificates with failures; ARG is comma-separated\n"
+                       "                             "
+                       "list of 'unknown-ca' (Unknown Authority),\n"
+                       "                             "
+                       "'cn-mismatch' (Hostname mismatch), 'expired'\n"
+                       "                             "
+                       "(Expired certificate), 'not-yet-valid' (Not yet\n"
+                       "                             "
+                       "valid certificate) and 'other' (all other not\n"
+                       "                             "
+                       "separately classified certificate errors).")},
+  {"config-dir",    opt_config_dir, 1,
+                    N_("read user configuration files from directory ARG")},
+  {"config-option", opt_config_option, 1,
+                    N_("set user configuration option in the format:\n"
+                       "                             "
+                       "    FILE:SECTION:OPTION=[VALUE]\n"
+                       "                             "
+                       "For example:\n"
+                       "                             "
+                       "    servers:global:http-library=serf")},
+  {"no-auth-cache", opt_no_auth_cache, 0,
+                    N_("do not cache authentication tokens")},
+  {"version",       opt_version, 0, N_("show program version information")},
+  {"verbose",       'v', 0, N_("print extra information")},
+  { NULL, 0, 0, NULL }
+};
+
 /* Control+ASCII character are represented as values 1-26 according to their
  * alphabetical order. */
 #define CTRL(ch) ((ch) - 'a' + 1)
@@ -258,18 +363,176 @@ view_draw(svn_browse__view_t *view, apr_
 }
 
 static svn_error_t *
-sub_main(int *code, int argc, char *argv[], apr_pool_t *pool)
+show_usage(apr_pool_t *scratch_pool)
+{
+  fprintf(stderr, "Type 'svnbrowse --help' for usage.\n");
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+show_help(apr_pool_t *scratch_pool,
+          const svn_browse__opt_state_t *opt_state)
+{
+  svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
+  const apr_getopt_option_t *opt;
+
+  svn_stringbuf_appendcstr(buf, N_(
+      "usage: svnbrowse <target> [options]\n"
+      "Interactively browse Subversion repositories\n"
+      "\n"
+  ));
+
+  svn_stringbuf_appendcstr(buf, N_("Valid options:\n"));
+  for (opt = svn_browse__options; opt->description; opt++)
+    {
+      if (opt->name)
+        {
+          const char *opts;
+          svn_opt_format_option(&opts, opt, TRUE /* doc */, scratch_pool);
+          svn_stringbuf_appendcstr(buf, "  ");
+          svn_stringbuf_appendcstr(buf, opts);
+          svn_stringbuf_appendbyte(buf, '\n');
+        }
+    }
+
+  return svn_error_trace(svn_cmdline_fputs(buf->data, stderr, scratch_pool));
+}
+
+static svn_error_t *
+show_version(apr_pool_t *scratch_pool,
+             const svn_browse__opt_state_t *opt_state)
+{
+  const char *ra_desc_start
+    = "The following repository access (RA) modules are available:\n\n";
+  svn_stringbuf_t *version_footer;
+
+  version_footer = svn_stringbuf_create(ra_desc_start, scratch_pool);
+  SVN_ERR(svn_ra_print_modules(version_footer, scratch_pool));
+
+  SVN_ERR(svn_opt_print_help5(NULL,
+                              "svnbrowse",
+                              TRUE /* print_version */,
+                              opt_state->quiet,
+                              opt_state->verbose,
+                              version_footer->data,
+                              NULL, NULL, NULL, NULL, NULL,
+                              scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+sub_main(int *code, int argc, const char *argv[], apr_pool_t *pool)
 {
   const char *url;
   svn_browse__model_t *ctx;
   svn_browse__view_t *view;
+  svn_browse__opt_state_t opt_state = { 0 };
+  svn_boolean_t read_pass_from_stdin = FALSE;
   apr_pool_t *iterpool;
+  apr_getopt_t *os;
+
+  opt_state.revision.kind = svn_opt_revision_head;
+  opt_state.config_options =
+      apr_array_make(pool, 0, sizeof(svn_cmdline__config_argument_t *));
 
-  if (argc != 2)
-    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                            "usage: svnbrowse <URL>");
+  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+  os->interleave = 1;
+
+  while (TRUE)
+    {
+      const char *opt_arg;
+      int opt_id;
+
+      /* Parse the next option. */
+      apr_status_t status = apr_getopt_long(os, svn_browse__options, &opt_id,
+                                            &opt_arg);
+
+      if (APR_STATUS_IS_EOF(status))
+        break;
+      else if (status != APR_SUCCESS)
+        {
+          SVN_ERR(show_usage(pool));
+          *code = EXIT_FAILURE;
+          return SVN_NO_ERROR;
+        }
+
+      switch (opt_id)
+        {
+          case 'r':
+            SVN_ERR(svn_opt_parse_one_revision(&opt_state.revision, opt_arg, pool));
+            break;
+          case 'h':
+          case '?':
+            opt_state.help = TRUE;
+            break;
+          case opt_version:
+            opt_state.version = TRUE;
+            break;
+          case opt_auth_username:
+            opt_state.auth_username = apr_pstrdup(pool, opt_arg);
+            break;
+          case opt_auth_password:
+            opt_state.auth_password = apr_pstrdup(pool, opt_arg);
+            break;
+          case opt_auth_password_from_stdin:
+            read_pass_from_stdin = TRUE;
+            break;
+          case opt_no_auth_cache:
+            opt_state.no_auth_cache = TRUE;
+            break;
+          /* ### can we drop it in a 1.16 tool? */
+          case opt_trust_server_cert: /* backwards compat to 1.8 */
+            opt_state.trust_server_cert_unknown_ca = TRUE;
+            break;
+          case opt_trust_server_cert_failures:
+            SVN_ERR(svn_cmdline__parse_trust_options(
+                          &opt_state.trust_server_cert_unknown_ca,
+                          &opt_state.trust_server_cert_cn_mismatch,
+                          &opt_state.trust_server_cert_expired,
+                          &opt_state.trust_server_cert_not_yet_valid,
+                          &opt_state.trust_server_cert_other_failure,
+                          opt_arg, pool));
+            break;
+          case opt_config_dir:
+            opt_state.config_dir = svn_dirent_internal_style(opt_arg, pool);
+            break;
+          case opt_config_option:
+            SVN_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
+                                                     opt_arg, "svnbrowse: ", pool));
+            break;
+          default:
+            break;
+        }
+    }
+
+  if (opt_state.version)
+    {
+      SVN_ERR(show_version(pool, &opt_state));
+      return SVN_NO_ERROR;
+    }
+
+  if (opt_state.help)
+    {
+      SVN_ERR(show_help(pool, &opt_state));
+      return SVN_NO_ERROR;
+    }
+
+  /* TODO: WC paths are not implemented; svn_uri_canonicalize_safe() will just
+   * fail in case of one */
+  url = (os->ind < argc) ? os->argv[os->ind++] : ".";
+  SVN_ERR(svn_uri_canonicalize_safe(&url, NULL, url, pool, pool));
+
+  /* we must fail if there are extra arguments */
+  if (os->ind < argc - 1)
+    {
+      printf("%d\n", os->ind);
+      *code = EXIT_FAILURE;
+      SVN_ERR(show_usage(pool));
+      return SVN_NO_ERROR;
+    }
 
-  SVN_ERR(svn_uri_canonicalize_safe(&url, NULL, argv[1], pool, pool));
+  SVN_ERR(svn_config_ensure(opt_state.config_dir, pool));
 
   SVN_ERR(model_create(&ctx, url, SVN_INVALID_REVNUM, pool, pool));
 
@@ -337,7 +600,7 @@ quit:
   return SVN_NO_ERROR;
 }
 
-int main(int argc, char *argv[])
+int main(int argc, const char *argv[])
 {
   apr_pool_t *pool;
   int exit_code = EXIT_SUCCESS;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.