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)