265 lines
7.8 KiB
Python
Executable File
265 lines
7.8 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import os
|
|
import sys
|
|
import getopt
|
|
import subprocess
|
|
from xml.etree import ElementTree
|
|
|
|
def read_manifest(file):
|
|
manifest = ElementTree.parse(file)
|
|
defaults = manifest.findall('default')[0]
|
|
for project in manifest.findall('project'):
|
|
if not 'remote' in project.attrib:
|
|
project.attrib['remote'] = defaults.attrib['remote']
|
|
if not 'revision' in project.attrib:
|
|
project.attrib['revision'] = defaults.attrib['revision']
|
|
return manifest
|
|
|
|
def manifest_defaults(manifest):
|
|
defaults = dict()
|
|
|
|
defaults['remote'] = ''
|
|
defaults['revision'] = ''
|
|
for e in manifest.findall('default'):
|
|
if 'remote' in e:
|
|
defaults['remote'] = e.attrib['remote']
|
|
if 'revision' in e:
|
|
defaults['revision'] = e.attrib['revision']
|
|
|
|
oldpwd = os.getcwd()
|
|
os.chdir('.repo/manifests')
|
|
pipe = subprocess.Popen(['git', 'remote', '-v'], stdout=subprocess.PIPE)
|
|
line = pipe.stdout.readline()
|
|
fields = line.split()
|
|
defaults['fetch'] = os.path.dirname(fields[1])
|
|
os.chdir(oldpwd)
|
|
|
|
return defaults
|
|
|
|
def manifest_remotes(manifest, defaults):
|
|
remotes = dict()
|
|
for e in manifest.findall('remote'):
|
|
if not 'name' in e.attrib:
|
|
print "ERROR: remote with no name attrib"
|
|
continue
|
|
name = e.attrib['name']
|
|
if not 'fetch' in e.attrib:
|
|
print "ERROR: remote with no fetch attrib"
|
|
continue
|
|
e.attrib['fetch'] = e.attrib['fetch'].rstrip('/')
|
|
if e.attrib['fetch'] == '.':
|
|
e.attrib['fetch'] = defaults['fetch']
|
|
if e.attrib['fetch'] == '..':
|
|
e.attrib['fetch'] = os.path.dirname(defaults['fetch'])
|
|
remotes[name] = e
|
|
return remotes
|
|
|
|
def manifest_projects(manifest, remotes, defaults):
|
|
projects = dict()
|
|
for e in manifest.findall('project'):
|
|
if not 'name' in e.attrib:
|
|
print "ERROR: project with no name attrib"
|
|
continue
|
|
if not 'path' in e.attrib:
|
|
e.attrib['path'] = e.attrib['name']
|
|
path = e.attrib['path']
|
|
if path in projects:
|
|
print "ERROR: duplicate project path %s" % path
|
|
continue
|
|
if not 'remote' in e.attrib:
|
|
e.attrib['remote'] = defaults['remote']
|
|
remote_name = e.attrib['remote']
|
|
r = remotes[remote_name]
|
|
e.attrib['remote_url'] = r.attrib['fetch'] + '/' + e.attrib['name']
|
|
projects[path] = e
|
|
return projects
|
|
|
|
def is_same_project(p1, p2):
|
|
if p1.attrib['path'] != p2.attrib['path']:
|
|
return False
|
|
if p1.attrib['remote_url'] != p2.attrib['remote_url']:
|
|
return False
|
|
return True
|
|
|
|
def run_git_in_dir(path, args):
|
|
pipe = os.popen("chdir %s && git %s" % (path, args))
|
|
lines = pipe.readlines()
|
|
pipe.close()
|
|
return lines
|
|
|
|
class TextEmitter:
|
|
def emit_header(self, f1, f2):
|
|
print "# Changelog"
|
|
print "# Created ..."
|
|
print "# %s vs %s" % (f1, f2)
|
|
|
|
def emit_footer(self):
|
|
# empty
|
|
pass
|
|
|
|
def emit_project_change_header(self):
|
|
print "=" * 72
|
|
print "Project changes ..."
|
|
|
|
def emit_project_change_text(self, text):
|
|
print "%s" % text
|
|
|
|
def emit_project_change_footer(self):
|
|
print ""
|
|
|
|
def emit_dir_changes_header(self, name, r1, r2):
|
|
print "=" * 72
|
|
print "Changes in %s [%s .. %s]" % (name, r1, r2)
|
|
print "=" * 72
|
|
print ""
|
|
|
|
def emit_dir_changes_text(self, remote_url, text):
|
|
for line in text:
|
|
print line.rstrip()
|
|
|
|
def emit_dir_changes_footer(self):
|
|
print ""
|
|
|
|
class HtmlEmitter:
|
|
def emit_header(self, f1, f2):
|
|
print "<html>"
|
|
print " <head>"
|
|
print " <title>Changelog</title>"
|
|
print " </head>"
|
|
print " <body>"
|
|
|
|
def emit_footer(self):
|
|
print " </body>"
|
|
print "</html>"
|
|
|
|
def emit_project_change_header(self):
|
|
print " <h3>Project changes</h3>"
|
|
print " <hr>"
|
|
|
|
def emit_project_change_text(self, text):
|
|
print " %s<br>" % text
|
|
|
|
def emit_project_change_footer(self):
|
|
# empty
|
|
pass
|
|
|
|
def emit_dir_changes_header(self, name, r1, r2):
|
|
print " <h3>Changes in %s [%s .. %s]</h3>" % (name, r1, r2)
|
|
print " <hr>"
|
|
|
|
def emit_dir_changes_text(self, remote_url, text):
|
|
print " <pre>"
|
|
for line in text:
|
|
txt = line.rstrip()
|
|
if line[0:7] == 'commit ' and remote_url.find('github.com') != -1:
|
|
id = line[7:]
|
|
lnk = '%s/commit/%s' % (remote_url, id)
|
|
txt = '<a href="%s">%s</a>' % (lnk, id)
|
|
print " %s" % txt
|
|
print " </pre>"
|
|
|
|
def emit_dir_changes_footer(self):
|
|
# empty
|
|
pass
|
|
|
|
fmt = 'text'
|
|
showmerges = False
|
|
short = False
|
|
|
|
opts, args = getopt.getopt(sys.argv[1:], 'f:ms', ['format=', 'show-merges', 'short'])
|
|
for k, v in opts:
|
|
if k in ('-f', '--format'):
|
|
fmt = v
|
|
if k in ('-m', '--show-merges'):
|
|
showmerges = True
|
|
if k in ('-s', '--short'):
|
|
short = True
|
|
|
|
if len(args) != 2:
|
|
print "Usage: %s [-f text|html] [-m] manifest1 manifest2"
|
|
sys.exit(1)
|
|
|
|
f1 = args[0]
|
|
f2 = args[1]
|
|
manifest1 = read_manifest(f1)
|
|
manifest2 = read_manifest(f2)
|
|
defaults1 = manifest_defaults(manifest1)
|
|
defaults2 = manifest_defaults(manifest2)
|
|
remotes1 = manifest_remotes(manifest1, defaults1)
|
|
remotes2 = manifest_remotes(manifest2, defaults2)
|
|
projects1 = manifest_projects(manifest1, remotes1, defaults1)
|
|
projects2 = manifest_projects(manifest2, remotes2, defaults2)
|
|
|
|
diffable_projects = []
|
|
changed_projects = []
|
|
new_projects = []
|
|
del_projects = []
|
|
|
|
for path in projects1.iterkeys():
|
|
if path in projects2:
|
|
p1 = projects1[path]
|
|
p2 = projects2[path]
|
|
if is_same_project(p1, p2):
|
|
diffable_projects.append(path)
|
|
else:
|
|
changed_projects.append(path)
|
|
else:
|
|
del_projects.append(path)
|
|
|
|
for path in projects2.iterkeys():
|
|
if not path in projects1:
|
|
new_projects.append(path)
|
|
|
|
if fmt == 'text':
|
|
emitter = TextEmitter()
|
|
else:
|
|
emitter = HtmlEmitter()
|
|
|
|
emitter.emit_header(f1, f2)
|
|
|
|
if len(new_projects) > 0 or len(del_projects) > 0 or len(changed_projects) > 0:
|
|
emitter.emit_project_change_header()
|
|
|
|
for path in new_projects:
|
|
remote_url = projects2[path].attrib['remote_url']
|
|
emitter.emit_project_change_text("new project %s: %s" % (path, remote_url))
|
|
|
|
for path in del_projects:
|
|
remote_url = projects1[path].attrib['remote_url']
|
|
emitter.emit_project_change_text("deleted project %s: %s" % (path, remote_url))
|
|
|
|
for path in changed_projects:
|
|
old_remote_url = projects1[path].attrib['remote_url']
|
|
new_remote_url = projects2[path].attrib['remote_url']
|
|
emitter.emit_project_change_text("changed project %s: %s => %s" % (path, old_remote_url, new_remote_url))
|
|
|
|
emitter.emit_project_change_footer()
|
|
|
|
for path in sorted(diffable_projects):
|
|
p1 = projects1[path]
|
|
r1 = p1.attrib['revision']
|
|
p2 = projects2[path]
|
|
r2 = p2.attrib['revision']
|
|
if r1 != r2:
|
|
lines = run_git_in_dir(path, "show --pretty=format:%%h %s" % (r1))
|
|
r1a = lines[0].rstrip('\n')
|
|
lines = run_git_in_dir(path, "show --pretty=format:%%h %s" % (r2))
|
|
r2a = lines[0].rstrip('\n')
|
|
gitargs = "log "
|
|
if not showmerges:
|
|
gitargs += "--no-merges "
|
|
if short:
|
|
gitargs += "--pretty=oneline "
|
|
else:
|
|
gitargs += "--pretty=short "
|
|
gitargs += "--abbrev-commit "
|
|
gitargs += "%s..%s" % (p1.attrib['revision'], p2.attrib['revision'])
|
|
lines = run_git_in_dir(path, gitargs)
|
|
if len(lines) > 0:
|
|
emitter.emit_dir_changes_header(path, r1a, r2a)
|
|
emitter.emit_dir_changes_text(p1.attrib['remote_url'], lines)
|
|
emitter.emit_dir_changes_footer()
|
|
|
|
emitter.emit_footer()
|