diff --git a/.gitignore b/.gitignore index 6af30ee7..6d19369b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.py[co] # emacs backup files *~ +# runbot work files +runbot/static/build +runbot/static/repo diff --git a/runbot/__openerp__.py b/runbot/__openerp__.py index c538112d..9e6fa1bb 100644 --- a/runbot/__openerp__.py +++ b/runbot/__openerp__.py @@ -2,7 +2,7 @@ 'name': 'Runbot', 'category': 'Website', 'summary': 'Runbot', - 'version': '1.0', + 'version': '1.1', 'description': "Runbot", 'author': 'OpenERP SA', 'depends': ['website'], diff --git a/runbot/migrations/8.0.1.1/post-migration.py b/runbot/migrations/8.0.1.1/post-migration.py new file mode 100644 index 00000000..e294b5d9 --- /dev/null +++ b/runbot/migrations/8.0.1.1/post-migration.py @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- + + +from openerp import SUPERUSER_ID +from openerp.modules.registry import RegistryManager + + +def get_legacy_name(original_name, version): + return 'legacy_%s_%s' % (version.replace('.', '_'), original_name) + + +def m2o_to_x2m(cr, model, table, field, source_field): + cr.execute('SELECT id, %(field)s ' + 'FROM %(table)s ' + 'WHERE %(field)s is not null' % { + 'table': table, + 'field': source_field, + }) + for row in cr.fetchall(): + model.write(cr, SUPERUSER_ID, row[0], {field: [(4, row[1])]}) + + +def migrate(cr, version): + if not version: + return + registry = RegistryManager.get(cr.dbname) + m2o_to_x2m( + cr, + registry['runbot.repo'], + 'runbot_repo', + 'dependency_ids', + get_legacy_name('fallback_id', version), + ) diff --git a/runbot/migrations/8.0.1.1/pre-migration.py b/runbot/migrations/8.0.1.1/pre-migration.py new file mode 100644 index 00000000..10a844c3 --- /dev/null +++ b/runbot/migrations/8.0.1.1/pre-migration.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +from openerp import release +import logging + +logger = logging.getLogger('upgrade') + + +def get_legacy_name(original_name, version): + return 'legacy_%s_%s' % (version.replace('.', '_'), original_name) + + +def rename_columns(cr, column_spec, version): + for table, renames in column_spec.iteritems(): + for old, new in renames: + if new is None: + new = get_legacy_name(old, version) + logger.info("table %s, column %s: renaming to %s", + table, old, new) + cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' + % (table, old, new,)) + cr.execute('DROP INDEX IF EXISTS "%s_%s_index"' + % (table, old)) + +column_renames = { + 'runbot_repo': [ + ('fallback_id', None) + ] +} + + +def migrate(cr, version): + if not version: + return + rename_columns(cr, column_renames, version) diff --git a/runbot/runbot.py b/runbot/runbot.py index f9b5c291..2664ec27 100644 --- a/runbot/runbot.py +++ b/runbot/runbot.py @@ -5,6 +5,7 @@ import fcntl import glob import hashlib import logging +import operator import os import re import resource @@ -172,8 +173,12 @@ class runbot_repo(osv.osv): 'nginx': fields.boolean('Nginx'), 'auto': fields.boolean('Auto'), 'duplicate_id': fields.many2one('runbot.repo', 'Repository for finding duplicate builds'), - 'fallback_id': fields.many2one('runbot.repo', 'Fallback repo'), - 'modules': fields.char("Modules to Install"), + 'modules': fields.char("Modules to Install", help="Comma-separated list of modules to install and test."), + 'dependency_ids': fields.many2many( + 'runbot.repo', 'runbot_repo_dep_rel', + id1='dependant_id', id2='dependency_id', + string='Extra dependencies', + help="Community addon repos which need to be present to run tests."), 'token': fields.char("Github token"), } _defaults = { @@ -277,7 +282,8 @@ class runbot_repo(osv.osv): 'name': sha, 'author': author, 'subject': subject, - 'date': dateutil.parser.parse(date[:19]) + 'date': dateutil.parser.parse(date[:19]), + 'modules': branch.repo_id.modules, } Build.create(cr, uid, build_info) @@ -449,6 +455,7 @@ class runbot_build(osv.osv): 'author': fields.char('Author'), 'subject': fields.text('Subject'), 'sequence': fields.integer('Sequence', select=1), + 'modules': fields.char("Modules to Install"), 'result': fields.char('Result'), # ok, ko, warn, skipped, killed 'pid': fields.integer('Pid'), 'state': fields.char('Status'), # pending, testing, running, done, duplicate @@ -513,6 +520,45 @@ class runbot_build(osv.osv): return port + def get_closest_branch_name(self, cr, uid, ids, target_repo_id, hint_branches, context=None): + """Return the name of the closest common branch between both repos + Find common branch names, get merge-base with the branch name and + return the most recent. + Fallback repos will not have always have the same names for branch + names. + Pull request branches should not have any association with PR of other + repos + """ + branch_pool = self.pool['runbot.branch'] + for build in self.browse(cr, uid, ids, context=context): + branch, repo = build.branch_id, build.repo_id + name = branch.branch_name + # Use github API to find name of branch on which the PR is made + if repo.token and name.startswith('refs/pull/'): + pull_number = name[len('refs/pull/'):] + pr = repo.github('/repos/:owner/:repo/pulls/%s' % pull_number) + name = 'refs/heads/' + pr['base']['ref'] + # Find common branch names between repo and target repo + branch_ids = branch_pool.search(cr, uid, [('repo_id.id', '=', repo.id)]) + target_ids = branch_pool.search(cr, uid, [('repo_id.id', '=', target_repo_id)]) + branch_names = branch_pool.read(cr, uid, branch_ids, ['branch_name', 'name'], context=context) + target_names = branch_pool.read(cr, uid, target_ids, ['branch_name', 'name'], context=context) + possible_repo_branches = set([i['branch_name'] for i in branch_names if i['name'].startswith('refs/heads')]) + possible_target_branches = set([i['branch_name'] for i in target_names if i['name'].startswith('refs/heads')]) + possible_branches = possible_repo_branches.intersection(possible_target_branches) + if name not in possible_branches: + hinted_branches = possible_branches.intersection(hint_branches) + if hinted_branches: + possible_branches = hinted_branches + common_refs = {} + for target_branch_name in possible_branches: + commit = repo.git(['merge-base', branch.name, target_branch_name]).strip() + cmd = ['log', '-1', '--format=%cd', '--date=iso', commit] + common_refs[target_branch_name] = repo.git(cmd).strip() + if common_refs: + name = sorted(common_refs.iteritems(), key=operator.itemgetter(1), reverse=True)[0][0] + return name + def path(self, cr, uid, ids, *l, **kw): for build in self.browse(cr, uid, ids, context=None): root = self.pool['runbot.repo'].root(cr, uid) @@ -542,18 +588,39 @@ class runbot_build(osv.osv): if os.path.isdir(build.path('bin/addons')): shutil.move(build.path('bin'), build.server()) - # fallback for addons-only community/projet branches + # fallback for addons-only community/project branches + additional_modules = [] if not os.path.isfile(build.server('__init__.py')): - l = glob.glob(build.path('*/__openerp__.py')) - for i in l: - shutil.move(os.path.dirname(i), build.server('addons')) - name = build.branch_id.branch_name.split('-',1)[0] - if build.repo_id.fallback_id: - build.repo_id.fallback_id.git_export(name, build.path()) + # Use modules to test previously configured in the repository + modules_to_test = build.repo_id.modules + if not modules_to_test: + # Find modules to test from the folder branch + modules_to_test = ','.join( + os.path.basename(os.path.dirname(a)) + for a in glob.glob(build.path('*/__openerp__.py')) + ) + build.write({'modules': modules_to_test}) + hint_branches = set() + for extra_repo in build.repo_id.dependency_ids: + closest_name = build.get_closest_branch_name(extra_repo.id, hint_branches) + hint_branches.add(closest_name) + extra_repo.git_export(closest_name, build.path()) + # Finally mark all addons to move to openerp/addons + additional_modules += [ + os.path.dirname(module) + for module in glob.glob(build.path('*/__openerp__.py')) + ] # move all addons to server addons path - for i in glob.glob(build.path('addons/*')): - shutil.move(i, build.server('addons')) + for module in set(glob.glob(build.path('addons/*')) + additional_modules): + basename = os.path.basename(module) + if not os.path.exists(build.server('addons', basename)): + shutil.move(module, build.server('addons')) + else: + build._log( + 'Building environment', + 'You have duplicate modules in your branches "%s"' % basename + ) def pg_dropdb(self, cr, uid, dbname): pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid' @@ -582,8 +649,8 @@ class runbot_build(osv.osv): server_path = build.path("bin/openerp-server.py") # modules - if build.repo_id.modules: - modules = build.repo_id.modules + if build.modules: + modules = build.modules else: l = glob.glob(build.server('addons', '*', '__init__.py')) modules = set(os.path.basename(os.path.dirname(i)) for i in l) diff --git a/runbot/runbot.xml b/runbot/runbot.xml index 39793784..5d133824 100644 --- a/runbot/runbot.xml +++ b/runbot/runbot.xml @@ -23,8 +23,10 @@ - + + +