Here's one I wrote a few months back.  I didn't send it here because I
didn't think it was a serious proposition, just something fun to
exercise my brain cells.  :-)

One thing "git bisect" does that's not implemented here is to support a
third response, "unable to test", and skip such revisions.  I tried
thinking about an algorithm to do that nicely, but didn't get anywhere
really concrete.

- Julian


Stefan Sperling wrote:
> On Tue, Jun 21, 2011 at 01:15:06PM +0530, Arwin Arni wrote:
> > Hi All,
> > 
> > I am currently trying to implement "svn bisect" subcommand. Yes, I
> > know there are some good scripts out there that work, but it's not
> > part of our API. I figured this would be a decent addition to our
> > code. Here are a few things I wanted to ask the community:
> > 
> > 1. Would it be better if the command ran as a single process
> > throughout the bisect operation and keep track of things in memory,
> > or should it work like the scripts that are out there which keep
> > track of things on disk (in a persistent file)?
> 
> Depends on your requirements. I suppose bisect is supposed to
> be restartable? If so it would probably make sense to stick
> state somewhere into wc.db?
> 
> > 2. For the scripts that are currently out there, the 'probe script'
> > runs in the environment in which the bisect script was run. Is it
> > safe to have a subcommand that runs an external script? Is there a
> > precedent to this kind of behaviour?
> 
> The script could delete files, trash the working copy, whatever.
> But it is, after all, supplied by the user doing the bisection, right?
> So I don't think there is any difference here to existing mechanisms
> that invoke diff commands and the like. Those are equally "unsafe".
> 
> > 3. Will this feature be considered at all (if it is any good) or am
> > I simply doing something to exercise my brain cells?
> 
> I would consider it useful.
> 
> Since you have some track record in getting patches committed,
> I'd like to offer you commit access to a branch in our repository
> so you work on this there if you like.

Add an "svn bisect" subcommand that performs a binary search through a range
of revisions, like "git bisect run".

Syntax is: svn bisect -r N:M run TEST-COMMAND WCPATH

See:
  git bisect:
    <http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html>
  hg bisect:
    <http://www.selenic.com/mercurial/hg.1.html>
  bzr bisect
    <http://doc.bazaar.canonical.com/plugins/en/bisect-plugin.html>
  svn-bisect.pl:
    <http://search.cpan.org/dist/App-SVN-Bisect/bin/svn-bisect>
  darcs trackdown:
    <http://darcs.net/manual/node7.html#SECTION007114000000000000000>

### TODO: Consider implementing as an external script instead.
### TODO: Implement skipping when return code is 125.
### TODO: Change syntax: have cmd provided by an option (so there can
          be a default later) instead of a required arg?
### TODO: Make WCPATH optional (default: ".")?
### TODO: Make the 'update' step optional like "hg bisect -U|--noupdate".

* subversion/svn/bisect-cmd.c
  New file.

* subversion/svn/cl.h
  (svn_opt_subcommand_t): Add svn_cl__bisect to the list.

* subversion/svn/main.c
  (svn_cl__cmd_table): Add the 'bisect' subcommand.
  (main): Allow svn_cl__bisect() to take a revision range.
--This line, and those below, will be ignored--

