]> git.donarmstrong.com Git - lilypond.git/blob - buildscripts/git-update-changelog.py
5ad922be93a9b3619c222f4aafc3f277be34834f
[lilypond.git] / buildscripts / git-update-changelog.py
1 #!/usr/bin/python
2
3 import sys
4 import time
5 import os
6 import re
7 import optparse
8
9 def read_pipe (x):
10     print 'pipe', x
11     return os.popen (x).read ()
12
13 def system (x):
14     print x
15     return os.system (x)
16     
17 class PatchFailed(Exception):
18     pass
19
20 class Commit:
21     def __init__ (self, dict):
22         for v in ('message',
23                   'date',
24                   'author',
25                   'committish'):
26             self.__dict__[v] = dict[v]
27         
28         self.date = ' '.join  (self.date.split (' ')[:-1])
29         self.date = time.strptime (self.date, '%a %b %d %H:%M:%S %Y')
30         
31         m = re.search ('(.*)<(.*)>', self.author)
32         self.email = m.group (2).strip ()
33         self.name = m.group (1).strip ()
34         self.diff = read_pipe ('git show %s' % self.committish)
35         
36     def touched_files (self):
37         files = []
38         def note_file (x):
39             files.append (x.group (1))
40             return ''
41
42         re.sub ('\n--- a/([^\n]+)\n',
43                 note_file, self.diff)
44         re.sub('\n--- /dev/null\n\\+\\+\\+ b/([^\n]+)',
45                note_file, self.diff)
46
47         return files
48
49     def has_patch (self):
50         return self.touched_files () <> []
51     
52     def apply (self, add_del_files):
53         def note_add_file (x):
54             add_del_files.append (('add', x.group (1)))
55             return ''
56         
57         def note_del_file (x):
58             add_del_files.append (('del', x.group (1)))
59             return ''
60         
61         re.sub('\n--- /dev/null\n\\+\\+\\+ b/([^\n]+)',
62                note_add_file, self.diff)
63         
64         re.sub('\n--- a/([^\n]+)\n\\+\\+\\+ /dev/null',
65                note_del_file, self.diff)
66
67         p = os.popen ('patch -f -p1 ', 'w')
68         p.write (self.diff)
69
70         if p.close ():
71             raise PatchFailed, self.committish
72         
73     
74 def parse_commit_log (log):
75     committish = re.search ('^([^\n]+)', log).group (1)
76     author = re.search ('\nAuthor:\s+([^\n]+)', log).group (1)
77     date_match = re.search ('\nDate:\s+([^\n]+)', log)
78     date = date_match.group (1)
79     log = log[date_match.end (1):]
80
81     message = re.sub ("\n *", '', log)
82     message = message.strip ()
83
84     c = Commit (locals ())
85     return c
86
87 def parse_add_changes (from_commit, max_count=0):
88     opt = ''
89     rest = '..'
90     if max_count:
91
92         # fixme.
93         assert max_count == 1
94         opt = '--max-count=%d' % max_count 
95         rest = ''
96         
97     log = read_pipe ('git log %(opt)s %(from_commit)s%(rest)s' % locals ())
98
99     log = log[len ('commit '):]
100     log = log.strip ()
101
102     if not log:
103         return []
104         
105     commits = map (parse_commit_log, re.split ('\ncommit ', log))
106     commits.reverse ()
107     
108     return commits
109
110
111 def header (commit):
112     return '%d-%02d-%02d  %s  <%s>\n' % (commit.date[:3] + (commit.name, commit.email))
113
114 def changelog_body (commit):
115     s = ''
116     s += ''.join ('\n* %s: ' % f for f in commit.touched_files())
117     s += '\n' + commit.message
118     
119     s = s.replace ('\n', '\n\t')
120     s += '\n'
121     return s
122
123 def main ():
124     p = optparse.OptionParser (usage="usage git-update-changelog.py [options] [commits]",
125                                description="""
126 Apply GIT patches and update change log.
127
128 Run this file from the CVS directory, with commits from the repository in --git-dir.
129
130
131
132
133 """)
134     p.add_option ("--start",
135                   action='store',
136                   default='',
137                   metavar="FIRST",
138                   dest="start",
139                   help="all commits starting with FIRST.")
140     
141     p.add_option ("--git-dir",
142                   action='store',
143                   default='',
144                   dest="gitdir",
145                   help="the GIT directory to merge.")
146
147     (options, args) = p.parse_args ()
148     
149     log = open ('ChangeLog').read ()
150
151     if options.gitdir:
152         os.environ['GIT_DIR'] = options.gitdir
153
154
155     if not args:
156         if not options.start:
157             print 'Must set start committish.'  
158             sys.exit (1)
159
160         commits = parse_add_changes (options.start)
161     else:
162         commits = [] 
163         for a in args:
164             commits += parse_add_changes (a, max_count=1)
165
166     if not commits:
167         return
168     
169     new_log = ''
170     last_commit = None
171
172     first = header (commits[0]) + '\n'
173     if first == log[:len (first)]:
174         log = log[len (first):]
175
176     file_adddel = []
177     collated_log = ''
178     collated_message = ''
179
180     commits_done = []
181     while commits:
182         c = commits[0]
183         commits = commits[1:]
184         commits_done.append (c) 
185
186         if not c.has_patch ():
187             print 'patchless commit (merge?)'
188             continue
189         
190         print 'patch ', c.committish
191         try:
192             c.apply (file_adddel)
193         except PatchFailed:
194             break
195         
196         if c.touched_files () == ['ChangeLog']:
197             continue
198         
199         if (last_commit
200             and c.author != last_commit.author
201             and c.date[:3] != last_commit.date[:3]):
202
203             new_log += header (last_commit)
204
205         collated_log = changelog_body (c)  + collated_log
206         last_commit = c
207
208         collated_message += c.message + '\n'
209         
210
211
212     for (op, f) in file_adddel:
213         if op == 'del':
214             system ('cvs remove %(f)s' % locals ())
215         if op == 'add':
216             system ('cvs add %(f)s' % locals ())
217
218     if last_commit: 
219         collated_log = header (last_commit) + collated_log + '\n'
220
221     log = collated_log + log
222
223     try:
224         os.unlink ('ChangeLog~')
225     except OSError:
226         pass
227     
228     os.rename ('ChangeLog', 'ChangeLog~')
229     open ('ChangeLog', 'w').write (log)
230
231     open ('.msg','w').write (collated_message)
232     print '\nCommit message\n**\n%s\n**\n' % collated_message
233     print '\nRun:\n\n\tcvs commit -F .msg\n\n'
234     print '\n\techo %s >> .git-commits-done\n\n' % ' '.join ([c.committish
235                                                               for c in commits_done]) 
236
237
238     if commits:
239         print 'Commits left to do:'
240         print ' '.join ([c.committish for c in commits])
241     
242 main ()
243     
244     
245