Before I go dig myself in deeper, I'd like some feedback and opinions on
whether this is the correct direction.
If I got it right, do say so, as then I can start adding some tests and
update the documentation.
Olliver
---
In some situations, it is needed to skip certain commits when bisecting,
because the compile doesn't work, or tests are known to fail.
For this purpose, we introduce the `--skip-when` flag which takes a
script as an input and is expected to return exit code 125 if a commit
is to be skipped, which uses a regular `git bisect skip` and the commit
thus ends up on the skipped pile.
In addition we also offer a git-hook, to make this as predictable and
painless as possible.
The script can do whatever it wants to to determine if a commit is to be
skipped; From comparing the hash against a known list, to checking git
notes for a keyword or, as the included example, the commit body.
Signed-off-by: Olliver Schinagl <oliver@xxxxxxxxxxx>
---
bisect.c | 2 +
builtin/bisect.c | 93 +++++++++++++++++++++++-
templates/hooks--bisect-skip_when.sample | 10 +++
3 files changed, 101 insertions(+), 4 deletions(-)
create mode 100755 templates/hooks--bisect-skip_when.sample
diff --git a/bisect.c b/bisect.c
index 60aae2fe50..185909cca9 100644
--- a/bisect.c
+++ b/bisect.c
@@ -476,6 +476,7 @@ static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
+static GIT_PATH_FUNC(git_path_bisect_skip_when, "BISECT_SKIP_WHEN")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
@@ -1179,6 +1180,7 @@ int bisect_clean_state(void)
unlink_or_warn(git_path_bisect_log());
unlink_or_warn(git_path_bisect_names());
unlink_or_warn(git_path_bisect_run());
+ unlink_or_warn(git_path_bisect_skip_when());
unlink_or_warn(git_path_bisect_terms());
unlink_or_warn(git_path_bisect_first_parent());
/*
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 9891cf2604..6870142b85 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -4,6 +4,7 @@
#include "environment.h"
#include "gettext.h"
#include "hex.h"
+#include "hook.h"
#include "object-name.h"
#include "oid-array.h"
#include "parse-options.h"
@@ -14,19 +15,21 @@
#include "revision.h"
#include "run-command.h"
#include "strvec.h"
+#include "wrapper.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
+static GIT_PATH_FUNC(git_path_bisect_skip_when, "BISECT_SKIP_WHEN")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_START_USAGE \
- N_("git bisect start [--term-(new|bad)=<term> --term-(old|good)=<term>]" \
- " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--]" \
- " [<pathspec>...]")
+ N_("git bisect start [--term-(new|bad)=<term> --term-(old|good)=<term>]\n" \
+ " [--no-checkout] [--first-parent] [--skip-when=<script>]\n" \
+ " [<bad> [<good>...]] [--] [<pathspec>...]")
#define BUILTIN_GIT_BISECT_STATE_USAGE \
N_("git bisect (good|bad) [<rev>...]")
#define BUILTIN_GIT_BISECT_TERMS_USAGE \
@@ -89,6 +92,7 @@ static const char vocab_bad[] = "bad|new";
static const char vocab_good[] = "good|old";
static int bisect_autostart(struct bisect_terms *terms);
+static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc, const char **argv);
/*
* Check whether the string `term` belongs to the set of strings
@@ -680,14 +684,74 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
return res;
}
+static int get_skip_when(const char **skip_when)
+{
+ struct strbuf str = STRBUF_INIT;
+ FILE *fp = NULL;
+ int res = 0;
+
+ fp = fopen(git_path_bisect_skip_when(), "r");
+ if (!fp) {
+ res = -1;
+ goto finish;
+ }
+
+ strbuf_getline_lf(&str, fp);
+ *skip_when = strbuf_detach(&str, NULL);
+
+finish:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&str);
+
+ return res;
+}
+
static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)
{
+ int no_checkout = ref_exists("BISECT_HEAD");
+ enum bisect_error res;
+ struct object_id oid;
+
if (bisect_next_check(terms, NULL)) {
bisect_print_status(terms);
return BISECT_OK;
}
- return bisect_next(terms, prefix);
+ res = bisect_next(terms, prefix);
+ if (res)
+ return res;
+
+ if (!read_ref(no_checkout ? "BISECT_HEAD" : "HEAD", &oid)) {
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+ char *rev = oid_to_hex(&oid);
+ const char *skip_when = NULL;
+ int ret = 0;
+
+ get_skip_when(&skip_when);
+ if (skip_when != NULL) {
+ struct child_process cmd = CHILD_PROCESS_INIT;
+
+ cmd.use_shell = 1;
+ cmd.no_stdin = 1;
+ strvec_pushl(&cmd.args, skip_when, rev, NULL);
+
+ printf(_("running '%s'\n"), skip_when);
+ ret = run_command(&cmd);
+ }
+
+ strvec_push(&opt.args, rev);
+ if ((ret == 125) ||
+ (run_hooks_opt("bisect-skip_when", &opt) == 125)) {
+ struct strvec argv = STRVEC_INIT;
+
+ printf(_("auto skipping commit [%s]...\n"), rev);
+ sq_dequote_to_strvec("skip", &argv);
+ res = bisect_skip(terms, argv.nr, argv.v);
+ }
+ }
+
+ return res;
}
static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
@@ -703,6 +767,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
struct strbuf start_head = STRBUF_INIT;
struct strbuf bisect_names = STRBUF_INIT;
struct object_id head_oid;
+ char *skip_when = NULL;
struct object_id oid;
const char *head;
@@ -727,6 +792,15 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
no_checkout = 1;
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
+ } else if (!strcmp(arg, "--skip-when")) {
+ i++;
+
+ if (argc <= i)
+ return error(_("'' is not a valid skip-when script"));
+
+ skip_when = xstrdup(argv[i]);
+ } else if (skip_prefix(arg, "--skip-when=", &arg)) {
+ skip_when = xstrdup(arg);
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
i++;
@@ -867,11 +941,22 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
goto finish;
}
+ if (skip_when) {
+ if (access(skip_when, X_OK)) {
+ res = error(_("%s: no such path in the working tree.\n"), skip_when);
+ goto finish;
+ }
+ write_to_file(git_path_bisect_skip_when(), "%s\n", skip_when);
+ }
+
res = bisect_append_log_quoted(argv);
if (res)
res = BISECT_FAILED;
finish:
+ if (skip_when)
+ free(skip_when);
+
string_list_clear(&revs, 0);
string_list_clear(&states, 0);
strbuf_release(&start_head);
diff --git a/templates/hooks--bisect-skip_when.sample b/templates/hooks--bisect-skip_when.sample
new file mode 100755
index 0000000000..ff3960841f
--- /dev/null
+++ b/templates/hooks--bisect-skip_when.sample
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# usage: ${0} <commit_object_name>
+# expected to exit with 125 when the commit should be skipped
+
+if git cat-file commit "${1:-HEAD}" | grep -q "^GIT_BISECT_SKIP=1$"; then
+ exit 125
+fi
+
+exit 0