Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:acaudwell/Gource
- Loading branch information
Showing
7 changed files
with
225 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#!/usr/bin/env python | ||
|
||
# python script to parse the mercurial repository log into the custom format | ||
# expected by Gource (http://code.google.com/p/gource/) | ||
# Gource is a software version control visualization program, pretty cool :) | ||
# | ||
# usage: | ||
# ./mercurial-gource.py [OUTPUT_FILE] | ||
# | ||
# where: | ||
# OUTPUT_FILE is the output file for the custom log | ||
# if OUTPUT_FILE is not provided, output to stdout | ||
# | ||
# example usage with Gource (assuming that Gource is installed): | ||
# ./mercurial-gource.py project.log && gource project.log | ||
# OR | ||
# ./mercurial-gource.py | gource --log-format custom - | ||
# but gource doesn't loop at the end when reading from stdin | ||
# | ||
# | ||
# This script should work for any mercurial repository (version number?), | ||
# | ||
# Something that may be worth doing is adding a "--limit NUMBER" to hg log, | ||
# where NUMBER is an argument to this script? | ||
# | ||
|
||
|
||
def custom_logformat(date, author, type, file): | ||
return '%(date)s|%(author)s|%(type)s|%(file)s' %{ | ||
'date': date, | ||
'author': author, | ||
'type': type, | ||
'file': file, | ||
} | ||
|
||
|
||
if __name__ == '__main__': | ||
import subprocess | ||
# get the log, in a easy to parse format | ||
args = ['hg', 'log', '--template', '{date}|{author|person}|m {files}|a {file_adds}|d {file_dels}\n'] | ||
process = subprocess.Popen(args, stdout=subprocess.PIPE) | ||
log_output, log_err = process.communicate() | ||
entries = log_output.split('\n') | ||
|
||
# regex used to parse the hg log | ||
import re | ||
custom_regex = r'([0-9.+-]+)\|([^|]+)\|m ([^|]+)?\|a ([^|]+)?\|d ([^|]+)?' | ||
regex = re.compile(custom_regex) | ||
|
||
# get the output (file or stdout) | ||
import sys | ||
outfile = None | ||
if len(sys.argv) == 2: | ||
outfile = open(sys.argv[1], 'w') | ||
else: | ||
outfile = sys.stdout | ||
|
||
# important fields of log | ||
date = '' | ||
author = '' | ||
# mercurial doesn't seem to have a 'file_modifies' template keyword, but | ||
# there is a 'files' template keyword, which includes: | ||
# modified, added and removed | ||
# so: modfied = files - added - removed | ||
modified = [] | ||
added = [] | ||
deleted = [] | ||
|
||
# hg log lists newest to oldest, gource wants oldest to newest | ||
# so just reverse the entries | ||
entries.reverse() | ||
for entry in entries: | ||
if entry == '': | ||
continue | ||
|
||
match = regex.match(entry) | ||
if not match: | ||
# sys.stderr.write('** Non-matching: %s\n' %entry) | ||
continue | ||
|
||
date, author, modified, added, deleted = match.groups() | ||
# dunno if this is the best way of parsing the timestamp, which should be | ||
# in the format: {UTC timestamp}{timezone offset} | ||
# e.g. 1234567890.0-7200 | ||
date = str(int(eval(date))) | ||
|
||
modified = modified.split() if modified else [] | ||
added = added.split() if added else [] | ||
deleted = deleted.split() if deleted else [] | ||
|
||
for f in added: | ||
outfile.write(custom_logformat(date, author, 'A', f)) | ||
outfile.write('\n') | ||
if f in modified: | ||
modified.remove(f) | ||
|
||
for f in deleted: | ||
outfile.write(custom_logformat(date, author, 'D', f)) | ||
outfile.write('\n') | ||
if f in modified: | ||
modified.remove(f) | ||
|
||
for f in modified: | ||
outfile.write(custom_logformat(date, author, 'M', f)) | ||
outfile.write('\n') | ||
|
||
outfile.close() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#!/usr/bin/python | ||
## Copyright (c) 2009 Cameron Hart (cam@bitshifter.net.nz) | ||
## All rights reserved. | ||
## | ||
## Redistribution and use in source and binary forms, with or without | ||
## modification, are permitted provided that the following conditions | ||
## are met: | ||
## 1. Redistributions of source code must retain the above copyright | ||
## notice, this list of conditions and the following disclaimer. | ||
## 2. Redistributions in binary form must reproduce the above copyright | ||
## notice, this list of conditions and the following disclaimer in the | ||
## documentation and/or other materials provided with the distribution. | ||
## 3. The name of the author may not be used to endorse or promote products | ||
## derived from this software without specific prior written permission. | ||
## | ||
## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
import sys | ||
import time | ||
import getopt | ||
import re | ||
from xml.etree import ElementTree | ||
|
||
opt_filter_dirs = False | ||
|
||
_USAGE = """ | ||
svn-gource.py [--help] [--filter-dirs] <file> | ||
The input file must be the output of the command svn log --verbose --xml. | ||
""" | ||
|
||
# regular expression for matching any file with an extension | ||
extn_prog = re.compile(".*/?[^/]+\.[^\.]+$") | ||
|
||
def reverse(data): | ||
"""Returns the log entries in reverse.""" | ||
for index in range(len(data)-1, -1, -1): | ||
yield data[index] | ||
|
||
def processXmltree(xmltree): | ||
global opt_filter_dirs | ||
for logentry in reverse(xmltree.getiterator("logentry")): | ||
datetext = logentry.find("date").text | ||
|
||
# svn xml logs always use UTC | ||
timestamp = (time.mktime(time.strptime(datetext[:-8], "%Y-%m-%dT%H:%M:%S"))) | ||
# a bit of a hack to get it into local time again... | ||
#timestamp = timestamp - time.timezone | ||
|
||
#author might not exist | ||
try: | ||
author = logentry.find("author").text | ||
except: | ||
author = "" | ||
|
||
# output all affected files | ||
for pathentry in logentry.getiterator("path"): | ||
|
||
# apply directory filtering strategy | ||
if opt_filter_dirs and not re.match(extn_prog, pathentry.text): | ||
continue; | ||
|
||
# join output | ||
print "|".join( ("%d" % int(timestamp), author.encode("utf-8"), pathentry.get("action"), pathentry.text.encode("utf-8"), "") ) | ||
|
||
def printUsage(message): | ||
sys.stderr.write(_USAGE) | ||
if message: | ||
sys.exit('\nFATAL ERROR: ' + message) | ||
else: | ||
sys.exit(1) | ||
|
||
def processArguments(): | ||
global opt_filter_dirs | ||
|
||
try: | ||
opts, filenames = getopt.getopt(sys.argv[1:], '', ['help', 'filter-dirs']) | ||
except getopt.GetoptError: | ||
printUsage('Invalid arguments.') | ||
|
||
for (opt, val) in opts: | ||
if opt == '--help': | ||
printUsage(None) | ||
elif opt == '--filter-dirs': | ||
opt_filter_dirs = True | ||
|
||
if not filenames: | ||
printUsage('No input file specified.') | ||
|
||
return filenames[0] | ||
|
||
|
||
if __name__ == "__main__": | ||
filename = processArguments() | ||
|
||
xmltree = ElementTree.parse(filename) | ||
|
||
processXmltree(xmltree) | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters