{"thread":{"id":"57748","subject":"[PATCH] rebase: use correct base for --keep-base when a branch is given","startedAt":"2022-04-18T01:27:31Z","lastAt":"2022-04-21T04:44:35Z","messageCount":4,"participants":["Alex Henrie","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"453799","messageId":"20220418012716.2683624-1-alexhenrie24@gmail.com","threadId":"57748","inReplyTo":null,"subject":"[PATCH] rebase: use correct base for --keep-base when a branch is given","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2022-04-18T01:27:16Z","receivedAt":"2022-04-18T01:27:31Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"--keep-base rebases onto the merge base of the given upstream and the\ncurrent HEAD regardless of whether a branch is given. This is contrary\nto the documentation and to the option's intended purpose. Instead,\nrebase onto the merge base of the given upstream and the given branch.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n Documentation/git-rebase.txt     |  5 +--\n builtin/rebase.c                 | 55 ++++++++++++++++----------------\n t/t3416-rebase-onto-threedots.sh | 33 +++++++++++++++++++\n 3 files changed, 64 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 9da4647061..262fb01aec 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -215,9 +215,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.\n \n --keep-base::\n \tSet the starting point at which to create the new commits to the\n-\tmerge base of <upstream> <branch>. Running\n+\tmerge base of <upstream> and <branch>. Running\n \t'git rebase --keep-base <upstream> <branch>' is equivalent to\n-\trunning 'git rebase --onto <upstream>... <upstream>'.\n+\trunning\n+\t'git rebase --onto <upstream>...<branch> <upstream> <branch>'.\n +\n This option is useful in the case where one is developing a feature on\n top of an upstream branch. While the feature is being worked on, the\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 27fde7bf28..7f3bffc0a2 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1583,33 +1583,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.upstream_arg = \"--root\";\n \t}\n \n-\t/* Make sure the branch to rebase onto is valid. */\n-\tif (keep_base) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, options.upstream_name);\n-\t\tstrbuf_addstr(&buf, \"...\");\n-\t\toptions.onto_name = xstrdup(buf.buf);\n-\t} else if (!options.onto_name)\n-\t\toptions.onto_name = options.upstream_name;\n-\tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n-\t\t\tif (keep_base)\n-\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n-\t\t\t\t    options.upstream_name);\n-\t\t\telse\n-\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t\t    options.onto_name);\n-\t\t}\n-\t\toptions.onto = lookup_commit_or_die(&merge_base,\n-\t\t\t\t\t\t    options.onto_name);\n-\t} else {\n-\t\toptions.onto =\n-\t\t\tlookup_commit_reference_by_name(options.onto_name);\n-\t\tif (!options.onto)\n-\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n-\t\t\t\toptions.onto_name);\n-\t}\n-\n \t/*\n \t * If the branch to rebase is given, that is the branch we will rebase\n \t * branch_name -- branch/commit being rebased, or\n@@ -1659,6 +1632,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t} else\n \t\tBUG(\"unexpected number of arguments left to parse\");\n \n+\t/* Make sure the branch to rebase onto is valid. */\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\tstrbuf_addstr(&buf, branch_name);\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n+\t\toptions.onto_name = options.upstream_name;\n+\tif (strstr(options.onto_name, \"...\")) {\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n+\t\toptions.onto = lookup_commit_or_die(&merge_base,\n+\t\t\t\t\t\t    options.onto_name);\n+\t} else {\n+\t\toptions.onto =\n+\t\t\tlookup_commit_reference_by_name(options.onto_name);\n+\t\tif (!options.onto)\n+\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n+\t\t\t\toptions.onto_name);\n+\t}\n+\n \tif (options.fork_point > 0) {\n \t\tstruct commit *head =\n \t\t\tlookup_commit_reference(the_repository,\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex 3716a42e81..d1db528e25 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -129,6 +129,22 @@ test_expect_success 'rebase --keep-base main from topic' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase --keep-base main topic from main' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\tgit checkout main &&\n+\n+\tgit rebase --keep-base main topic &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base main HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase --keep-base main from side' '\n \tgit reset --hard &&\n \tgit checkout side &&\n@@ -153,6 +169,23 @@ test_expect_success 'rebase -i --keep-base main from topic' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase -i --keep-base main topic from main' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\tgit checkout main &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base main topic &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base main HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase -i --keep-base main from side' '\n \tgit reset --hard &&\n \tgit checkout side &&\n-- \n2.35.3\n\n"},{"id":"454031","messageId":"xmqqh76no53x.fsf@gitster.g","threadId":"57748","inReplyTo":"20220418012716.2683624-1-alexhenrie24@gmail.com","subject":"Re: [PATCH] rebase: use correct base for --keep-base when a branch is given","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-04-20T20:46:26Z","receivedAt":"2022-04-20T20:46:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alex Henrie <alexhenrie24@gmail.com> writes:\n\n> -\t/* Make sure the branch to rebase onto is valid. */\n\nSo, we used to ...\n\n> -\tif (keep_base) {\n> -\t\tstrbuf_reset(&buf);\n> -\t\tstrbuf_addstr(&buf, options.upstream_name);\n> -\t\tstrbuf_addstr(&buf, \"...\");\n> -\t\toptions.onto_name = xstrdup(buf.buf);\n\n... store \"<upstream>...\" in onto_name ...\n\n> -\t} else if (!options.onto_name)\n> -\t\toptions.onto_name = options.upstream_name;\n\n... or used <upstream> as onto_name, and then ...\n\n> -\tif (strstr(options.onto_name, \"...\")) {\n\n... immediately used the \"...\" as a cue to compute the merge base\nand ...\n\n> -\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n> -\t\t\tif (keep_base)\n> -\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> -\t\t\t\t    options.upstream_name);\n> -\t\t\telse\n> -\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> -\t\t\t\t    options.onto_name);\n> -\t\t}\n> -\t\toptions.onto = lookup_commit_or_die(&merge_base,\n> -\t\t\t\t\t\t    options.onto_name);\n\n... used the \"<upstream>...\" as a label, and the merge base as the\n\"onto\" commit.\n\n> -\t} else {\n> -\t\toptions.onto =\n> -\t\t\tlookup_commit_reference_by_name(options.onto_name);\n> -\t\tif (!options.onto)\n> -\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n> -\t\t\t\toptions.onto_name);\n> -\t}\n> -\n\nBut that was done before we even examined the optinal \"this one, not\nthe current branch, is to be rebased\" argument.  So it all works by\nassuming the current branch is what is being rebase, which clearly\nis wrong.\n\nIt is surprising that a basic and trivial bug was with us forever.\nI wonder if the addition of --keep-base was faulty and the feature\nwas broken from the beginning, or there was an unrelated change that\nbroke the interaction between --keep-base and branch-to-be-rebased?\n\n    ... goes and looks ...\n    \n    Between 640f9cd5 (Merge branch 'dl/rebase-i-keep-base',\n    2019-09-30) which was where \"--keep-base\" was introduced, and\n    today's codebase, there are many unrelated changes to\n    builtin/rebase.c but this part is essentially unchanged.  Before\n    the option was introduced, it didn't matter if the computation\n    of \"onto\" came before or after the next part that is not shown\n    in this patch, because \"...\" could have come only from the\n    end-user input and there the end-user must have given the branch\n    to be rebased on the right hand side of \"...\" if that is what\n    they meant.  So, the feature was broken from the moment the\n    \"--keep-base\" option was introduced.\n\nNow we got rid of all the above, so we need to be careful that we do\nnot depend on options.onto_name and options.onto in the part that\nyou did not touch, from here to the precontext of the next hunk.\n\nAnd I looked for onto and onto_name, these strings do not appear\nfrom this point until the next hunk where we see an added line, so\nwe are not breaking that part of the code by removing this block of\ncode from here and moving it down.\n\n>  \t/*\n>  \t * If the branch to rebase is given, that is the branch we will rebase\n>  \t * branch_name -- branch/commit being rebased, or\n> @@ -1659,6 +1632,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t} else\n>  \t\tBUG(\"unexpected number of arguments left to parse\");\n>  \n> +\t/* Make sure the branch to rebase onto is valid. */\n\nAnd now we do what the removed block wanted to do here, but we do so\nin a different context.  We know know which branch gets rebased in\nthe branch_name variable, so we do not use \"<upstream>...\" notation\nto imply \"the current branch\" on the RHS; we can be explicit.\n\n> +\tif (keep_base) {\n> +\t\tstrbuf_reset(&buf);\n> +\t\tstrbuf_addstr(&buf, options.upstream_name);\n> +\t\tstrbuf_addstr(&buf, \"...\");\n> +\t\tstrbuf_addstr(&buf, branch_name);\n\n... and that is what happens here, which is good.\n\nThe rest of this new block is a literal copy from the old code\nremoved above, which is as expected.\n\n> +\t\toptions.onto_name = xstrdup(buf.buf);\n> +\t} else if (!options.onto_name)\n> +\t\toptions.onto_name = options.upstream_name;\n> +\tif (strstr(options.onto_name, \"...\")) {\n> +\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n> +\t\t\tif (keep_base)\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> +\t\t\t\t    options.upstream_name);\n> +\t\t\telse\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> +\t\t\t\t    options.onto_name);\n> +\t\t}\n> +\t\toptions.onto = lookup_commit_or_die(&merge_base,\n> +\t\t\t\t\t\t    options.onto_name);\n> +\t} else {\n> +\t\toptions.onto =\n> +\t\t\tlookup_commit_reference_by_name(options.onto_name);\n> +\t\tif (!options.onto)\n> +\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n> +\t\t\t\toptions.onto_name);\n> +\t}\n> +\n>  \tif (options.fork_point > 0) {\n>  \t\tstruct commit *head =\n>  \t\t\tlookup_commit_reference(the_repository,\n\n\n\n> diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n> index 3716a42e81..d1db528e25 100755\n> --- a/t/t3416-rebase-onto-threedots.sh\n> +++ b/t/t3416-rebase-onto-threedots.sh\n> @@ -129,6 +129,22 @@ test_expect_success 'rebase --keep-base main from topic' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'rebase --keep-base main topic from main' '\n> +\tgit reset --hard &&\n\nClearing whatever cruft the last test left is good, but ...\n\n> +\tgit checkout topic &&\n> +\tgit reset --hard G &&\n> +\tgit checkout main &&\n\nit would be more efficient to just do\n\n\tgit checkout main &&\n\tgit branch -f topic G &&\n\nno?  Instead of rewriting the working tree three times, you only\nneed to do so once here.\n\n"},{"id":"454049","messageId":"CAMMLpeTLkg7=WwhpkpA53bQ5ucSPH3zTXRdhR7uj9OKA=7s=kw@mail.gmail.com","threadId":"57748","inReplyTo":"xmqqh76no53x.fsf@gitster.g","subject":"Re: [PATCH] rebase: use correct base for --keep-base when a branch is given","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2022-04-21T04:38:49Z","receivedAt":"2022-04-21T04:39:05Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"On Wed, Apr 20, 2022 at 2:46 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> It is surprising that a basic and trivial bug was with us forever.\n\nI was also surprised. Like you, I dug through the mailing list\narchives and my impression was that the code review was mostly\nconcerned about the case where there are multiple merge bases. No one\nseemed to have read the man page closely enough to notice that 'git\nrebase --keep-base <upstream> <branch>' cannot be equivalent to 'git\nrebase --onto <upstream>... <upstream>' because the latter doesn't\nhave <branch> at all. Seeing that discrepancy is what prompted me to\ntry out what actually happens when the branch is specified on the\ncommand line, and then I realized that it was more than just a\ndocumentation error.\n\n> > +test_expect_success 'rebase --keep-base main topic from main' '\n> > +     git reset --hard &&\n>\n> Clearing whatever cruft the last test left is good, but ...\n>\n> > +     git checkout topic &&\n> > +     git reset --hard G &&\n> > +     git checkout main &&\n>\n> it would be more efficient to just do\n>\n>         git checkout main &&\n>         git branch -f topic G &&\n>\n> no?  Instead of rewriting the working tree three times, you only\n> need to do so once here.\n\nSure. I'll make that change.\n\n-Alex\n"},{"id":"454050","messageId":"20220421044233.894255-1-alexhenrie24@gmail.com","threadId":"57748","inReplyTo":"20220418012716.2683624-1-alexhenrie24@gmail.com","subject":"[PATCH v2] rebase: use correct base for --keep-base when a branch is given","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2022-04-21T04:42:33Z","receivedAt":"2022-04-21T04:44:35Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"--keep-base rebases onto the merge base of the given upstream and the\ncurrent HEAD regardless of whether a branch is given. This is contrary\nto the documentation and to the option's intended purpose. Instead,\nrebase onto the merge base of the given upstream and the given branch.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\nv2: clear previous test cruft more efficiently\n---\n Documentation/git-rebase.txt     |  5 +--\n builtin/rebase.c                 | 55 ++++++++++++++++----------------\n t/t3416-rebase-onto-threedots.sh | 29 +++++++++++++++++\n 3 files changed, 60 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 9da4647061..262fb01aec 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -215,9 +215,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.\n \n --keep-base::\n \tSet the starting point at which to create the new commits to the\n-\tmerge base of <upstream> <branch>. Running\n+\tmerge base of <upstream> and <branch>. Running\n \t'git rebase --keep-base <upstream> <branch>' is equivalent to\n-\trunning 'git rebase --onto <upstream>... <upstream>'.\n+\trunning\n+\t'git rebase --onto <upstream>...<branch> <upstream> <branch>'.\n +\n This option is useful in the case where one is developing a feature on\n top of an upstream branch. While the feature is being worked on, the\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 27fde7bf28..7f3bffc0a2 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1583,33 +1583,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.upstream_arg = \"--root\";\n \t}\n \n-\t/* Make sure the branch to rebase onto is valid. */\n-\tif (keep_base) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, options.upstream_name);\n-\t\tstrbuf_addstr(&buf, \"...\");\n-\t\toptions.onto_name = xstrdup(buf.buf);\n-\t} else if (!options.onto_name)\n-\t\toptions.onto_name = options.upstream_name;\n-\tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n-\t\t\tif (keep_base)\n-\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n-\t\t\t\t    options.upstream_name);\n-\t\t\telse\n-\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t\t    options.onto_name);\n-\t\t}\n-\t\toptions.onto = lookup_commit_or_die(&merge_base,\n-\t\t\t\t\t\t    options.onto_name);\n-\t} else {\n-\t\toptions.onto =\n-\t\t\tlookup_commit_reference_by_name(options.onto_name);\n-\t\tif (!options.onto)\n-\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n-\t\t\t\toptions.onto_name);\n-\t}\n-\n \t/*\n \t * If the branch to rebase is given, that is the branch we will rebase\n \t * branch_name -- branch/commit being rebased, or\n@@ -1659,6 +1632,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t} else\n \t\tBUG(\"unexpected number of arguments left to parse\");\n \n+\t/* Make sure the branch to rebase onto is valid. */\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\tstrbuf_addstr(&buf, branch_name);\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n+\t\toptions.onto_name = options.upstream_name;\n+\tif (strstr(options.onto_name, \"...\")) {\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n+\t\toptions.onto = lookup_commit_or_die(&merge_base,\n+\t\t\t\t\t\t    options.onto_name);\n+\t} else {\n+\t\toptions.onto =\n+\t\t\tlookup_commit_reference_by_name(options.onto_name);\n+\t\tif (!options.onto)\n+\t\t\tdie(_(\"Does not point to a valid commit '%s'\"),\n+\t\t\t\toptions.onto_name);\n+\t}\n+\n \tif (options.fork_point > 0) {\n \t\tstruct commit *head =\n \t\t\tlookup_commit_reference(the_repository,\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex 3716a42e81..3e04802cb0 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -129,6 +129,20 @@ test_expect_success 'rebase --keep-base main from topic' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase --keep-base main topic from main' '\n+\tgit checkout main &&\n+\tgit branch -f topic G &&\n+\n+\tgit rebase --keep-base main topic &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base main HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase --keep-base main from side' '\n \tgit reset --hard &&\n \tgit checkout side &&\n@@ -153,6 +167,21 @@ test_expect_success 'rebase -i --keep-base main from topic' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase -i --keep-base main topic from main' '\n+\tgit checkout main &&\n+\tgit branch -f topic G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base main topic &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base main HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'rebase -i --keep-base main from side' '\n \tgit reset --hard &&\n \tgit checkout side &&\n-- \n2.36.0\n\n"}]}