From: fREW Schmidt Date: Wed, 26 Oct 2011 14:37:56 GMT Subject: Re: git-fixup-assigner.perl -- automatically decide where to "fixup!" Message-ID: In-Reply-To: <201012140309.59378.trast@student.ethz.ch> On Mon, Dec 13, 2010 at 8:09 PM, Thomas Rast 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 <        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 <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