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;
         }

Reply via email to