#!/usr/bin/python
+import sys
import time
import os
import re
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',
'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)
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 ()
return []
commits = map (parse_commit_log, re.split ('\ncommit ', log))
+ commits.reverse ()
return commits
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
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
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 ()