Clone depth for travis is set to 5000. Using the current HEAD as reference means we won't have to update it again for awhile.
397 lines
15 KiB
Python
397 lines
15 KiB
Python
#! python
|
|
# -*- coding: utf-8 -*-
|
|
# (c) 2006 Werner Mayer LGPL
|
|
#
|
|
# FreeCAD RevInfo script to get the revision information from Subversion, Bazaar, and Git.
|
|
#
|
|
# Under Linux the Subversion tool SubWCRev shipped with TortoiseSVN isn't
|
|
# available which is provided by this script.
|
|
# 2012/02/01: The script was extended to support git
|
|
# 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
|
|
try:
|
|
from StringIO import StringIO
|
|
except ImportError:
|
|
from io 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 = line.replace('$WCREV$',self.rev)
|
|
line = line.replace('$WCDATE$',self.date)
|
|
line = line.replace('$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.out"):
|
|
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.out"):
|
|
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, separated by " +" reflects the number of commits that are
|
|
different from the master repository"""
|
|
referencecommit="7d8e53aaab17961d85c5009de34f69f2af084e8b"
|
|
referencerevision=14555
|
|
|
|
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 meaningful 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.items():
|
|
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 contained 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 = self.date.replace('T',' ')
|
|
self.date = self.date.replace('-','/')
|
|
|
|
#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.out" % (bindir),"w");
|
|
out.writelines(lines)
|
|
out.write('\n')
|
|
out.close()
|
|
i.printInfo()
|
|
sys.stdout.write("%s/src/Build/Version.h.out written\n" % (bindir))
|
|
break
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|