def validate_version(base_version, vcs_info): branch = vcs_info.branch brnorm = utils.normalize_branch_name(branch) btypestr = utils.get_branch_type(branch) try: btype = BRANCH_TYPES[btypestr] except KeyError: allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys()) raise ValueError("Malformed branch name '%s', cannot classify as one " "of %s" % (btypestr, allowed_branches)) if btype.versioned: try: bverstr = brnorm.split("-")[1] except IndexError: # No version raise ValueError("Branch name '%s' should contain version" % branch) # Check that version is well-formed if not re.match(VERSION_RE, bverstr): raise ValueError("Malformed version '%s' in branch name '%s'" % (bverstr, branch)) m = re.match(btype.allowed_version_re, base_version) if not m or (btype.versioned and m.groupdict()["bverstr"] != bverstr): raise ValueError("Base version '%s' unsuitable for branch name '%s'" % (base_version, branch))
def python_version(base_version, vcs_info, mode): """Generate a Python distribution version following devtools conventions. This helper generates a Python distribution version from a repository commit, following devtools conventions. The input data are: * base_version: a base version number, presumably stored in text file inside the repository, e.g., /version * vcs_info: vcs information: current branch name and revision no * mode: "snapshot", or "release" This helper assumes a git branching model following: http://nvie.com/posts/a-successful-git-branching-model/ with 'master', 'develop', 'release-X', 'hotfix-X' and 'feature-X' branches. General rules: a) any repository commit can get as a Python version b) a version is generated either in 'release' or in 'snapshot' mode c) the choice of mode depends on the branch, see following table. A python version is of the form A_NNN, where A: X.Y.Z{,next,rcW} and NNN: a revision number for the commit, as returned by vcs_info(). For every combination of branch and mode, releases are numbered as follows: BRANCH: / MODE: snapshot release -------- ------------------------------ feature 0.14next_150 N/A develop 0.14next_151 N/A release 0.14rc2_249 0.14rc2 master N/A 0.14 hotfix 0.14.1rc6_121 0.14.1rc6 N/A 0.14.1 The suffix 'next' in a version name is used to denote the upcoming version, the one being under development in the develop and release branches. Version '0.14next' is the version following 0.14, and only lives on the develop and feature branches. The suffix 'rc' is used to denote release candidates. 'rc' versions live only in release and hotfix branches. Suffixes 'next' and 'rc' have been chosen to ensure proper ordering according to setuptools rules: http://www.python.org/dev/peps/pep-0386/#setuptools Every branch uses a value for A so that all releases are ordered based on the branch they came from, so: So 0.13next < 0.14rcW < 0.14 < 0.14next < 0.14.1 and >>> V("0.14next") > V("0.14") True >>> V("0.14next") > V("0.14rc7") True >>> V("0.14next") > V("0.14.1") False >>> V("0.14rc6") > V("0.14") False >>> V("0.14.2rc6") > V("0.14.1") True The value for _NNN is chosen based of the revision number of the specific commit. It is used to ensure ascending ordering of consecutive releases from the same branch. Every version of the form A_NNN comes *before* than A: All snapshots are ordered so they come before the corresponding release. So 0.14next_* < 0.14 0.14.1_* < 0.14.1 etc and >>> V("0.14next_150") < V("0.14next") True >>> V("0.14.1next_150") < V("0.14.1next") True >>> V("0.14.1_149") < V("0.14.1") True >>> V("0.14.1_149") < V("0.14.1_150") True Combining both of the above, we get 0.13next_* < 0.13next < 0.14rcW_* < 0.14rcW < 0.14_* < 0.14 < 0.14next_* < 0.14next < 0.14.1_* < 0.14.1 and >>> V("0.13next_102") < V("0.13next") True >>> V("0.13next") < V("0.14rc5_120") True >>> V("0.14rc3_120") < V("0.14rc3") True >>> V("0.14rc3") < V("0.14_1") True >>> V("0.14_120") < V("0.14") True >>> V("0.14") < V("0.14next_20") True >>> V("0.14next_20") < V("0.14next") True Note: one of the tests above fails because of constraints in the way setuptools parses version numbers. It does not affect us because the specific version format that triggers the problem is not contained in the table showing allowed branch / mode combinations, above. """ branch = vcs_info.branch brnorm = utils.normalize_branch_name(branch) btypestr = utils.get_branch_type(branch) try: btype = BRANCH_TYPES[btypestr] except KeyError: allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys()) raise ValueError("Malformed branch name '%s', cannot classify as one " "of %s" % (btypestr, allowed_branches)) if btype.versioned: try: bverstr = brnorm.split("-")[1] except IndexError: # No version raise ValueError("Branch name '%s' should contain version" % branch) # Check that version is well-formed if not re.match(VERSION_RE, bverstr): raise ValueError("Malformed version '%s' in branch name '%s'" % (bverstr, branch)) m = re.match(btype.allowed_version_re, base_version) if not m or (btype.versioned and m.groupdict()["bverstr"] != bverstr): raise ValueError("Base version '%s' unsuitable for branch name '%s'" % (base_version, branch)) if mode not in ["snapshot", "release"]: raise ValueError("Specified mode '%s' should be one of 'snapshot' or " "'release'" % mode) snap = mode == "snapshot" if (snap and not btype.builds_snapshot) or (not snap and not btype.builds_release): # nopep8 raise ValueError("Invalid mode '%s' in branch type '%s'" % (mode, btypestr)) if snap: v = "%s_%d_%s" % (base_version, vcs_info.revno, vcs_info.revid) else: v = base_version return v