{"thread":{"id":"3414","subject":"rewriting pathnames in history","startedAt":"2006-02-21T07:53:42Z","lastAt":"2006-02-21T20:54:54Z","messageCount":2,"participants":["Jeff King","Sam Vilain"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"16494","messageId":"20060221075342.GA13814@coredump.intra.peff.net","threadId":"3414","inReplyTo":null,"subject":"rewriting pathnames in history","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2006-02-21T07:53:42Z","receivedAt":"2006-02-21T07:53:42Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"I recently ran into an interesting situation with git. I created a\nrepository that consisted of several directories (and files in them).\nLater, after many commits, I realized I would prefer each directory have\nits own git repository. That is, given a repo with the files:\n  foo/bar\n  baz/bleep\nI wanted two repos, \"foo\" containing the file \"bar\" and \"baz\" containing\nthe file \"bleep\".\n\nObviously, one could simply make new repositories (one for each\ndirectory), rename the files, and commit. However, I wanted to keep the\nhistory for each new repo tidy, as well. So my solution was to replay\nthe history once for each new repo, omitting any revisions which had no\neffect, and rewriting paths to move \"dir/foo\" to \"foo\".\n\nThe script I used is included at the end of this mail. I'm posting in\ncase anyone else finds it useful (comments are also welcome).\n\nI also have a question regarding this task. I wanted to split the whole\nhistory, so I wanted a \"blank\" commit to start adding my replay to (that\nis, a commit with no files and no parent).  What's the best way using\ngit to get a blank commit? I ended up creating a new repo (with cogito,\nwhich I regularly use), and then fetching it into the original repo and\nswitching to it as a branch. \n\n-Peff\n\n--------------\n#!/usr/bin/perl\n# Rewrite history by replaying and modifying pathnames.\n# Public domain.\n#\n# 1. Switch your HEAD to the head where the rewritten history will go.\n# 2. Figure out which revs you want to replay (e.g., git-rev-list master)\n# 3. Figure out which paths you want to include (e.g., '/^prefix/)\n# 4. Figure out how you want to modify the path (e.g., 's!^prefix/!!')\n# 5. Run the script:\n#      git-rev-list master | perl rewrite.pl /^prefix/ 's!^prefix/!!'\n\nmy $USAGE = 'usage: rewrite.pl match rewrite';\nmy $match = shift or die \"$USAGE, halting\";\nmy $rewrite = shift or die \"$USAGE, halting\";\n\nmy @revs = ('HEAD', reverse(map { chomp; $_ } <>));\nforeach my $i (1 .. $#revs) {\n  my @files = difftree($revs[$i-1], $revs[$i]);\n  @files = grep { match($match, $_) } @files\n    or next;\n  @files = map { rewrite($rewrite, $_) } @files;\n  update_index(@files);\n  commit($revs[$i]);\n}\n\nsub difftree {\n  my ($x, $y) = @_;\n  my @files;\n  open(my $fh, \"git-diff-tree -r $x $y|\")\n    or die \"unable to open git-diff-tree: $!, halting\";\n  while(my $line = <$fh>) {\n    chomp $line;\n    $line =~ /^:\\d+ (\\d+) [0-9a-f]+ ([0-9a-f]+) .\\t(.*)/\n      or die \"bad diff-tree output: $line, halting\";\n    push @files, [$1, $2, $3];\n  }\n  $? and die \"git-diff-tree returned error: $!, halting\";\n  return @files;\n}\n\nsub match {\n  my $m = shift;\n  my $f = shift;\n  local $_ = $f->[2];\n  return eval $m;\n}\n\nsub rewrite {\n  my $r = shift;\n  my $f = shift;\n  local $_ = $f->[2];\n  eval $r;\n  $@ and die $@;\n  $f->[2] = $_;\n  return $f;\n}\n\nsub update_index {\n  open(my $fh, '|git-update-index --index-info')\n    or die \"unable to open git-update-index, halting\";\n  foreach my $f (@_) {\n    print $fh \"$f->[0] $f->[1]\\t$f->[2]\\n\";\n  }\n  close($fh);\n  $? and die \"git-update-index reported failure, halting\";\n}\n\nsub commit {\n  my $r = shift;\n  system(\"git-commit -C $r\")\n    and die \"git-commit reported failure, halting\";\n}\n"},{"id":"16521","messageId":"43FB7E1E.6070605@vilain.net","threadId":"3414","inReplyTo":"20060221075342.GA13814@coredump.intra.peff.net","subject":"Re: rewriting pathnames in history","fromName":"Sam Vilain","fromEmail":"sam@vilain.net","sentAt":"2006-02-21T20:54:54Z","receivedAt":"2006-02-21T20:54:54Z","isPatch":false,"sender":{"key":"sam@vilain.net","avatar":"https://gravatar.com/avatar/8fc840ca854dbf6f7065b4335e3b934951c1dca3b11db688e95e471901f8f4a8?d=mp&s=160"},"body":"Jeff King wrote:\n> I recently ran into an interesting situation with git. I created a\n> repository that consisted of several directories (and files in them).\n> Later, after many commits, I realized I would prefer each directory have\n> its own git repository. That is, given a repo with the files:\n>   foo/bar\n>   baz/bleep\n> I wanted two repos, \"foo\" containing the file \"bar\" and \"baz\" containing\n> the file \"bleep\".\n\nNice work, but I think you should be able to get it *really* fast, much \nfaster than that.\n\nInstead of replaying a checked out copy, just go through the commit \nhistory, and when the treeID for that subdirectory has changed, then \nthat directory has a new revision.  So, make a new commit object with \nthat as the treeid.  in other words, you'll be constructing a very \nlightweight branch, but with its tree IDs all corresponding to \nsub-directory treeids on the combined branch.  The history ripple script \nthat was posted the other day probably has most of the pieces you need. \n  Once this is done, you can just clone that branch to \"get it out\".\n\nSam.\n"}]}