On Thu, May 01, 2008 at 07:14:27PM -0400, Jeff King wrote: > 1. write a proof-of-concept that shows directory renaming after the > fact (e.g., take a conflicted merge, scan the diff for directory > renames, and then fix up the files). That way it is available, but > doesn't impact git at all. Here's a toy script that finds directory renames. I'm sure there are a ton of corner cases it doesn't handle (like directory renames inside of directory renames). My test case was the very trivial: mkdir repo && cd repo && git init mkdir subdir for i in 1 2 3; do echo content $i >subdir/file$i done git add subdir git commit -m initial git mv subdir new git commit -m move git checkout -b other HEAD^ echo content 4 >subdir/file4 git add subdir git commit -m new git merge --no-commit master perl ../find-dir-rename.pl git commit At which point you should see the merged commit with new/file4. Script is below. -- >8 -- #!/usr/bin/perl # # Find renamed directories, and move any files in the "old" # directory into the "new". # # usage: # git merge --no-commit <whatever> # find-dir-rename # git commit use strict; foreach my $r (renamed_dirs()) { move_dir_contents($r->{from}, $r->{to}); } exit 0; sub renamed_dirs { my $base = `git merge-base HEAD MERGE_HEAD`; chomp $base; return grep { $_->{score} == 1 } (renamed_dirs_between($base, 'HEAD'), renamed_dirs_between($base, 'MERGE_HEAD')); } sub renamed_dirs_between { my ($base, $commit) = @_; my %sources; foreach my $pair (renamed_files($base, $commit)) { my $d1 = dir_of($pair->[0]); my $d2 = dir_of($pair->[1]); next unless defined($d1) && defined($d2); $sources{$d1}->{total}++; $sources{$d1}->{dests}->{$d2}++; } return map { my $from = $_; map { { from => $from, to => $_, score => $sources{$from}->{dests}->{$_} / $sources{$from}->{total}, } } keys(%{$sources{$from}->{dests}}); } removed_directories($base, $commit); } sub dir_of { local $_ = shift; s{/[^/]+$}{} or return undef; return $_; } sub renamed_files { my ($from, $to) = @_; open(my $fh, '-|', qw(git diff-tree -r -M), $from, $to) or die "unable to open diff-tree: $!"; return map { chomp; m/ R\d+\t([^\t]+)\t(.*)/ ? [$1 => $2] : () } <$fh>; } sub removed_directories { my ($base, $commit) = @_; my %new_dirs = map { $_ => 1 } directories($commit); return grep { !exists $new_dirs{$_} } directories($base); } sub directories { my $commit = shift; return uniq( map { s{/[^/]+$}{} ? $_ : () } files($commit) ); } sub files { my $commit = shift; open(my $fh, '-|', qw(git ls-tree -r), $commit) or die "unable to open ls-tree: $!"; return map { chomp; s/^[^\t]*\t//; $_ } <$fh>; } sub uniq { my %seen; return grep { !$seen{$_}++ } @_; } sub move_dir_contents { my ($from, $to) = @_; my @files = glob("$from/*"); return unless @files; system(qw(git mv), @files, "$to/") and die "unable to move $from/* to $to"; rmdir($from); # ignore error since there may be untracked files } -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html