FreeCAD/src/Tools/SubWCRev.py

395 lines
14 KiB
Python

#! python
# -*- coding: utf-8 -*-
# (c) 2006 Werner Mayer LGPL
#
# FreeCAD RevInfo script to get the revision information from Subversion.
#
# Under Linux the Subversion tool SubWCRev shipped with TortoiseSVN isn't
# available which is provided by this script.
# 2011/02/05: The script was extended to support also Bazaar
import os,sys,string,re,time,getopt
import xml.sax
import xml.sax.handler
import xml.sax.xmlreader
import StringIO
# SAX handler to parse the subversion output
class SvnHandler(xml.sax.handler.ContentHandler):
def __init__(self):
self.inUrl = 0
self.inDate = 0
self.mapping = {}
def startElement(self, name, attributes):
if name == "entry":
self.buffer = ""
self.mapping["Rev"] = attributes["revision"]
elif name == "url":
self.inUrl = 1
elif name == "date":
self.inDate = 1
def characters(self, data):
if self.inUrl:
self.buffer += data
elif self.inDate:
self.buffer += data
def endElement(self, name):
if name == "url":
self.inUrl = 0
self.mapping["Url"] = self.buffer
self.buffer = ""
elif name == "date":
self.inDate = 0
self.mapping["Date"] = self.buffer
self.buffer = ""
class VersionControl:
def __init__(self):
self.rev = ""
self.date = ""
self.url = ""
def extractInfo(self, srcdir):
return False
def printInfo(self):
print ""
def writeVersion(self, lines):
content=[]
for line in lines:
line = string.replace(line,'$WCREV$',self.rev)
line = string.replace(line,'$WCDATE$',self.date)
line = string.replace(line,'$WCURL$',self.url)
content.append(line)
return content
class UnknownControl(VersionControl):
def extractInfo(self, srcdir):
# Do not overwrite existing file with almost useless information
if os.path.exists(srcdir+"/src/Build/Version.h"):
return False
self.rev = "Unknown"
self.date = "Unknown"
self.url = "Unknown"
return True
def printInfo(self):
print "Unknown version control"
class DebianChangelog(VersionControl):
def extractInfo(self, srcdir):
# Do not overwrite existing file with almost useless information
if os.path.exists(srcdir+"/src/Build/Version.h"):
return False
try:
f = open(srcdir+"/debian/changelog")
except:
return False
c = f.readline()
f.close()
r=re.search("bzr(\\d+)",c)
if r != None:
self.rev = r.groups()[0] + " (Launchpad)"
t = time.localtime()
self.date = ("%d/%02d/%02d %02d:%02d:%02d") % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
self.url = "https://code.launchpad.net/~vcs-imports/freecad/trunk"
return True
def printInfo(self):
print "debian/changelog"
class BazaarControl(VersionControl):
def extractInfo(self, srcdir):
info=os.popen("bzr log -l 1 %s" % (srcdir)).read()
if len(info) == 0:
return False
lines=info.split("\n")
for i in lines:
r = re.match("^revno: (\\d+)$", i)
if r != None:
self.rev = r.groups()[0]
continue
r=re.match("^timestamp: (\\w+ \\d+-\\d+-\\d+ \\d+:\\d+:\\d+)",i)
if r != None:
self.date = r.groups()[0]
continue
return True
def printInfo(self):
print "bazaar"
class GitControl(VersionControl):
#http://www.hermanradtke.com/blog/canonical-version-numbers-with-git/
#http://blog.marcingil.com/2011/11/creating-build-numbers-using-git-commits/
#http://gitref.org/remotes/#fetch
#http://cworth.org/hgbook-git/tour/
#http://git.or.cz/course/svn.html
#git help log
def getremotes(self):
"""return a mapping of remotes and their fetch urls"""
rr=os.popen("git remote -v")
rrstr=rr.read().strip()
if rr.close() is None: # exit code == 0
self.remotes=dict(l[:-8].split('\t') for l in rrstr.splitlines() \
if l.endswith(' (fetch)'))
self.branchlst=os.popen("git show -s --pretty=%d HEAD").read()\
.strip(" ()\n").split(', ') #used for possible remotes
def geturl(self):
urls=[]
for ref in self.branchlst:
if '/' in ref:
remote,branch = ref.split('/',1)
if remote in self.remotes:
url=self.remotes[remote]
#rewrite github to public url
import re
match = re.match('git@github\.com:(\S+?)/(\S+\.git)',url) \
or re.match('https://github\.com/(\S+)/(\S+\.git)'\
,url)
if match is not None:
url = 'git://github.com/%s/%s' % match.groups()
match = re.match('ssh://\S+?@(\S+)',url)
if match is not None:
url = 'git://%s' % match.group(1)
entryscore=(url == "git://github.com/FreeCAD/FreeCAD.git",\
'github.com' in url,branch==self.branch,\
branch=='master', '@' not in url)
#used for sorting the list
if branch==self.branch: #add branch name
url = '%s %s' % (url,branch)
urls.append((entryscore,url))
if len(urls) > 0:
self.url = sorted(urls)[-1][1]
else:
self.url = "Unknown"
def revisionNumber(self, srcdir,origin=None):
"""sets the revision number
for master and release branches all commits are counted
for other branches the version numver is split in two parts
the first number reflects the number of commits in common with the
blessed master repository.
the second part, seperated by " +"reflects the number of commits that are
different form the master repository"""
#referencecommit="f119e740c87918b103140b66b2316ae96f136b0e"
#referencerevision=4138
referencecommit="6b3d7b17a749e03bcbf2cf79bbbb903137298c44"
referencerevision=5235
result = None
countallfh=os.popen("git rev-list --count %s..HEAD" % \
referencecommit)
countallstr=countallfh.read().strip()
if countallfh.close() is not None: #reference commit not present
self.rev = '%04d (Git shallow)' % referencerevision
return
else:
countall = int(countallstr)
if origin is not None and self.branch.lower() != 'master' and \
'release' not in self.branch.lower():
mbfh=os.popen("git merge-base %s/master HEAD" % origin)
mergebase = mbfh.read().strip()
if mbfh.close() is None: # exit code == 0
try:
countmergebase=int(os.popen("git rev-list --count %s..%s"\
% (referencecommit,mergebase)).read().strip())
if countall > countmergebase:
result = '%04d +%d (Git)' % (countmergebase +\
referencerevision,countall-countmergebase)
except ValueError:
pass
self.rev = result or ('%04d (Git)' % (countall+referencerevision))
def namebranchbyparents(self):
"""name multiple branches in case that the last commit was a merge
a merge is identified by having two or more parents
if the describe does not return a ref name (the hash is added)
if one parent is the master and the second one has no ref name, one branch was
merged."""
parents=os.popen("git log -n1 --pretty=%P").read()\
.strip().split(' ')
if len(parents) >= 2: #merge commit
parentrefs=[]
names=[]
hasnames=0
for p in parents:
refs=os.popen("git show -s --pretty=%%d %s" % p).read()\
.strip(" ()\n").split(', ')
if refs[0] != '': #has a ref name
parentrefs.append(refs)
names.append(refs[-1])
hasnames += 1
else:
parentrefs.append(p)
names.append(p[:7])
if hasnames >=2: # merging master into dev is not enough
self.branch=','.join(names)
def extractInfo(self, srcdir):
self.hash=os.popen("git log -1 --pretty=format:%H").read().strip()
if self.hash == "":
return False # not a git repo
# date/time
import time
info=os.popen("git log -1 --date=raw --pretty=format:%cd").read()
# commit time is more meaningfull than author time
# use UTC
self.date = time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(\
float(info.strip().split(' ',1)[0])))
for self.branch in os.popen("git branch --no-color").read().split('\n'):
if re.match( "\*", self.branch ) != None:
break
self.branch=self.branch[2:]
self.getremotes() #setup self.remotes and branchlst
remote='origin' #used to determine the url
self.geturl()
origin = None #remote for the blessed master
for fetchurl in ("git@github.com:FreeCAD/FreeCAD.git",\
"https://github.com/FreeCAD/FreeCAD.git"):
for key,url in self.remotes.iteritems():
if fetchurl in url:
origin = key
break
if origin is not None:
break
self.revisionNumber(srcdir,origin)
if self.branch.lower() != 'master' and \
'release' not in self.branch.lower():
self.namebranchbyparents()
if self.branch == '(no branch)': #check for remote branches
if len(self.branchlst) >= 2:
self.branch = self.branchlst[1]
if '/' in self.branch:
remote=self.branch.split('/',1)[0]
else: # guess
self.branch = '(%s)' % \
os.popen("git describe --all --dirty").read().strip()
#if the branch name conainted any slashes but was not a remote
#there might be not result by now. Hence we assume origin
if self.url == "Unknown":
for i in info:
r = re.match("origin\\W+(\\S+)",i)
if r != None:
self.url = r.groups()[0]
break
return True
def printInfo(self):
print "git"
def writeVersion(self, lines):
content = VersionControl.writeVersion(self, lines)
content.append('// Git relevant stuff\n')
content.append('#define FCRepositoryHash "%s"\n' % (self.hash))
content.append('#define FCRepositoryBranch "%s"\n' % (self.branch))
return content
class MercurialControl(VersionControl):
def extractInfo(self, srcdir):
return False
def printInfo(self):
print "mercurial"
class Subversion(VersionControl):
def extractInfo(self, srcdir):
parser=xml.sax.make_parser()
handler=SvnHandler()
parser.setContentHandler(handler)
#Create an XML stream with the required information and read in with a SAX parser
Ver=os.popen("svnversion %s -n" % (srcdir)).read()
Info=os.popen("svn info %s --xml" % (srcdir)).read()
try:
inpsrc = xml.sax.InputSource()
strio=StringIO.StringIO(Info)
inpsrc.setByteStream(strio)
parser.parse(inpsrc)
except:
return False
#Information of the Subversion stuff
self.url = handler.mapping["Url"]
self.rev = handler.mapping["Rev"]
self.date = handler.mapping["Date"]
self.date = self.date[:19]
#Same format as SubWCRev does
self.date = string.replace(self.date,'T',' ')
self.date = string.replace(self.date,'-','/')
#Date is given as GMT. Now we must convert to local date.
m=time.strptime(self.date,"%Y/%m/%d %H:%M:%S")
#Copy the tuple and set tm_isdst to 0 because it's GMT
l=(m.tm_year,m.tm_mon,m.tm_mday,m.tm_hour,m.tm_min,m.tm_sec,m.tm_wday,m.tm_yday,0)
#Take timezone into account
t=time.mktime(l)-time.timezone
self.date=time.strftime("%Y/%m/%d %H:%M:%S",time.localtime(t))
#Get the current local date
self.time = time.strftime("%Y/%m/%d %H:%M:%S")
self.mods = 'Src not modified'
self.mixed = 'Src not mixed'
self.range = self.rev
# if version string ends with an 'M'
r=re.search("M$",Ver)
if r != None:
self.mods = 'Src modified'
# if version string contains a range
r=re.match("^\\d+\\:\\d+",Ver)
if r != None:
self.mixed = 'Src mixed'
self.range = Ver[:r.end()]
return True
def printInfo(self):
print "subversion"
def main():
#if(len(sys.argv) != 2):
# sys.stderr.write("Usage: SubWCRev \"`svn info .. --xml`\"\n")
srcdir="."
bindir="."
try:
opts, args = getopt.getopt(sys.argv[1:], "sb:", ["srcdir=","bindir="])
except getopt.GetoptError:
pass
for o, a in opts:
if o in ("-s", "--srcdir"):
srcdir = a
if o in ("-b", "--bindir"):
bindir = a
vcs=[GitControl(), BazaarControl(), Subversion(), MercurialControl(), DebianChangelog(), UnknownControl()]
for i in vcs:
if i.extractInfo(srcdir):
# Open the template file and the version file
inp = open("%s/src/Build/Version.h.in" % (bindir))
lines = inp.readlines()
inp.close()
lines = i.writeVersion(lines)
out = open("%s/src/Build/Version.h" % (bindir),"w");
out.writelines(lines)
out.write('\n')
out.close()
i.printInfo()
sys.stdout.write("%s/src/Build/Version.h written\n" % (bindir))
break
if __name__ == "__main__":
main()