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)