diff --git a/release/release_bob.py b/release/release_bob.py
index 27d8ce686424fc45040cf69b37eca50c10aadd11..fbf7690557295b2c6feec443089da0ef44a96a7b 100755
--- a/release/release_bob.py
+++ b/release/release_bob.py
@@ -21,10 +21,14 @@ Options:
 """
 
 import sys
+import os
 from docopt import docopt
 import gitlab
 import datetime
 import re
+from distutils.version import StrictVersion as Version
+import numpy
+import time
 
 
 def _insure_correct_package(candidates, group_name, pkg_name):
@@ -35,125 +39,31 @@ def _insure_correct_package(candidates, group_name, pkg_name):
     raise ValueError('Package "{0}" was not found inside group "{1}"'.format(pkg_name, group_name))
 
 
-def get_packages_list(gl, gl_group=None):
-    if gl_group:
-        grp_nightlies = gl_group.projects.list(search='bob.nightlies')[0]
-        nightlies = gl.projects.get(id=grp_nightlies.id)
-    else:
-        nightlies = gl.projects.list(search='bob.nightlies')[0]
-    nightlies_order = nightlies.files.get(file_path='order.txt', ref='master')
-    pkg_list_ordered = nightlies_order.decode().decode().split('\n')
-    pkg_list_ordered = [line for line in pkg_list_ordered if (line.strip() and not line.startswith('#'))]
-    return pkg_list_ordered
-
-
-# release date of last Bob release + 1 day
-def bob_last_release(gl):
-    bobpkg = gl.projects.get(id=1535)  # 1535 is id of 'bob' meta-package
-    last_bob_tag = bobpkg.tags.list()[0]  # get the last tag
-    return last_bob_tag.commit['committed_date']
-
-
-def get_datetime_from_gitdate(gitdate):
-    return datetime.datetime.strptime(gitdate[:-6], '%Y-%m-%dT%H:%M:%S.%f')
-
-
-def sort_commits(commits):
-    return sorted(commits, key=lambda x: get_datetime_from_gitdate(x.committed_date))
-
-
-def sort_tags(tags):
-    return sorted(tags, key=lambda x: get_datetime_from_gitdate(x.commit['committed_date']))
-
-
-def get_tag_changelog(tag):
-    try:
-        return tag.release['description']
-    except Exception:
-        return ''
-
-
-def print_tags(pkg_name, gitpkg, since='2017-01-01T00:00:00Z'):
-    since = get_datetime_from_gitdate(since)
-    tags = gitpkg.tags.list()
-    # sort tags by date
-    tags = filter(lambda x: get_datetime_from_gitdate(x.commit['committed_date']) >= since, tags)
-    tags = sort_tags(tags)
-    print('* ' + pkg_name)
-    for tag in tags:
-        print_one_tag(pkg_name, tag)
-
-
-def print_one_tag(pkg_name, tag):
-    print('  * ' + tag.name + ' (' + get_datetime_from_gitdate(tag.commit['committed_date']).strftime(
-        '%b %d, %Y %H:%M') + ')')
-    for line in get_tag_changelog(tag).split('\r\n'):
-        line = line.strip()
-        if line.startswith('* ') or line.startswith('- '):
-            line = line[2:]
-        line = line.replace('!', pkg_name + '!')
-        line = line.replace('#', pkg_name + '#')
-        if not line:
-            continue
-        print(' ' * 5 + '* ' + line)
-
-
-def print_commits(pkg_name, gitpkg, since='2017-01-01T00:00:00Z'):
-    #    import ipdb; ipdb.set_trace()
-    commits = gitpkg.commits.list(since=since, all=True)
-    # sort commits by date
-    commits = sort_commits(commits)
-    print('* ' + pkg_name)
-    print_commits_range(pkg_name, commits)
-
-
-def print_commits_range(pkg_name, commits):
-    for commit in commits:
-        commit_title = commit.title
-        # skip commits that do not carry much useful information
-        if '[skip ci]' in commit_title or \
-                        'Merge branch' in commit_title or \
-                        'Increased stable' in commit_title:
-            continue
-        commit_title = commit_title.strip()
-        commit_title = commit_title.replace('!', pkg_name + '!')
-        commit_title = commit_title.replace('#', pkg_name + '#')
-        # print('  - ' + get_datetime_from_gitdate(x.committed_date).strftime('%Y-%m-%d %H:%M:%S') + ":" + commit_title)
-        print(' ' * 5 + '- ' + commit_title)
-
-
-def print_tags_with_commits(pkg_name, gitpkg, since='2017-01-01T00:00:00Z'):
-    # get tags since release and sort them
-    datetime_since = get_datetime_from_gitdate(since)
-    tags = gitpkg.tags.list()
-    # sort tags by date
-    tags = filter(lambda x: get_datetime_from_gitdate(x.commit['committed_date']) >= datetime_since, tags)
-    tags = sort_tags(tags)
-    # get commits since release date and sort them too
-    commits = gitpkg.commits.list(since=since, all=True)
-    # sort commits by date
-    commits = sort_commits(commits)
-    print('* ' + pkg_name)
-    # go through tags and print each with its message and corresponding commits
-    start_date = datetime_since
-    for tag in tags:
-        # print tag name and its text
-        print_one_tag(pkg_name, tag)
-        # print commits from the previous tag up to this one
-        end_date = get_datetime_from_gitdate(tag.commit['committed_date'])
-        commits4tag = filter(lambda x: (
-            get_datetime_from_gitdate(x.committed_date) > start_date and get_datetime_from_gitdate(
-                x.committed_date) <= end_date), commits)
-        print_commits_range(pkg_name, commits4tag)
-        start_date = end_date
-    # print the tentative patch version bump for the future tag
-    print('  * patch')
-    # print leftover commits that were not tagged yet
-    leftover_commits = filter(lambda x: get_datetime_from_gitdate(x.committed_date) > start_date, commits)
-    print_commits_range(pkg_name, leftover_commits)
-
-
-def correct_tag(gitpkg, tag):
+# adapted from the same-name function in new_version.py script of bob.extension package
+def _update_readme(readme, version=None):
+    # replace the travis badge in the README.rst with the given version
+    DOC_IMAGE = re.compile(r'\-(stable|(v\d+\.\d+\.\d+([abc]\d+)?))\-')
+    BRANCH_RE = re.compile(r'/(stable|master|(v\d+\.\d+\.\d+([abc]\d+)?))')
+
+    new_readme = []
+    for line in readme.splitlines():
+        if BRANCH_RE.search(line) is not None:
+            if "gitlab" in line:  # gitlab links
+                replacement = "/v%s" % version if version is not None else "/master"
+                line = BRANCH_RE.sub(replacement, line)
+            if "software/bob" in line:  # our doc server
+                if 'master' not in line:  # don't replace 'latest' pointer
+                    replacement = "/v%s" % version if version is not None \
+                        else "/stable"
+                    line = BRANCH_RE.sub(replacement, line)
+        if DOC_IMAGE.search(line) is not None:
+            replacement = '-v%s-' % version if version is not None else '-stable-'
+            line = DOC_IMAGE.sub(replacement, line)
+        new_readme.append(line)
+    return '\n'.join(new_readme)
+
+
+def get_parsed_tag(gitpkg, tag):
     """
         An older tag is formatted as 'v2.1.3 (Sep 22, 2017 10:37)', from which we need only v2.1.3
         The latest tag is either patch, minor, major, or none
