{"thread":{"id":"46749","subject":"how to remove from history just *one* version of a file/dir?","startedAt":"2017-09-14T11:32:19Z","lastAt":"2017-09-15T11:35:44Z","messageCount":4,"participants":["Robert P. J. Day","Jeff King"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"328042","messageId":"alpine.LFD.2.21.1709140721130.19770@localhost.localdomain","threadId":"46749","inReplyTo":null,"subject":"how to remove from history just *one* version of a file/dir?","fromName":"Robert P. J. Day","fromEmail":"rpjday@crashcourse.ca","sentAt":"2017-09-14T11:32:11Z","receivedAt":"2017-09-14T11:32:19Z","isPatch":false,"sender":{"key":"rpjday@crashcourse.ca","avatar":"https://avatars.githubusercontent.com/u/226084077?v=4"},"body":"\n  [is this the right place to ask questions about git usage? or is\nthere a different forum where one can submit possibly embarrassingly\nsilly questions?]\n\n  i've been perusing \"git filter-branch\", and i'm curious if i have\nthe right idea about how to very selectively get rid of some useless\nhistory.\n\n  say, early on, one commits a sizable directory of content, call it\n/mydir. that directory sits there for a while until it becomes obvious\nit's out of date and worthless and should never have been committed.\nthe obvious solution would seem to be:\n\n  $ git filter-branch --tree-filter 'rm -rf /mydir' HEAD\n\ncorrect?\n\n  however, say one version of that directory was committed early on,\nthen later tossed for being useless with \"git rm\", and subsequently\nreplaced by newer content under exactly the same name. now i'd like to\ngo back and delete the history related to that early version of\n/mydir, but not the second.\n\n  obviously, i can't use the above command as it would delete both\nversions. so it appears the solution would be a trivial application of\nthe \"--commit-filter\" option:\n\n   git filter-branch --commit-filter '\n     if [ \"$GIT_COMMIT\" = \"<commit-id>\" ] ; then\n       skip_commit \"$@\";\n     else\n       git commit-tree \"$@\";\n     fi' HEAD\n\nwhere <commit-id> is the commit that introduced the first verrsion of\n/mydir. do i have that right? is there a simpler way to do this?\n\nrday\n\n-- \n\n========================================================================\nRobert P. J. Day                                 Ottawa, Ontario, CANADA\n                        http://crashcourse.ca\n\nTwitter:                                       http://twitter.com/rpjday\nLinkedIn:                               http://ca.linkedin.com/in/rpjday\n========================================================================\n"},{"id":"328044","messageId":"20170914122338.4vlgx2rjr4mk62rh@sigill.intra.peff.net","threadId":"46749","inReplyTo":"alpine.LFD.2.21.1709140721130.19770@localhost.localdomain","subject":"Re: how to remove from history just *one* version of a file/dir?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-09-14T12:23:38Z","receivedAt":"2017-09-14T12:23:45Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Sep 14, 2017 at 07:32:11AM -0400, Robert P. J. Day wrote:\n\n>   [is this the right place to ask questions about git usage? or is\n> there a different forum where one can submit possibly embarrassingly\n> silly questions?]\n\nNo, this is the right place for embarrassing questions. :)\n\n>   say, early on, one commits a sizable directory of content, call it\n> /mydir. that directory sits there for a while until it becomes obvious\n> it's out of date and worthless and should never have been committed.\n> the obvious solution would seem to be:\n> \n>   $ git filter-branch --tree-filter 'rm -rf /mydir' HEAD\n> \n> correct?\n\nThat would work, though note that using an --index-filter would be more\nefficient (since it avoids checking out each tree as it walks the\nhistory).\n\n>   however, say one version of that directory was committed early on,\n> then later tossed for being useless with \"git rm\", and subsequently\n> replaced by newer content under exactly the same name. now i'd like to\n> go back and delete the history related to that early version of\n> /mydir, but not the second.\n\nMakes sense as a goal.\n\n>   obviously, i can't use the above command as it would delete both\n> versions. so it appears the solution would be a trivial application of\n> the \"--commit-filter\" option:\n> \n>    git filter-branch --commit-filter '\n>      if [ \"$GIT_COMMIT\" = \"<commit-id>\" ] ; then\n>        skip_commit \"$@\";\n>      else\n>        git commit-tree \"$@\";\n>      fi' HEAD\n> \n> where <commit-id> is the commit that introduced the first verrsion of\n> /mydir. do i have that right? is there a simpler way to do this?\n\nNo, this won't work. Filter-branch is not walking the history and\napplying the changes to each commit, like rebase does.  It's literally\noperating on each commit object, and recall that each commit object\npoints to a tree that is a snapshot of the repository contents.\n\nSo if you skip a commit, that commit itself goes away. But the commit\nafter it (which didn't touch the unwanted contents) will still mention\nthose contents in its tree.\n\nI think you want to stick with a --tree-filter (or an --index-filter),\nbut just selectively decide when to do the deletion. For example, if you\ncan tell the difference between the two states based on the presence of\nsome file, then perhaps:\n\n  git filter-branch --prune-empty --index-filter '\n\tif git rev-parse --verify :dir/sentinel >/dev/null 2>&1\n\tthen\n\t  git rm --cached -rf dir\n\tfi\n  ' HEAD\n\nThe \"--prune-empty\" is optional, but will drop commits that become empty\nbecause they _only_ touched that directory.\n\nWe use \":dir/sentinel\" to see if the entry is in the index, because the\nindex filter won't have the tree checked out. Likewise, we need to use\n\"rm --cached\" to just touch the index.\n\n-Peff\n"},{"id":"328115","messageId":"alpine.LFD.2.21.1709150701010.4944@localhost.localdomain","threadId":"46749","inReplyTo":"20170914122338.4vlgx2rjr4mk62rh@sigill.intra.peff.net","subject":"Re: how to remove from history just *one* version of a file/dir?","fromName":"Robert P. J. Day","fromEmail":"rpjday@crashcourse.ca","sentAt":"2017-09-15T11:06:43Z","receivedAt":"2017-09-15T11:06:51Z","isPatch":false,"sender":{"key":"rpjday@crashcourse.ca","avatar":"https://avatars.githubusercontent.com/u/226084077?v=4"},"body":"On Thu, 14 Sep 2017, Jeff King wrote:\n\n> On Thu, Sep 14, 2017 at 07:32:11AM -0400, Robert P. J. Day wrote:\n>\n> >   [is this the right place to ask questions about git usage? or is\n> > there a different forum where one can submit possibly\n> > embarrassingly silly questions?]\n>\n> No, this is the right place for embarrassing questions. :)\n>\n> >   say, early on, one commits a sizable directory of content, call\n> > it /mydir. that directory sits there for a while until it becomes\n> > obvious it's out of date and worthless and should never have been\n> > committed. the obvious solution would seem to be:\n> >\n> >   $ git filter-branch --tree-filter 'rm -rf /mydir' HEAD\n> >\n> > correct?\n>\n> That would work, though note that using an --index-filter would be\n> more efficient (since it avoids checking out each tree as it walks\n> the history).\n\n  i'm just digging into --index-filter as we speak, i realize it's\nnoticeably faster.\n\n> >   however, say one version of that directory was committed early\n> > on, then later tossed for being useless with \"git rm\", and\n> > subsequently replaced by newer content under exactly the same\n> > name. now i'd like to go back and delete the history related to\n> > that early version of /mydir, but not the second.\n>\n> Makes sense as a goal.\n>\n> >   obviously, i can't use the above command as it would delete both\n> > versions. so it appears the solution would be a trivial\n> > application of the \"--commit-filter\" option:\n> >\n> >    git filter-branch --commit-filter '\n> >      if [ \"$GIT_COMMIT\" = \"<commit-id>\" ] ; then\n> >        skip_commit \"$@\";\n> >      else\n> >        git commit-tree \"$@\";\n> >      fi' HEAD\n> >\n> > where <commit-id> is the commit that introduced the first verrsion of\n> > /mydir. do i have that right? is there a simpler way to do this?\n>\n> No, this won't work. Filter-branch is not walking the history and\n> applying the changes to each commit, like rebase does.  It's\n> literally operating on each commit object, and recall that each\n> commit object points to a tree that is a snapshot of the repository\n> contents.\n>\n> So if you skip a commit, that commit itself goes away. But the\n> commit after it (which didn't touch the unwanted contents) will\n> still mention those contents in its tree.\n\n  ah, of course, duh.\n\n> I think you want to stick with a --tree-filter (or an\n> --index-filter), but just selectively decide when to do the\n> deletion. For example, if you can tell the difference between the\n> two states based on the presence of some file, then perhaps:\n>\n>   git filter-branch --prune-empty --index-filter '\n> \tif git rev-parse --verify :dir/sentinel >/dev/null 2>&1\n> \tthen\n> \t  git rm --cached -rf dir\n> \tfi\n>   ' HEAD\n>\n> The \"--prune-empty\" is optional, but will drop commits that become\n> empty because they _only_ touched that directory.\n>\n> We use \":dir/sentinel\" to see if the entry is in the index, because\n> the index filter won't have the tree checked out. Likewise, we need\n> to use \"rm --cached\" to just touch the index.\n\n  got it. one last query -- i note that there is no \"else\" clause in\nthat code for \"--index-filter\". am i assuming correctly that if i was\nusing \"--tree-filter\" instead, i really would need if/then/else along\nthe lines of:\n\n  if blah ; then\n    skip_commit \"$@\"\n  else\n    git commit-tree \"$@\"\n  fi\n\nthank you kindly.\n\nrday\n\n-- \n\n========================================================================\nRobert P. J. Day                                 Ottawa, Ontario, CANADA\n                        http://crashcourse.ca\n\nTwitter:                                       http://twitter.com/rpjday\nLinkedIn:                               http://ca.linkedin.com/in/rpjday\n========================================================================\n"},{"id":"328119","messageId":"20170915113535.vpeltdmewmq6ekhl@sigill.intra.peff.net","threadId":"46749","inReplyTo":"alpine.LFD.2.21.1709150701010.4944@localhost.localdomain","subject":"Re: how to remove from history just *one* version of a file/dir?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-09-15T11:35:35Z","receivedAt":"2017-09-15T11:35:44Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Sep 15, 2017 at 07:06:43AM -0400, Robert P. J. Day wrote:\n\n> > I think you want to stick with a --tree-filter (or an\n> > --index-filter), but just selectively decide when to do the\n> > deletion. For example, if you can tell the difference between the\n> > two states based on the presence of some file, then perhaps:\n> >\n> >   git filter-branch --prune-empty --index-filter '\n> > \tif git rev-parse --verify :dir/sentinel >/dev/null 2>&1\n> > \tthen\n> > \t  git rm --cached -rf dir\n> > \tfi\n> >   ' HEAD\n> >\n> > The \"--prune-empty\" is optional, but will drop commits that become\n> > empty because they _only_ touched that directory.\n> >\n> > We use \":dir/sentinel\" to see if the entry is in the index, because\n> > the index filter won't have the tree checked out. Likewise, we need\n> > to use \"rm --cached\" to just touch the index.\n> \n>   got it. one last query -- i note that there is no \"else\" clause in\n> that code for \"--index-filter\". am i assuming correctly that if i was\n> using \"--tree-filter\" instead, i really would need if/then/else along\n> the lines of:\n> \n>   if blah ; then\n>     skip_commit \"$@\"\n>   else\n>     git commit-tree \"$@\"\n>   fi\n> \n> thank you kindly.\n\nNo, I think a tree-filter would just be:\n\n  if test -e dir/sentinel\n  then\n    rm -rf dir\n    git add -u\n  fi\n\n(I can't remember if the \"add -u\" is necessary or not; I rarely use tree\nfilters).\n\nIn other words, for each commit you are just saying \"if the bad version\nof the directory is present, then get rid of it\". You shouldn't need to\ndeal with commit-tree at all. The filter-branch script will take care of\ncommitting whatever tree state your filter leaves in place.\n\nDo note that I didn't test either of the versions I sent to you, so it's\npossible I'm missing some subtle thing. But I'm pretty sure the general\ndirection is correct.\n\n-Peff\n"}]}