add support for "last"
[homepage.git] / blog / posts / 2008 / 01 / bts_followup / followup.patch
1 --- /usr/bin/bts        2008-01-11 21:59:45.000000000 +0100
2 +++ bts 2008-01-27 20:38:48.000000000 +0100
3 @@ -266,6 +266,13 @@
4  shell.  Default is 'mutt -f %s'.  (Also, %% will be substituted by a
5  single % if this is needed.)
6  
7 +=item --mailcomposer=COMPOSER
8 +
9 +Specify the command to compose mail from a template. Must contain a "%s"
10 +as per --mailreader, however it will be replaced by a mail template
11 +containing some headers and body, similar to what is expected by mutt
12 +when invoked with -H. Default is, no wonder, 'mutt -H %s'.
13 +
14  =item --cc-addr=CC_EMAIL_ADDRESS
15  
16  Send carbon copies to a list of users. CC_EMAIL_ADDRESS should be a 
17 @@ -339,6 +346,7 @@
18  my $refreshmode=0;
19  my $updatemode=0;
20  my $mailreader='mutt -f %s';
21 +my $mailcomposer='mutt -H %s';
22  my $sendmailcmd='/usr/sbin/sendmail';
23  my $smtphost='';
24  my $noaction=0;
25 @@ -361,6 +369,7 @@
26                        'BTS_FORCE_REFRESH' => 'no',
27                        'BTS_ONLY_NEW' => 'no',
28                        'BTS_MAIL_READER' => 'mutt -f %s',
29 +                      'BTS_MAIL_COMPOSER' => 'mutt -H %s',
30                        'BTS_SENDMAIL_COMMAND' => '/usr/sbin/sendmail',
31                        'BTS_INCLUDE_RESOLVED' => 'yes',
32                        'BTS_SMTP_HOST' => '',
33 @@ -423,6 +432,7 @@
34      $refreshmode = $config_vars{'BTS_FORCE_REFRESH'} eq 'yes' ? 1 : 0;
35      $updatemode = $config_vars{'BTS_ONLY_NEW'} eq 'yes' ? 1 : 0;
36      $mailreader = $config_vars{'BTS_MAIL_READER'};
37 +    $mailcomposer = $config_vars{'BTS_MAIL_COMPOSER'};
38      $sendmailcmd = $config_vars{'BTS_SENDMAIL_COMMAND'};
39      $smtphost = $config_vars{'BTS_SMTP_HOST'};
40      $includeresolved = $config_vars{'BTS_INCLUDE_RESOLVED'} eq 'yes' ? 1 : 0;
41 @@ -433,7 +443,7 @@
42  }
43  
44  my ($opt_help, $opt_version, $opt_noconf);
45 -my ($opt_cachemode, $opt_mailreader, $opt_sendmail, $opt_smtphost);
46 +my ($opt_cachemode, $opt_mailreader, $opt_mailcomposer, $opt_sendmail, $opt_smtphost);
47  my $opt_cachedelay=5;
48  my $mboxmode = 0;
49  my $quiet=0;
50 @@ -451,6 +461,7 @@
51            "cache-delay=i" => \$opt_cachedelay,
52            "m|mbox" => \$mboxmode,
53            "mailreader|mail-reader=s" => \$opt_mailreader,
54 +          "mailcomposer|mail-composer=s" => \$opt_mailcomposer,
55            "cc-addr=s" => \$ccemail,
56            "sendmail=s" => \$opt_sendmail,
57            "smtp-host|smtphost=s" => \$opt_smtphost,
58 @@ -478,6 +489,14 @@
59      }
60  }
61  
62 +if ($opt_mailcomposer) {
63 +    if ($opt_mailcomposer =~ /\%s/) {
64 +       $mailcomposer=$opt_mailcomposer;
65 +    } else {
66 +       warn "bts: ignoring invalid --mailcomposer option: invalid mail command following it.\n";
67 +    }
68 +}
69 +
70  if ($opt_sendmail and $opt_smtphost) {
71      die "bts: --sendmail and --smtp-host mutually exclusive\n";
72  }
73 @@ -901,6 +920,77 @@
74       print map {qq($_\n)} @{$bugs};
75  }
76  
77 +=item followup <bug> [bug log message ID]
78 +
79 +Fire up a mail user agent to follow up to a given bug report, quoting the bug
80 +log text. Per default the text of the first message in the bug log is inlined
81 +for quoting purposes, you can specify an alternative one providing an optional
82 +bug log ID. The first message in the bug log has bug log ID 1, second message
83 +2, and so on. Alternatively, "last" can be specified as a bug log ID to choose
84 +the last message, "last-1" to choose the next to last, and so on.
85 +
86 +=cut
87 +
88 +sub bts_followup {
89 +  die "bts: Couldn't run bts followup: $soap_broken\n" unless have_soap();
90 +  my $bug = checkbug(shift) or die "bts followup: follow up on which bug?\n";
91 +  my $msglogid = shift;
92 +  $msglogid = 1 unless defined $msglogid;
93 +  my $soap = SOAP::Lite->uri($soapurl)->proxy($soapproxyurl);
94 +  my $log = $soap->get_bug_log($bug)->result();
95 +  my @logs = @$log;
96 +  my $msg;
97 +  if ($msglogid =~ /^last(-(\d+))?$/i) {
98 +    my $idx = defined $2 ? $#logs-$2 : $#logs;
99 +    $msg = $logs[$idx];
100 +  } else {
101 +    $msg = $logs[$msglogid-1];
102 +  }
103 +  my %headers = parse_rfc822_headers($msg->{header});
104 +
105 +  # extract data needed to compose the follow up email
106 +  my $qbody = # quoted text body
107 +    join "\n", map { s/^/> /; $_ } (split /\r?\n/, $msg->{body});
108 +  my $subject = "follow up for $bug";  # default subject
109 +  $subject = "Re: $headers{subject}" if defined $headers{'subject'};
110 +  my $from = "anonymous"; # default author
111 +  if (defined $headers{'from'} and $headers{'from'} =~ /^(.*)\s+<[^<>]+>\s*$/) {
112 +    $from = $1;
113 +  }
114 +  my $ref = "";        # id for In-Reply-To 
115 +  $ref = $headers{'message-id'} if defined $headers{'message-id'};
116 +  my $submitter = "";
117 +  $submitter = $headers{'from'} if defined $headers{'from'};
118 +
119 +  # compose follow up email
120 +  my ($fh, $mailtpl) = tempfile("btsXXXXXX",
121 +    SUFFIX => ".mail",
122 +    DIR => File::Spec->tmpdir,
123 +    UNLINK => 1);
124 +  open (MAIL, ">/dev/fd/" . fileno($fh))
125 +    or die "bts: writing to temporary file: $!\n";
126 +  print MAIL "To: $submitter\n";
127 +  print MAIL "Cc: $bug\@$btsserver\n";
128 +  print MAIL "Subject: $subject\n";
129 +  print MAIL "In-Reply-To: $ref\n" if $ref;
130 +  print MAIL "\n";
131 +  print MAIL "On $headers{'date'}, " if defined $headers{'date'};
132 +  print MAIL "$from wrote:\n";
133 +  print MAIL "$qbody\n";
134 +  close MAIL;
135 +
136 +  # fire up MUA on email template
137 +  my $cmd = $mailcomposer;
138 +  $cmd =~ s/\%s/$mailtpl/g;
139 +  system $cmd;
140 +  unless ($? == 0) {
141 +    my $rc = $? >> 8;
142 +    print "Failure to follow up: mail composer returned exit status $rc.\n";
143 +    print "Dumping intended mail template to standard output:\n\n";
144 +    system "cat $mailtpl";
145 +  }
146 +}
147 +
148  =item clone <bug> [new IDs]
149  
150  The clone control command allows you to duplicate a bug report. It is useful
151 @@ -2194,6 +2284,24 @@
152      return $header;
153  }
154  
155 +# Given mail headers as a raw string, parses them and return a hash mapping
156 +# (lowercase) header names to header values; handles properly (?) RFC822 line
157 +# continuation.
158 +sub parse_rfc822_headers {
159 +  my ($raw) = @_;
160 +  my %headers;
161 +  my $key = "";        # remember last seen header name
162 +  foreach my $line (split /\r?\n/, $raw) {
163 +    if ($line =~ /^([^:]+):\s+(.*)$/) {        # 1st header field
164 +      my $key = lc $1;
165 +      $headers{$key} = $2;
166 +    } elsif ($line =~ /^\s+(.*)/ ) { # header field continuation
167 +      $headers{$key} .= " $1";
168 +    }
169 +  }
170 +  return %headers;
171 +}
172 +
173  ##########  Browsing and caching subroutines
174  
175  # Mirrors a given thing; if the online version is no newer than our
176 @@ -3217,6 +3325,11 @@
177  If this is set, specifies a mail reader to use instead of mutt.  Same as
178  the --mailreader command line option.
179  
180 +=item BTS_MAIL_COMPOSER
181 +
182 +If this is set, specifies a mail composer to use instead of mutt.  Same
183 +as the --mailcomposer command line option.
184 +
185  =item BTS_SENDMAIL_COMMAND
186  
187  If this is set, specifies a sendmail command to use instead of