Commit c4a8e98a authored by Nikos Skalkotos's avatar Nikos Skalkotos
Browse files

Merge branch 'release-0.13'

parents 4149b207 eb7e67ec
......@@ -22,6 +22,3 @@ dist
_build
.coverage
cover/
# version module created automatically from setup.py
/devflow/version.py
Copyright (C) 2012, 2013 GRNET S.A. All rights reserved.
Copyright (C) 2012-2016 GRNET S.A. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
......
#Changelog for feature-improve-changelog
* Prefix commit message in changelog with '* '
#Changelog for feature-fix-release-version
* Fix debian tag when finishing release
* Strip rc from version when ending a release
* Make bump version modular.
* Add a _bump version function without any validation checking. Use the new
validate_version to perform the validation.
* Split version validation from version generating
2016-07-21, v02016-07-20, v0.13rc3
* Fix bug in git-buildpkg call were arguments contained extra spaces
* Fix extra ~ bug in python->debian version translation
2016-07-20, v02016-07-20, v0.13rc2
* Bump new version because 0.13rc1 was registered in pypi
2016-07-20, v02016-07-20, v0.13rc1
* Use PEP 440 compatible versions
* Add support for newer git-buildpackage
include distribute_setup.py
include README.md Changelog
include version_template
......@@ -15,7 +15,7 @@ Quickstart
```
import os
from devflow.version import get_python_version
from devflow.versioning import get_python_version
os.environ["DEVFLOW_BUILD_MODE"] = "snapshot"
print "This commit's Python version is", get_python_version()
```
......
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
# Copyright 2012-2016 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -44,13 +44,15 @@ from collections import namedtuple
branch_type = namedtuple("branch_type", ["builds_snapshot", "builds_release",
"versioned", "allowed_version_re",
"debian_branch"])
VERSION_RE = "[0-9]+\.[0-9]+(\.[0-9]+)*" # pylint: disable=W1401
RC_RE = "rc[1-9][0-9]*"
VERSION_RE = r'[0-9]+\.[0-9]+(\.[0-9]+)*'
RC_RE = r'rc[1-9][0-9]*'
BRANCH_TYPES = {
"feature": branch_type(True, False, False, "^%snext$" % VERSION_RE,
"feature": branch_type(True, False, False,
"^%s(next|\.?dev)?$" % VERSION_RE,
"debian-develop"),
"develop": branch_type(True, False, False, "^%snext$" % VERSION_RE,
"develop": branch_type(True, False, False,
"^%s(next|\.?dev)?$" % VERSION_RE,
"debian-develop"),
"release": branch_type(True, True, True,
"^(?P<bverstr>%s)(%s)+$" % (VERSION_RE, RC_RE),
......@@ -58,7 +60,7 @@ BRANCH_TYPES = {
"master": branch_type(True, True, False,
"^%s$" % VERSION_RE, "debian"),
"hotfix": branch_type(True, True, True,
"^(?P<bverstr>^%s\.[1-9][0-9]*)(%s)*$" %\
(VERSION_RE, RC_RE),
r"^(?P<bverstr>^%s\.[1-9][0-9]*)(%s)*$" %
(VERSION_RE, RC_RE),
"debian")}
BASE_VERSION_FILE = "version"
# Copyright 2012-2014 GRNET S.A. All rights reserved.
# Copyright 2012-2016 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -31,14 +31,26 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
"""Helper script for automatic build of debian packages."""
"""Helper script for automatic build of Debian packages."""
import os
import sys
import subprocess
from git import GitCommandError
from optparse import OptionParser
from sh import mktemp, cd, rm, git_dch # pylint: disable=E0611
from sh import mktemp, cd, rm # pylint: disable=E0611
from functools import partial
try:
from sh import git_dch as gbp_dch # pylint: disable=E0611
gbp_buildpackage = ['git-buildpackage']
except ImportError:
# In newer versions of git-buildpackage the executables have changed.
# Instead of having various git-* executables, there is only a gbp one,
# which expects the command (dch, buildpackage, etc) as the first argument.
from sh import gbp # pylint: disable=E0611
gbp_dch = partial(gbp, 'dch')
gbp_buildpackage = ['gbp', 'buildpackage']
from devflow import versioning
from devflow import utils
......@@ -47,9 +59,9 @@ from devflow import BRANCH_TYPES
AVAILABLE_MODES = ["release", "snapshot"]
DESCRIPTION = """Tool for automatical build of debian packages.
DESCRIPTION = """Tool for automatic build of Debian packages.
%(prog)s is a helper script for automatic build of debian packages from
%(prog)s is a helper script for automatic build of Debian packages from
repositories that follow the `git flow` development model
<http://nvie.com/posts/a-successful-git-branching-model/>.
......@@ -60,11 +72,11 @@ following steps:
* Compute the version of the new package and update the python
version files
* Create a new entry in debian/changelog, using `git-dch`
* Create the debian packages, using `git-buildpackage`
* Create the Debian packages, using `git-buildpackage`
* Tag the appropriate branches if in `release` mode
%(prog)s will work with the packages that are declared in `autopkg.conf`
file, which must exist in the toplevel directory of the git repository.
%(prog)s will work with the packages that are declared in `devflow.conf'
file, which must exist in the top-level directory of the git repository.
"""
......@@ -90,7 +102,7 @@ def main():
parser.add_option("-b", "--build-dir",
dest="build_dir",
default=None,
help="Directory to store created pacakges")
help="Directory to store created packages")
parser.add_option("-r", "--repo-dir",
dest="repo_dir",
default=None,
......@@ -134,7 +146,7 @@ def main():
parser.add_option("--color",
dest="color_output",
default="auto",
help="Enable/disable colored output. Default mode is" +
help="Enable/disable colored output. Default mode is"
" auto, available options are yes/no")
(options, args) = parser.parse_args()
......@@ -144,10 +156,7 @@ def main():
elif options.color_output == "no":
use_colors = False
else:
if sys.stdout.isatty():
use_colors = True
else:
use_colors = False
use_colors = sys.stdout.isatty()
red = lambda x: x
green = lambda x: x
......@@ -174,8 +183,8 @@ def main():
except IndexError:
mode = utils.get_build_mode()
if mode not in AVAILABLE_MODES:
raise ValueError(red("Invalid argument! Mode must be one: %s"
% ", ".join(AVAILABLE_MODES)))
raise ValueError(red("Invalid argument! Mode must be one: %s" %
", ".join(AVAILABLE_MODES)))
# Load the repository
original_repo = utils.get_repository()
......@@ -228,8 +237,8 @@ def main():
# Create the debian branch
repo.git.branch(debian_branch, origin_debian)
print_green("Created branch '%s' to track '%s'" % (debian_branch,
origin_debian))
print_green("Created branch '%s' to track '%s'" %
(debian_branch, origin_debian))
# Go to debian branch
repo.git.checkout(debian_branch)
......@@ -270,7 +279,7 @@ def main():
repo.git.tag(upstream_tag, branch)
# Update changelog
dch = git_dch("--debian-branch=%s" % debian_branch,
dch = gbp_dch("--debian-branch=%s" % debian_branch,
"--git-author",
"--ignore-regex=\".*\"",
"--multimaint-merge",
......@@ -294,7 +303,7 @@ def main():
f.close()
if mode == "release":
call("vim debian/changelog")
subprocess.check_call(['editor', "debian/changelog"])
# Add changelog to INDEX
repo.git.add("debian/changelog")
......@@ -320,19 +329,25 @@ def main():
# Export version info to debuilg environment
os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version
os.environ["DEB_DEVFLOW_VERSION"] = python_version
build_cmd = "git-buildpackage --git-export-dir=%s"\
" --git-upstream-branch=%s --git-debian-branch=%s"\
" --git-export=INDEX --git-ignore-new -sa"\
" --source-option=--auto-commit"\
" --git-upstream-tag=%s"\
% (build_dir, branch, debian_branch, upstream_tag)
args = list(gbp_buildpackage)
args.extend(["--git-export-dir=%s" % build_dir,
"--git-upstream-branch=%s" % branch,
"--git-debian-branch=%s" % debian_branch,
"--git-export=INDEX",
"--git-ignore-new",
"-sa",
"--source-option=--auto-commit",
"--git-upstream-tag=%s" % upstream_tag])
if options.source_only:
build_cmd += " -S"
args.append("-S")
if not options.sign:
build_cmd += " -uc -us"
args.extend(["-uc", "-us"])
elif options.keyid:
build_cmd += " -k\"'%s'\"" % options.keyid
call(build_cmd)
args.append("-k\"'%s'\"" % options.keyid)
subprocess.check_call(args)
# Remove cloned repo
if mode != 'release' and not options.keep_repo:
......@@ -372,11 +387,5 @@ def create_temp_directory(suffix):
return create_dir_cmd.stdout.strip()
def call(cmd):
rc = os.system(cmd)
if rc:
raise RuntimeError("Command '%s' failed!" % cmd)
if __name__ == "__main__":
sys.exit(main())
# Copyright 2012-2016 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import os
import re
import subprocess
import logging
logging.basicConfig()
#from optparse import OptionParser
from argparse import ArgumentParser
os.environ["GIT_PYTHON_TRACE"] = "full"
from devflow import utils, versioning, RC_RE
from devflow.version import __version__
from devflow.autopkg import call
from devflow.ui import query_action, query_user, query_yes_no
from functools import wraps, partial
from contextlib import contextmanager
......@@ -47,42 +80,51 @@ def conflicts():
except GitCommandError as e:
if e.status != 128:
print "An error occured. Resolve it and type 'exit 0'"
tmpbashrc=create_temp_file("bashrc")
tmpbashrc = create_temp_file("bashrc")
f = open(tmpbashrc, 'w')
f.write("source $HOME/.bashrc ; export PS1=(Conflict)\"$PS1\"")
f.close()
call('bash --rcfile %s' % tmpbashrc)
subprocess.check_call(['bash', '--rcfile', tmpbashrc])
os.unlink(tmpbashrc)
else:
raise
def get_release_version(develop_version):
version = develop_version.rstrip('next')
parts = version.split('.')
major_version = int(parts[0])
minor_version = int(parts[1])
#return str(major_version) + '.' + str(minor_version+1) + 'rc1'
return str(major_version) + '.' + str(minor_version+1)
'''Given a development version it will return the release version'''
# Old version scheme
if 'next' in develop_version:
version = develop_version.rstrip('next')
parts = version.split('.')
major = int(parts[0])
minor = int(parts[1])
return "%d.%d" % (major, minor+1)
# New version may or may not contain dev:
# 0.19 is fine, same as 0.19.dev or 0.19dev
return develop_version.rstrip('.dev').rstrip('dev')
def get_develop_version_from_release(release_version):
#version = re.sub('rc[0-9]+$', '', release_version)
'''Given a release version it will return the next develop version'''
# version = re.sub('rc[0-9]+$', '', release_version)
version = release_version
parts = version.split('.')
major_version = int(parts[0])
minor_version = int(parts[1])
return str(major_version) + '.' + str(minor_version+1) + 'next'
major = int(parts[0])
minor = int(parts[1])
return "%d.%ddev" % (major, minor+1)
def get_hotfix_version(version):
"""Given a version it will return the next hotfix version"""
parts = version.split('.')
major_version = int(parts[0])
minor_version = int(parts[1])
if (len(parts) > 2):
hotfix_version = int(parts[2])
else:
hotfix_version = 0
major = int(parts[0])
minor = int(parts[1])
hotfix = int(parts[2]) if len(parts) > 2 else 0
return "%d.%d.%d" % (major, minor, hotfix+1)
return str(major_version) + '.' + str(minor_version) + '.'\
+ str(hotfix_version+1)
class GitManager(object):
def __init__(self):
......@@ -94,7 +136,11 @@ class GitManager(object):
self.log.info("Repository: %s. HEAD: %s", self.repo, self.start_hex)
self.new_branches = []
self.new_tags = []
#self.repo.git.pull("origin")
# self.repo.git.pull("origin")
# Check if version is obsolete
versioning.check_obsolete_version()
def get_branch(self, mode, version):
if mode not in ["release", "hotfix"]:
......@@ -106,10 +152,10 @@ class GitManager(object):
raise ValueError("Unknown mode: %s" % mode)
return "debian-%s-%s" % (mode, version)
def doit(self, action_yes=None, action_no=None, question="Do it", args=None,
default=False):
def doit(self, action_yes=None, action_no=None, question="Do it",
args=None, default=False):
if not args.defaults:
ret = query_yes_no(question, default = "yes" if default else "no")
ret = query_yes_no(question, default="yes" if default else "no")
else:
ret = default
......@@ -123,7 +169,6 @@ class GitManager(object):
for b in branches:
print "git branch -D %s" % b
def __cleanup_branches(self, branches):
repo = self.repo
for b in branches:
......@@ -137,13 +182,12 @@ class GitManager(object):
self.__print_cleanup(branches)
return
question="Remove branches %s" % branches
question = "Remove branches %s" % branches
action_yes = partial(self.__cleanup_branches, branches)
action_no = partial(self.__print_cleanup, branches)
self.doit(action_yes=action_yes, action_no=action_no,
question=question, args=args, default=default)
def check_edit_changelog(self, edit_action, args, default=True):
if args.edit_changelog is not None:
if args.edit_changelog:
......@@ -169,9 +213,9 @@ class GitManager(object):
def edit_changelog(self, branch, base_branch=None):
repo = self.repo
if not branch in repo.branches:
if branch not in repo.branches:
raise ValueError("Branch %s does not exist." % branch)
if base_branch and not base_branch in repo.branches:
if base_branch and base_branch not in repo.branches:
raise ValueError("Branch %s does not exist." % base_branch)
repo.git.checkout(branch)
......@@ -181,23 +225,21 @@ class GitManager(object):
lines = []
lines.append("#Changelog for %s\n" % branch)
if base_branch:
commits = repo.git.rev_list("%s..%s" % (base_branch, branch)).split("\n")
commits = repo.git.rev_list(
"%s..%s" % (base_branch, branch)).split("\n")
for c in commits:
commit = repo.commit(c)
lines.append("* " + commit.message.split("\n")[0])
lines.append("\n")
f = open(changelog, 'rw+')
f = open(changelog, 'r+')
lines.extend(f.readlines())
f.seek(0)
f.truncate(0)
f.writelines(lines)
f.close()
editor = os.getenv('EDITOR')
if not editor:
editor = 'vim'
call("%s %s" % (editor, changelog))
subprocess.check_call(['editor', changelog])
repo.git.add(changelog)
repo.git.commit(m="Update changelog")
print "Updated changelog on branch %s" % branch
......@@ -216,7 +258,7 @@ class GitManager(object):
if not args.defaults:
version = query_user("Release version", default=version)
else:
#validate version?
# validate version?
pass
rc_version = "%src1" % version
new_develop_version = "%snext" % version
......@@ -224,13 +266,13 @@ class GitManager(object):
upstream_branch = self.get_branch("release", version)
debian_branch = self.get_debian_branch("release", version)
#create release branch
# create release branch
repo.git.branch(upstream_branch, upstream)
self.new_branches.append(upstream_branch)
repo.git.checkout(upstream_branch)
versioning.bump_version(rc_version)
#create debian release branch
# create debian release branch
repo.git.checkout(debian)
repo.git.branch(debian_branch, debian)
self.new_branches.append(debian_branch)
......@@ -238,21 +280,20 @@ class GitManager(object):
repo.git.checkout(upstream_branch)
repo.git.checkout(debian)
#bump develop version
# bump develop version
repo.git.checkout(upstream)
versioning.bump_version(new_develop_version)
repo.git.checkout(upstream_branch)
@cleanup
def start_hotfix(self, args):
repo = self.repo
upstream = "master"
debian = "debian"
repo.git.checkout(upstream)
#maybe provide major.minor version, find the latest release/hotfix and
#branch from there ?
# maybe provide major.minor version, find the latest release/hotfix and
# branch from there ?
vcs = utils.get_vcs_info()
version = versioning.get_base_version(vcs)
......@@ -261,7 +302,7 @@ class GitManager(object):
if not args.defaults:
version = query_user("Hotfix version", default=version)
else:
#validate version?
# validate version?
pass
rc_version = "%src1" % version
......@@ -270,13 +311,13 @@ class GitManager(object):
upstream_branch = self.get_branch("hotfix", version)
debian_branch = self.get_debian_branch("hotfix", version)
#create hotfix branch
# create hotfix branch
repo.git.branch(upstream_branch, upstream)
self.new_branches.append(upstream_branch)
repo.git.checkout(upstream_branch)
versioning.bump_version(rc_version)
#create debian hotfix branch
# create debian hotfix branch
repo.git.checkout(debian)
repo.git.branch(debian_branch, debian)
self.new_branches.append(debian_branch)
......@@ -284,10 +325,10 @@ class GitManager(object):
repo.git.checkout(upstream_branch)
repo.git.checkout(debian)
#bump develop version. Ask first or verify we have the same
#major.minornext?
#repo.git.checkout(upstream)
#versioning.bump_version(new_develop_version)
# bump develop version. Ask first or verify we have the same
# major.minornext?
# repo.git.checkout(upstream)
# versioning.bump_version(new_develop_version)
repo.git.checkout(upstream_branch)
......@@ -313,17 +354,17 @@ class GitManager(object):
new_version = re.sub(RC_RE, '', release_version)
versioning._bump_version(new_version, vcs)
#merge to master
# merge to master
self._merge_branches(master, upstream_branch)
self._merge_branches(debian_master, debian_branch)
#create tags
# create tags
repo.git.checkout(master)
repo.git.tag("%s" % tag)
repo.git.checkout(debian)
repo.git.tag("%s" % debian_tag)
#merge release changes to upstream
# merge release changes to upstream
self.merge_branches(upstream, upstream_branch, args, default=True)
self.merge_branches(debian, debian_branch, args, default=True)
......@@ -342,7 +383,7 @@ class GitManager(object):
upstream_branch = self.get_branch("hotfix", version)
debian_branch = self.get_debian_branch("hotfix", version)
#create tags?
# create tags?
self._merge_branches(upstream, upstream_branch)
self._merge_branches(debian, debian_branch)
......@@ -368,14 +409,14 @@ class GitManager(object):
feature_name = args.feature_name
repo = self.repo
feature_upstream = "feature-%s" % feature_name
if not feature_upstream in repo.branches:
if feature_upstream not in repo.branches:
raise ValueError("Branch %s does not exist." % feature_upstream)
feature_debian = "debian-%s" % feature_upstream
edit_action = partial(self.edit_changelog, feature_upstream, "develop")
self.check_edit_changelog(edit_action, args, default=True)
#merge to develop
# merge to develop
self._merge_branches("develop", feature_upstream)
if feature_debian in repo.branches:
self._merge_branches("debian-develop", feature_debian)
......@@ -394,93 +435,89 @@ def refhead(repo):
def main():
parser = ArgumentParser(description="Devflow tool")
parser.add_argument('-V', '--version', action='version',
version='devflow-flow %s' % __version__)
parser.add_argument('-d', '--defaults', action='store_true', default=False,
help="Assume default on every choice, unless a value is provided")
version='devflow-flow %s' % __version__)
parser.add_argument(
'-d', '--defaults', action='store_true', default=False,
help="Assume default on every choice, unless a value is provided")
subparsers = parser.add_subparsers()
init_parser = subparsers.add_parser('init',
help="Initialize a new devflow repo")
help="Initialize a new devflow repo")
init_parser.add_argument('-m', '--master', type=str, nargs='?',
help="Master branch")
help="Master branch")
init_parser.add_argument('-d', '--develop', type=str, nargs='?',
help="Develop branch")
help="Develop branch")
init_parser.set_defaults(func='init_repo')
feature_parser = subparsers.add_parser('feature', help="Feature options")