{"thread":{"id":"16456","subject":"[PATCH 1/9 v5] bisect: add \"git bisect replace\" subcommand","startedAt":"2008-11-24T21:13:03Z","lastAt":"2008-11-24T21:13:03Z","messageCount":1,"participants":["Christian Couder"],"isPatch":true,"patchVersion":5,"patchTotal":9},"messages":[{"id":"96445","messageId":"20081124221303.7ec1c862.chriscool@tuxfamily.org","threadId":"16456","inReplyTo":null,"subject":"[PATCH 1/9 v5] bisect: add \"git bisect replace\" subcommand","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2008-11-24T21:13:03Z","receivedAt":"2008-11-24T21:13:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This subcommand should be used when you have a branch or a part of a\nbranch that isn't easily bisectable because of a bug that has been\nfixed latter.\n\nWe suppose that a bug as been introduced at some point, say A, and\nthat it has been fixed latter at another point, say B, but that\nbetween these points the code is not easily testable because of the\nbug, so it's not easy to bisect between these points.\n\nIn this case you can create a branch starting at the parent of A, say\nO, that has a fixed history. In this fixed history for example, there\ncould be first a commit C that is the result of squashing A and B\ntogether and then all the commits between A and B that have been\ncherry picked.\n\nFor example, let's say the commits between A and B are X1, X2, ... Xn\nand they have been cherry picked after C as Y1, Y2, ... Yn:\n\n        C--Y1--Y2--...--Yn\n      /\n...--O--A--X1--X2--...--Xn--B--...\n\nBy design, the last cherry picked commit (Yn) should point to the same\ntree as commit B.\n\nSo in this case you can say:\n\n$ git bisect replace B Yn\n\nand a special ref will be created that points to commit Yn. This ref\nwill be named: \"refs/replace/bisect/B\"\n\nWhen bisecting, the refs in \"refs/replace/bisect/\" will be scanned and\neach ref named X found there and pointing to commit Y will be grafted\nso that X will only have Y as parent.\n\nIn the example above, that means that instead of the above graph, the\nfollowing graph will be bisected:\n\n        C--Y1--Y2--...--Yn\n      /                   \\\n...--O                     B--...\n\nThis means that the bisections on this branch will be much easier\nbecause the bug introduced by commit A and fixed by commit B will not\nannoy you anymore.\n\nAs the refs created by \"git bisect replace\" can be shared between\ndevelopers, this feature might be especially usefull on big projects\nwhere many people often bisect the same code base.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin-rev-list.c |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++-\n git-bisect.sh      |   36 +++++++++++++++++++++++++-\n refs.c             |    5 +++\n refs.h             |    1 +\n 4 files changed, 110 insertions(+), 3 deletions(-)\n\n\tJunio wrote:\n\t>\n\t> I have a separate idea make 'grafts' easier on object transfer, that is\n\t> somewhat related to this one, by the way.  Instead of making the grafts\n\t> completely a local matter as we do now, we can reserve refs/replace/\n\t> namespace, and record a new commit object to replace an existing commit\n\t> whose object name is $sha1 as refs/replace/$sha1.  We make almost all the\n\t> commands except object enumeration (fsck, receive-pack, send-pack, prune,\n\t> etc.  Roughly speaking, anything that involves \"rev-list --objects\") honor\n\t> this commit replacement, so that any time you ask for commit $sha1, the\n\t> object layer gives you the replacement commit object back.  In this way,\n\t> you can clone or fetch from such a repository (along with refs in\n\t> refs/replace/ hierarchy) and fsck/prune won't lose the original parents\n\t> (because it does not see replacements).  Things like paranoid update hook\n\t> needs to become very careful about refs/replace/ for security reasons, but\n\t> I think this would make the grafts much easier to use.\n\n\tIn this new version the \"bisect-replace-*\" branches have been replaced by\n\trefs like \"refs/replace/bisect/$sha1\". Most of the code changes are in the\n\tfirst patch. But there are also many documentation changes in the series. \n\n\tI will see later if I can change usual grafts into refs in \"refs/replace/grafts/\"\n\tor something like that.\n\n\ndiff --git a/builtin-rev-list.c b/builtin-rev-list.c\nindex 857742a..8adf269 100644\n--- a/builtin-rev-list.c\n+++ b/builtin-rev-list.c\n@@ -574,6 +574,72 @@ static struct commit_list *find_bisection(struct commit_list *list,\n \treturn best;\n }\n \n+static void replace_parents(struct commit *commit,\n+\t\t\t    const char *refname,\n+\t\t\t    const unsigned char *sha1)\n+{\n+\tstruct commit *new_parent = lookup_commit(sha1);\n+\tif (!new_parent) {\n+\t\twarning(\"replace ref '%s' points to unknown commit '%s'\",\n+\t\t\trefname, sha1_to_hex(sha1));\n+\t\treturn;\n+\t}\n+\n+\tfree_commit_list(commit->parents);\n+\tcommit->parents = NULL;\n+\tcommit_list_insert(new_parent, &commit->parents);\n+}\n+\n+static int bisect_replace(const char *refname, const unsigned char *sha1,\n+\t\t\t  int flag, void *cb_data)\n+{\n+\tunsigned char child[20];\n+\tstruct object *obj;\n+\tstruct commit_graft *graft;\n+\n+\tif (prefixcmp(refname, \"bisect/\"))\n+\t\treturn 0;\n+\n+\tif (get_sha1_hex(refname + 7, child)) {\n+\t\twarning(\"bad sha1 in replace ref named '%s'\", refname);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Check if child commit exist and is already parsed */\n+\n+\tobj = lookup_object(child);\n+\tif (obj) {\n+\t\tstruct commit *commit;\n+\t\tif (obj->type != OBJ_COMMIT) {\n+\t\t\twarning(\"replace ref name '%s' refers to non commit '%s'\",\n+\t\t\t\trefname, refname + 7);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcommit = (struct commit *) obj;\n+\t\tif (commit->object.parsed) {\n+\t\t\treplace_parents(commit, refname, sha1);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t/* Create a graft to replace child commit parents */\n+\n+\tgraft = xmalloc(sizeof(*graft) + 20);\n+\n+\thashcpy(graft->sha1, child);\n+\tgraft->nr_parent = 1;\n+\thashcpy(graft->parent[0], sha1);\n+\n+\tregister_commit_graft(graft, 1);\n+\n+\treturn 0;\n+}\n+\n+static void bisect_replace_all(void)\n+{\n+\tfor_each_replace_ref(bisect_replace, NULL);\n+}\n+\n int cmd_rev_list(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit_list *list;\n@@ -646,8 +712,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)\n \n \tsave_commit_buffer = revs.verbose_header ||\n \t\trevs.grep_filter.pattern_list;\n-\tif (bisect_list)\n+\n+\tif (bisect_list) {\n+\t\tbisect_replace_all();\n \t\trevs.limited = 1;\n+\t}\n \n \tif (prepare_revision_walk(&revs))\n \t\tdie(\"revision walk setup failed\");\ndiff --git a/git-bisect.sh b/git-bisect.sh\nindex 0d0e278..ed7c620 100755\n--- a/git-bisect.sh\n+++ b/git-bisect.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'\n+USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|replace|run]'\n LONG_USAGE='git bisect help\n         print this long help message.\n git bisect start [<bad> [<good>...]] [--] [<pathspec>...]\n@@ -21,6 +21,8 @@ git bisect replay <logfile>\n         replay bisection log.\n git bisect log\n         show bisect log.\n+git bisect replace <rev> [<rev>]\n+        use another branch for bisection.\n git bisect run <cmd>...\n         use <cmd>... to automatically bisect.\n \n@@ -566,6 +568,35 @@ bisect_replay () {\n \tbisect_auto_next\n }\n \n+bisect_replace() {\n+    test \"$#\" -ge 1 -a \"$#\" -le 2 ||\n+        die \"'git bisect replace' accept one or two arguments\"\n+\n+    source=\"$1\"\n+    target=\"${2:-HEAD}\"\n+\n+    # Check arguments\n+    src_commit=$(git rev-parse --verify \"$source^{commit}\") ||\n+        die \"Bad rev input: $source\"\n+    tgt_commit=$(git rev-parse --verify \"$target^{commit}\") ||\n+        die \"Bad rev input: $target\"\n+\n+    test \"$src_commit\" != \"tgt_commit\" ||\n+        die \"source and target should be different commits\"\n+\n+    # Check that trees from source and target are identical\n+    src_tree=$(git rev-parse --verify \"$src_commit^{tree}\") ||\n+        die \"Could not get tree for source: $source\"\n+    tgt_tree=$(git rev-parse --verify \"$tgt_commit^{tree}\") ||\n+        die \"Could not get tree for target: $target\"\n+\n+    test \"$src_tree\" = \"$tgt_tree\" ||\n+        die \"source and target should point to the same tree\"\n+\n+    # Create a ref in \"refs/replace/bisect/\" for the target commit\n+    git update-ref -m bisect \"refs/replace/bisect/$src_commit\" \"$tgt_commit\"\n+}\n+\n bisect_run () {\n     bisect_next_check fail\n \n@@ -618,7 +649,6 @@ bisect_run () {\n     done\n }\n \n-\n case \"$#\" in\n 0)\n     usage ;;\n@@ -643,6 +673,8 @@ case \"$#\" in\n \tbisect_replay \"$@\" ;;\n     log)\n \tcat \"$GIT_DIR/BISECT_LOG\" ;;\n+    replace)\n+        bisect_replace \"$@\" ;;\n     run)\n         bisect_run \"$@\" ;;\n     *)\ndiff --git a/refs.c b/refs.c\nindex 33ced65..042106d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -632,6 +632,11 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n \treturn do_for_each_ref(\"refs/remotes/\", fn, 13, cb_data);\n }\n \n+int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn do_for_each_ref(\"refs/replace/\", fn, 13, cb_data);\n+}\n+\n /*\n  * Make sure \"ref\" is something reasonable to have under \".git/refs/\";\n  * We do not like it if:\ndiff --git a/refs.h b/refs.h\nindex 06ad260..8d2ee5a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -23,6 +23,7 @@ extern int for_each_ref(each_ref_fn, void *);\n extern int for_each_tag_ref(each_ref_fn, void *);\n extern int for_each_branch_ref(each_ref_fn, void *);\n extern int for_each_remote_ref(each_ref_fn, void *);\n+extern int for_each_replace_ref(each_ref_fn, void *);\n \n /*\n  * Extra refs will be listed by for_each_ref() before any actual refs\n-- \n1.5.6.1.1657.g6a50\n"}]}