@@ -161,13 +71,14 @@ def correct_tag(gitpkg, tag):
     m = re.search(r"(v\d.\d.\d)", tag)
     if m:
         return m.group(0)
+    # tag = Version(tag)
 
     # if we bump the version, we need to find the latest released version for this package
     if 'patch' == tag or 'minor' == tag or 'major' == tag:
-        latest_tags = gitpkg.tags.list()
-        latest_tags = sort_tags(latest_tags)
-        latest_tag_name = latest_tags[-1].name
+        latest_tag = gitpkg.tags.list(per_page=1, page=1)[0]
+        latest_tag_name = latest_tag.name
         # check that it has expected format v#.#.#
+        # latest_tag_name = Version(latest_tag_name)
         m = re.match(r"(v\d.\d.\d)", latest_tag_name)
         if not m:
             raise ValueError(
@@ -186,55 +97,166 @@ def correct_tag(gitpkg, tag):
     raise ValueError('Cannot parse changelog tag {0} of the package {1}'.format(tag, gitpkg.name))
 
 
-def main(private_token, group_name='bob', changelog='changelog.rst'):
+def update_tag_comments(gitpkg, tag_name, tag_comments_list):
+    # get tag and update its description
+    tag = gitpkg.tags.get(tag_name)
+    print('package {0}, tag {1}, updating comments with:'.format(gitpkg.name, tag.name))
+    print(tag_comments_list)
+    tag.set_release_description('\n'.join(tag_comments_list))
+    return tag
+
+
+def commit_files(gitpkg, files_list, message='Updated files'):
+    data = {
+        'branch': 'master',  # v4
+        'commit_message': message,
+        'actions': []
+    }
+    # add files to update
+    for filename in files_list.keys():
+        update_action = dict(action='update', file_path=filename)
+        # with open(filename, 'r') as f:
+        #     update_action['content'] = f.read()
+        update_action['content'] = files_list[filename]
+        data['actions'].append(update_action)
+
+    print("Committing changes")
+    gitpkg.commits.create(data)
+
+
+def just_build_package(gitpkg):
+    # we assume the last pipeline is with commit [skip ci]
+    # so, we take the pipeline that can be re-built, which the previous to the last one
+    last_pipeline = gitpkg.pipelines.list(per_page=2, page=1)[1]
+
+    # check that the chosen pipeline is the one we are looking for
+    latest_tag_name = gitpkg.tags.list(per_page=1, page=1)[0].name
+    # the pipeline should be the one built for the latest tag, so check if it is the correct choice
+    if last_pipeline.ref != latest_tag_name:
+        raise ValueError('While deploying package, found pipeline {0} but it does not match '
+                         'the latest tag {1}'.format(last_pipeline.id, latest_tag_name))
+    # the pipeline should have succeeded, otherwise we cannot release
+    if last_pipeline.status != 'success':
+        raise ValueError('While deploying package, found pipeline {0} but its status '
+                         'is "{1}" instead of the expected "sucess"'.format(last_pipeline.id, last_pipeline.status))
+
+    print("Retrying pipeline {0}".format(last_pipeline.id))
+    print(last_pipeline)
+    last_pipeline.retry()
+
+
+def wait_for_pipeline_to_finish(gitpkg, tag):
+    sleep_step = 30
+    max_sleep = 60 * 60  # one hour
+    if tag == 'none':
+        # take the pipeline before the last
+        pipeline = gitpkg.pipelines.list(per_page=2, page=1)[1]
+    else:
+        # otherwise just take the last pipeline
+        pipeline = gitpkg.pipelines.list(per_page=1, page=1)[0]
+
+    # probe and wait for the pipeline to finish
+    pipeline_id = pipeline.id
+    slept_so_far = 0
+    while pipeline.status == 'running' or pipeline.status == 'pending':
+        time.sleep(sleep_step)
+        slept_so_far += sleep_step
+        if slept_so_far > max_sleep:
+            raise ValueError('I cannot wait longer than {0} seconds for '
+                             'pipeline {1} to finish running!'.format(max_sleep, pipeline_id))
+        # probe gitlab to update the status of the pipeline
+        pipeline = gitpkg.pipelines.get(pipeline_id)
+
+    # finished running, now check if it succeeded
+    if pipeline.status != 'success':
+        raise ValueError('Pipeline {0} of project {1} exited with undesired status "{2}". '
+                         'Release is not possible.'.format(pipeline_id, gitpkg.name, pipeline.status))
+
+
+def release_package(gitpkg, tag_name, tag_comments_list):
+    # if there is nothing to release, just rebuild the package
+    if tag_name == 'none':
+        print("Since the tag is 'none', we just re-build the last pipeline")
+        return just_build_package(gitpkg)
+
+    # 1. Replace branch tag in Readme to new tag, change version file to new version tag. Add and commit to gitlab
+    version_number = tag_name[1:]  # remove 'v' in front
+    readme_file = gitpkg.files.get(file_path='README.rst', ref='master')
+    readme_content = readme_file.decode().decode()
+    readme_content = _update_readme(readme_content, version=version_number)
+    # commit and push changes
+    commit_files(gitpkg, {'README.rst': readme_content, 'version.txt': version_number},
+                 'Increased stable version to %s [skip ci]' % version_number)
+
+    # 2. Tag package with new tag and push
+    print("creating tag {}".format(tag_name))
+    print('package {0}, tag {1}, updating comments with:'.format(gitpkg.name, tag_name))
+    print(tag_comments_list)
+    tag = gitpkg.tags.create({'tag_name': tag_name, 'ref': 'master'})
+    # update tag with comments
+    tag.set_release_description('\n'.join(tag_comments_list))
+
+    # 3. Replace branch tag in Readme to master, change version file to beta version tag. Git add, commit, and push.
+    readme_content = _update_readme(readme_content)
+    version_number += 'b0'
+    # commit and push changes
+    commit_files(gitpkg, {'README.rst': readme_content, 'version.txt': version_number},
+                 'Increased latest version to %s [skip ci]' % version_number)
+
+
+def parse_and_process_package_changelog(gl, bob_group, pkg_name, package_changelog):
+    cur_tag = None
+    cur_tag_comments = []
+
+    grpkg = _insure_correct_package(bob_group.projects.list(search=pkg_name), bob_group.name, pkg_name)
+    # so, we need to retrieve the full info from GitLab using correct project id
+    gitpkg = gl.projects.get(id=grpkg.id)
+
+    # we assume that changelog is formatted as structured text
+    # first line is the name of the package
+    for line in package_changelog:
+        if '  *' == line[:3]:  # a tag level
+            # write the comments collected for the previous tag
+            if cur_tag:
+                update_tag_comments(gitpkg, cur_tag, cur_tag_comments)
+                cur_tag_comments = []  # reset comments
+
+            # parse the current tag name
+            cur_tag = get_parsed_tag(gitpkg, line[3:].strip())
+
+        else:  # all other lines are assumed to be comments
+            cur_tag_comments.append(line.strip())
+
+    # return the last tag and comments for release
+    return gitpkg, cur_tag, cur_tag_comments
+
+
+def main(private_token, group_name='bob', changelog_file='changelog.rst'):
     gl = gitlab.Gitlab('https://gitlab.idiap.ch', private_token=private_token, api_version=4)
     bob_group = gl.groups.list(search=group_name)[0]
 
     # traverse all packages in the changelog, edit older tags with updated comments,
     # tag them with a suggested version, then try to release, and
     # wait until done to proceed to the next package
-    with open(changelog) as f:
-        changelog_insides = f.readlines()
-        pkg_name = cur_gitpkg = None
-        cur_tag = None
-        cur_tag_comments = []
-        # we assume that changelog is formatted as structured text
-        for line in changelog_insides:
-            if '*' == line[0]:  # name of the package
-
-                if pkg_name and cur_gitpkg:  # release previous package
-                    release_package(cur_gitpkg, cur_tag, cur_tag_comments)
-
-                pkg_name = line[1:].strip()
-                # find the correct package in GitLab
-                # group returns a simplified description of the project
-                grpkg = _insure_correct_package(bob_group.projects.list(search=pkg_name), group_name, pkg_name)
-                # so, we need to retrieve the full info from GitLab using correct project id
-                cur_gitpkg = gl.projects.get(id=grpkg.id)
-
-            elif '  *' == line[:3]:  # a tag level
-                if not cur_gitpkg:  # it better be not None, as we are in the middle of the tags for the package
-                    raise ValueError('How come package for {0} is empty?'.format(pkg_name))
-
-                # write the collected comments in the previous tag
-                if cur_tag:
-                    update_tag_comments(cur_gitpkg, cur_tag, cur_tag_comments)
-                    cur_tag_comments = []  # reset comments
-
-                # parse the current tag name
-                cur_tag = correct_tag(cur_gitpkg, line[3:].strip())
-
-                if 'none' == cur_tag:  # no tagging, just deploy the package
-                    deploy_package(cur_gitpkg)
-                    continue
-
-            else:  # all other lines are assumed to be comments
-                cur_tag_comments += line.strip()
-
-        if pkg_name and cur_gitpkg:  # release the last package
-            release_package(cur_gitpkg, cur_tag, cur_tag_comments)
+    with open(changelog_file) as f:
+        changelog = f.readlines()
+
+    # find the starts of each package's description in the changelog
+    pkgs = numpy.asarray([i for i, line in enumerate(changelog) if line[0] == '*'])
+    pkgs = numpy.concatenate([pkgs, [len(changelog)]])
+    for i in range(pkgs.shape[0] - 1):
+        print('Processing package {0}'.format(changelog[pkgs[i]]))
+        # print(changelog[pkgs[i] + 1: pkgs[i + 1]])
+        gitpkg, tag, tag_comments = parse_and_process_package_changelog(gl, bob_group, changelog[pkgs[i]][1:].strip(),
+                                                                        changelog[pkgs[i] + 1: pkgs[i + 1]])
+        # release the package with the found tag and its comments
+        if gitpkg:
+            release_package(gitpkg, tag, tag_comments)
+            # now, wait for the pipeline to finish, before we can release the next package
+            wait_for_pipeline_to_finish(gitpkg, tag)
 
 
 if __name__ == '__main__':
     arguments = docopt(__doc__.format(sys.argv[0]), version='Changelog 0.0.1')
-    main(arguments['<private_token>'],, group_name = arguments['--group-name'], changelog = arguments['--changelog'])
+    main(arguments['<private_token>'], group_name=arguments['--group-name'],
+         changelog_file=arguments['--changelog-file'])