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 ...@@ -22,6 +22,3 @@ dist
_build _build
.coverage .coverage
cover/ 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 Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following without modification, are permitted provided that the following
......
#Changelog for feature-improve-changelog 2016-07-21, v02016-07-20, v0.13rc3
* Prefix commit message in changelog with '* ' * Fix bug in git-buildpkg call were arguments contained extra spaces
#Changelog for feature-fix-release-version * Fix extra ~ bug in python->debian version translation
* Fix debian tag when finishing release
* Strip rc from version when ending a release 2016-07-20, v02016-07-20, v0.13rc2
* Make bump version modular. * Bump new version because 0.13rc1 was registered in pypi
* Add a _bump version function without any validation checking. Use the new
validate_version to perform the validation. 2016-07-20, v02016-07-20, v0.13rc1
* Split version validation from version generating * Use PEP 440 compatible versions
* Add support for newer git-buildpackage
include distribute_setup.py
include README.md Changelog include README.md Changelog
include version_template include version_template
...@@ -15,7 +15,7 @@ Quickstart ...@@ -15,7 +15,7 @@ Quickstart
``` ```
import os import os
from devflow.version import get_python_version from devflow.versioning import get_python_version
os.environ["DEVFLOW_BUILD_MODE"] = "snapshot" os.environ["DEVFLOW_BUILD_MODE"] = "snapshot"
print "This commit's Python version is", get_python_version() 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 # Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following # without modification, are permitted provided that the following
...@@ -44,13 +44,15 @@ from collections import namedtuple ...@@ -44,13 +44,15 @@ from collections import namedtuple
branch_type = namedtuple("branch_type", ["builds_snapshot", "builds_release", branch_type = namedtuple("branch_type", ["builds_snapshot", "builds_release",
"versioned", "allowed_version_re", "versioned", "allowed_version_re",
"debian_branch"]) "debian_branch"])
VERSION_RE = "[0-9]+\.[0-9]+(\.[0-9]+)*" # pylint: disable=W1401 VERSION_RE = r'[0-9]+\.[0-9]+(\.[0-9]+)*'
RC_RE = "rc[1-9][0-9]*" RC_RE = r'rc[1-9][0-9]*'
BRANCH_TYPES = { BRANCH_TYPES = {
"feature": branch_type(True, False, False, "^%snext$" % VERSION_RE, "feature": branch_type(True, False, False,
"^%s(next|\.?dev)?$" % VERSION_RE,
"debian-develop"), "debian-develop"),
"develop": branch_type(True, False, False, "^%snext$" % VERSION_RE, "develop": branch_type(True, False, False,
"^%s(next|\.?dev)?$" % VERSION_RE,
"debian-develop"), "debian-develop"),
"release": branch_type(True, True, True, "release": branch_type(True, True, True,
"^(?P<bverstr>%s)(%s)+$" % (VERSION_RE, RC_RE), "^(?P<bverstr>%s)(%s)+$" % (VERSION_RE, RC_RE),
...@@ -58,7 +60,7 @@ BRANCH_TYPES = { ...@@ -58,7 +60,7 @@ BRANCH_TYPES = {
"master": branch_type(True, True, False, "master": branch_type(True, True, False,
"^%s$" % VERSION_RE, "debian"), "^%s$" % VERSION_RE, "debian"),
"hotfix": branch_type(True, True, True, "hotfix": branch_type(True, True, True,
"^(?P<bverstr>^%s\.[1-9][0-9]*)(%s)*$" %\ r"^(?P<bverstr>^%s\.[1-9][0-9]*)(%s)*$" %
(VERSION_RE, RC_RE), (VERSION_RE, RC_RE),
"debian")} "debian")}
BASE_VERSION_FILE = "version" 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 # Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following # without modification, are permitted provided that the following
...@@ -31,14 +31,26 @@ ...@@ -31,14 +31,26 @@
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
# or implied, of GRNET S.A. # 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 os
import sys import sys
import subprocess
from git import GitCommandError from git import GitCommandError
from optparse import OptionParser 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 versioning
from devflow import utils from devflow import utils
...@@ -47,9 +59,9 @@ from devflow import BRANCH_TYPES ...@@ -47,9 +59,9 @@ from devflow import BRANCH_TYPES
AVAILABLE_MODES = ["release", "snapshot"] 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 repositories that follow the `git flow` development model
<http://nvie.com/posts/a-successful-git-branching-model/>. <http://nvie.com/posts/a-successful-git-branching-model/>.
...@@ -60,11 +72,11 @@ following steps: ...@@ -60,11 +72,11 @@ following steps:
* Compute the version of the new package and update the python * Compute the version of the new package and update the python
version files version files
* Create a new entry in debian/changelog, using `git-dch` * 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 * Tag the appropriate branches if in `release` mode
%(prog)s will work with the packages that are declared in `autopkg.conf` %(prog)s will work with the packages that are declared in `devflow.conf'
file, which must exist in the toplevel directory of the git repository. file, which must exist in the top-level directory of the git repository.
""" """
...@@ -90,7 +102,7 @@ def main(): ...@@ -90,7 +102,7 @@ def main():
parser.add_option("-b", "--build-dir", parser.add_option("-b", "--build-dir",
dest="build_dir", dest="build_dir",
default=None, default=None,
help="Directory to store created pacakges") help="Directory to store created packages")
parser.add_option("-r", "--repo-dir", parser.add_option("-r", "--repo-dir",
dest="repo_dir", dest="repo_dir",
default=None, default=None,
...@@ -134,7 +146,7 @@ def main(): ...@@ -134,7 +146,7 @@ def main():
parser.add_option("--color", parser.add_option("--color",
dest="color_output", dest="color_output",
default="auto", default="auto",
help="Enable/disable colored output. Default mode is" + help="Enable/disable colored output. Default mode is"
" auto, available options are yes/no") " auto, available options are yes/no")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
...@@ -144,10 +156,7 @@ def main(): ...@@ -144,10 +156,7 @@ def main():
elif options.color_output == "no": elif options.color_output == "no":
use_colors = False use_colors = False
else: else:
if sys.stdout.isatty(): use_colors = sys.stdout.isatty()
use_colors = True
else:
use_colors = False
red = lambda x: x red = lambda x: x
green = lambda x: x green = lambda x: x
...@@ -174,8 +183,8 @@ def main(): ...@@ -174,8 +183,8 @@ def main():
except IndexError: except IndexError:
mode = utils.get_build_mode() mode = utils.get_build_mode()
if mode not in AVAILABLE_MODES: if mode not in AVAILABLE_MODES:
raise ValueError(red("Invalid argument! Mode must be one: %s" raise ValueError(red("Invalid argument! Mode must be one: %s" %
% ", ".join(AVAILABLE_MODES))) ", ".join(AVAILABLE_MODES)))
# Load the repository # Load the repository
original_repo = utils.get_repository() original_repo = utils.get_repository()
...@@ -228,8 +237,8 @@ def main(): ...@@ -228,8 +237,8 @@ def main():
# Create the debian branch # Create the debian branch
repo.git.branch(debian_branch, origin_debian) repo.git.branch(debian_branch, origin_debian)
print_green("Created branch '%s' to track '%s'" % (debian_branch, print_green("Created branch '%s' to track '%s'" %
origin_debian)) (debian_branch, origin_debian))
# Go to debian branch # Go to debian branch
repo.git.checkout(debian_branch) repo.git.checkout(debian_branch)
...@@ -270,7 +279,7 @@ def main(): ...@@ -270,7 +279,7 @@ def main():
repo.git.tag(upstream_tag, branch) repo.git.tag(upstream_tag, branch)
# Update changelog # Update changelog
dch = git_dch("--debian-branch=%s" % debian_branch, dch = gbp_dch("--debian-branch=%s" % debian_branch,
"--git-author", "--git-author",
"--ignore-regex=\".*\"", "--ignore-regex=\".*\"",
"--multimaint-merge", "--multimaint-merge",
...@@ -294,7 +303,7 @@ def main(): ...@@ -294,7 +303,7 @@ def main():
f.close() f.close()
if mode == "release": if mode == "release":
call("vim debian/changelog") subprocess.check_call(['editor', "debian/changelog"])
# Add changelog to INDEX # Add changelog to INDEX
repo.git.add("debian/changelog") repo.git.add("debian/changelog")
...@@ -320,19 +329,25 @@ def main(): ...@@ -320,19 +329,25 @@ def main():
# Export version info to debuilg environment # Export version info to debuilg environment
os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version
os.environ["DEB_DEVFLOW_VERSION"] = python_version os.environ["DEB_DEVFLOW_VERSION"] = python_version
build_cmd = "git-buildpackage --git-export-dir=%s"\
" --git-upstream-branch=%s --git-debian-branch=%s"\ args = list(gbp_buildpackage)
" --git-export=INDEX --git-ignore-new -sa"\ args.extend(["--git-export-dir=%s" % build_dir,
" --source-option=--auto-commit"\ "--git-upstream-branch=%s" % branch,
" --git-upstream-tag=%s"\ "--git-debian-branch=%s" % debian_branch,
% (build_dir, branch, debian_branch, upstream_tag) "--git-export=INDEX",
"--git-ignore-new",
"-sa",
"--source-option=--auto-commit",
"--git-upstream-tag=%s" % upstream_tag])
if options.source_only: if options.source_only:
build_cmd += " -S" args.append("-S")
if not options.sign: if not options.sign:
build_cmd += " -uc -us" args.extend(["-uc", "-us"])
elif options.keyid: elif options.keyid:
build_cmd += " -k\"'%s'\"" % options.keyid args.append("-k\"'%s'\"" % options.keyid)
call(build_cmd)
subprocess.check_call(args)
# Remove cloned repo # Remove cloned repo
if mode != 'release' and not options.keep_repo: if mode != 'release' and not options.keep_repo:
...@@ -372,11 +387,5 @@ def create_temp_directory(suffix): ...@@ -372,11 +387,5 @@ def create_temp_directory(suffix):
return create_dir_cmd.stdout.strip() return create_dir_cmd.stdout.strip()
def call(cmd):
rc = os.system(cmd)
if rc:
raise RuntimeError("Command '%s' failed!" % cmd)
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(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 os
import re import re
import subprocess
import logging import logging
logging.basicConfig() logging.basicConfig()
#from optparse import OptionParser
from argparse import ArgumentParser from argparse import ArgumentParser
os.environ["GIT_PYTHON_TRACE"] = "full" os.environ["GIT_PYTHON_TRACE"] = "full"
from devflow import utils, versioning, RC_RE from devflow import utils, versioning, RC_RE
from devflow.version import __version__ from devflow.version import __version__
from devflow.autopkg import call
from devflow.ui import query_action, query_user, query_yes_no from devflow.ui import query_action, query_user, query_yes_no
from functools import wraps, partial from functools import wraps, partial
from contextlib import contextmanager from contextlib import contextmanager
...@@ -47,42 +80,51 @@ def conflicts(): ...@@ -47,42 +80,51 @@ def conflicts():
except GitCommandError as e: except GitCommandError as e:
if e.status != 128: if e.status != 128:
print "An error occured. Resolve it and type 'exit 0'" 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 = open(tmpbashrc, 'w')
f.write("source $HOME/.bashrc ; export PS1=(Conflict)\"$PS1\"") f.write("source $HOME/.bashrc ; export PS1=(Conflict)\"$PS1\"")
f.close() f.close()
call('bash --rcfile %s' % tmpbashrc) subprocess.check_call(['bash', '--rcfile', tmpbashrc])
os.unlink(tmpbashrc) os.unlink(tmpbashrc)
else: else:
raise raise
def get_release_version(develop_version): def get_release_version(develop_version):
version = develop_version.rstrip('next') '''Given a development version it will return the release version'''
parts = version.split('.')
major_version = int(parts[0]) # Old version scheme
minor_version = int(parts[1]) if 'next' in develop_version:
#return str(major_version) + '.' + str(minor_version+1) + 'rc1' version = develop_version.rstrip('next')
return str(major_version) + '.' + str(minor_version+1) 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): 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 version = release_version
parts = version.split('.') parts = version.split('.')
major_version = int(parts[0]) major = int(parts[0])
minor_version = int(parts[1]) minor = int(parts[1])
return str(major_version) + '.' + str(minor_version+1) + 'next' return "%d.%ddev" % (major, minor+1)
def get_hotfix_version(version): def get_hotfix_version(version):
"""Given a version it will return the next hotfix version"""
parts = version.split('.') parts = version.split('.')
major_version = int(parts[0]) major = int(parts[0])
minor_version = int(parts[1]) minor = int(parts[1])
if (len(parts) > 2): hotfix = int(parts[2]) if len(parts) > 2 else 0
hotfix_version = int(parts[2])
else: return "%d.%d.%d" % (major, minor, hotfix+1)
hotfix_version = 0
return str(major_version) + '.' + str(minor_version) + '.'\
+ str(hotfix_version+1)
class GitManager(object): class GitManager(object):
def __init__(self): def __init__(self):
...@@ -94,7 +136,11 @@ class GitManager(object): ...@@ -94,7 +136,11 @@ class GitManager(object):
self.log.info("Repository: %s. HEAD: %s", self.repo, self.start_hex) self.log.info("Repository: %s. HEAD: %s", self.repo, self.start_hex)
self.new_branches = [] self.new_branches = []
self.new_tags = [] 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): def get_branch(self, mode, version):
if mode not in ["release", "hotfix"]: if mode not in ["release", "hotfix"]:
...@@ -106,10 +152,10 @@ class GitManager(object): ...@@ -106,10 +152,10 @@ class GitManager(object):
raise ValueError("Unknown mode: %s" % mode) raise ValueError("Unknown mode: %s" % mode)
return "debian-%s-%s" % (mode, version) return "debian-%s-%s" % (mode, version)
def doit(self, action_yes=None, action_no=None, question="Do it", args=None, def doit(self, action_yes=None, action_no=None, question="Do it",
default=False): args=None, default=False):
if not args.defaults: 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: else:
ret = default ret = default
...@@ -123,7 +169,6 @@ class GitManager(object): ...@@ -123,7 +169,6 @@ class GitManager(object):
for b in branches: for b in branches:
print "git branch -D %s" % b print "git branch -D %s" % b
def __cleanup_branches(self, branches): def __cleanup_branches(self, branches):
repo = self.repo repo = self.repo
for b in branches: for b in branches:
...@@ -137,13 +182,12 @@ class GitManager(object): ...@@ -137,13 +182,12 @@ class GitManager(object):
self.__print_cleanup(branches) self.__print_cleanup(branches)
return return
question="Remove branches %s" % branches question = "Remove branches %s" % branches
action_yes = partial(self.__cleanup_branches, branches) action_yes = partial(self.__cleanup_branches, branches)
action_no = partial(self.__print_cleanup, branches) action_no = partial(self.__print_cleanup, branches)
self.doit(action_yes=action_yes, action_no=action_no, self.doit(action_yes=action_yes, action_no=action_no,
question=question, args=args, default=default) question=question, args=args, default=default)
def check_edit_changelog(self, edit_action, args, default=True): def check_edit_changelog(self, edit_action, args, default=True):
if args.edit_changelog is not None: if args.edit_changelog is not None:
if args.edit_changelog: if args.edit_changelog:
...@@ -169,9 +213,9 @@ class GitManager(object): ...@@ -169,9 +213,9 @@ class GitManager(object):
def edit_changelog(self, branch, base_branch=None): def edit_changelog(self, branch, base_branch=None):
repo = self.repo repo = self.repo
if not branch in repo.branches: if branch not in repo.branches:
raise ValueError("Branch %s does not exist." % branch) 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) raise ValueError("Branch %s does not exist." % base_branch)
repo.git.checkout(branch) repo.git.checkout(branch)
...@@ -181,23 +225,21 @@ class GitManager(object): ...@@ -181,23 +225,21 @@ class GitManager(object):
lines = [] lines = []
lines.append("#Changelog for %s\n" % branch) lines.append("#Changelog for %s\n" % branch)
if base_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: for c in commits:
commit = repo.commit(c) commit = repo.commit(c)
lines.append("* " + commit.message.split("\n")[0]) lines.append("* " + commit.message.split("\n")[0])
lines.append("\n") lines.append("\n")
f = open(changelog, 'rw+') f = open(changelog, 'r+')
lines.extend(f.readlines()) lines.extend(f.readlines())
f.seek(0) f.seek(0)
f.truncate(0) f.truncate(0)
f.writelines(lines) f.writelines(lines)
f.close() f.close()
editor = os.getenv('EDITOR') subprocess.check_call(['editor', changelog])
if not editor:
editor = 'vim'
call("%s %s" % (editor, changelog))
repo.git.add(changelog) repo.git.add(changelog)
repo.git.commit(m="Update changelog") repo.git.commit(m="Update changelog")
print "Updated changelog on branch %s" % branch print "Updated changelog on branch %s" % branch
...@@ -216,7 +258,7 @@ class GitManager(object): ...@@ -216,7 +258,7 @@ class GitManager(object):
if not args.defaults: if <