Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 9d2fbc646b
Fetching contributors…

Cannot retrieve contributors at this time

executable file 287 lines (245 sloc) 10.381 kb
#!/usr/bin/python
from optparse import OptionParser
import subprocess
import re
import os.path
import fnmatch
import os
import sys
# subtract 8 for the leading tabstop
fill_column = 74 - 8
path_to_root = None
all_changelogs = {}
def git (command, *args):
popen = subprocess.Popen (["git", command] + list (args), stdout = subprocess.PIPE)
output = popen.communicate () [0]
if popen.returncode != 0:
print >> sys.stderr, "Error: git failed"
exit (1)
return output
def changelog_path (changelog):
global path_to_root
if not path_to_root:
path_to_root = git ("rev-parse", "--show-cdup").strip ()
(pathname, filename) = changelog
return path_to_root + "./" + pathname + "/" + filename
def changelog_for_file (filename):
while filename != "":
dirname = os.path.dirname (filename)
if dirname in all_changelogs:
return (dirname, all_changelogs [dirname])
filename = dirname
assert False
def changelogs_for_file_pattern (pattern, changed_files):
changelogs = set ()
for filename in changed_files:
suffix = filename
while suffix != "":
# FIXME: fnmatch doesn't support the {x,y} pattern
if fnmatch.fnmatch (suffix, pattern):
changelogs.add (changelog_for_file (filename))
(_, _, suffix) = suffix.partition ("/")
return changelogs
def format_paragraph (paragraph):
lines = []
words = paragraph.split ()
if len (words) == 0:
return lines
current = words [0]
for word in words [1:]:
if len (current) + 1 + len (word) <= fill_column:
current += " " + word
else:
lines.append ("\t" + current)
current = word
lines.append ("\t" + current)
return lines
def format_changelog_paragraph (files, paragraph):
files_string = ""
for (filename, entity) in files:
if len (files_string) > 0:
files_string += ", "
files_string += filename
if entity:
files_string += " (" + entity + ")"
return format_paragraph ("* " + files_string + ": " + paragraph)
def append_paragraph (lines, paragraph):
if len (lines):
lines.append ("")
lines += paragraph
def format_changelog_entries (commit, changed_files, prefix, file_entries, all_paragraphs):
changelogs = set ()
for f in changed_files:
changelogs.add (changelog_for_file (f))
marked_changelogs = set ()
author_line = git ("log", "-n1", "--date=short", "--format=%ad %an <%ae>", commit).strip ()
paragraphs = {}
for changelog in changelogs:
paragraphs [changelog] = [author_line]
for (files, comments) in file_entries:
changelog_entries = {}
for (filename, entity) in files:
entry_changelogs = changelogs_for_file_pattern (filename, changed_files)
if len (entry_changelogs) == 0:
print "Warning: could not match file %s in commit %s" % (filename, commit)
for changelog in entry_changelogs:
if changelog not in changelog_entries:
changelog_entries [changelog] = []
changelog_entries [changelog].append ((filename, entity))
marked_changelogs.add (changelog)
for (changelog, files) in changelog_entries.items ():
append_paragraph (paragraphs [changelog], format_changelog_paragraph (files, comments [0]))
for paragraph in comments [1:]:
append_paragraph (paragraphs [changelog], format_paragraph (paragraph))
unmarked_changelogs = changelogs - marked_changelogs
for changelog in unmarked_changelogs:
if len (prefix) == 0:
print "Warning: empty entry in %s for commit %s" % (changelog_path (changelog), commit)
insert_paragraphs = all_paragraphs
else:
insert_paragraphs = prefix
for paragraph in insert_paragraphs:
append_paragraph (paragraphs [changelog], format_paragraph (paragraph))
return paragraphs
def debug_print_commit (commit, raw_message, prefix, file_entries, changed_files, changelog_entries):
print "===================== Commit"
print commit
print "--------------------- RAW"
print raw_message
print "--------------------- Prefix"
for line in prefix:
print line
print "--------------------- File entries"
for (files, comments) in file_entries:
files_str = ""
for (filename, entity) in files:
if len (files_str):
files_str = files_str + ", "
files_str = files_str + filename
if entity:
files_str = files_str + " (" + entity + ")"
print files_str
for line in comments:
print " " + line
print "--------------------- Files touched"
for f in changed_files:
print f
print "--------------------- ChangeLog entries"
for ((dirname, filename), lines) in changelog_entries.items ():
print "%s/%s:" % (dirname, filename)
for line in lines:
print line
def process_commit (commit):
changed_files = map (lambda l: l.split () [2], git ("diff-tree", "--numstat", commit).splitlines () [1:])
if len (filter (lambda f: re.search ("(^|/)Change[Ll]og$", f), changed_files)):
return None
raw_message = git ("log", "-n1", "--format=%B", commit)
# filter SVN migration message
message = re.sub ("(^|\n)svn path=[^\n]+revision=\d+(?=$|\n)", "", raw_message)
# filter ChangeLog headers
message = re.sub ("(^|\n)\d+-\d+-\d+[ \t]+((\w|[.-])+[ \t]+)+<[^\n>]+>(?=$|\n)", "", message)
# filter leading whitespace
message = re.sub ("^\s+", "", message)
# filter trailing whitespace
message = re.sub ("\s+$", "", message)
# paragraphize - first remove whitespace at beginnings and ends of lines
message = re.sub ("[ \t]*\n[ \t]*", "\n", message)
# paragraphize - now replace three or more consecutive newlines with two
message = re.sub ("\n\n\n+", "\n\n", message)
# paragraphize - replace single newlines with a space
message = re.sub ("(?<!\n)\n(?!\n)", " ", message)
# paragraphize - finally, replace double newlines with single ones
message = re.sub ("\n\n", "\n", message)
# A list of paragraphs (strings without newlines) that occur
# before the first file comments
prefix = []
# A list of tuples of the form ([(filename, entity), ...], [paragraph, ...]).
#
# Each describes a file comment, containing multiple paragraphs.
# Those paragraphs belong to a list of files, each with an
# optional entity (usually a function name).
file_entries = []
current_files = None
current_files_comments = None
message_lines = message.splitlines ()
for line in message_lines:
if re.match ("\*\s[^:]+:", line):
if current_files:
file_entries.append ((current_files, current_files_comments))
(files, _, comments) = line.partition (":")
current_files_comments = [comments.strip ()]
current_files = []
for f in re.split ("\s*,\s*", files [1:].strip ()):
m = re.search ("\(([^()]+)\)$", f)
if m:
filename = f [:m.start (0)].strip ()
entity = m.group (1).strip ()
else:
filename = f
entity = None
current_files.append ((filename, entity))
else:
if current_files:
current_files_comments.append (line)
else:
prefix.append (line)
if current_files:
file_entries.append ((current_files, current_files_comments))
changelog_entries = format_changelog_entries (commit, changed_files, prefix, file_entries, message_lines)
#debug_print_commit (commit, raw_message, prefix, file_entries, changed_files, changelog_entries)
return changelog_entries
def start_changelog (changelog):
full_path = changelog_path (changelog)
old_name = full_path + ".old"
os.rename (full_path, old_name)
return open (full_path, "w")
def finish_changelog (changelog, file):
old_file = open (changelog_path (changelog) + ".old")
file.write (old_file.read ())
old_file.close ()
file.close ()
def append_lines (file, lines):
for line in lines:
file.write (line + "\n")
file.write ("\n")
def main ():
usage = "usage: %prog [options] <start-commit>"
parser = OptionParser (usage)
parser.add_option ("-r", "--root", dest = "root", help = "Root directory of the working tree to be changed")
(options, args) = parser.parse_args ()
if len (args) != 1:
parser.error ("incorrect number of arguments")
start_commit = args [0]
if options.root:
global path_to_root
path_to_root = options.root + "/"
# MonkeyWrench uses a shared git repo but sets BUILD_REVISION,
# if present we use it instead of HEAD
HEAD = "HEAD"
if 'BUILD_REVISION' in os.environ:
HEAD = os.environ['BUILD_REVISION']
#see if git supports %B in --format
output = git ("log", "-n1", "--format=%B", HEAD)
if output.startswith ("%B"):
print >> sys.stderr, "Error: git doesn't support %B in --format - install version 1.7.2 or newer"
exit (1)
for filename in git ("ls-tree", "-r", "--name-only", HEAD).splitlines ():
if re.search ("(^|/)Change[Ll]og$", filename):
(path, name) = os.path.split (filename)
all_changelogs [path] = name
commits = git ("rev-list", "--no-merges", HEAD, "^%s" % start_commit).splitlines ()
touched_changelogs = {}
for commit in commits:
entries = process_commit (commit)
if entries == None:
continue
for (changelog, lines) in entries.items ():
if not os.path.exists (changelog_path (changelog)):
continue
if changelog not in touched_changelogs:
touched_changelogs [changelog] = start_changelog (changelog)
append_lines (touched_changelogs [changelog], lines)
for (changelog, file) in touched_changelogs.items ():
finish_changelog (changelog, file)
if __name__ == "__main__":
main ()
Jump to Line
Something went wrong with that request. Please try again.