Attempt to fix failing issue on update
[git-perl-utils.git] / script / git-to-bugzilla.pl
1 #!/usr/bin/env perl
2 #
3 #
4 use strict;
5 use warnings;
6 use Carp;
7 use Config::Any;
8 use Data::Printer;
9 use File::Slurp;
10 use FindBin;
11 use Getopt::Long;
12 use Git::Repository;
13 use Try::Tiny;
14
15 use lib "$FindBin::Bin/../lib";
16 use WWW::Bugzilla;
17
18 my $verbose;
19 my $debug;
20
21 # ------------------------------------------------------------------------
22 sub update_bugzilla {
23     my $cfg  = shift;
24     my $info = shift;
25     my $set  = shift;
26
27     my $bz = WWW::Bugzilla->new(
28         server     => $cfg->{bugzilla}{server},
29         email      => $cfg->{bugzilla}{user},
30         password   => $cfg->{bugzilla}{pass},
31         bug_number => $set->{bug}
32     ) || croak "Cannot open bz - $!";
33     p $bz if ($debug);
34
35     my $header =
36       sprintf( "Git commit: %s/commitdiff/%s\n", $cfg->{gitweb}, $info->{rev} );
37     if ( scalar( @{ $info->{diff} } ) > 50 ) {
38
39         # big diff - we skip the diff
40         $bz->additional_comments(
41             join( "\n",
42                 $header,           @{ $info->{info} }, '',
43                 @{ $info->{log} }, '----',             @{ $info->{diffstat} } )
44         );
45     }
46     else {
47
48         # otherwise we do the whole thing
49         $bz->additional_comments( join( "\n", $header, @{ $info->{all} } ) );
50     }
51
52     $bz->change_status("fixed")  if ( $set->{action} =~ /fixes/ );
53     $bz->change_status("closed") if ( $set->{action} =~ /closes/ );
54     p $bz                        if ($debug);
55
56     try { $bz->commit; } catch { warn "Error from bugzilla commit - $_"; };
57
58     printf( "[%d] %s %s [%s]\n",
59         $set->{bug}, $info->{rev}, $info->{log}[0], $set->{action} );
60 }
61
62 # ------------------------------------------------------------------------
63 sub find_bugzilla_references {
64     my $info = shift;
65     my $cfg  = shift;
66
67     my @results;
68     my $action = '';
69     my $bugid;
70     foreach my $line ( @{ $info->{log} } ) {
71         $line = lc($line);
72         if ( $line =~
73             /(closes|fixes|references):?\s*(?:bug(?:zilla)?)?\s*\#?(\d+)/ )
74         {
75             $action = $1;
76             $bugid  = $2;
77         }
78         elsif ( $line =~ /\b(?:bug(?:zilla)?)\s*\#?(\d+)/ ) {
79             $action = 'references';
80             $bugid  = $1;
81         }
82         else {
83             next;
84         }
85
86         # remap actions
87
88         push( @results, { bug => $bugid, action => $action } );
89         ##printf( "%s\n\taction = %s bugid = %s\n", $info->{rev}, $action, $bugid );
90     }
91     return @results;
92 }
93
94 # ------------------------------------------------------------------------
95
96 sub git_commit_info {
97     my $git = shift;
98     my $rev = shift;
99
100     my @lines =
101       $git->run( 'show', '-M', '-C', '--patch-with-stat', '--pretty=fuller',
102         $rev );
103
104     my $info = {
105         rev      => $rev,
106         info     => [],
107         log      => [],
108         diffstat => [],
109         diff     => [],
110         all      => [@lines],    # deliberate copy
111     };
112
113     while ( my $line = shift @lines ) {
114         last if ( $line =~ /^$/ );
115         push( @{ $info->{info} }, $line );
116     }
117
118     while ( my $line = shift @lines ) {
119         last if ( $line =~ /^---\s*$/ );
120         push( @{ $info->{log} }, $line );
121     }
122
123     while ( my $line = shift @lines ) {
124         last if ( $line =~ /^$/ );
125         push( @{ $info->{diffstat} }, $line );
126     }
127
128     # all the rest
129     $info->{diff} = \@lines;
130
131     return $info;
132 }
133
134 # ------------------------------------------------------------------------
135
136 sub walk_git_commits {
137     my $git = shift;
138     my $cfg = shift;
139
140     my $lastrev = $git->run( 'rev-parse', $cfg->{lastref} );
141     my $headrev = $git->run( 'rev-parse', $cfg->{branch_head} );
142
143     return if ( $lastrev eq $headrev );
144
145     my @revs = $git->run( 'rev-list', '--topo-order', '--no-merges',
146         ( $lastrev . '..' . $headrev ) );
147
148     foreach my $rev ( reverse(@revs) ) {
149         my $info = git_commit_info( $git, $rev );
150
151         #ddx($info);
152         #dd( $info->{info}, $info->{log}[0] );
153         my @sets = find_bugzilla_references( $info, $cfg );
154         foreach my $set (@sets) {
155             update_bugzilla( $cfg, $info, $set );
156         }
157     }
158     return $headrev;
159 }
160
161 # ------------------------------------------------------------------------
162
163 # main
164 {
165     my $config;
166
167     GetOptions(
168         'config=s' => \$config,
169         'debug!'   => \$debug,
170         'verbose!' => \$verbose,
171     ) or die "Incorrect options";
172     die "No config file given\n" unless ( $config and -f $config );
173     my $cfg = (
174         values(
175             %{ Config::Any->load_files( { files => [$config], use_ext => 1 } )
176                   ->[0]
177             }
178         )
179     )[0];
180
181     die "No git_dir specified\n" unless ( $cfg->{git_dir} );
182     $cfg->{lasttag} ||= $cfg->{git_dir} . '/refs/tags/BugzillaDone';
183     $cfg->{branch_head} ||= 'HEAD';
184
185     $cfg->{lastref} =
186       -f $cfg->{lasttag} ? read_file( $cfg->{lasttag} ) : 'HEAD';
187     chomp( $cfg->{lastref} );
188
189     my $git = Git::Repository->new( git_dir => $cfg->{git_dir} )
190       || die "No valid git repo\n";
191
192     my $newlast = walk_git_commits( $git, $cfg );
193     if ($newlast) {
194         write_file( $cfg->{lasttag}, $newlast );
195     }
196 }