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;