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;

Reply via email to