#!/usr/bin/env perl # See copyright, etc in below POD section. ###################################################################### use warnings; use Getopt::Long; #use Data::Dumper; $Data::Dumper::Indent=1; $Data::Dumper::Sortkeys=1; #Debug use IO::File; use IO::Dir; use Pod::Usage; use strict; use vars qw($Debug); our $VERSION = '0.001'; our $Opt_Widen = 1; #====================================================================== # main autoflush STDOUT 1; autoflush STDERR 1; Getopt::Long::config("no_auto_abbrev"); if (! GetOptions ( "debug" => sub { $Debug = 1; }, "help" => sub { print "Version $VERSION\n"; pod2usage(-verbose=>2, -exitval => 0, output=>\*STDOUT, -noperldoc=>1); }, "narrow!" => sub { $Opt_Widen = 0; }, "version" => sub { print "Version $VERSION\n"; exit(0); }, "widen!" => sub { $Opt_Widen = 1; }, # Default, undocumented "<>" => sub { die "%Error: Unknown parameter: $_[0]\n"; }, )) { die "%Error: Bad usage, try 'git_untabify --help'\n"; } read_patch(); ####################################################################### sub read_patch { my $filename = undef; my $lineno = 0; my $hunk = 0; my $editlines = {}; while (defined(my $line = )) { chomp $line; if ($line =~ m!^\+\+\+ b/(.*)!) { find_edits($editlines); edit_file($filename, $editlines); $filename = $1; $lineno = 0; $editlines = {}; print "FILE $filename\n" if $Debug; } elsif ($line =~ m!^@@ -?[0-9]+,?[0-9]* \+?([0-9]+)!) { $lineno = $1 - 1; ++$hunk; print " LINE $1 $line" if $Debug; } elsif ($line =~ m!^ !) { ++$lineno; $editlines->{$lineno} = {line => $line, hunk => $hunk, user_edit => 0, }; } elsif ($line =~ m!^\+!) { ++$lineno; print " $lineno: $line" if $Debug; $editlines->{$lineno} = {line => $line, hunk => $hunk, user_edit => 1, }; } } find_edits($editlines); edit_file($filename, $editlines); } sub find_edits { my $editlines = shift; # Expand edit regions so if have edited line, non-edited line, edited # line, we will tab expand the middle ones. my %hunk_firstlines; foreach my $lineno (sort {$a <=> $b} keys %$editlines) { my $hunk = $editlines->{$lineno}{hunk}; $hunk_firstlines{$hunk} ||= $lineno if $editlines->{$lineno}{user_edit}; } my %hunk_lastlines; foreach my $lineno (sort {$b <=> $a} keys %$editlines) { my $hunk = $editlines->{$lineno}{hunk}; $hunk_lastlines{$hunk} ||= $lineno if $editlines->{$lineno}{user_edit}; } if ($Opt_Widen) { # Expand to include }'s that finish basic block foreach my $hunk (keys %hunk_lastlines) { while (my $lineno = $hunk_lastlines{$hunk}) { ++$lineno; if (($editlines->{$lineno}{line} || "") =~ /^[+ ]\s+}[ \t};]*$/) { $hunk_lastlines{$hunk} = $lineno; } else { last; } } } # Expand to always untabify at least 3 lines (so that future diff will # have non-tabs within a edit hunk distance foreach my $hunk (keys %hunk_firstlines) { if ($hunk_firstlines{$hunk} == $hunk_lastlines{$hunk}) { --$hunk_firstlines{$hunk}; ++$hunk_lastlines{$hunk}; } elsif ($hunk_firstlines{$hunk}+1 == $hunk_lastlines{$hunk}) { ++$hunk_lastlines{$hunk}; } } } foreach my $lineno (sort {$a <=> $b} keys %$editlines) { if (($editlines->{$lineno}{line}||"") =~ /\t/) { my $hunk = $editlines->{$lineno}{hunk}; if ($hunk_firstlines{$hunk} <= $lineno && $hunk_lastlines{$hunk} >= $lineno) { $editlines->{$lineno}{editit} = 1; } } } } sub edit_file { my $filename = shift; my $editlines = shift; my @editits; foreach my $lineno (sort {$a <=> $b} keys %$editlines) { push @editits, $lineno if $editlines->{$lineno}{editit}; } return if $#editits < 0; if (ignore($filename)) { print "%Warning: Ignoring $filename\n"; return; } print "Edit $filename ",join(",",@editits),"\n"; my $lineno = 0; my @out; { my $fh = IO::File->new("<$filename") or die "%Error: $! $filename\n"; while (defined(my $line = $fh->getline)) { ++$lineno; if ($editlines->{$lineno}{editit}) { print $line; push @out, untabify($line); } else { push @out, $line; } } $fh->close; } { my $fh = IO::File->new(">${filename}.untab") or die "%Error: $! ${filename}.untab,"; $fh->print(join('',@out)); $fh->close; my ($dev,$ino,$mode) = stat($filename); chmod $mode, "${filename}.untab"; } rename("${filename}.untab", $filename) or die "%Error: $! ${filename}.untab,"; } sub ignore { my $filename = shift; return 1 if ($filename =~ /(Makefile|\.mk)/); return 1 if ($filename =~ /\.(y|l|out|vcd)$/); return 1 if ($filename =~ /gtkwave/); # return 0 if ($filename =~ /\.(sv|v|vh|svh|h|vc|cpp|pl)$/); return 0; } sub untabify { my $line = shift; my $out = ""; my $col = 0; foreach my $c (split //, $line) { if ($c eq "\t") { my $destcol = int(($col+8)/8)*8; while ($col < $destcol) { ++$col; $out .= " "; } } else { $out .= $c; $col++; } } return $out; } ####################################################################### __END__ =pod =head1 NAME git_untabify - Pipe a git diff report and untabify differences =head1 SYNOPSIS git diff a..b | git_untabify =head1 DESCRIPTION Take a patch file, and edit the files in the destination patch list to untabify the related patch lines. =head1 ARGUMENTS =over 4 =item --help Displays this message and program version and exits. =item --narrow Only edit lines which the user edited. If not provided, also edit other lines within at least a 3 line area around the edit, and between any edits within the same hunk. =item --version Displays program version and exits. =back =head1 DISTRIBUTION Copyright 2005-2020 by Wilson Snyder. This program is free software; you can redistribute it and/or modify it under the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =head1 AUTHORS Wilson Snyder =head1 SEE ALSO =cut ###################################################################### ### Local Variables: ### compile-command: "./git_untabify " ### End: