]> git.donarmstrong.com Git - lilypond.git/blobdiff - buildscripts/git-update-changelog.py
Doc -- updates to music glossary by Kurtis Kroon
[lilypond.git] / buildscripts / git-update-changelog.py
index a2c3e5356ee28645afef542873a5c62d989b2065..09f0d16b7afebad8214b9463cef4076fdc5f1301 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/python
 
+import sys
 import time
 import os
 import re
@@ -9,6 +10,22 @@ 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',
@@ -16,34 +33,94 @@ 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 = read_pipe ('git show %s' % self.committish)
+
         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 parse_commit_log (log):
-    print log
+    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)
     date_match = re.search ('\nDate:\s+([^\n]+)', log)
@@ -56,9 +133,17 @@ def parse_commit_log (log):
     c = Commit (locals ())
     return c
 
-def parse_add_changes (from_commit):
-    
-    log = read_pipe ('git log %(from_commit)s..' % locals ())
+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 ()
@@ -67,6 +152,7 @@ def parse_add_changes (from_commit):
         return []
         
     commits = map (parse_commit_log, re.split ('\ncommit ', log))
+    commits.reverse ()
     
     return commits
 
@@ -75,46 +161,54 @@ 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.match ('^(\\d+-\\d+-\\d+)[^\n]+\n*\tgit commit ([a-f0-9]+)', log)
-    
-    if m:
-        return (m.group (1), m.group (2))
-
-    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:
-        (time, id) = find_last_checked_in_commit (log)
-        options.start = id
+    if options.gitdir:
+        os.environ['GIT_DIR'] = options.gitdir
+
 
-        print 'processing commits from ', id, options.start
+    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
     
@@ -124,8 +218,47 @@ def main ():
     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
         
@@ -135,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:
+    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 ()