Index: subversion/svn/bisect-cmd.c
===================================================================
--- subversion/svn/bisect-cmd.c	(revision 0)
+++ subversion/svn/bisect-cmd.c	(working copy)
@@ -0,0 +1,201 @@
+/*
+ * bisect-cmd.c -- Find a revision by binary search
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "../libsvn_client/client.h"
+#include "cl.h"
+
+
+/*** Code. ***/
+
+/* Update the WC at WC_PATH to revision REVNUM. */
+static void
+update_wc(const char *wc_path,
+          svn_revnum_t revnum,
+          svn_boolean_t quiet,
+          svn_client_ctx_t *ctx,
+          apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *wc_paths
+    = apr_array_make(scratch_pool, 0, sizeof(char *));
+  svn_opt_revision_t revision = { svn_opt_revision_number, { revnum }};
+
+  APR_ARRAY_PUSH(wc_paths, const char *) = wc_path;
+  if (quiet)
+    ctx->notify_func = NULL;
+  svn_client_update4(NULL, wc_paths, &revision, svn_depth_infinity,
+                     FALSE /* depth_is_sticky */,
+                     FALSE /* ingore_externals */,
+                     FALSE /* allow_unver_obstructions */,
+                     FALSE /* make_parents */,
+                     ctx, scratch_pool);
+}
+
+struct receiver_baton
+{
+  apr_array_header_t *revs;
+};
+
+/* Implements svn_log_entry_receiver_t */
+static svn_error_t *
+receiver(void *baton,
+         svn_log_entry_t *log_entry,
+         apr_pool_t *pool)
+{
+  struct receiver_baton *rb = baton;
+
+  APR_ARRAY_PUSH(rb->revs, svn_revnum_t) = log_entry->revision;
+  return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__bisect(apr_getopt_t *os,
+               void *baton,
+               apr_pool_t *pool)
+{
+  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+  apr_pool_t *iterpool = svn_pool_create(pool);
+  const char *wc_path, *log_path;
+  svn_opt_revision_t wc_peg_rev, log_peg_rev;
+  const char *cmd;
+  apr_array_header_t *revs
+    = apr_array_make(pool, 1, sizeof(svn_revnum_t));
+  int i_min, i_max;
+
+  if (os->argc != os->ind + 3)
+    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+  /* The first argument must be "run" */
+  if (strcmp(os->argv[os->ind++], "run") != 0)
+    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+  /* The command to run is the next argument after "run" */
+  cmd = os->argv[os->ind++];
+
+  /* The WC abspath or URL to update */
+  SVN_ERR(svn_cl__opt_parse_path(&wc_peg_rev, &wc_path, os->argv[os->ind++], pool));
+  if (!svn_path_is_url(wc_path))
+    SVN_ERR(svn_dirent_get_absolute(&wc_path, wc_path, pool));
+
+  /* The WC abspath or URL whose changes we are to bisect */
+  /* Assume same as path to update, for now. */
+  log_peg_rev = wc_peg_rev;
+  log_path = wc_path;
+
+  /* Get the list of revs into REVS. */
+  {
+    apr_array_header_t *log_paths = apr_array_make(pool, 0, sizeof(char *));
+    apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
+
+    svn_opt_revision_range_t rev_range
+      = { opt_state->start_revision, opt_state->end_revision };
+    apr_array_header_t *rev_ranges
+      = apr_array_make(pool, 1, sizeof(svn_opt_revision_range_t *));
+    struct receiver_baton rb = { revs };
+
+    APR_ARRAY_PUSH(log_paths, const char *) = log_path;
+    APR_ARRAY_PUSH(rev_ranges, svn_opt_revision_range_t *) = &rev_range;
+
+    SVN_ERR(svn_client_log5(log_paths, &log_peg_rev, rev_ranges,
+                            0 /* limit */,
+                            FALSE /* discover_changed_paths */,
+                            FALSE /* strict_node_history */,
+                            FALSE /* include_merged_revisions */,
+                            revprops,
+                            receiver, &rb, ctx, iterpool));
+  }
+
+  i_min = 0;
+  i_max = revs->nelts - 1;
+
+  /* Binary chop. Loop invariant: () */
+  for (;;)
+    {
+      int i = (i_min + i_max) / 2;
+      svn_revnum_t rev = APR_ARRAY_IDX(revs, i, svn_revnum_t);
+      int result;
+
+      svn_pool_clear(iterpool);
+      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+      if (! opt_state->quiet)
+        {
+          svn_revnum_t rev_min = APR_ARRAY_IDX(revs, i_min, svn_revnum_t);
+          svn_revnum_t rev_max = APR_ARRAY_IDX(revs, i_max, svn_revnum_t);
+          printf("svn-bisect: untried [%d:%d]=r%ld:%ld; trying [%d]=r%ld\n",
+                 i_min, i_max, rev_min, rev_max, i, rev);
+        }
+      update_wc(wc_path, rev, opt_state->quiet, ctx, iterpool);
+
+      if (! opt_state->quiet)
+        printf("svn-bisect: running '%s'\n", cmd);
+      result = system(cmd) / 256;
+
+      /* Special exit code for skipping - see manual of 'git bisect run' */
+      if (result == 125)
+        {
+          /* Skip this rev; choose another. */
+          /* ### TODO */
+          printf("svn-bisect: status code %d from command '%s'; skipping\n",
+                 result, cmd);
+          break;
+        }
+
+      /* Exit if the status code indicates a more severe problem than test
+       * failure */
+      if (result < 0 || result > 127)
+        {
+          printf("svn-bisect: status code %d from command '%s'; aborting\n",
+                 result, cmd);
+          break;
+        }
+
+      /* Binary chop */
+      if (result != 0)  /* choose the lower half */
+        {
+          if (i == i_min)
+            break;
+          i_max = i - 1;
+        }
+      else  /* choose the higher half */
+        {
+          if (i == i_max)
+            break;
+          i_min = i + 1;
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
Index: subversion/svn/bisect-cmd.c
===================================================================
--- subversion/svn/bisect-cmd.c	(revision 0)
+++ subversion/svn/bisect-cmd.c	(working copy)

Property changes on: subversion/svn/bisect-cmd.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h	(revision 1036873)
+++ subversion/svn/cl.h	(working copy)
@@ -243,6 +243,7 @@ typedef struct
 /* Declare all the command procedures */
 svn_opt_subcommand_t
   svn_cl__add,
+  svn_cl__bisect,
   svn_cl__blame,
   svn_cl__cat,
   svn_cl__changelist,
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c	(revision 1036873)
+++ subversion/svn/main.c	(working copy)
@@ -413,6 +413,23 @@ const svn_opt_subcommand_desc2_t svn_cl_
      opt_no_autoprops, opt_parents },
      {{opt_parents, N_("add intermediate parents")}} },
 
+  { "bisect", svn_cl__bisect, {0}, N_
+    ("Find a revision by binary search with a user-specified criterion.\n"
+     "usage: bisect [-r N:M] run TEST-COMMAND WCPATH\n"
+     "\n"
+     "  Find the revisions in which WCPATH was changed in the range N:M\n"
+     "  (default 1:BASE).  Repeatedly select a revision from that list,\n"
+     "  starting in the middle, and searching higher or lower in the list\n"
+     "  depending on the result of running TEST-COMMAND on that revision.\n"
+     "  TEST-COMMAND is a shell command line.  Update WCPATH to the trial\n"
+     "  revision before running TEST-COMMAND.\n"
+     "\n"
+     "  If the return status code of TEST-COMMAND is 0, narrow the range of\n"
+     "  revisions to try to the higher half of the current range; if 1 to 127\n"
+     "  excluding 125, narrow it to the lower half; (TODO:) if 125, skip this\n"
+     "  revision and try a different one instead.\n"),
+    {'r', 'q'} },
+
   { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, N_
     ("Output the content of specified files or\n"
      "URLs with revision and author information in-line.\n"
@@ -2115,6 +2132,7 @@ main(int argc, const char *argv[])
   /* Only a few commands can accept a revision range; the rest can take at
      most one revision number. */
   if (subcommand->cmd_func != svn_cl__blame
+      && subcommand->cmd_func != svn_cl__bisect
       && subcommand->cmd_func != svn_cl__diff
       && subcommand->cmd_func != svn_cl__log
       && subcommand->cmd_func != svn_cl__merge)

Reply via email to