FreeCAD / src /Tools /SubWCRev.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
#! python
# SPDX-License-Identifier: LGPL-2.1-or-later
# (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, re, datetime, time, getopt
from urllib.parse import urlparse
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):
super().__init__()
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, bindir):
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, bindir):
# Do not overwrite existing file with almost useless information
if os.path.exists(bindir + "/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, bindir):
# Do not overwrite existing file with almost useless information
if os.path.exists(bindir + "/src/Build/Version.h.out"):
return False
try:
f = open(srcdir + "/debian/changelog")
except Exception:
return False
c = f.readline()
f.close()
r = re.search("bzr(\\d+)", c)
if r is not 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, bindir):
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 is not None:
self.rev = r.groups()[0]
continue
r = re.match("^timestamp: (\\w+ \\d+-\\d+-\\d+ \\d+:\\d+:\\d+)", i)
if r is not None:
self.date = r.groups()[0]
continue
return True
def printInfo(self):
print("bazaar")
class DebianGitHub(VersionControl):
# https://gist.github.com/0penBrain/7be59a48aba778c955d992aa69e524c5
# https://gist.github.com/yershalom/a7c08f9441d1aadb13777bce4c7cdc3b
# https://github.community/t5/GitHub-API-Development-and/How-to-get-all-branches-which-contain-a-commit-from-SHA-using/td-p/25006
def extractInfo(self, srcdir, bindir):
try:
f = open(srcdir + "/debian/git-build-recipe.manifest")
except Exception:
return False
# Read the first two lines
recipe = f.readline()
commit = f.readline()
f.close()
base_url = "https://api.github.com"
owner = "FreeCAD"
repo = "FreeCAD"
sha = commit[commit.rfind(":") + 1 : -1]
self.hash = sha
try:
import requests
request_url = "{}/repos/{}/{}/commits?per_page=1&sha={}".format(
base_url, owner, repo, sha
)
commit_req = requests.get(request_url)
if not commit_req.ok:
return False
commit_date = commit_req.headers.get("last-modified")
except Exception:
# if connection fails then use the date of the file git-build-recipe.manifest
commit_date = recipe[recipe.rfind("~") + 1 : -1]
try:
# Try to convert into the same format as GitControl
t = time.strptime(commit_date, "%a, %d %b %Y %H:%M:%S GMT")
commit_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,
)
except Exception:
t = time.strptime(commit_date, "%Y%m%d%H%M")
commit_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.date = commit_date
self.branch = "unknown"
try:
# Try to determine the branch of the sha
# There is no function of the rest API of GH but with the url below we get HTML code
branch_url = "https://github.com/{}/{}/branch_commits/{}".format(owner, repo, sha)
branch_req = requests.get(branch_url)
if branch_req.ok:
html = branch_req.text
pattern = '<li class="branch"><a href='
start = html.find(pattern) + len(pattern)
end = html.find("\n", start)
link = html[start:end]
start = link.find(">") + 1
end = link.find("<", start)
self.branch = link[start:end]
link = commit_req.headers.get("link")
beg = link.rfind("&page=") + 6
end = link.rfind(">")
self.rev = link[beg:end] + " (GitHub)"
except Exception:
pass
self.url = "git://github.com/{}/{}.git {}".format(owner, repo, self.branch)
return True
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
def printInfo(self):
print("Debian/GitHub")
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
match = re.match(r"git@github\.com:(\S+?)/(\S+\.git)", url) or re.match(
r"https://github\.com/(\S+)/(\S+\.git)", url
)
if match is not None:
url = "git://github.com/%s/%s" % match.groups()
match = re.match(r"ssh://\S+?@(\S+)", url)
if match is not None:
url = "git://%s" % match.group(1)
parsed_url = urlparse(url)
entryscore = (
url == "git://github.com/FreeCAD/FreeCAD.git",
parsed_url.netloc == "github.com",
branch == self.branch,
branch == "main",
"@" 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 main and release branches all commits are counted
for other branches. The version number is split in to two parts:
The first number reflects the number of commits in common with the
blessed main repository. The second part (separated by " +") reflects
the number of commits that are different from the main repository"""
referencecommit = "7d8e53aaab17961d85c5009de34f69f2af084e8b"
referencerevision = 14555
result = None
null_device = "nul" if os.name == "nt" else "/dev/null"
countallfh = os.popen(f"git rev-list --count {referencecommit}..HEAD 2>{null_device}")
countallstr = countallfh.read().strip()
if countallfh.close() is not None: # reference commit not present, use the date
date_object = datetime.datetime.strptime(self.date, "%Y/%m/%d %H:%M:%S")
formatted_date = date_object.strftime("%Y%m%d")
self.rev = f"{formatted_date} (Git shallow)"
return
else:
countall = int(countallstr)
if (
origin is not None
and self.branch.lower() != "main"
and "release" not in self.branch.lower()
):
mbfh = os.popen("git merge-base %s/main 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 main 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 main into dev is not enough
self.branch = ",".join(names)
def extractInfo(self, srcdir, bindir):
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(r"\*", self.branch) is not None:
break
self.branch = self.branch[2:]
self.getremotes() # setup self.remotes and branchlst
self.geturl()
origin = None # remote for the blessed main
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() != "main" 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]
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 no result by now. Hence we assume origin
if self.url == "Unknown":
for i in info:
r = re.match("origin\\W+(\\S+)", i)
if r is not 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, bindir):
return False
def printInfo(self):
print("mercurial")
class Subversion(VersionControl):
def extractInfo(self, srcdir, bindir):
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 Exception:
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 is not None:
self.mods = "Src modified"
# if version string contains a range
r = re.match("^\\d+\\:\\d+", Ver)
if r is not 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(),
DebianGitHub(),
BazaarControl(),
Subversion(),
MercurialControl(),
DebianChangelog(),
UnknownControl(),
]
for i in vcs:
if i.extractInfo(srcdir, bindir):
# 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()