git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: weird diff output?

From
Jeff King <peff@peff.net>
Date
Mar 30, 2016, 04:55 UTC
Message-ID
<20160330045554.GA11007@sigill.intra.peff.net>
In-Reply-To
<CA+P7+xoLZhKzHf6khQfT_pZ2=CQAp8Nmhc9B8+10+9=YYUZH3w@mail.gmail.com>
On Tue, Mar 29, 2016 at 04:05:57PM -0700, Jacob Keller wrote:
Show 16 quoted lines
> > This is what we want in both cases.
> > And I would argue it would appease many other kinds of text as well, because
> > an empty line is usually a strong indicator for any text that a
> > different thing comes along.
> > (Other programming languages, such as Java, C++ and any other C like
> > language behaves
> > that way; even when writing latex figures you'd rather want to break
> > at new lines?)
> >
> > Thanks,
> > Stefan
> 
> This seems like a good heuristic. Can we think of any examples where
> it would produce wildly confusing diffs? I don't think it necessarily
> needs to be default but just a possible option when formatting diffs,
> much like we already have today.

One thing I like to do when playing with new diff ideas is to pipe all of "log -p" for a real project through it and see what differences it produces.

Below is a perl script that implements Stefan's heuristic. I checked its output on git.git with:

  git log --format='commit %H' -p >old
  perl /path/to/script <old >new
  diff -F ^commit -u old new | less

which shows the differences, with the commit id in the hunk header (which makes it easy to "git show $commit | perl /path/to/script" to see the new diff with more context.

In addition to the cases discussed, it seems to improve C comments by turning:

   /*
  + * new function
  + */
  +void foo(void);
  +
  +/*
    * old function
    ...
into:
  +/*
  + * my function
  + */
  +void foo(void);
  +
   /*
    * old function
    ...
See 47fe3f6e for an example.

It also seems to do OK with shell scripts. Commit e6bb5f78 is an example where it improves a here-doc, as in the motivating example from this thread. Similarly, the headers in 4df1e79 are much improved (though I'm confused why the final one in that diff doesn't seem to have been caught).

I also ran into an interesting case in 86d26f24, where we have:
  + test_expect_success '
  +   foo
  +
  +'
  +

and there are _two_ blank lines to choose from. It looks really terrible if you use the first one, but the second one looks good (and the script below chooses the second, as it's closest to the hunk boundary). There may be cases where that's bad, though.

This is just a proof of concept. I guess we'd want to somehow integrate the heuristic into git.

-- >8 -- #!/usr/bin/perl

use strict; use warnings 'all';

use constant {
  STATE_NONE => 0,
  STATE_LEADING_CONTEXT => 1,
  STATE_IN_CHUNK => 2,
};
my $state = STATE_NONE;
my @hunk;
while(<>) {
  if ($state == STATE_NONE) {
    print;
    if (/^@/) {
      $state = STATE_LEADING_CONTEXT;
    }
  } else {
    if (/^ /) {
      flush_hunk() if $state != STATE_LEADING_CONTEXT;
      push @hunk, $_;
    } elsif(/^[-+]/) {
      push @hunk, $_;
      $state = STATE_IN_CHUNK;
    } else {
      flush_hunk();
      $state = STATE_NONE;
      print;
    }
  }
}
flush_hunk();
sub flush_hunk {
  my $context_len = 0;
  while ($context_len < @hunk && $hunk[$context_len] =~ /^ /) {
    $context_len++;
  }
  # Find the length of the ambiguous portion.
  # Assumes our hunks have context first, and ambiguous additions at the end,
  # which is how git generates them
  my $ambig_len = 0;
  while ($ambig_len < $context_len) {
    my $i = $context_len - $ambig_len - 1;
    my $j = @hunk - $ambig_len - 1;
    if ($hunk[$j] =~ /^\+/ && substr($hunk[$i], 1) eq substr($hunk[$j], 1)) {
      $ambig_len++;
    } else {
      last;
    }
  }
  # Now look for an empty line in the ambiguous portion (we can just look in
  # the context side, as it is equivalent to the addition side at the end).
  # We count down, though, as we prefer to use the line closest to the
  # hunk as the cutoff.
  my $empty;
  for (my $i = $context_len - 1; $i >= $context_len - $ambig_len; $i--) {
    if (length($hunk[$i]) == 2) {
      $empty = $i;
      last;
    }
  }
  if (defined $empty) {
    # move empty lines after the chunk to be part of it
    for (my $i = $empty + 1; $i < $context_len; $i++) {
      $hunk[$i] =~ s/^ /+/;
      $hunk[@hunk - $context_len + $i] =~ s/^\+/ /;
    }
  }
  print @hunk;
  @hunk = ();
}
Previous: Junio C HamanoNext: Stefan Beller
Message 7 of 27 in “weird diff output?”
  1. Jacob KellerMar 29, 2016
  2. Stefan BellerMar 29, 2016
  3. Junio C HamanoMar 29, 2016
  4. Stefan BellerMar 29, 2016
  5. Jacob KellerMar 29, 2016
  6. Junio C HamanoMar 30, 2016
  7. Jeff KingMar 30, 2016
  8. Stefan BellerMar 30, 2016
  9. Jacob KellerMar 30, 2016
  10. Jacob KellerMar 30, 2016
  11. Jacob KellerMar 30, 2016
  12. Stefan BellerMar 30, 2016
  13. Junio C HamanoApr 1, 2016
  14. Jeff KingMar 31, 2016
  15. Jacob KellerApr 6, 2016
  16. Stefan BellerApr 12, 2016
  17. Davide LibenziApr 14, 2016
  18. Jeff KingApr 14, 2016
  19. Stefan BellerApr 14, 2016
  20. Implement better chunk heuristics.Stefan Beller, Apr 15, 2016
  21. Jacob KellerApr 15, 2016
  22. Stefan BellerApr 15, 2016
  23. Jacob KellerApr 15, 2016
  24. Junio C HamanoApr 15, 2016
  25. Stefan BellerApr 15, 2016
  26. Jacob KellerApr 15, 2016
  27. Jeff KingApr 15, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.