On Mon, Dec 13, 2010 at 8:09 PM, Thomas Rast <trast@xxxxxxxxxxxxxxx> wrote: > > While cleaning up the 'log -L' series I gathered a large number of > little fixups, and decided it would be smart if git could > automatically figure out where to put them. > > It works like this: > > * Split the diff by hunk. I'm using -U1 here for finer splits, but it > could be tunable. > > * For each hunk, run blame to find out which commit's lines were > affected. > > * Group the hunks by this commit, and output them with a suitable > command to make a fixup. > > My git-fixup is > > $ g config alias.fixup > !sh -c 'r=$1; git commit -m"fixup! $(git log -1 --pretty=%s $r)"' - > > so that is "suitable". > > You would run it with the changes unstaged in your tree as > > ./git-fixup-assigner.perl > fixups > > and can then review with 'less fixups', or run 'sh fixups' to commit > them. > > It's certainly not perfect, notably the detection logic should ignore > context, but it got the job done. > > --- 8< --- > #!/usr/bin/perl > > use warnings; > use strict; > > sub parse_hunk_header { > my ($line) = @_; > my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = > $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; > $o_cnt = 1 unless defined $o_cnt; > $n_cnt = 1 unless defined $n_cnt; > return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); > } > > sub find_commit { > my ($file, $begin, $end) = @_; > my $blame; > open($blame, '-|', 'git', '--no-pager', 'blame', 'HEAD', "-L$begin,$end", $file) or die; > my %candidate; > while (<$blame>) { > $candidate{$1} += 1 if /^([0-9a-f]+)/; > } > close $blame or die; > my @sorted = sort { $candidate{$b} <=> $candidate{$a} } keys %candidate; > if (1 < scalar @sorted) { > print STDERR "ambiguous split $file:$begin..$end\n"; > foreach my $c (@sorted) { > print STDERR "\t$candidate{$c}\t$c\n"; > } > } > return $sorted[0]; > } > > my $diff; > open($diff, '-|', 'git', '--no-pager', 'diff', '-U1') or die; > > my %by_commit; > my @cur_hunk = (); > my $cur_commit; > my ($filename, $prefilename, $postfilename); > > while (<$diff>) { > if (m{^diff --git ./(.*) ./\1$}) { > if (@cur_hunk) { > push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk; > @cur_hunk = (); > } > $filename = $1; > $prefilename = "./" . $1; > $postfilename = "./" . $1; > } elsif (m{^index}) { > # ignore > } elsif (m{^new file}) { > $prefilename = '/dev/null'; > } elsif (m{^delete file}) { > $postfilename = '/dev/null'; > } elsif (m{^--- $prefilename$}) { > } elsif (m{^\+\+\+ $postfilename$}) { > } elsif (m{^@@ }) { > if (@cur_hunk) { > push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk; > @cur_hunk = (); > } > push @cur_hunk, $_; > die "I don't handle this diff" if ($prefilename ne $postfilename); > my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) > = parse_hunk_header($_); > my $o_end = $o_ofs + $o_cnt - 1; > $cur_commit = find_commit($filename, $o_ofs, $o_end); > } elsif (m{^[-+ \\]}) { > push @cur_hunk, $_; > } else { > die "unhandled diff line: '$_'"; > } > } > > close $diff or die; > > if (@cur_hunk) { > push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk; > @cur_hunk = (); > } > > print "#!/bin/sh\n\n"; > > foreach my $commit (keys %by_commit) { > print "git apply --cached <<EOF\n"; > foreach my $filename (keys %{$by_commit{$commit}}) { > print "diff --git a/$filename b/$filename\n"; > print "--- a/$filename\n"; > print "+++ b/$filename\n"; > print @{$by_commit{$commit}{$filename}}; > } > print "EOF\n\n"; > print "git fixup $commit\n\n"; > } > --- >8 --- This is super neat, but I'm having trouble getting it to work. First, I made one small change: - print "git fixup $commit\n\n"; + print "git commit --fixup $commit\n\n"; To try it out I made a very simple change: $ git diff diff --git a/App/lib/MyApp/Controller/DashboardTemplates.pm b/App/lib/MyApp/Controller/DashboardTemplates.pm index aefdc3c..a53b534 100644 --- a/App/lib/MyApp/Controller/DashboardTemplates.pm +++ b/App/lib/MyApp/Controller/DashboardTemplates.pm @@ -13,7 +13,7 @@ cat_has $_ => ( is => 'rw' ) for qw(set); sub base : Chained('/') PathPart('dashboard_templates') CaptureArgs(0) { my ($self, $c) = @_; - $self->set($c, $c->model('DB')->schema->kiokudb_handle->lookup('dashboard templates')); + $self->set($c, $c->model('Kioku')->lookup('dashboard templates')); } my $renderer = sub { Then I tried it out $ git fixup-assigner.pl > fixups && less fixups #!/bin/sh git apply --cached <<EOF diff --git a/App/lib/MyApp/Controller/DashboardTemplates.pm b/App/lib/MyApp/Controller/DashboardTemplates.pm --- a/App/lib/MyApp/Controller/DashboardTemplates.pm +++ b/App/lib/MyApp/Controller/DashboardTemplates.pm @@ -15,3 +15,3 @@ sub base : Chained('/') PathPart('dashboard_templates') CaptureArgs(0) { my ($self, $c) = @_; - $self->set($c, $c->model('DB')->schema->kiokudb_handle->lookup('dashboard templates')); + $self->set($c, $c->model('Kioku')->lookup('dashboard templates')); } EOF git commit --fixup 7765cbd2 Looks fine to me. But then I try to use it: $ git checkout . && sh fixups error: patch failed: App/lib/MyApp/Controller/DashboardTemplates.pm:15 error: App/lib/MyApp/Controller/DashboardTemplates.pm: patch does not apply Any ideas what I'm doing wrong? -- fREW Schmidt http://blog.afoolishmanifesto.com -- 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