Re: git-fixup-assigner.perl -- automatically decide where to "fixup!"

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]