X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=buildscripts%2Fgit-update-changelog.py;h=09f0d16b7afebad8214b9463cef4076fdc5f1301;hb=87eedcd59f4082cb0841528ad5bc82cb1d1191e3;hp=c24f13d381ea10d83c57125fda2724dd8ee91a2a;hpb=a0d28219eb870f5b49fe022321ee4e2db05bf0b3;p=lilypond.git diff --git a/buildscripts/git-update-changelog.py b/buildscripts/git-update-changelog.py index c24f13d381..09f0d16b7a 100644 --- a/buildscripts/git-update-changelog.py +++ b/buildscripts/git-update-changelog.py @@ -1,10 +1,31 @@ #!/usr/bin/python +import sys import time import os import re import optparse +def read_pipe (x): + print 'pipe', x + return os.popen (x).read () + +def system (x): + print x + return os.system (x) + +class PatchFailed(Exception): + pass + +def sign (x): + if x < 0: + return -1 + if x > 0: + return 1 + + return 0 + + class Commit: def __init__ (self, dict): for v in ('message', @@ -12,31 +33,93 @@ class Commit: 'author', 'committish'): self.__dict__[v] = dict[v] - - # Sat Oct 28 18:52:30 2006 +0200 self.date = ' '.join (self.date.split (' ')[:-1]) self.date = time.strptime (self.date, '%a %b %d %H:%M:%S %Y') - + m = re.search ('(.*)<(.*)>', self.author) - self.email = m.group (2) - self.name = m.group (1) + self.email = m.group (2).strip () + self.name = m.group (1).strip () + self.diff = read_pipe ('git show %s' % self.committish) + def compare (self, other): + return sign (time.mktime (self.date) - time.mktime (other.date)) - def touched_files (self): + def check_diff_chunk (self, filename, chunk): + removals = [] + def note_removal (m): + removals.append (m.group (1)) + + re.sub ('\n-([^\n]+)', note_removal, chunk) + + if removals == []: + return True + if not os.path.exists (filename): + return False + + contents = open (filename).read () + for r in removals: + if r not in contents: + return False + + return True + + def check_diff (self): + chunks = re.split ('\ndiff --git ', self.diff) + + ok = True + for c in chunks: + m = re.search ('^a/([^ ]+)', c) + if not m: + continue + + file = m.group (1) + + c = re.sub('\n--- [^\n]+', '', c) + ok = ok and self.check_diff_chunk (file, c) + if not ok: + break + + return ok + + def touched_files (self): files = [] def note_file (x): files.append (x.group (1)) return '' - - diff = os.popen ('git show %s' % self.committish).read () + re.sub ('\n--- a/([^\n]+)\n', - note_file, diff) + note_file, self.diff) re.sub('\n--- /dev/null\n\\+\\+\\+ b/([^\n]+)', - note_file, diff) + note_file, self.diff) return files + def has_patch (self): + return self.touched_files () <> [] + + def apply (self, add_del_files): + def note_add_file (x): + add_del_files.append (('add', x.group (1))) + return '' + + def note_del_file (x): + add_del_files.append (('del', x.group (1))) + return '' + + re.sub('\n--- /dev/null\n\\+\\+\\+ b/([^\n]+)', + note_add_file, self.diff) + + re.sub('\n--- a/([^\n]+)\n\\+\\+\\+ /dev/null', + note_del_file, self.diff) + + p = os.popen ('patch -f -p1 ', 'w') + p.write (self.diff) + + if p.close (): + raise PatchFailed, self.committish + + def parse_commit_log (log): committish = re.search ('^([^\n]+)', log).group (1) author = re.search ('\nAuthor:\s+([^\n]+)', log).group (1) @@ -50,13 +133,27 @@ def parse_commit_log (log): c = Commit (locals ()) return c -def parse_add_changes (from_commit): - log = os.popen ('git log %(from_commit)s..' % locals ()).read () +def parse_add_changes (from_commit, max_count=0): + opt = '' + rest = '..' + if max_count: + + # fixme. + assert max_count == 1 + opt = '--max-count=%d' % max_count + rest = '' + + log = read_pipe ('git log %(opt)s %(from_commit)s%(rest)s' % locals ()) log = log[len ('commit '):] + log = log.strip () + + if not log: + return [] + commits = map (parse_commit_log, re.split ('\ncommit ', log)) commits.reverse () - + return commits @@ -64,55 +161,104 @@ def header (commit): return '%d-%02d-%02d %s <%s>\n' % (commit.date[:3] + (commit.name, commit.email)) def changelog_body (commit): - s = '' - s += "\ngit commit %s\n" % commit.committish s += ''.join ('\n* %s: ' % f for f in commit.touched_files()) s += '\n' + commit.message s = s.replace ('\n', '\n\t') s += '\n' return s - -def find_last_checked_in_commit (log): - m = re.search (r'^(\d+-\d+\d+)[^\n]+\n*\t\*git commit ([a-f0-9]+):', log) - - if m: - return (m.group (0), m.group (1)) - - return None - +def main (): + p = optparse.OptionParser (usage="usage git-update-changelog.py [options] [commits]", + description=""" +Apply GIT patches and update change log. +Run this file from the CVS directory, with commits from the repository in --git-dir. -def main (): - p = optparse.OptionParser ("usage git-update-changelog.py --options") +""") p.add_option ("--start", action='store', default='', + metavar="FIRST", dest="start", - help="start of log messages to merge.") + help="all commits starting with FIRST (exclusive).") + + p.add_option ("--git-dir", + action='store', + default='', + dest="gitdir", + help="the GIT directory to merge.") (options, args) = p.parse_args () log = open ('ChangeLog').read () - if not options.start: - options.start = find_last_checked_in_commit (log) + if options.gitdir: + os.environ['GIT_DIR'] = options.gitdir + + + if not args: + if not options.start: + print 'Must set start committish.' + sys.exit (1) + + commits = parse_add_changes (options.start) + else: + commits = [] + for a in args: + commits += parse_add_changes (a, max_count=1) - commits = parse_add_changes (options.start) if not commits: return - new_log = '' last_commit = None - first = header (commits[0]) + first = header (commits[0]) + '\n' if first == log[:len (first)]: log = log[len (first):] + + try: + previously_done = dict((c, 1) for c in open ('.git-commits-done').read ().split ('\n')) + except IOError: + previously_done = {} + + commits = [c for c in commits if not previously_done.has_key (c.committish)] + commits = sorted (commits, cmp=Commit.compare) + + system ('cvs up') - for c in commits: + file_adddel = [] + collated_log = '' + collated_message = '' + commits_done = [] + while commits: + c = commits[0] + + if not c.has_patch (): + print 'patchless commit (merge?)' + continue + + ok = c.check_diff () + + if not ok: + print "Patch doesn't seem to apply" + print 'skipping', c.committish + print 'message:', c.message + + break + + + commits = commits[1:] + commits_done.append (c) + + print 'patch ', c.committish + try: + c.apply (file_adddel) + except PatchFailed: + break + if c.touched_files () == ['ChangeLog']: continue @@ -122,19 +268,42 @@ def main (): new_log += header (last_commit) - new_log += changelog_body (c) + collated_log = changelog_body (c) + collated_log last_commit = c + + collated_message += c.message + '\n' - new_log = header (last_commit) + new_log + '\n' - log = new_log + log + + for (op, f) in file_adddel: + if op == 'del': + system ('cvs remove %(f)s' % locals ()) + if op == 'add': + system ('cvs add %(f)s' % locals ()) + + if last_commit: + collated_log = header (last_commit) + collated_log + '\n' + + log = collated_log + log + try: os.unlink ('ChangeLog~') - except IOError: - passa + except OSError: + pass os.rename ('ChangeLog', 'ChangeLog~') open ('ChangeLog', 'w').write (log) + + open ('.msg','w').write (collated_message) + print '\nCommit message\n**\n%s\n**\n' % collated_message + print '\nRun:\n\n\tcvs commit -F .msg\n\n' + print '\n\techo %s >> .git-commits-done\n\n' % ' '.join ([c.committish + for c in commits_done]) + + + if commits: + print 'Commits left to do:' + print ' '.join ([c.committish for c in commits]) main ()