Commit e583530b authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Merge branch 'master' into debian

parents 91b54dd6 4149b207
#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
include distribute_setup.py
include README Changelog
include README.md Changelog
include version_template
devflow
========
devflow is a set of Python tools to manage versions, help implement the git
flow development process, and build Python and Debian packages. Although it has
been developed primarily for use with Synnefo, other projects may find it
useful too.
Quickstart
==========
import os
from devflow.version import get_python_version
os.environ["DEVFLOW_BUILD_MODE"] = "snapshot"
print "This commit's Python version is", get_python_version()
or take a look at devflow's own setup.py for ideas.
Feedback
========
For any comments or suggestions for improvement, please contact
the GRNET dev team <okeanos-dev@lists.grnet.gr>
devflow
=======
Overview
--------
devflow is a set of Python tools to manage versions, help implement the git
flow development process, and build Python and Debian packages. Although it has
been developed primarily for use with Synnefo, other projects may find it
useful too.
Quickstart
----------
```
import os
from devflow.version import get_python_version
os.environ["DEVFLOW_BUILD_MODE"] = "snapshot"
print "This commit's Python version is", get_python_version()
```
or take a look at devflow's own setup.py for ideas.
Project Page
------------
Please see the [official Synnefo site](http://www.synnefo.org)
for more information.
Copyright and license
=====================
Copyright 2012-2014 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.
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
# Copyright 2012-2014 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,25 +44,6 @@ from devflow import versioning
from devflow import utils
from devflow import BRANCH_TYPES
if sys.stdout.isatty():
try:
import colors
use_colors = True
except AttributeError:
use_colors = False
else:
use_colors = False
if use_colors:
red = colors.red
green = colors.green
else:
red = lambda x: x
green = lambda x: x
print_red = lambda x: sys.stdout.write(red(x) + "\n")
print_green = lambda x: sys.stdout.write(green(x) + "\n")
AVAILABLE_MODES = ["release", "snapshot"]
......@@ -150,9 +131,38 @@ def main():
default=False,
action="store_true",
help="Automatically push branches and tags to repo.")
parser.add_option("--color",
dest="color_output",
default="auto",
help="Enable/disable colored output. Default mode is" +
" auto, available options are yes/no")
(options, args) = parser.parse_args()
if options.color_output == "yes":
use_colors = True
elif options.color_output == "no":
use_colors = False
else:
if sys.stdout.isatty():
use_colors = True
else:
use_colors = False
red = lambda x: x
green = lambda x: x
if use_colors:
try:
import colors
red = colors.red
green = colors.green
except AttributeError:
pass
print_red = lambda x: sys.stdout.write(red(x) + "\n")
print_green = lambda x: sys.stdout.write(green(x) + "\n")
if options.help:
print_help(parser.get_prog_name())
parser.print_help()
......@@ -297,14 +307,16 @@ def main():
if mode == "release":
repo.git.tag(debian_branch_tag, sign_tag_opt, "-m %s" % tag_message)
# Add version.py files to repo
call("grep \"__version_vcs\" -r . -l -I | xargs git add -f")
# Create debian packages
cd(repo_dir)
version_files = []
for _, pkg_info in config['packages'].items():
version_files.append(pkg_info['version_file'])
if pkg_info.get("version_file"):
version_files.extend(pkg_info.as_list('version_file'))
# Add version.py files to repo
repo.git.add("-f", *version_files)
# Export version info to debuilg environment
os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version
os.environ["DEB_DEVFLOW_VERSION"] = python_version
......
import os
import re
import logging
logging.basicConfig()
from optparse import OptionParser
#from optparse import OptionParser
from argparse import ArgumentParser
os.environ["GIT_PYTHON_TRACE"] = "full"
from devflow import utils, versioning
from devflow import utils, versioning, RC_RE
from devflow.version import __version__
from devflow.autopkg import call
from functools import wraps
from devflow.ui import query_action, query_user, query_yes_no
from functools import wraps, partial
from contextlib import contextmanager
from git.exc import GitCommandError
from sh import mktemp
def create_temp_file(suffix):
create_dir_cmd = mktemp("/tmp/" + suffix + "-XXXXX")
return create_dir_cmd.stdout.strip()
def cleanup(func):
......@@ -37,11 +46,43 @@ def conflicts():
yield
except GitCommandError as e:
if e.status != 128:
print "An error occured. Resolve it and type 'exit'"
call("bash")
print "An error occured. Resolve it and type 'exit 0'"
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)
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)
def get_develop_version_from_release(release_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'
def get_hotfix_version(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
return str(major_version) + '.' + str(minor_version) + '.'\
+ str(hotfix_version+1)
class GitManager(object):
def __init__(self):
......@@ -53,46 +94,267 @@ 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")
@cleanup
def start_release(self, version):
self.start_common("release", version)
def get_branch(self, mode, version):
if mode not in ["release", "hotfix"]:
raise ValueError("Unknown mode: %s" % mode)
return "%s-%s" % (mode, version)
@cleanup
def start_hotfix(self, version):
self.start_common("hotfix", version)
def get_debian_branch(self, mode, version):
if mode not in ["release", "hotfix"]:
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):
if not args.defaults:
ret = query_yes_no(question, default = "yes" if default else "no")
else:
ret = default
if ret and action_yes:
action_yes()
elif not ret and action_no:
action_no()
def __print_cleanup(self, branches):
print "To remove obsolete branches run:"
for b in branches:
print "git branch -D %s" % b
@cleanup
def end_release(self, version):
self.end_common("release", version)
def __cleanup_branches(self, branches):
repo = self.repo
for b in branches:
repo.git.branch("-D", b)
def cleanup_branches(self, branches, args, default=False):
if args.cleanup is not None:
if args.cleanup:
self.__cleanup_branches(branches)
else:
self.__print_cleanup(branches)
return
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:
edit_action()
return
question = "Edit changelog ?"
self.doit(action_yes=edit_action, question=question, args=args,
default=default)
def _merge_branches(self, branch_to, branch_from):
repo = self.repo
cur_branch = repo.active_branch.name
repo.git.checkout(branch_to)
with conflicts():
repo.git.merge("--no-ff", branch_from)
repo.git.checkout(cur_branch)
def merge_branches(self, branch_to, branch_from, args, default=True):
action = partial(self._merge_branches, branch_to, branch_from)
question = "Merge branch %s to %s ?" % (branch_from, branch_to)
self.doit(action_yes=action, question=question, args=args,
default=default)
def edit_changelog(self, branch, base_branch=None):
repo = self.repo
if not branch in repo.branches:
raise ValueError("Branch %s does not exist." % branch)
if base_branch and not base_branch in repo.branches:
raise ValueError("Branch %s does not exist." % base_branch)
repo.git.checkout(branch)
topdir = repo.working_dir
changelog = os.path.join(topdir, "Changelog")
lines = []
lines.append("#Changelog for %s\n" % branch)
if base_branch:
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+')
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))
repo.git.add(changelog)
repo.git.commit(m="Update changelog")
print "Updated changelog on branch %s" % branch
@cleanup
def end_hotfix(self, version):
self.end_common("hotfix", version)
def start_release(self, args):
repo = self.repo
upstream = "develop"
debian = "debian-develop"
repo.git.checkout(upstream)
def start_common(self, mode, version):
if mode not in ["release", "hotfix"]:
raise ValueError("Unknown mode: %s" % mode)
vcs = utils.get_vcs_info()
develop_version = versioning.get_base_version(vcs)
if not args.version:
version = get_release_version(develop_version)
if not args.defaults:
version = query_user("Release version", default=version)
else:
#validate version?
pass
rc_version = "%src1" % version
new_develop_version = "%snext" % version
upstream_branch = self.get_branch("release", version)
debian_branch = self.get_debian_branch("release", version)
#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
repo.git.checkout(debian)
repo.git.branch(debian_branch, debian)
self.new_branches.append(debian_branch)
repo.git.checkout(upstream_branch)
repo.git.checkout(debian)
#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 = "develop" if mode == "release" else "master"
debian = "debian-develop" if mode == "release" else "debian"
upstream_branch = "%s-%s" % (mode, version)
debian_branch = "debian-%s-%s" % (mode, version)
upstream = "master"
debian = "debian"
repo.git.checkout(upstream)
#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)
if not args.version:
version = get_hotfix_version(version)
if not args.defaults:
version = query_user("Hotfix version", default=version)
else:
#validate version?
pass
rc_version = "%src1" % version
new_develop_version = "%snext" % version
upstream_branch = self.get_branch("hotfix", version)
debian_branch = self.get_debian_branch("hotfix", version)
#create hotfix branch
repo.git.branch(upstream_branch, upstream)
self.new_branches.append(upstream_branch)
versioning.bump_version("%snext" % version)
repo.git.checkout(upstream_branch)
versioning.bump_version("%src1" % version)
versioning.bump_version(rc_version)
#create debian hotfix branch
repo.git.checkout(debian)
repo.git.branch(debian_branch, debian)
self.new_branches.append(debian_branch)
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)
repo.git.checkout(upstream_branch)
@cleanup
def start_feature(self, feature_name):
def end_release(self, args):
version = args.version
repo = self.repo
master = "master"
debian_master = "debian"
upstream = "develop"
debian = "debian-develop"
upstream_branch = self.get_branch("release", version)
debian_branch = self.get_debian_branch("release", version)
tag = upstream_branch
debian_tag = "debian/" + tag
edit_action = partial(self.edit_changelog, upstream_branch, "develop")
self.check_edit_changelog(edit_action, args, default=True)
vcs = utils.get_vcs_info()
release_version = versioning.get_base_version(vcs)
if re.match('.*'+RC_RE, release_version):
new_version = re.sub(RC_RE, '', release_version)
versioning._bump_version(new_version, vcs)
#merge to master
self._merge_branches(master, upstream_branch)
self._merge_branches(debian_master, debian_branch)
#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
self.merge_branches(upstream, upstream_branch, args, default=True)
self.merge_branches(debian, debian_branch, args, default=True)
repo.git.checkout(upstream)
branches = [upstream_branch, debian_branch]
self.cleanup_branches(branches, args, default=True)
@cleanup
def end_hotfix(self, args):
version = args.version
repo = self.repo
upstream = "master"
debian = "debian"
upstream_branch = self.get_branch("hotfix", version)
debian_branch = self.get_debian_branch("hotfix", version)
#create tags?
self._merge_branches(upstream, upstream_branch)
self._merge_branches(debian, debian_branch)
repo.git.checkout(upstream)
branches = [upstream_branch, debian_branch]
self.cleanup_branches(branches, args, default=True)
@cleanup
def start_feature(self, args):
feature_name = args.feature_name
repo = self.repo
feature_upstream = "feature-%s" % feature_name
feature_debian = "debian-%s" % feature_upstream
......@@ -102,47 +364,27 @@ class GitManager(object):
self.new_branches.append(feature_debian)
@cleanup
def end_feature(self, feature_name):
def end_feature(self, args):
feature_name = args.feature_name
repo = self.repo
feature_upstream = "feature-%s" % feature_name
if not feature_upstream in repo.branches:
raise ValueError("Branch %s does not exist." % feature_upstream)
feature_debian = "debian-%s" % feature_upstream
repo.git.checkout("develop")
with conflicts():
repo.git.merge(feature_upstream)
repo.git.checkout("debian-develop")
edit_action = partial(self.edit_changelog, feature_upstream, "develop")
self.check_edit_changelog(edit_action, args, default=True)
#merge to develop
self._merge_branches("develop", feature_upstream)
if feature_debian in repo.branches:
with conflicts():
repo.git.merge(feature_debian)
self._merge_branches("debian-develop", feature_debian)
repo.git.checkout("develop")
print "To remove obsolete branches run:"
print "git branch -D %s" % feature_upstream
if feature_debian in repo.branches:
print "git branch -D %s" % feature_debian
def end_common(self, mode, version):
if mode not in ["release", "hotfix"]:
raise ValueError("Unknown mode: %s" % mode)
repo = self.repo
master = "master"
upstream = "develop" if mode == "release" else "master"
debian = "debian-develop" if mode == "release" else "debian"
upstream_branch = "%s-%s" % (mode, version)
debian_branch = "debian-%s-%s" % (mode, version)
repo.git.checkout(upstream)
with conflicts():
repo.git.merge("--no-ff", upstream_branch)
repo.git.checkout(master)
with conflicts():
repo.git.merge("--no-ff", upstream_branch)
repo.git.checkout(debian)
with conflicts():
repo.git.merge("--no-ff", debian_branch)
repo.git.checkout(upstream)
print "To remove obsolete branches run:"
print "git branch -d %s" % upstream_branch
print "git branch -d %s" % debian_branch
branches = [feature_upstream]
if feature_debian in repo.branches:
branches.append(feature_debian)
self.