Author: rinrab
Date: Wed Apr 1 17:44:08 2026
New Revision: 1932708
Log:
svnbrowse: Implement the most basic functionality of the directory browser.
- Simple command-line parser that takes the first argument processes it as the
URL to operate on.
- Initialize the client context with pre-set authentication handlers. Use the
standard command line handlers for that.
- Retrieving the directory contents and display them.
- Allow some control to navigate to subdirs.
* subversion/svnbrowse/svnbrowse.c
(includes): Include required headers.
(item_t): Declare struct to hold information about a single directory.
(svn_browse__ctx_t): Declare struct to hold context of the browser.
(init_client, list_cb, enter_path): New functions.
(ui_draw): Render the list.
(sub_main): Add initialization and handle browser commands.
Modified:
subversion/trunk/subversion/svnbrowse/svnbrowse.c
Modified: subversion/trunk/subversion/svnbrowse/svnbrowse.c
==============================================================================
--- subversion/trunk/subversion/svnbrowse/svnbrowse.c Wed Apr 1 17:21:18
2026 (r1932707)
+++ subversion/trunk/subversion/svnbrowse/svnbrowse.c Wed Apr 1 17:44:08
2026 (r1932708)
@@ -24,6 +24,9 @@
#include <apr.h>
+#include "svn_client.h"
+#include "svn_opt.h"
+#include "svn_path.h"
#include "svn_pools.h"
#include "svn_cmdline.h"
#include "svn_error.h"
@@ -34,15 +37,124 @@
* alphabetical order. */
#define CTRL(ch) (ch - 'a' + 1)
+typedef struct item_t {
+ const char *relpath;
+ const svn_dirent_t *dirent;
+} item_t;
+
+typedef struct svn_browse__ctx_t {
+ const char *root;
+ const char *relpath;
+ const char *abspath;
+ svn_opt_revision_t revision;
+
+ svn_client_ctx_t *client;
+
+ apr_array_header_t *list;
+ int selection;
+ apr_pool_t *list_pool;
+} svn_browse__ctx_t;
+
+static svn_error_t *
+init_client(svn_browse__ctx_t *ctx, apr_pool_t *pool)
+{
+ svn_auth_baton_t *auth;
+
+ SVN_ERR(svn_client_create_context2(&ctx->client, NULL, pool));
+
+ /* Set up Authentication stuff. */
+ SVN_ERR(svn_cmdline_create_auth_baton2(&auth, FALSE, NULL, NULL, NULL, FALSE,
+ FALSE, FALSE, FALSE, FALSE, FALSE,
+ NULL, NULL, NULL, pool));
+
+ ctx->client->auth_baton = auth;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+list_cb(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ svn_browse__ctx_t *ctx = baton;
+ item_t *item = apr_pcalloc(ctx->list_pool, sizeof(*item));
+ item->relpath = apr_pstrdup(ctx->list_pool, path);
+ item->dirent = svn_dirent_dup(dirent, ctx->list_pool);
+ APR_ARRAY_PUSH(ctx->list, item_t *) = item;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+enter_path(svn_browse__ctx_t *ctx, const char *relpath, apr_pool_t *pool)
+{
+ ctx->relpath = apr_pstrdup(pool, relpath);
+ ctx->abspath = svn_path_url_add_component2(ctx->root, relpath, pool);
+
+ ctx->list = apr_array_make(pool, 0, sizeof(item_t *));
+ ctx->selection = 0;
+
+ SVN_ERR(svn_client_list4(ctx->abspath, &ctx->revision, &ctx->revision, NULL,
+ svn_depth_immediates, SVN_DIRENT_ALL, TRUE, TRUE,
+ list_cb, ctx, ctx->client, pool));
+
+ return SVN_NO_ERROR;
+}
+
static void
-ui_draw(apr_pool_t *pool)
+ui_draw(svn_browse__ctx_t *ctx, apr_pool_t *pool)
{
- mvprintw(0, 4, "svnbrowse: work in progress");
+ int i;
+
+ mvprintw(0, 4, "Browsing: %s", ctx->abspath);
+
+ for (i = 0; i < ctx->list->nelts; i++)
+ {
+ item_t *item = APR_ARRAY_IDX(ctx->list, i, item_t *);
+
+ if (i == ctx->selection)
+ standout();
+
+ if (i == 0)
+ mvprintw(i + 1, 0, "../");
+ else if (item->dirent->kind == svn_node_dir)
+ mvprintw(i + 1, 0, "%s/", item->relpath);
+ else if (item->dirent->kind == svn_node_file)
+ mvprintw(i + 1, 0, "%s", item->relpath);
+ else
+ abort();
+
+ mvprintw(i + 1, COLS - 40, "%8ld KiB r%-8ld %s",
+ item->dirent->size / 1024,
+ item->dirent->created_rev,
+ item->dirent->last_author);
+
+ if (i == ctx->selection)
+ standend();
+ }
}
static svn_error_t *
sub_main(int *code, int argc, char *argv[], apr_pool_t *pool)
{
+ svn_browse__ctx_t ctx = { 0 };
+
+ if (argc != 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ "usage: svnbrowse <URL>");
+
+ SVN_ERR(svn_uri_canonicalize_safe(&ctx.root, NULL, argv[1], pool, pool));
+ ctx.revision.kind = svn_opt_revision_head;
+ ctx.list_pool = pool;
+
+ SVN_ERR(init_client(&ctx, pool));
+ SVN_ERR(enter_path(&ctx, "", pool));
+
/* init the display */
initscr();
@@ -56,7 +168,7 @@ sub_main(int *code, int argc, char *argv
int ch;
clear();
- ui_draw(pool);
+ ui_draw(&ctx, pool);
refresh();
ch = getch();
@@ -71,9 +183,28 @@ sub_main(int *code, int argc, char *argv
* 4. If shift is held, they just become uppercased.
*/
+ if (ch == KEY_UP || ch == 'k')
+ {
+ ctx.selection--;
+ }
+ else if (ch == KEY_DOWN || ch == 'j')
+ {
+ ctx.selection++;
+ }
+ else if (ch == '\n' || ch == '\r')
+ {
+ item_t *item = APR_ARRAY_IDX(ctx.list, ctx.selection, item_t *);
+ const char *new_url = svn_relpath_join(ctx.relpath, item->relpath,
pool);
+ SVN_ERR(enter_path(&ctx, new_url, pool));
+ }
+ else if (ch == KEY_BACKSPACE || ch == '-' || ch == 'u')
+ {
+ const char *new_url = svn_relpath_dirname(ctx.relpath, pool);
+ SVN_ERR(enter_path(&ctx, new_url, pool));
+ }
/* TODO: quit via escape. some say just check for 27, but it I think it's
* a bit ugly. */
- if (ch == 'q')
+ else if (ch == 'q')
{
break;
}