{"thread":{"id":"66098","subject":"[PATCH] fiter-branch: fix commit map init from state branch","startedAt":"2026-08-01T03:39:05Z","lastAt":"2026-10-05T06:11:08Z","messageCount":12,"participants":["Grant Moyer","Michele Locati","Patrick Steinhardt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"549372","messageId":"20260801033127.10606-1-dev@grantmoyer.com","threadId":"66098","inReplyTo":null,"subject":"[PATCH] fiter-branch: fix commit map init from state branch","fromName":"Grant Moyer","fromEmail":"dev@grantmoyer.com","sentAt":"2026-08-01T03:31:27Z","receivedAt":"2026-08-01T03:39:05Z","isPatch":true,"body":"The commit map dir is populated from the state branch assuming a\n\"to_commit:from_commit\" format, but the state branch is written with a\n\"from_commit:to_commit\" format, resulting in an inverted mapping when the\nmap is populated from the state branch. This is especially evident when\n--prune-empty is used and creates commits which map to nothing; when the\nmap dir is populated from this state on subsequent runs, git-filter-branch\noutputs many errors while trying to create files with empty names, like:\n\n> /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n\nThis change corrects the population of the commit map dir to match the\n\"from_commit:to_commit\" format.\n\nSigned-off-by: Grant Moyer <dev@grantmoyer.com>\n---\n git-filter-branch.sh     | 4 +++-\n t/t7003-filter-branch.sh | 2 +-\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/git-filter-branch.sh b/git-filter-branch.sh\nindex 24fa317aaa..9aa07be6e1 100755\n--- a/git-filter-branch.sh\n+++ b/git-filter-branch.sh\n@@ -302,7 +302,9 @@ then\n \t\tdo\n \t\t\tcase \"$line\" in\n \t\t\t*:*)\n-\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n+\t\t\t\tfrom_commit=${line%:*}\n+\t\t\t\tto_commit=${line#*:}\n+\t\t\t\techo \"$to_commit\" >../map/\"$from_commit\";;\n \t\t\t*)\n \t\t\t\tdie \"Unable to load state from $state_branch:filter.map\";;\n \t\t\tesac\ndiff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\nindex 86011e7b1f..3934cc4a11 100755\n--- a/t/t7003-filter-branch.sh\n+++ b/t/t7003-filter-branch.sh\n@@ -121,7 +121,7 @@ W=$(git rev-parse HEAD)\n test_expect_success 'using --state-branch to skip already rewritten commits' '\n \ttest_when_finished git reset --hard $V &&\n \tgit reset --hard $V &&\n-\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n+\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n \ttest_cmp_rev $W HEAD\n '\n \n\nbase-commit: a97fcc37c2bc6340a8d7ce78dedf227aac4e9aa7\n-- \n2.55.0\n\n"},{"id":"553605","messageId":"20260929143150.2420-1-michele@locati.it","threadId":"66098","inReplyTo":"20260801033127.10606-1-dev@grantmoyer.com","subject":"Re: [PATCH] fiter-branch: fix commit map init from state branch","fromName":"Michele Locati","fromEmail":"michele@locati.it","sentAt":"2026-09-29T14:31:50Z","receivedAt":"2026-09-29T14:35:07Z","isPatch":true,"body":"Hi Grant,\n\nthanks for this patch: I hit the same bug, and I was about to report it.\n\nCc'ing Patrick and Junio, since the problem was introduced by\nf6d855091e (filter-branch: stop depending on Perl, 2025-04-16),\nreleased in 2.50.0.\n\nI tested your fix with git 2.56.0, running filter-branch incrementally\nwith --state-branch, --prune-empty and --subdirectory-filter:\n\n- without the fix, the new commits are attached to the original\n  parents instead of the rewritten ones, and swapped entries are saved\n  back to the state branch;\n- with the fix, the parents are the rewritten commits and the state\n  is saved correctly.\n\nTested-by: Michele Locati <michele@locati.it>\n\nWe know that the use of filter-branch is not recommended, but we rely\non --state-branch to split a repository incrementally, that is,\nprocessing only the new commits instead of the whole history at every\nrun.\n\nIt would be great to have this fix merged.\n\nA minor note: there's a typo in the subject (\"fiter-branch\").\n\nThanks,\nMichele\n"},{"id":"553709","messageId":"ar0fRtN8XMG-fyis@pks.im","threadId":"66098","inReplyTo":"20260801033127.10606-1-dev@grantmoyer.com","subject":"Re: [PATCH] fiter-branch: fix commit map init from state branch","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-30T14:40:06Z","receivedAt":"2026-09-30T14:40:13Z","isPatch":true,"body":"On Fri, Jul 31, 2026 at 11:31:27PM -0400, Grant Moyer wrote:\n\nSorry, this slipped may radar. Thanks for bumping this thread, Michele.\n\nNit: pointed out by Michele: the subject has a typo in \"fiter-branch\".\n\n> The commit map dir is populated from the state branch assuming a\n> \"to_commit:from_commit\" format, but the state branch is written with a\n> \"from_commit:to_commit\" format, resulting in an inverted mapping when the\n> map is populated from the state branch. This is especially evident when\n> --prune-empty is used and creates commits which map to nothing; when the\n> map dir is populated from this state on subsequent runs, git-filter-branch\n> outputs many errors while trying to create files with empty names, like:\n> \n> > /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n> \n> This change corrects the population of the commit map dir to match the\n> \"from_commit:to_commit\" format.\n\nSo... does this mean that we don't have test coverage for this case at\nall?\n\nIt might make sense to also mention f6d855091e (filter-branch: stop\ndepending on Perl, 2025-04-16) for context, as this is where the issue\nwas introduced.\n\n> diff --git a/git-filter-branch.sh b/git-filter-branch.sh\n> index 24fa317aaa..9aa07be6e1 100755\n> --- a/git-filter-branch.sh\n> +++ b/git-filter-branch.sh\n> @@ -302,7 +302,9 @@ then\n>  \t\tdo\n>  \t\t\tcase \"$line\" in\n>  \t\t\t*:*)\n> -\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n> +\t\t\t\tfrom_commit=${line%:*}\n> +\t\t\t\tto_commit=${line#*:}\n> +\t\t\t\techo \"$to_commit\" >../map/\"$from_commit\";;\n>  \t\t\t*)\n>  \t\t\t\tdie \"Unable to load state from $state_branch:filter.map\";;\n>  \t\t\tesac\n\nOkay. A simpler fix could've been the following diff:\n\n-\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n+\t\t\t\techo \"${line#*:}\" >../map/\"${line%:*}\";;\n\nBut I guess it doesn't hurt to have proper naming here.\n\n> diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\n> index 86011e7b1f..3934cc4a11 100755\n> --- a/t/t7003-filter-branch.sh\n> +++ b/t/t7003-filter-branch.sh\n> @@ -121,7 +121,7 @@ W=$(git rev-parse HEAD)\n>  test_expect_success 'using --state-branch to skip already rewritten commits' '\n>  \ttest_when_finished git reset --hard $V &&\n>  \tgit reset --hard $V &&\n> -\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n> +\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n>  \ttest_cmp_rev $W HEAD\n>  '\n\nSo does this now detect the issue? If so, it feels somewhat roundabout.\n\nMichele, it seems like you have already invested some time into\nreproducing the issue. Can this maybe be put into a proper test case\nthat directly exercises your issues (that is, the swapped entries and\nsuch)?\n\nThanks!\n\nPatrick\n"},{"id":"553723","messageId":"c6d3deb4-088b-476e-921c-c2ee788fb309@grantmoyer.com","threadId":"66098","inReplyTo":"ar0fRtN8XMG-fyis@pks.im","subject":"Re: [PATCH] fiter-branch: fix commit map init from state branch","fromName":"Grant Moyer","fromEmail":"dev@grantmoyer.com","sentAt":"2026-09-30T15:48:25Z","receivedAt":"2026-09-30T15:53:55Z","isPatch":true,"body":"On September 30, 2026 10:40:06 AM EDT, Patrick Steinhardt <ps@pks.im> wrote:\n>Okay. A simpler fix could've been the following diff:\n>\n>- echo \"${line%:*}\" >../map/\"${line#*:}\";;\n>+ echo \"${line#*:}\" >../map/\"${line%:*}\";;\n\nYep, that's equivalent and was my first version, but I decided to err on \nthe side of clearer intent. Plus, the more verbose version mirrors how \nthe state branch is constructed further down in the script.\n\n>So... does this mean that we don't have test coverage for this case at\n>all?\n\nYeah, the test for this case was broken.\n\n>>  test_expect_success 'using --state-branch to skip already rewritten commits' '\n>>  test_when_finished git reset --hard $V &&\n>>  git reset --hard $V &&\n>> - git filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n>> + git filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n>>  test_cmp_rev $W HEAD\n>>  '\n>\n>So does this now detect the issue? If so, it feels somewhat roundabout.\n\nThe current test seemingly intends to check if the --tree-filter filter \nwas run on already processed commits, but it only checks that the filter \nproduces the same final result. Since the filter is deterministic, the \nfinal result is the same whether or not the filter is re-run on already \nprocessed commits.\n\nThe proposed change makes the test fail immediately if the tree-filter\nis re-run. I looked around other tests for a test_* command or\nconventions to fail with a message, but I didn't find anything. I've\nchecked that the test fails without the filter-branch change, and passes\nwith it.\n\n>Nit: pointed out by Michele: the subject has a typo in \"fiter-branch\".\n\nI'm new here. Is this something I should fix by submitting a new version \nof the patch, or will the maintainer fix it  up if/when they merge the \npatch?\n\n\nGrant\n"},{"id":"553726","messageId":"ar00BTrDQ4voz38a@pks.im","threadId":"66098","inReplyTo":"c6d3deb4-088b-476e-921c-c2ee788fb309@grantmoyer.com","subject":"Re: [PATCH] fiter-branch: fix commit map init from state branch","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-30T16:08:37Z","receivedAt":"2026-09-30T16:08:45Z","isPatch":true,"body":"On Wed, Sep 30, 2026 at 11:48:25AM -0400, Grant Moyer wrote:\n> On September 30, 2026 10:40:06 AM EDT, Patrick Steinhardt <ps@pks.im> wrote:\n> > Okay. A simpler fix could've been the following diff:\n> > \n> > - echo \"${line%:*}\" >../map/\"${line#*:}\";;\n> > + echo \"${line#*:}\" >../map/\"${line%:*}\";;\n> \n> Yep, that's equivalent and was my first version, but I decided to err on the\n> side of clearer intent. Plus, the more verbose version mirrors how the state\n> branch is constructed further down in the script.\n> \n> > So... does this mean that we don't have test coverage for this case at\n> > all?\n> \n> Yeah, the test for this case was broken.\n> \n> > >  test_expect_success 'using --state-branch to skip already rewritten commits' '\n> > >  test_when_finished git reset --hard $V &&\n> > >  git reset --hard $V &&\n> > > - git filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n> > > + git filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n> > >  test_cmp_rev $W HEAD\n> > >  '\n> > \n> > So does this now detect the issue? If so, it feels somewhat roundabout.\n> \n> The current test seemingly intends to check if the --tree-filter filter was\n> run on already processed commits, but it only checks that the filter\n> produces the same final result. Since the filter is deterministic, the final\n> result is the same whether or not the filter is re-run on already processed\n> commits.\n> \n> The proposed change makes the test fail immediately if the tree-filter\n> is re-run. I looked around other tests for a test_* command or\n> conventions to fail with a message, but I didn't find anything. I've\n> checked that the test fails without the filter-branch change, and passes\n> with it.\n\nThe thing that I'm worried about is that the test seems to only\ncoincidentally exercise the code. I think it may be helpful to have a\ntest that explicitly tests for the scenarios that were reported as\nbroken by Michele. Let's maybe wait for them to respond -- if we're\nlucky they already have a test we can reuse here that more directly\nexercises a couple of the reportedly-broken scenarios.\n\n> > Nit: pointed out by Michele: the subject has a typo in \"fiter-branch\".\n> \n> I'm new here. Is this something I should fix by submitting a new version of\n> the patch, or will the maintainer fix it  up if/when they merge the patch?\n\nIt depends. For a small fix like this the maintainer may make the change\nthemselves if nothing else needs to be changed. But Junio will often\ntell you in that case, so if he stays quiet without the series getting\nmerged down, then I'd eventually just send a new version. Also helps to\nbump up the patch series on the mailing list again :)\n\nThanks!\n\nPatrick\n"},{"id":"553757","messageId":"20260930203822.958-1-michele@locati.it","threadId":"66098","inReplyTo":"ar0fRtN8XMG-fyis@pks.im","subject":"Re: [PATCH] fiter-branch: fix commit map init from state branch","fromName":"Michele Locati","fromEmail":"michele@locati.it","sentAt":"2026-09-30T20:38:22Z","receivedAt":"2026-09-30T20:41:35Z","isPatch":true,"body":"Hi Patrick,\n\n> Michele, it seems like you have already invested some time into\n> reproducing the issue. Can this maybe be put into a proper test case\n> that directly exercises your issues (that is, the swapped entries and\n> such)?\n\nSure. The test below performs an incremental rewrite in two runs:\nthe second run must attach the new commit to the parent rewritten by\nthe first run, and the saved state must contain \"original:rewritten\"\nentries. Without the fix the new commit is attached to the original\nparent, and the test fails at test_cmp_rev.\n\nI checked it against master (a018953): it fails without the fix and\npasses with it, and the other tests of t7003 still pass. It also\npasses with v2.49.1, the last version before the regression.\n\nGrant, feel free to squash it into your patch.\n\n--- a/t/t7003-filter-branch.sh\n+++ b/t/t7003-filter-branch.sh\n@@ -126,6 +126,27 @@ test_expect_success 'using --state-branch to skip already rewritten commits' '\n \ttest_cmp_rev $W HEAD\n '\n \n+test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '\n+\tgit init incremental &&\n+\t(\n+\t\tcd incremental &&\n+\t\tmkdir sub &&\n+\t\ttest_commit first sub/file &&\n+\t\ttest_commit outside root-file &&\n+\t\tgit filter-branch --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- HEAD &&\n+\t\trewritten_first=$(git rev-parse HEAD) &&\n+\t\tgit reset --hard outside &&\n+\t\ttest_commit second sub/file &&\n+\t\tgit filter-branch -f --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- outside..HEAD &&\n+\t\ttest_cmp_rev $rewritten_first HEAD^ &&\n+\t\tgit show refs/state:filter.map >map &&\n+\t\techo \"$(git rev-parse second):$(git rev-parse HEAD)\" >expect &&\n+\t\tgrep \"^$(git rev-parse second):\" map >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n git tag oldD HEAD~4\n test_expect_success 'rewrite one branch, keeping a side branch' '\n \tgit branch modD oldD &&\n\nThanks,\nMichele\n"},{"id":"553786","messageId":"20261001012347.3998801-1-dev@grantmoyer.com","threadId":"66098","inReplyTo":"20260801033127.10606-1-dev@grantmoyer.com","subject":"[PATCH v2] filter-branch: fix commit map init from state branch","fromName":"Grant Moyer","fromEmail":"dev@grantmoyer.com","sentAt":"2026-10-01T01:23:47Z","receivedAt":"2026-10-01T01:24:31Z","isPatch":true,"body":"The commit map dir is populated from the state branch assuming a\n\"to_commit:from_commit\" format, but the state branch is written with a\n\"from_commit:to_commit\" format, resulting in an inverted mapping when the\nmap is populated from the state branch. This is especially evident when\n--prune-empty is used and creates commits which map to nothing; when the\nmap dir is populated from this state on subsequent runs, git-filter-branch\noutputs many errors while trying to create files with empty names, like:\n\n> /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n\nThis change corrects the population of the commit map dir to match the\n\"from_commit:to_commit\" format and adds/updates tests to check that the\nstate branch is written correctly.\n\nSigned-off-by: Grant Moyer <dev@grantmoyer.com>\nTested-by: Michele Locati <michele@locati.it>\nCo-authored-by: Michele Locati <michele@locati.it>\n---\n git-filter-branch.sh     |  4 +++-\n t/t7003-filter-branch.sh | 24 +++++++++++++++++++++++-\n 2 files changed, 26 insertions(+), 2 deletions(-)\n\ndiff --git a/git-filter-branch.sh b/git-filter-branch.sh\nindex 24fa317aaa..9aa07be6e1 100755\n--- a/git-filter-branch.sh\n+++ b/git-filter-branch.sh\n@@ -302,7 +302,9 @@ then\n \t\tdo\n \t\t\tcase \"$line\" in\n \t\t\t*:*)\n-\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n+\t\t\t\tfrom_commit=${line%:*}\n+\t\t\t\tto_commit=${line#*:}\n+\t\t\t\techo \"$to_commit\" >../map/\"$from_commit\";;\n \t\t\t*)\n \t\t\t\tdie \"Unable to load state from $state_branch:filter.map\";;\n \t\t\tesac\ndiff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\nindex 86011e7b1f..cf225b0f0f 100755\n--- a/t/t7003-filter-branch.sh\n+++ b/t/t7003-filter-branch.sh\n@@ -121,10 +121,32 @@ W=$(git rev-parse HEAD)\n test_expect_success 'using --state-branch to skip already rewritten commits' '\n \ttest_when_finished git reset --hard $V &&\n \tgit reset --hard $V &&\n-\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n+\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n \ttest_cmp_rev $W HEAD\n '\n \n+test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '\n+\tgit init incremental &&\n+\t(\n+\t\tcd incremental &&\n+\t\tmkdir sub &&\n+\t\ttest_commit first sub/file &&\n+\t\ttest_commit outside root-file &&\n+\t\tgit filter-branch --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- HEAD &&\n+\t\trewritten_first=$(git rev-parse HEAD) &&\n+\t\tgit reset --hard outside &&\n+\t\ttest_commit second sub/file &&\n+\t\tgit filter-branch -f --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- outside..HEAD &&\n+\t\ttest_cmp_rev $rewritten_first HEAD^ &&\n+\t\tgit show refs/state:filter.map >map &&\n+\t\techo \"$(git rev-parse second):$(git rev-parse HEAD)\" >expect &&\n+\t\tgrep \"^$(git rev-parse second):\" map >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n git tag oldD HEAD~4\n test_expect_success 'rewrite one branch, keeping a side branch' '\n \tgit branch modD oldD &&\n-- \n2.55.0\n\n"},{"id":"553805","messageId":"ar3xSurCd0-yDruA@pks.im","threadId":"66098","inReplyTo":"20261001012347.3998801-1-dev@grantmoyer.com","subject":"Re: [PATCH v2] filter-branch: fix commit map init from state branch","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-01T05:36:10Z","receivedAt":"2026-10-01T05:36:16Z","isPatch":true,"body":"On Wed, Sep 30, 2026 at 09:23:47PM -0400, Grant Moyer wrote:\n> The commit map dir is populated from the state branch assuming a\n> \"to_commit:from_commit\" format, but the state branch is written with a\n> \"from_commit:to_commit\" format, resulting in an inverted mapping when the\n> map is populated from the state branch. This is especially evident when\n> --prune-empty is used and creates commits which map to nothing; when the\n> map dir is populated from this state on subsequent runs, git-filter-branch\n> outputs many errors while trying to create files with empty names, like:\n\nThis still doesn't mention the commit that introduced the regression.\n\n> > /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n> \n> This change corrects the population of the commit map dir to match the\n> \"from_commit:to_commit\" format and adds/updates tests to check that the\n> state branch is written correctly.\n\nNit: we typically write commit messages in imperative mood, as if you\nwere instructing the code to change.\n\n> Signed-off-by: Grant Moyer <dev@grantmoyer.com>\n> Tested-by: Michele Locati <michele@locati.it>\n> Co-authored-by: Michele Locati <michele@locati.it>\n\nYour Signed-off-by should always go last because you're signing off on\neverything that comes before it.\n\nHow about this message:\n\n  The \"--state-branch\" option asks git-filter-branch(1) to write a\n  mapping from old to new objects into the branch. This map is a\n  simple blob stored in \"$state_branch:filter.map\" with the format\n  \"$to_commit:$from_commit\".\n\n  In f6d855091e (filter-branch: stop depending on Perl, 2025-04-16), we\n  have refactored git-filter-branch(1) to no longer require Perl. But as\n  part of that change we accidentally started to interpret the above\n  format in reverse when populating the map directory. This of course\n  breaks incremental rewrites that use the commit map multiple times.\n  But we don't seem to  have any tests for this feature, and as a\n  consequence we didn't notice the regression.\n\n  Fix this bug by interpreting the mappings in the correct order again.\n\n  Tested-by: Michele Locati <michele@locati.it>\n  Co-authored-by: Michele Locati <michele@locati.it>\n  Signed-off-by: Grant Moyer <dev@grantmoyer.com>\n\n> diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\n> index 86011e7b1f..cf225b0f0f 100755\n> --- a/t/t7003-filter-branch.sh\n> +++ b/t/t7003-filter-branch.sh\n> @@ -121,10 +121,32 @@ W=$(git rev-parse HEAD)\n>  test_expect_success 'using --state-branch to skip already rewritten commits' '\n>  \ttest_when_finished git reset --hard $V &&\n>  \tgit reset --hard $V &&\n> -\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n> +\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n>  \ttest_cmp_rev $W HEAD\n>  '\n>  \n> +test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '\n\nWe could add `test_when_finished rm -rf incremental` here.\n\n> +\tgit init incremental &&\n> +\t(\n> +\t\tcd incremental &&\n> +\t\tmkdir sub &&\n> +\t\ttest_commit first sub/file &&\n> +\t\ttest_commit outside root-file &&\n> +\t\tgit filter-branch --state-branch refs/state \\\n> +\t\t\t--prune-empty --subdirectory-filter sub -- HEAD &&\n> +\t\trewritten_first=$(git rev-parse HEAD) &&\n> +\t\tgit reset --hard outside &&\n> +\t\ttest_commit second sub/file &&\n> +\t\tgit filter-branch -f --state-branch refs/state \\\n> +\t\t\t--prune-empty --subdirectory-filter sub -- outside..HEAD &&\n> +\t\ttest_cmp_rev $rewritten_first HEAD^ &&\n> +\t\tgit show refs/state:filter.map >map &&\n> +\t\techo \"$(git rev-parse second):$(git rev-parse HEAD)\" >expect &&\n> +\t\tgrep \"^$(git rev-parse second):\" map >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n\nThanks!\n\nPatrick\n"},{"id":"553811","messageId":"20261001063306.616-1-michele@locati.it","threadId":"66098","inReplyTo":"ar3xSurCd0-yDruA@pks.im","subject":"Re: [PATCH v2] filter-branch: fix commit map init from state branch","fromName":"Michele Locati","fromEmail":"michele@locati.it","sentAt":"2026-10-01T06:33:06Z","receivedAt":"2026-10-01T06:36:19Z","isPatch":true,"body":"Hi Patrick,\n\n> simple blob stored in \"$state_branch:filter.map\" with the format\n> \"$to_commit:$from_commit\".\n\nSmall correction: the format is \"$from_commit:$to_commit\" (the original\ncommit first, then the rewritten one), that's what the fix restores.\n\nThe test_when_finished cleanup sounds good to me.\n\nSince I'm listed as co-author:\n\nSigned-off-by: Michele Locati <michele@locati.it>\n\nThanks,\nMichele\n"},{"id":"553845","messageId":"xmqqh5j57677.fsf@gitster.g","threadId":"66098","inReplyTo":"20261001012347.3998801-1-dev@grantmoyer.com","subject":"Re: [PATCH v2] filter-branch: fix commit map init from state branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-01T16:10:04Z","receivedAt":"2026-10-01T16:10:07Z","isPatch":true,"body":"Grant Moyer <dev@grantmoyer.com> writes:\n\n> The commit map dir is populated from the state branch assuming a\n> \"to_commit:from_commit\" format, but the state branch is written with a\n> \"from_commit:to_commit\" format, resulting in an inverted mapping when the\n> map is populated from the state branch. This is especially evident when\n> --prune-empty is used and creates commits which map to nothing; when the\n> map dir is populated from this state on subsequent runs, git-filter-branch\n> outputs many errors while trying to create files with empty names, like:\n>\n>> /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n>\n> This change corrects the population of the commit map dir to match the\n> \"from_commit:to_commit\" format and adds/updates tests to check that the\n> state branch is written correctly.\n>\n> Signed-off-by: Grant Moyer <dev@grantmoyer.com>\n> Tested-by: Michele Locati <michele@locati.it>\n> Co-authored-by: Michele Locati <michele@locati.it>\n> ---\n>  git-filter-branch.sh     |  4 +++-\n>  t/t7003-filter-branch.sh | 24 +++++++++++++++++++++++-\n>  2 files changed, 26 insertions(+), 2 deletions(-)\n>\n> diff --git a/git-filter-branch.sh b/git-filter-branch.sh\n> index 24fa317aaa..9aa07be6e1 100755\n> --- a/git-filter-branch.sh\n> +++ b/git-filter-branch.sh\n> @@ -302,7 +302,9 @@ then\n>  \t\tdo\n>  \t\t\tcase \"$line\" in\n>  \t\t\t*:*)\n> -\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n> +\t\t\t\tfrom_commit=${line%:*}\n> +\t\t\t\tto_commit=${line#*:}\n> +\t\t\t\techo \"$to_commit\" >../map/\"$from_commit\";;\n>  \t\t\t*)\n>  \t\t\t\tdie \"Unable to load state from $state_branch:filter.map\";;\n>  \t\t\tesac\n\nI very much appreciate the clear description in the proposed log\nmessage above that makes what this change corrects very easy to\nunderstand.\n\n> diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\n> index 86011e7b1f..cf225b0f0f 100755\n> --- a/t/t7003-filter-branch.sh\n> +++ b/t/t7003-filter-branch.sh\n> @@ -121,10 +121,32 @@ W=$(git rev-parse HEAD)\n>  test_expect_success 'using --state-branch to skip already rewritten commits' '\n>  \ttest_when_finished git reset --hard $V &&\n>  \tgit reset --hard $V &&\n> -\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n> +\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n>  \ttest_cmp_rev $W HEAD\n>  '\n\nI am not sure what this change is about.  Care to explain in the\ncommit log?\n\n> +test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '\n\nIt is a minor nit, but do we want to have\n\n\ttest_when_finished \"rm -fr incremental\" &&\n\nhere, before creating a new repository that is used only for this\ntest, or do we expect that in the future we will add more pieces of\ntests that work inside this new repository (in which case leaving it\nthere may indeed be a better choice)?\n\n> +\tgit init incremental &&\n> +\t(\n> +\t\tcd incremental &&\n> +\t\tmkdir sub &&\n> +\t\ttest_commit first sub/file &&\n> +\t\ttest_commit outside root-file &&\n> +\t\tgit filter-branch --state-branch refs/state \\\n> +\t\t\t--prune-empty --subdirectory-filter sub -- HEAD &&\n> +\t\trewritten_first=$(git rev-parse HEAD) &&\n> +\t\tgit reset --hard outside &&\n> +\t\ttest_commit second sub/file &&\n> +\t\tgit filter-branch -f --state-branch refs/state \\\n> +\t\t\t--prune-empty --subdirectory-filter sub -- outside..HEAD &&\n> +\t\ttest_cmp_rev $rewritten_first HEAD^ &&\n> +\t\tgit show refs/state:filter.map >map &&\n> +\t\techo \"$(git rev-parse second):$(git rev-parse HEAD)\" >expect &&\n> +\t\tgrep \"^$(git rev-parse second):\" map >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>  git tag oldD HEAD~4\n>  test_expect_success 'rewrite one branch, keeping a side branch' '\n>  \tgit branch modD oldD &&\n\nThanks.\n"},{"id":"554036","messageId":"20261003010128.256757-1-dev@grantmoyer.com","threadId":"66098","inReplyTo":"20261001012347.3998801-1-dev@grantmoyer.com","subject":"[PATCH v3] filter-branch: fix commit map init from state branch","fromName":"Grant Moyer","fromEmail":"dev@grantmoyer.com","sentAt":"2026-10-03T01:01:27Z","receivedAt":"2026-10-03T01:09:00Z","isPatch":true,"body":"The \"--state-branch\" option asks git-filter-branch(1) to write a\nmapping from old to new objects into a branch to enable incremental\nprocessing of large histories. This object mapping is stored as\na simple blob at \"$state_branch:filter.map\" with one object pair\nper line in the format \"$from_commit:$to_commit\". Before processing\ncommits in a subsequent run, the state branch is used to populate an\nobject mapping directory, where each file is named \"map/$from_commit\"\nand has contents \"$to_commit\".\n\nIn f6d855091e (filter-branch: stop depending on Perl, 2025-04-16),\nwe refactored git-filter-branch(1) to no longer require Perl,\nbut accidentally started to interpret object pairs in reverse (as\n\"$to_commit:$from_commit\") when populating the map directory from\nthe state branch. This can cause all kinds of bad behavior from the\nstate-branch being effectively ignored to previously filtered objects\naccidentally being mapped back to unfiltered objects. One especially\nevident case occurs when \"git filter-branch --prune-empty ...\" maps\nsome commits to nothing, then on subsequent runs outputs many errors\nwhile trying to create files with empty names, like:\n\n> /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n\nThis regression went unnoticed because, since the introduction of\nthe only test for \"--state-branch\" in 709cfe848a (filter-branch: skip\ncommits present on --state-branch, 2018-06-26), the test accidentally\npasses even if commits are not skipped. The test checks that after\npopulating a state branch with git-filter-branch(1), then running\nit again with that state branch, the resulting filtered commits for\nthe first run and second run match. However since the filter used is\ndeterministic, the commits always match, even if the commits in the\nstate branch are re-filtered.\n\nFix the population of the object mapping dir by interpreting\nobject pairs as \"$from_commit:$to_commit\". Also fix the existing\n\"--state-branch\" test by directly exiting with a non-zero code if\nany commits from the state branch aren't skipped. Finally, add a new\n\"--state-branch\" test which directly checks that a commit from the\nstate branch is used when incrementally filtering a repo.\n\nTested-by: Michele Locati <michele@locati.it>\nCo-authored-by: Michele Locati <michele@locati.it>\nSigned-off-by: Michele Locati <michele@locati.it>\nSigned-off-by: Grant Moyer <dev@grantmoyer.com>\n---\n git-filter-branch.sh     |  4 +++-\n t/t7003-filter-branch.sh | 25 ++++++++++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/git-filter-branch.sh b/git-filter-branch.sh\nindex 24fa317aaa..9aa07be6e1 100755\n--- a/git-filter-branch.sh\n+++ b/git-filter-branch.sh\n@@ -302,7 +302,9 @@ then\n \t\tdo\n \t\t\tcase \"$line\" in\n \t\t\t*:*)\n-\t\t\t\techo \"${line%:*}\" >../map/\"${line#*:}\";;\n+\t\t\t\tfrom_commit=${line%:*}\n+\t\t\t\tto_commit=${line#*:}\n+\t\t\t\techo \"$to_commit\" >../map/\"$from_commit\";;\n \t\t\t*)\n \t\t\t\tdie \"Unable to load state from $state_branch:filter.map\";;\n \t\t\tesac\ndiff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh\nindex 86011e7b1f..801cc83e5e 100755\n--- a/t/t7003-filter-branch.sh\n+++ b/t/t7003-filter-branch.sh\n@@ -121,10 +121,33 @@ W=$(git rev-parse HEAD)\n test_expect_success 'using --state-branch to skip already rewritten commits' '\n \ttest_when_finished git reset --hard $V &&\n \tgit reset --hard $V &&\n-\tgit filter-branch --state-branch state -f --tree-filter \"touch file || :\" HEAD &&\n+\tgit filter-branch --state-branch state -f --tree-filter \"exit 1\" HEAD &&\n \ttest_cmp_rev $W HEAD\n '\n \n+test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '\n+\ttest_when_finished \"rm -fr incremental\" &&\n+\tgit init incremental &&\n+\t(\n+\t\tcd incremental &&\n+\t\tmkdir sub &&\n+\t\ttest_commit first sub/file &&\n+\t\ttest_commit outside root-file &&\n+\t\tgit filter-branch --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- HEAD &&\n+\t\trewritten_first=$(git rev-parse HEAD) &&\n+\t\tgit reset --hard outside &&\n+\t\ttest_commit second sub/file &&\n+\t\tgit filter-branch -f --state-branch refs/state \\\n+\t\t\t--prune-empty --subdirectory-filter sub -- outside..HEAD &&\n+\t\ttest_cmp_rev $rewritten_first HEAD^ &&\n+\t\tgit show refs/state:filter.map >map &&\n+\t\techo \"$(git rev-parse second):$(git rev-parse HEAD)\" >expect &&\n+\t\tgrep \"^$(git rev-parse second):\" map >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n git tag oldD HEAD~4\n test_expect_success 'rewrite one branch, keeping a side branch' '\n \tgit branch modD oldD &&\n-- \n2.55.0\n\n"},{"id":"554145","messageId":"asM_dqY2aSdplISU@pks.im","threadId":"66098","inReplyTo":"20261003010128.256757-1-dev@grantmoyer.com","subject":"Re: [PATCH v3] filter-branch: fix commit map init from state branch","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-05T06:11:02Z","receivedAt":"2026-10-05T06:11:08Z","isPatch":true,"body":"On Fri, Oct 02, 2026 at 09:01:27PM -0400, Grant Moyer wrote:\n> The \"--state-branch\" option asks git-filter-branch(1) to write a\n> mapping from old to new objects into a branch to enable incremental\n> processing of large histories. This object mapping is stored as\n> a simple blob at \"$state_branch:filter.map\" with one object pair\n> per line in the format \"$from_commit:$to_commit\". Before processing\n> commits in a subsequent run, the state branch is used to populate an\n> object mapping directory, where each file is named \"map/$from_commit\"\n> and has contents \"$to_commit\".\n> \n> In f6d855091e (filter-branch: stop depending on Perl, 2025-04-16),\n> we refactored git-filter-branch(1) to no longer require Perl,\n> but accidentally started to interpret object pairs in reverse (as\n> \"$to_commit:$from_commit\") when populating the map directory from\n> the state branch. This can cause all kinds of bad behavior from the\n> state-branch being effectively ignored to previously filtered objects\n> accidentally being mapped back to unfiltered objects. One especially\n> evident case occurs when \"git filter-branch --prune-empty ...\" maps\n> some commits to nothing, then on subsequent runs outputs many errors\n> while trying to create files with empty names, like:\n> \n> > /usr/lib/git-core/git-filter-branch: line 305: ../map/: Is a directory\n> \n> This regression went unnoticed because, since the introduction of\n> the only test for \"--state-branch\" in 709cfe848a (filter-branch: skip\n> commits present on --state-branch, 2018-06-26), the test accidentally\n> passes even if commits are not skipped. The test checks that after\n> populating a state branch with git-filter-branch(1), then running\n> it again with that state branch, the resulting filtered commits for\n> the first run and second run match. However since the filter used is\n> deterministic, the commits always match, even if the commits in the\n> state branch are re-filtered.\n> \n> Fix the population of the object mapping dir by interpreting\n> object pairs as \"$from_commit:$to_commit\". Also fix the existing\n> \"--state-branch\" test by directly exiting with a non-zero code if\n> any commits from the state branch aren't skipped. Finally, add a new\n> \"--state-branch\" test which directly checks that a commit from the\n> state branch is used when incrementally filtering a repo.\n> \n> Tested-by: Michele Locati <michele@locati.it>\n> Co-authored-by: Michele Locati <michele@locati.it>\n> Signed-off-by: Michele Locati <michele@locati.it>\n> Signed-off-by: Grant Moyer <dev@grantmoyer.com>\n\nThanks, I'm happy with this version.\n\nPatrick\n"}]}