Re: git-fixup-assigner.perl -- automatically decide where to "fixup!"
From: fREW Schmidt <hidden>
Date: 2016-06-15 22:52:19
On Mon, Dec 13, 2010 at 8:09 PM, Thomas Rast [off-list ref] wrote:
quoted hunk ↗ jump to hunk
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