395 lines
14 KiB
Python
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()
|
|
|