dch.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # vim: set fileencoding=utf-8 :
  2. #
  3. # (C) 2010 Rob Browning <rlb@defaultvalue.org>
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, please see
  16. # <http://www.gnu.org/licenses/>
  17. """provides git-dch helpers"""
  18. import re
  19. MAX_CHANGELOG_LINE_LENGTH = 76
  20. def extract_git_dch_cmds(lines, options):
  21. """Return a dictionary of all Git-Dch: commands found in lines.
  22. The command keys will be lowercased, i.e. {'ignore' : True,
  23. 'short': True}. For now, all the options are binary. Also return
  24. all of the lines that do not contain Git-Dch: commands."""
  25. commands = {}
  26. other_lines = []
  27. for line in lines:
  28. if line.startswith('Git-Dch: ') or line.startswith('Gbp-Dch: '):
  29. cmd = line.split(' ', 1)[1].strip().lower()
  30. commands[cmd] = True
  31. else:
  32. other_lines.append(line)
  33. return (commands, other_lines)
  34. def filter_ignore_rx_matches(lines, options):
  35. """Filter any lines that match options.ignore_regex
  36. (i.e. --ignore-regex)."""
  37. if options.ignore_regex:
  38. ignore_re = re.compile(options.ignore_regex)
  39. return [line for line in lines if not ignore_re.match(line)]
  40. else:
  41. return lines
  42. def extract_bts_cmds(lines, opts):
  43. """Return a dictionary of the bug tracking system commands
  44. contained in the the given lines. i.e. {'closed' : [1], 'fixed':
  45. [3, 4]}. Right now, this will only notice a single directive
  46. clause on a line. Also return all of the lines that do not
  47. contain bug tracking system commands."""
  48. _bug_re = re.compile(opts.meta_closes_bugnum, re.I)
  49. bts_rx = re.compile(r'(?P<bts>%s):\s+%s' % (opts.meta_closes, opts.meta_closes_bugnum), re.I)
  50. commands = {}
  51. other_lines = []
  52. for line in lines:
  53. m = bts_rx.match(line)
  54. if m:
  55. bug_nums = [ bug.strip() for bug in _bug_re.findall(line, re.I) ]
  56. try:
  57. commands[m.group('bts')] += bug_nums
  58. except KeyError:
  59. commands[m.group('bts')] = bug_nums
  60. else:
  61. other_lines.append(line)
  62. return (commands, other_lines)
  63. def extract_thanks_info(lines, options):
  64. """Return a list of all of the Thanks: entries, and a list of all
  65. of the lines that do not contain Thanks: entries."""
  66. thanks = []
  67. other_lines = []
  68. for line in lines:
  69. if line.startswith('Thanks: '):
  70. thanks.append(line.split(' ', 1)[1].strip())
  71. else:
  72. other_lines.append(line)
  73. return (thanks, other_lines)
  74. def _ispunct(ch):
  75. return not ch.isalnum() and not ch.isspace()
  76. def terminate_first_line_if_needed(lines):
  77. """Terminate the first line of lines with a '.' if multi-line."""
  78. # Don't add a period to empty or one line commit messages.
  79. if len(lines) < 2:
  80. return lines
  81. if lines[0] and _ispunct(lines[0][-1]):
  82. return lines
  83. if lines[1] and (_ispunct(lines[1][0]) or lines[1][0].islower()):
  84. return lines
  85. return [lines[0] + "."] + lines[1:]
  86. def format_changelog_entry(commit_info, options, last_commit=False):
  87. """Return a list of lines (without newlines) as the changelog
  88. entry for commit_info (generated by
  89. GitRepository.get_commit_info()). If last_commit is not False,
  90. then this entry is the last one in the series."""
  91. entry = [commit_info['subject']]
  92. body = commit_info['body'].splitlines()
  93. commitid = commit_info['id']
  94. (git_dch_cmds, body) = extract_git_dch_cmds(body, options)
  95. if 'ignore' in git_dch_cmds:
  96. return None
  97. if options.idlen:
  98. entry[0] = '[%s] ' % commitid[0:options.idlen] + entry[0]
  99. bts_cmds = {}
  100. thanks = []
  101. if options.meta:
  102. (bts_cmds, body) = extract_bts_cmds(body, options)
  103. (thanks, body) = extract_thanks_info(body, options)
  104. body = filter_ignore_rx_matches(body, options)
  105. if 'full' in git_dch_cmds or (options.full and not 'short' in git_dch_cmds):
  106. # Add all non-blank body lines.
  107. entry.extend([line for line in body if line.strip()])
  108. if thanks:
  109. # Last wins for now (match old behavior).
  110. thanks_msg = 'Thanks to %s' % thanks[-1]
  111. entry.extend([thanks_msg])
  112. for bts in bts_cmds:
  113. bts_msg = '(%s: %s)' % (bts, ', '.join(bts_cmds[bts]))
  114. if len(entry[-1]) + len(bts_msg) >= MAX_CHANGELOG_LINE_LENGTH:
  115. entry.extend([''])
  116. else:
  117. entry[-1] += " "
  118. entry[-1] += bts_msg
  119. entry = terminate_first_line_if_needed(entry)
  120. return entry