xref: /netbsd-src/external/gpl3/gcc/dist/contrib/legacy/mklog (revision b1e838363e3c6fc78a55519254d99869742dd33c)
1*b1e83836Smrg#!/usr/bin/env python3
2*b1e83836Smrg
3*b1e83836Smrg# Copyright (C) 2017-2019 Free Software Foundation, Inc.
4*b1e83836Smrg#
5*b1e83836Smrg# This file is part of GCC.
6*b1e83836Smrg#
7*b1e83836Smrg# GCC is free software; you can redistribute it and/or modify
8*b1e83836Smrg# it under the terms of the GNU General Public License as published by
9*b1e83836Smrg# the Free Software Foundation; either version 3, or (at your option)
10*b1e83836Smrg# any later version.
11*b1e83836Smrg#
12*b1e83836Smrg# GCC is distributed in the hope that it will be useful,
13*b1e83836Smrg# but WITHOUT ANY WARRANTY; without even the implied warranty of
14*b1e83836Smrg# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15*b1e83836Smrg# GNU General Public License for more details.
16*b1e83836Smrg#
17*b1e83836Smrg# You should have received a copy of the GNU General Public License
18*b1e83836Smrg# along with GCC; see the file COPYING.  If not, write to
19*b1e83836Smrg# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
20*b1e83836Smrg# Boston, MA 02110-1301, USA.
21*b1e83836Smrg
22*b1e83836Smrg# This script parses a .diff file generated with 'diff -up' or 'diff -cp'
23*b1e83836Smrg# and adds a skeleton ChangeLog file to the file. It does not try to be
24*b1e83836Smrg# too smart when parsing function names, but it produces a reasonable
25*b1e83836Smrg# approximation.
26*b1e83836Smrg#
27*b1e83836Smrg# This is a straightforward adaptation of original Perl script.
28*b1e83836Smrg#
29*b1e83836Smrg# Author: Yury Gribov <tetra2005@gmail.com>
30*b1e83836Smrg
31*b1e83836Smrgimport argparse
32*b1e83836Smrgimport sys
33*b1e83836Smrgimport re
34*b1e83836Smrgimport os.path
35*b1e83836Smrgimport os
36*b1e83836Smrgimport tempfile
37*b1e83836Smrgimport time
38*b1e83836Smrgimport shutil
39*b1e83836Smrgfrom subprocess import Popen, PIPE
40*b1e83836Smrg
41*b1e83836Smrgme = os.path.basename(sys.argv[0])
42*b1e83836Smrg
43*b1e83836Smrgpr_regex = re.compile('\+(\/(\/|\*)|[Cc*!])\s+(PR [a-z+-]+\/[0-9]+)')
44*b1e83836Smrg
45*b1e83836Smrgdef error(msg):
46*b1e83836Smrg  sys.stderr.write("%s: error: %s\n" % (me, msg))
47*b1e83836Smrg  sys.exit(1)
48*b1e83836Smrg
49*b1e83836Smrgdef warn(msg):
50*b1e83836Smrg  sys.stderr.write("%s: warning: %s\n" % (me, msg))
51*b1e83836Smrg
52*b1e83836Smrgclass RegexCache(object):
53*b1e83836Smrg  """Simple trick to Perl-like combined match-and-bind."""
54*b1e83836Smrg
55*b1e83836Smrg  def __init__(self):
56*b1e83836Smrg    self.last_match = None
57*b1e83836Smrg
58*b1e83836Smrg  def match(self, p, s):
59*b1e83836Smrg    self.last_match = re.match(p, s) if isinstance(p, str) else p.match(s)
60*b1e83836Smrg    return self.last_match
61*b1e83836Smrg
62*b1e83836Smrg  def search(self, p, s):
63*b1e83836Smrg    self.last_match = re.search(p, s) if isinstance(p, str) else p.search(s)
64*b1e83836Smrg    return self.last_match
65*b1e83836Smrg
66*b1e83836Smrg  def group(self, n):
67*b1e83836Smrg    return self.last_match.group(n)
68*b1e83836Smrg
69*b1e83836Smrgcache = RegexCache()
70*b1e83836Smrg
71*b1e83836Smrgdef run(cmd, die_on_error):
72*b1e83836Smrg  """Simple wrapper for Popen."""
73*b1e83836Smrg  proc = Popen(cmd.split(' '), stderr = PIPE, stdout = PIPE)
74*b1e83836Smrg  (out, err) = proc.communicate()
75*b1e83836Smrg  if die_on_error and proc.returncode != 0:
76*b1e83836Smrg    error("`%s` failed:\n" % (cmd, proc.stderr))
77*b1e83836Smrg  return proc.returncode, out.decode(), err
78*b1e83836Smrg
79*b1e83836Smrgdef read_user_info():
80*b1e83836Smrg  dot_mklog_format_msg = """\
81*b1e83836SmrgThe .mklog format is:
82*b1e83836SmrgNAME = ...
83*b1e83836SmrgEMAIL = ...
84*b1e83836Smrg"""
85*b1e83836Smrg
86*b1e83836Smrg  # First try to read .mklog config
87*b1e83836Smrg  mklog_conf = os.path.expanduser('~/.mklog')
88*b1e83836Smrg  if os.path.exists(mklog_conf):
89*b1e83836Smrg    attrs = {}
90*b1e83836Smrg    f = open(mklog_conf)
91*b1e83836Smrg    for s in f:
92*b1e83836Smrg      if cache.match(r'^\s*([a-zA-Z0-9_]+)\s*=\s*(.*?)\s*$', s):
93*b1e83836Smrg        attrs[cache.group(1)] = cache.group(2)
94*b1e83836Smrg    f.close()
95*b1e83836Smrg    if 'NAME' not in attrs:
96*b1e83836Smrg      error("'NAME' not present in .mklog")
97*b1e83836Smrg    if 'EMAIL' not in attrs:
98*b1e83836Smrg      error("'EMAIL' not present in .mklog")
99*b1e83836Smrg    return attrs['NAME'], attrs['EMAIL']
100*b1e83836Smrg
101*b1e83836Smrg  # Otherwise go with git
102*b1e83836Smrg
103*b1e83836Smrg  rc1, name, _ = run('git config user.name', False)
104*b1e83836Smrg  name = name.rstrip()
105*b1e83836Smrg  rc2, email, _ = run('git config user.email', False)
106*b1e83836Smrg  email = email.rstrip()
107*b1e83836Smrg
108*b1e83836Smrg  if rc1 != 0 or rc2 != 0:
109*b1e83836Smrg    error("""\
110*b1e83836SmrgCould not read git user.name and user.email settings.
111*b1e83836SmrgPlease add missing git settings, or create a %s.
112*b1e83836Smrg""" % mklog_conf)
113*b1e83836Smrg
114*b1e83836Smrg  return name, email
115*b1e83836Smrg
116*b1e83836Smrgdef get_parent_changelog (s):
117*b1e83836Smrg  """See which ChangeLog this file change should go to."""
118*b1e83836Smrg
119*b1e83836Smrg  if s.find('\\') == -1 and s.find('/') == -1:
120*b1e83836Smrg    return "ChangeLog", s
121*b1e83836Smrg
122*b1e83836Smrg  gcc_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
123*b1e83836Smrg
124*b1e83836Smrg  d = s
125*b1e83836Smrg  while d:
126*b1e83836Smrg    clname = d + "/ChangeLog"
127*b1e83836Smrg    if os.path.exists(gcc_root + '/' + clname) or os.path.exists(clname):
128*b1e83836Smrg      relname = s[len(d)+1:]
129*b1e83836Smrg      return clname, relname
130*b1e83836Smrg    d, _ = os.path.split(d)
131*b1e83836Smrg
132*b1e83836Smrg  return "Unknown ChangeLog", s
133*b1e83836Smrg
134*b1e83836Smrgclass FileDiff:
135*b1e83836Smrg  """Class to represent changes in a single file."""
136*b1e83836Smrg
137*b1e83836Smrg  def __init__(self, filename):
138*b1e83836Smrg    self.filename = filename
139*b1e83836Smrg    self.hunks = []
140*b1e83836Smrg    self.clname, self.relname = get_parent_changelog(filename);
141*b1e83836Smrg
142*b1e83836Smrg  def dump(self):
143*b1e83836Smrg    print("Diff for %s:\n  ChangeLog = %s\n  rel name = %s\n" % (self.filename, self.clname, self.relname))
144*b1e83836Smrg    for i, h in enumerate(self.hunks):
145*b1e83836Smrg      print("Next hunk %d:" % i)
146*b1e83836Smrg      h.dump()
147*b1e83836Smrg
148*b1e83836Smrgclass Hunk:
149*b1e83836Smrg  """Class to represent a single hunk of changes."""
150*b1e83836Smrg
151*b1e83836Smrg  def __init__(self, hdr):
152*b1e83836Smrg    self.hdr = hdr
153*b1e83836Smrg    self.lines = []
154*b1e83836Smrg    self.ctx_diff = is_ctx_hunk_start(hdr)
155*b1e83836Smrg
156*b1e83836Smrg  def dump(self):
157*b1e83836Smrg    print('%s' % self.hdr)
158*b1e83836Smrg    print('%s' % '\n'.join(self.lines))
159*b1e83836Smrg
160*b1e83836Smrg  def is_file_addition(self):
161*b1e83836Smrg    """Does hunk describe addition of file?"""
162*b1e83836Smrg    if self.ctx_diff:
163*b1e83836Smrg      for line in self.lines:
164*b1e83836Smrg        if re.match(r'^\*\*\* 0 \*\*\*\*', line):
165*b1e83836Smrg          return True
166*b1e83836Smrg    else:
167*b1e83836Smrg      return re.match(r'^@@ -0,0 \+1.* @@', self.hdr)
168*b1e83836Smrg
169*b1e83836Smrg  def is_file_removal(self):
170*b1e83836Smrg    """Does hunk describe removal of file?"""
171*b1e83836Smrg    if self.ctx_diff:
172*b1e83836Smrg      for line in self.lines:
173*b1e83836Smrg        if re.match(r'^--- 0 ----', line):
174*b1e83836Smrg          return True
175*b1e83836Smrg    else:
176*b1e83836Smrg      return re.match(r'^@@ -1.* \+0,0 @@', self.hdr)
177*b1e83836Smrg
178*b1e83836Smrgdef is_file_diff_start(s):
179*b1e83836Smrg  # Don't be fooled by context diff line markers:
180*b1e83836Smrg  #   *** 385,391 ****
181*b1e83836Smrg  return ((s.startswith('*** ') and not s.endswith('***'))
182*b1e83836Smrg          or (s.startswith('--- ') and not s.endswith('---')))
183*b1e83836Smrg
184*b1e83836Smrgdef is_ctx_hunk_start(s):
185*b1e83836Smrg  return re.match(r'^\*\*\*\*\*\**', s)
186*b1e83836Smrg
187*b1e83836Smrgdef is_uni_hunk_start(s):
188*b1e83836Smrg  return re.match(r'^@@ .* @@', s)
189*b1e83836Smrg
190*b1e83836Smrgdef is_hunk_start(s):
191*b1e83836Smrg  return is_ctx_hunk_start(s) or is_uni_hunk_start(s)
192*b1e83836Smrg
193*b1e83836Smrgdef remove_suffixes(s):
194*b1e83836Smrg  if s.startswith('a/') or s.startswith('b/'):
195*b1e83836Smrg    s = s[2:]
196*b1e83836Smrg  if s.endswith('.jj'):
197*b1e83836Smrg    s = s[:-3]
198*b1e83836Smrg  return s
199*b1e83836Smrg
200*b1e83836Smrgdef find_changed_funs(hunk):
201*b1e83836Smrg  """Find all functions touched by hunk.  We don't try too hard
202*b1e83836Smrg     to find good matches.  This should return a superset
203*b1e83836Smrg     of the actual set of functions in the .diff file.
204*b1e83836Smrg  """
205*b1e83836Smrg
206*b1e83836Smrg  fns = []
207*b1e83836Smrg  fn = None
208*b1e83836Smrg
209*b1e83836Smrg  if (cache.match(r'^\*\*\*\*\*\** ([a-zA-Z0-9_].*)', hunk.hdr)
210*b1e83836Smrg      or cache.match(r'^@@ .* @@ ([a-zA-Z0-9_].*)', hunk.hdr)):
211*b1e83836Smrg    fn = cache.group(1)
212*b1e83836Smrg
213*b1e83836Smrg  for i, line in enumerate(hunk.lines):
214*b1e83836Smrg    # Context diffs have extra whitespace after first char;
215*b1e83836Smrg    # remove it to make matching easier.
216*b1e83836Smrg    if hunk.ctx_diff:
217*b1e83836Smrg      line = re.sub(r'^([-+! ]) ', r'\1', line)
218*b1e83836Smrg
219*b1e83836Smrg    # Remember most recent identifier in hunk
220*b1e83836Smrg    # that might be a function name.
221*b1e83836Smrg    if cache.match(r'^[-+! ]([a-zA-Z0-9_#].*)', line):
222*b1e83836Smrg      fn = cache.group(1)
223*b1e83836Smrg
224*b1e83836Smrg    change = line and re.match(r'^[-+!][^-]', line)
225*b1e83836Smrg
226*b1e83836Smrg    # Top-level comment cannot belong to function
227*b1e83836Smrg    if re.match(r'^[-+! ]\/\*', line):
228*b1e83836Smrg      fn = None
229*b1e83836Smrg
230*b1e83836Smrg    if change and fn:
231*b1e83836Smrg      if cache.match(r'^((class|struct|union|enum)\s+[a-zA-Z0-9_]+)', fn):
232*b1e83836Smrg        # Struct declaration
233*b1e83836Smrg        fn = cache.group(1)
234*b1e83836Smrg      elif cache.search(r'#\s*define\s+([a-zA-Z0-9_]+)', fn):
235*b1e83836Smrg        # Macro definition
236*b1e83836Smrg        fn = cache.group(1)
237*b1e83836Smrg      elif cache.match('^DEF[A-Z0-9_]+\s*\(([a-zA-Z0-9_]+)', fn):
238*b1e83836Smrg        # Supermacro
239*b1e83836Smrg        fn = cache.group(1)
240*b1e83836Smrg      elif cache.search(r'([a-zA-Z_][^()\s]*)\s*\([^*]', fn):
241*b1e83836Smrg        # Discard template and function parameters.
242*b1e83836Smrg        fn = cache.group(1)
243*b1e83836Smrg        fn = re.sub(r'<[^<>]*>', '', fn)
244*b1e83836Smrg        fn = fn.rstrip()
245*b1e83836Smrg      else:
246*b1e83836Smrg        fn = None
247*b1e83836Smrg
248*b1e83836Smrg      if fn and fn not in fns:  # Avoid dups
249*b1e83836Smrg        fns.append(fn)
250*b1e83836Smrg
251*b1e83836Smrg      fn = None
252*b1e83836Smrg
253*b1e83836Smrg  return fns
254*b1e83836Smrg
255*b1e83836Smrgdef parse_patch(contents):
256*b1e83836Smrg  """Parse patch contents to a sequence of FileDiffs."""
257*b1e83836Smrg
258*b1e83836Smrg  diffs = []
259*b1e83836Smrg
260*b1e83836Smrg  lines = contents.split('\n')
261*b1e83836Smrg
262*b1e83836Smrg  i = 0
263*b1e83836Smrg  while i < len(lines):
264*b1e83836Smrg    line = lines[i]
265*b1e83836Smrg
266*b1e83836Smrg    # Diff headers look like
267*b1e83836Smrg    #   --- a/gcc/tree.c
268*b1e83836Smrg    #   +++ b/gcc/tree.c
269*b1e83836Smrg    # or
270*b1e83836Smrg    #   *** gcc/cfgexpand.c     2013-12-25 20:07:24.800350058 +0400
271*b1e83836Smrg    #   --- gcc/cfgexpand.c     2013-12-25 20:06:30.612350178 +0400
272*b1e83836Smrg
273*b1e83836Smrg    if is_file_diff_start(line):
274*b1e83836Smrg      left = re.split(r'\s+', line)[1]
275*b1e83836Smrg    else:
276*b1e83836Smrg      i += 1
277*b1e83836Smrg      continue
278*b1e83836Smrg
279*b1e83836Smrg    left = remove_suffixes(left);
280*b1e83836Smrg
281*b1e83836Smrg    i += 1
282*b1e83836Smrg    line = lines[i]
283*b1e83836Smrg
284*b1e83836Smrg    if not cache.match(r'^[+-][+-][+-] +(\S+)', line):
285*b1e83836Smrg      error("expected filename in line %d" % i)
286*b1e83836Smrg    right = remove_suffixes(cache.group(1));
287*b1e83836Smrg
288*b1e83836Smrg    # Extract real file name from left and right names.
289*b1e83836Smrg    filename = None
290*b1e83836Smrg    if left == right:
291*b1e83836Smrg      filename = left
292*b1e83836Smrg    elif left == '/dev/null':
293*b1e83836Smrg      filename = right;
294*b1e83836Smrg    elif right == '/dev/null':
295*b1e83836Smrg      filename = left;
296*b1e83836Smrg    else:
297*b1e83836Smrg      comps = []
298*b1e83836Smrg      while left and right:
299*b1e83836Smrg        left, l = os.path.split(left)
300*b1e83836Smrg        right, r = os.path.split(right)
301*b1e83836Smrg        if l != r:
302*b1e83836Smrg          break
303*b1e83836Smrg        comps.append(l)
304*b1e83836Smrg
305*b1e83836Smrg      if not comps:
306*b1e83836Smrg        error("failed to extract common name for %s and %s" % (left, right))
307*b1e83836Smrg
308*b1e83836Smrg      comps.reverse()
309*b1e83836Smrg      filename = '/'.join(comps)
310*b1e83836Smrg
311*b1e83836Smrg    d = FileDiff(filename)
312*b1e83836Smrg    diffs.append(d)
313*b1e83836Smrg
314*b1e83836Smrg    # Collect hunks for current file.
315*b1e83836Smrg    hunk = None
316*b1e83836Smrg    i += 1
317*b1e83836Smrg    while i < len(lines):
318*b1e83836Smrg      line = lines[i]
319*b1e83836Smrg
320*b1e83836Smrg      # Create new hunk when we see hunk header
321*b1e83836Smrg      if is_hunk_start(line):
322*b1e83836Smrg        if hunk is not None:
323*b1e83836Smrg          d.hunks.append(hunk)
324*b1e83836Smrg        hunk = Hunk(line)
325*b1e83836Smrg        i += 1
326*b1e83836Smrg        continue
327*b1e83836Smrg
328*b1e83836Smrg      # Stop when we reach next diff
329*b1e83836Smrg      if (is_file_diff_start(line)
330*b1e83836Smrg          or line.startswith('diff ')
331*b1e83836Smrg          or line.startswith('Index: ')):
332*b1e83836Smrg        i -= 1
333*b1e83836Smrg        break
334*b1e83836Smrg
335*b1e83836Smrg      if hunk is not None:
336*b1e83836Smrg        hunk.lines.append(line)
337*b1e83836Smrg      i += 1
338*b1e83836Smrg
339*b1e83836Smrg    d.hunks.append(hunk)
340*b1e83836Smrg
341*b1e83836Smrg  return diffs
342*b1e83836Smrg
343*b1e83836Smrg
344*b1e83836Smrgdef get_pr_from_testcase(line):
345*b1e83836Smrg    r = pr_regex.search(line)
346*b1e83836Smrg    if r != None:
347*b1e83836Smrg        return r.group(3)
348*b1e83836Smrg    else:
349*b1e83836Smrg        return None
350*b1e83836Smrg
351*b1e83836Smrgdef main():
352*b1e83836Smrg  name, email = read_user_info()
353*b1e83836Smrg
354*b1e83836Smrg  help_message =  """\
355*b1e83836SmrgGenerate ChangeLog template for PATCH.
356*b1e83836SmrgPATCH must be generated using diff(1)'s -up or -cp options
357*b1e83836Smrg(or their equivalent in Subversion/git).
358*b1e83836Smrg"""
359*b1e83836Smrg
360*b1e83836Smrg  inline_message = """\
361*b1e83836SmrgPrepends ChangeLog to PATCH.
362*b1e83836SmrgIf PATCH is not stdin, modifies PATCH in-place,
363*b1e83836Smrgotherwise writes to stdout.'
364*b1e83836Smrg"""
365*b1e83836Smrg
366*b1e83836Smrg  parser = argparse.ArgumentParser(description = help_message)
367*b1e83836Smrg  parser.add_argument('-v', '--verbose', action = 'store_true', help = 'Verbose messages')
368*b1e83836Smrg  parser.add_argument('-i', '--inline', action = 'store_true', help = inline_message)
369*b1e83836Smrg  parser.add_argument('input', nargs = '?', help = 'Patch file (or missing, read standard input)')
370*b1e83836Smrg  args = parser.parse_args()
371*b1e83836Smrg  if args.input == '-':
372*b1e83836Smrg      args.input = None
373*b1e83836Smrg  input = open(args.input) if args.input else sys.stdin
374*b1e83836Smrg  contents = input.read()
375*b1e83836Smrg  diffs = parse_patch(contents)
376*b1e83836Smrg
377*b1e83836Smrg  if args.verbose:
378*b1e83836Smrg    print("Parse results:")
379*b1e83836Smrg    for d in diffs:
380*b1e83836Smrg      d.dump()
381*b1e83836Smrg
382*b1e83836Smrg  # Generate template ChangeLog.
383*b1e83836Smrg
384*b1e83836Smrg  logs = {}
385*b1e83836Smrg  prs = []
386*b1e83836Smrg  for d in diffs:
387*b1e83836Smrg    log_name = d.clname
388*b1e83836Smrg
389*b1e83836Smrg    logs.setdefault(log_name, '')
390*b1e83836Smrg    logs[log_name] += '\t* %s' % d.relname
391*b1e83836Smrg
392*b1e83836Smrg    change_msg = ''
393*b1e83836Smrg
394*b1e83836Smrg    # Check if file was removed or added.
395*b1e83836Smrg    # Two patterns for context and unified diff.
396*b1e83836Smrg    if len(d.hunks) == 1:
397*b1e83836Smrg      hunk0 = d.hunks[0]
398*b1e83836Smrg      if hunk0.is_file_addition():
399*b1e83836Smrg        if re.search(r'testsuite.*(?<!\.exp)$', d.filename):
400*b1e83836Smrg          change_msg = ': New test.\n'
401*b1e83836Smrg          pr = get_pr_from_testcase(hunk0.lines[0])
402*b1e83836Smrg          if pr and pr not in prs:
403*b1e83836Smrg              prs.append(pr)
404*b1e83836Smrg        else:
405*b1e83836Smrg          change_msg = ": New file.\n"
406*b1e83836Smrg      elif hunk0.is_file_removal():
407*b1e83836Smrg        change_msg = ": Remove.\n"
408*b1e83836Smrg
409*b1e83836Smrg    _, ext = os.path.splitext(d.filename)
410*b1e83836Smrg    if (not change_msg and ext in ['.c', '.cpp', '.C', '.cc', '.h', '.inc', '.def']
411*b1e83836Smrg        and not 'testsuite' in d.filename):
412*b1e83836Smrg      fns = []
413*b1e83836Smrg      for hunk in d.hunks:
414*b1e83836Smrg        for fn in find_changed_funs(hunk):
415*b1e83836Smrg          if fn not in fns:
416*b1e83836Smrg            fns.append(fn)
417*b1e83836Smrg
418*b1e83836Smrg      for fn in fns:
419*b1e83836Smrg        if change_msg:
420*b1e83836Smrg          change_msg += "\t(%s):\n" % fn
421*b1e83836Smrg        else:
422*b1e83836Smrg          change_msg = " (%s):\n" % fn
423*b1e83836Smrg
424*b1e83836Smrg    logs[log_name] += change_msg if change_msg else ":\n"
425*b1e83836Smrg
426*b1e83836Smrg  if args.inline and args.input:
427*b1e83836Smrg    # Get a temp filename, rather than an open filehandle, because we use
428*b1e83836Smrg    # the open to truncate.
429*b1e83836Smrg    fd, tmp = tempfile.mkstemp("tmp.XXXXXXXX")
430*b1e83836Smrg    os.close(fd)
431*b1e83836Smrg
432*b1e83836Smrg    # Copy permissions to temp file
433*b1e83836Smrg    # (old Pythons do not support shutil.copymode)
434*b1e83836Smrg    shutil.copymode(args.input, tmp)
435*b1e83836Smrg
436*b1e83836Smrg    # Open the temp file, clearing contents.
437*b1e83836Smrg    out = open(tmp, 'w')
438*b1e83836Smrg  else:
439*b1e83836Smrg    tmp = None
440*b1e83836Smrg    out = sys.stdout
441*b1e83836Smrg
442*b1e83836Smrg  # Print log
443*b1e83836Smrg  date = time.strftime('%Y-%m-%d')
444*b1e83836Smrg  bugmsg = ''
445*b1e83836Smrg  if len(prs):
446*b1e83836Smrg    bugmsg = '\n'.join(['\t' + pr for pr in prs]) + '\n'
447*b1e83836Smrg
448*b1e83836Smrg  for log_name, msg in sorted(logs.items()):
449*b1e83836Smrg    out.write("""\
450*b1e83836Smrg%s:
451*b1e83836Smrg
452*b1e83836Smrg%s  %s  <%s>
453*b1e83836Smrg
454*b1e83836Smrg%s%s\n""" % (log_name, date, name, email, bugmsg, msg))
455*b1e83836Smrg
456*b1e83836Smrg  if args.inline:
457*b1e83836Smrg    # Append patch body
458*b1e83836Smrg    out.write(contents)
459*b1e83836Smrg
460*b1e83836Smrg    if args.input:
461*b1e83836Smrg      # Write new contents atomically
462*b1e83836Smrg      out.close()
463*b1e83836Smrg      shutil.move(tmp, args.input)
464*b1e83836Smrg
465*b1e83836Smrgif __name__ == '__main__':
466*b1e83836Smrg    main()
467