Пример #1
0
def _get_recipes_filenames(ct, repodir, layerdir, logger):
    import glob
    ct_files = []
    layerdir_start = os.path.normpath(layerdir) + os.sep

    files = utils.runcmd(
        ['git', 'log', '--name-only', '--format=%n', '-n', '1', ct],
        repodir,
        logger=logger)

    incdirs = []
    for f in files.split("\n"):
        if f != "":
            fullpath = os.path.join(repodir, f)
            # Skip deleted files in commit
            if not os.path.exists(fullpath):
                continue
            if not fullpath.startswith(layerdir_start):
                # Ignore files in repo that are outside of the layer
                continue
            (typename, _,
             filename) = recipeparse.detect_file_type(fullpath, layerdir_start)
            if typename == 'recipe':
                ct_files.append(fullpath)
            elif fullpath.endswith('.inc'):
                fpath = os.path.dirname(fullpath)
                if not fpath in incdirs:
                    incdirs.append(fpath)
    for fpath in incdirs:
        # Let's just assume that all .bb files next to a .inc need to be checked
        for f in glob.glob(os.path.join(fpath, '*.bb')):
            if not f in ct_files:
                ct_files.append(f)

    return ct_files
Пример #2
0
def checkout_layer_deps(layerbranch, commit, fetchdir, logger):
    """ Check out the repositories for a layer and its dependencies """
    # Some layers will be in the same repository, so we only want to check those out once
    done_repos = []

    def checkout_layer(lb, lcommit=None, lcommitdate=None, force=False):
        urldir = str(lb.layer.get_fetch_dir())
        repodir = os.path.join(fetchdir, urldir)
        if not repodir in done_repos:
            if not lcommit:
                lcommit = utils.runcmd([
                    'git', 'rev-list', '-1',
                    '--before=%s' % lcommitdate, 'origin/master'
                ],
                                       repodir,
                                       logger=logger).strip()
            utils.checkout_repo(repodir, lcommit, logger, force)
            done_repos.append(repodir)

    # We "force" here because it's almost certain we'll be checking out a
    # different revision for the layer itself
    checkout_layer(layerbranch, commit, force=True)
    layer_urldir = str(layerbranch.layer.get_fetch_dir())
    layer_repodir = os.path.join(fetchdir, layer_urldir)
    commitdate = utils.runcmd(['git', 'show', '-s', '--format=%ci'],
                              layer_repodir,
                              logger=logger)

    for dep in layerbranch.get_recursive_dependencies():
        checkout_layer(dep, lcommitdate=commitdate)

    return commitdate
def run_internal(maintplanlayerbranch,
                 commit,
                 commitdate,
                 options,
                 logger,
                 bitbake_map,
                 initial=False):
    from layerindex.models import PythonEnvironment
    from rrs.models import Release
    if commitdate < maintplanlayerbranch.python3_switch_date:
        # Python 2
        if maintplanlayerbranch.python2_environment:
            cmdprefix = maintplanlayerbranch.python2_environment.get_command()
        else:
            cmdprefix = 'python'
        # Ensure we're using a bitbake version that is python 2 compatible
        if commitdate > datetime(2016, 5, 10):
            commitdate = datetime(2016, 5, 10)
    else:
        # Python 3
        if maintplanlayerbranch.python3_environment:
            cmdprefix = maintplanlayerbranch.python3_environment.get_command()
        else:
            cmdprefix = 'python3'

    bitbake_rev = utils.runcmd([
        'git', 'rev-list', '-1',
        '--before=%s' % str(commitdate), 'origin/master'
    ],
                               bitbakepath,
                               logger=logger)
    check_rev = bitbake_map.get(bitbake_rev, None)
    if check_rev:
        logger.debug('Preferring bitbake revision %s over %s' %
                     (check_rev, bitbake_rev))
        bitbake_rev = check_rev

    cmd = '%s upgrade_history_internal.py %s %s' % (
        cmdprefix, maintplanlayerbranch.layerbranch.id, commit)
    if initial:
        release = Release.get_by_date(maintplanlayerbranch.plan, commitdate)
        if release:
            comment = 'Initial import at %s release start.' % release.name
        else:
            comment = 'Initial import at %s' % commit
        cmd += ' --initial="%s"' % comment
    if bitbake_rev:
        cmd += ' --bitbake-rev %s' % bitbake_rev
    if options.filter_files:
        cmd += ' --filter-files %s' % options.filter_files
    if options.dry_run:
        cmd += ' --dry-run'
    if options.loglevel == logging.DEBUG:
        cmd += ' --debug'
    logger.debug('Running %s' % cmd)
    ret, output = utils.run_command_interruptible(cmd)
    if ret == 254:
        # Interrupted by user, break out of loop
        logger.info('Update interrupted, exiting')
        sys.exit(254)
 def remap_range(start, end, replacewith=None):
     if replacewith is None:
         replacewith = end
     bitbake_commits = utils.runcmd(
         ['git', 'rev-list',
          '%s^..%s^' % (start, end)],
         bitbakepath,
         logger=logger)
     bitbake_commit_list = bitbake_commits.splitlines()
     for commit in bitbake_commit_list:
         bitbake_map[commit] = replacewith
Пример #5
0
 def checkout_layer(lb, lcommit=None, lcommitdate=None, force=False):
     urldir = str(lb.layer.get_fetch_dir())
     repodir = os.path.join(fetchdir, urldir)
     if not repodir in done_repos:
         if not lcommit:
             lcommit = utils.runcmd(
                 'git rev-list -1 --before="%s" origin/master' %
                 lcommitdate,
                 repodir,
                 logger=logger).strip()
         utils.checkout_repo(repodir, lcommit, logger, force)
         done_repos.append(repodir)
def checkout_layer_deps(layerbranch, commit, fetchdir, logger):
    """ Check out the repositories for a layer and its dependencies """

    oecore_map = {}
    if layerbranch.layer.name != 'openembedded-core':
        # Filter out some bad commits for OE-Core
        for good_rev, bad_revs in oecore_bad_revs.items():
            for bad_rev in bad_revs:
                oecore_map[bad_rev] = good_rev

    # Some layers will be in the same repository, so we only want to check those out once
    done_repos = []
    def checkout_layer(lb, lcommit=None, lcommitdate=None, force=False):
        urldir = str(lb.layer.get_fetch_dir())
        repodir = os.path.join(fetchdir, urldir)
        if not repodir in done_repos:
            if not lcommit:
                lcommit = utils.runcmd(['git', 'rev-list', '-1', '--before=%s' % lcommitdate, 'origin/master'], repodir, logger=logger).strip()
            if lb.layer.name == 'openembedded-core':
                lmapcommit = oecore_map.get(lcommit, None)
                if lmapcommit:
                    logger.debug('Preferring OE-Core revision %s over %s' % (lmapcommit, lcommit))
                    lcommit = lmapcommit
            utils.checkout_repo(repodir, lcommit, logger, force)
            if lcommit in oecore_bad_revs_2:
                # Fix issue that was introduced in 309a02931779f32d1139cc1169a039cbe4638706 and fixed in 40a904bf8bc1279c3da0893c003f740f1d2066c2
                with open(os.path.join(repodir, 'meta/conf/bitbake.conf'), 'r') as f:
                    lines = f.readlines()
                lines.insert(0, 'BBINCLUDED ?= ""\n')
                with open(os.path.join(repodir, 'meta/conf/bitbake.conf'), 'w') as f:
                    f.writelines(lines)
            done_repos.append(repodir)

    # We "force" here because it's almost certain we'll be checking out a
    # different revision for the layer itself
    checkout_layer(layerbranch, commit, force=True)
    layer_urldir = str(layerbranch.layer.get_fetch_dir())
    layer_repodir = os.path.join(fetchdir, layer_urldir)
    commitdate = utils.runcmd(['git', 'show', '-s', '--format=%ci'], layer_repodir, logger=logger)

    for dep in layerbranch.get_recursive_dependencies():
        checkout_layer(dep, lcommitdate=commitdate)

    return commitdate
 def checkout_layer(lb, lcommit=None, lcommitdate=None, force=False):
     urldir = str(lb.layer.get_fetch_dir())
     repodir = os.path.join(fetchdir, urldir)
     if not repodir in done_repos:
         if not lcommit:
             lcommit = utils.runcmd(['git', 'rev-list', '-1', '--before=%s' % lcommitdate, 'origin/master'], repodir, logger=logger).strip()
         if lb.layer.name == 'openembedded-core':
             lmapcommit = oecore_map.get(lcommit, None)
             if lmapcommit:
                 logger.debug('Preferring OE-Core revision %s over %s' % (lmapcommit, lcommit))
                 lcommit = lmapcommit
         utils.checkout_repo(repodir, lcommit, logger, force)
         if lcommit in oecore_bad_revs_2:
             # Fix issue that was introduced in 309a02931779f32d1139cc1169a039cbe4638706 and fixed in 40a904bf8bc1279c3da0893c003f740f1d2066c2
             with open(os.path.join(repodir, 'meta/conf/bitbake.conf'), 'r') as f:
                 lines = f.readlines()
             lines.insert(0, 'BBINCLUDED ?= ""\n')
             with open(os.path.join(repodir, 'meta/conf/bitbake.conf'), 'w') as f:
                 f.writelines(lines)
         done_repos.append(repodir)
def maintainers_inc_history(options, logger, maintplan, layerbranch, repodir,
                            layerdir):
    maintainers_full_path = os.path.join(layerdir, MAINTAINERS_INCLUDE_PATH)
    if not os.path.exists(maintainers_full_path):
        logger.warning(
            'Maintainer style is maintainers.inc for plan %s but no maintainers.inc exists in for %s'
            % (maintplan, layerbranch))
        return

    logger.debug('Checking maintainers.inc history for %s' % layerbranch)

    commits = utils.runcmd(
        "git log --format='%%H' --reverse --date=rfc origin/master %s" %
        os.path.join(layerbranch.vcs_subdir, MAINTAINERS_INCLUDE_PATH),
        repodir,
        logger=logger)

    no_maintainer, _ = Maintainer.objects.get_or_create(name='No maintainer')

    try:
        with transaction.atomic():
            for commit in commits.strip().split("\n"):
                if RecipeMaintainerHistory.objects.filter(
                        layerbranch=layerbranch, sha1=commit):
                    continue

                logger.debug("Analysing commit %s ..." % (commit))

                (author_name, author_email, date, title) = \
                    get_commit_info(utils.runcmd("git show " + commit, repodir,
                        logger=logger), logger)

                author = Maintainer.create_or_update(author_name, author_email)
                rms = RecipeMaintainerHistory(title=title,
                                              date=date,
                                              author=author,
                                              sha1=commit,
                                              layerbranch=layerbranch)
                rms.save()

                utils.runcmd("git checkout %s -f" % commit,
                             repodir,
                             logger=logger)

                lines = [line.strip() for line in open(maintainers_full_path)]
                for line in lines:
                    res = get_recipe_maintainer(line, logger)
                    if res:
                        (pn, name, email) = res
                        qry = Recipe.objects.filter(pn=pn,
                                                    layerbranch=layerbranch)

                        if qry:
                            m = Maintainer.create_or_update(name, email)

                            rm = RecipeMaintainer()
                            rm.recipe = qry[0]
                            rm.maintainer = m
                            rm.history = rms
                            rm.save()

                            logger.debug("%s: Change maintainer to %s in commit %s." % \
                                    (pn, m.name, commit))
                        else:
                            logger.debug("%s: Not found in %s." % \
                                    (pn, layerbranch))

                # set missing recipes to no maintainer
                for recipe in layerbranch.recipe_set.all():
                    if not RecipeMaintainer.objects.filter(recipe=recipe,
                                                           history=rms):
                        rm = RecipeMaintainer()
                        rm.recipe = recipe
                        link_maintainer = RecipeMaintenanceLink.link_maintainer(
                            recipe.pn, rms)
                        if link_maintainer:
                            rm.maintainer = link_maintainer.maintainer
                        else:
                            rm.maintainer = no_maintainer
                        rm.history = rms
                        rm.save()
                        if link_maintainer:
                            logger.debug(
                                "%s: linked to maintainer for %s" %
                                (recipe.pn, link_maintainer.recipe.pn))
                        else:
                            logger.debug("%s: Not found maintainer in commit %s set to 'No maintainer'." % \
                                            (recipe.pn, rms.sha1))

            # set new recipes to no maintainer if don't have one
            rms = RecipeMaintainerHistory.get_last(layerbranch)
            for recipe in layerbranch.recipe_set.all():
                if not RecipeMaintainer.objects.filter(recipe=recipe,
                                                       history=rms):
                    rm = RecipeMaintainer()
                    rm.recipe = recipe
                    link_maintainer = RecipeMaintenanceLink.link_maintainer(
                        recipe.pn, rms)
                    if link_maintainer:
                        rm.maintainer = link_maintainer.maintainer
                    else:
                        rm.maintainer = no_maintainer
                    rm.history = rms
                    rm.save()
                    if link_maintainer:
                        logger.debug(
                            "%s: New recipe linked to maintainer for %s" %
                            (recipe.pn, link_maintainer.recipe.pn))
                    else:
                        logger.debug("%s: New recipe not found maintainer set to 'No maintainer'." % \
                                    (recipe.pn))
        if options.dry_run:
            raise DryRunRollbackException
    except DryRunRollbackException:
        pass
Пример #9
0
def generate_history(options, layerbranch_id, commit, logger):
    from layerindex.models import LayerBranch
    from rrs.models import Release
    layerbranch = LayerBranch.objects.get(id=layerbranch_id)

    fetchdir = settings.LAYER_FETCH_DIR
    if not fetchdir:
        logger.error("Please set LAYER_FETCH_DIR in settings.py")
        sys.exit(1)

    layer = layerbranch.layer
    urldir = str(layer.get_fetch_dir())
    repodir = os.path.join(fetchdir, urldir)
    layerdir = os.path.join(repodir, str(layerbranch.vcs_subdir))

    commitdate = checkout_layer_deps(layerbranch, commit, fetchdir, logger)

    if options.initial:
        fns = None
    else:
        fns = _get_recipes_filenames(commit, repodir, layerdir, logger)
        if not fns:
            return

    # setup bitbake
    bitbakepath = os.path.join(fetchdir, 'bitbake')
    if options.bitbake_rev:
        bitbake_rev = options.bitbake_rev
        if not re.match('^[0-9a-f]{40}$', bitbake_rev):
            # Branch name, need to check out detached
            bitbake_rev = 'origin/%s' % bitbake_rev
    else:
        bitbake_rev = utils.runcmd([
            'git', 'rev-list', '-1',
            '--before=%s' % commitdate, 'origin/master'
        ],
                                   bitbakepath,
                                   logger=logger).strip()
    utils.checkout_repo(bitbakepath, bitbake_rev, logger)
    sys.path.insert(0, os.path.join(bitbakepath, 'lib'))

    (tinfoil, d, recipes, tempdir) = load_recipes(layerbranch,
                                                  bitbakepath,
                                                  fetchdir,
                                                  settings,
                                                  logger,
                                                  recipe_files=fns,
                                                  nocheckout=True)
    try:

        if options.initial:
            title = options.initial
            info = 'No maintainer;;' + utils.runcmd([
                'git', 'log', '--format=%ad;%cd', '--date=rfc', '-n', '1',
                commit
            ],
                                                    destdir=repodir,
                                                    logger=logger)
            recordcommit = ''
        else:
            title = utils.runcmd(
                ['git', 'log', '--format=%s', '-n', '1', commit],
                repodir,
                logger=logger)
            info = utils.runcmd([
                'git', 'log', '--format=%an;%ae;%ad;%cd', '--date=rfc', '-n',
                '1', commit
            ],
                                destdir=repodir,
                                logger=logger)
            recordcommit = commit

        try:
            with transaction.atomic():
                for recipe_data in recipes:
                    _create_upgrade(recipe_data,
                                    layerbranch,
                                    recordcommit,
                                    title,
                                    info,
                                    logger,
                                    initial=options.initial)
                if options.dry_run:
                    raise DryRunRollbackException
        except DryRunRollbackException:
            pass

    finally:
        if tinfoil and hasattr(tinfoil, 'shutdown') and (LooseVersion(
                bb.__version__) > LooseVersion("1.27")):
            tinfoil.shutdown()
        utils.rmtree_force(tempdir)
def upgrade_history(options, logger):
    from rrs.models import MaintenancePlan, RecipeUpgrade, Release, Milestone

    if options.plan:
        maintplans = MaintenancePlan.objects.filter(id=int(options.plan))
        if not maintplans.exists():
            logger.error('No maintenance plan with ID %s found' % options.plan)
            sys.exit(1)
    else:
        maintplans = MaintenancePlan.objects.filter(updates_enabled=True)
        if not maintplans.exists():
            logger.error('No enabled maintenance plans found')
            sys.exit(1)

    lockfn = os.path.join(fetchdir, "layerindex.lock")
    lockfile = utils.lock_file(lockfn)
    if not lockfile:
        logger.error("Layer index lock timeout expired")
        sys.exit(1)
    try:
        for maintplan in maintplans:
            # Check releases and milestones
            current = date.today()
            current_release = Release.get_by_date(maintplan, current)
            if current_release:
                current_milestone = Milestone.get_by_release_and_date(
                    current_release, current)
                if not current_milestone:
                    logger.warning(
                        '%s: there is no milestone defined in the latest release (%s) that covers the current date, so up-to-date data will not be visible in the web interface.'
                        % (maintplan, current_release))
            else:
                logger.warning(
                    '%s: there is no release defined that covers the current date, so up-to-date data will not be visible in the web interface.'
                    % maintplan)

            for maintplanbranch in maintplan.maintenanceplanlayerbranch_set.all(
            ):
                layerbranch = maintplanbranch.layerbranch
                if options.fullreload and not options.dry_run:
                    RecipeUpgrade.objects.filter(
                        recipe__layerbranch=layerbranch).delete()
                layer = layerbranch.layer
                urldir = layer.get_fetch_dir()
                repodir = os.path.join(fetchdir, urldir)
                layerdir = os.path.join(repodir, layerbranch.vcs_subdir)

                if options.commit:
                    initial = False
                    since = options.commit
                    since_option = '%s^..%s' % (options.commit, options.commit)
                elif maintplanbranch.upgrade_rev and not options.fullreload:
                    initial = False
                    since = maintplanbranch.upgrade_date
                    since_option = '%s..origin/master' % maintplanbranch.upgrade_rev
                else:
                    initial = True
                    since = options.since
                    since_option = '--since="%s" origin/master' % since

                repo = git.Repo(repodir)
                assert repo.bare == False

                commits = utils.runcmd(
                    "git log %s --format='%%H %%ct' --reverse" % since_option,
                    repodir,
                    logger=logger)
                commit_list = commits.split('\n')

                bitbake_map = {}
                # Filter out some bad commits
                bitbake_commits = utils.runcmd(
                    "git rev-list fef18b445c0cb6b266cd939b9c78d7cbce38663f^..39780b1ccbd76579db0fc6fb9369c848a3bafa9d^",
                    bitbakepath,
                    logger=logger)
                bitbake_commit_list = bitbake_commits.splitlines()
                for commit in bitbake_commit_list:
                    bitbake_map[
                        commit] = '39780b1ccbd76579db0fc6fb9369c848a3bafa9d'

                if initial:
                    logger.debug("Adding initial upgrade history ....")

                    ct, ctepoch = commit_list.pop(0).split()
                    ctdate = datetime.fromtimestamp(int(ctepoch))
                    run_internal(maintplanbranch,
                                 ct,
                                 ctdate,
                                 options,
                                 logger,
                                 bitbake_map,
                                 initial=True)

                if layerbranch.vcs_subdir:
                    layersubdir_start = layerbranch.vcs_subdir
                    if not layersubdir_start.endswith('/'):
                        layersubdir_start += '/'
                else:
                    layersubdir_start = ''
                logger.debug("Adding upgrade history from %s to %s ..." %
                             (since, datetime.today().strftime("%Y-%m-%d")))
                for item in commit_list:
                    if item:
                        ct, ctepoch = item.split()
                        ctdate = datetime.fromtimestamp(int(ctepoch))
                        commitobj = repo.commit(ct)
                        touches_recipe = False
                        for parent in commitobj.parents:
                            diff = parent.diff(commitobj)
                            for diffitem in diff:
                                if layersubdir_start and not (
                                        diffitem.a_path.startswith(
                                            layersubdir_start) or diffitem.
                                        b_path.startswith(layersubdir_start)):
                                    # Not in this layer, skip it
                                    continue
                                if diffitem.a_path.endswith(
                                    ('.bb',
                                     '.inc')) or diffitem.b_path.endswith(
                                         ('.bb', '.inc')):
                                    # We need to look at this commit
                                    touches_recipe = True
                                    break
                            if touches_recipe:
                                break
                        if not touches_recipe:
                            # No recipes in the layer changed in this commit
                            # NOTE: Whilst it's possible that a change to a class might alter what's
                            # in the recipe, we can ignore that since we are only concerned with actual
                            # upgrades which would always require some sort of change to the recipe
                            # or an include file, so we can safely skip commits that don't do that
                            logger.debug("Skipping commit %s" % ct)
                            continue
                        logger.debug("Analysing commit %s ..." % ct)
                        run_internal(maintplanbranch, ct, ctdate, options,
                                     logger, bitbake_map)
                        if not options.dry_run:
                            maintplanbranch.upgrade_rev = ct
                            maintplanbranch.upgrade_date = ctdate
                            maintplanbranch.save()
    finally:
        utils.unlock_file(lockfile)
def send_email(maintplan, recipes, options):
    upgradable_count = 0
    no_upgradable_count = 0
    maintainers = Maintainer.objects.all().order_by("name")

    RecipeUpgradeLine = namedtuple(
        'RecipeUpgradeLine',
        ['pn', 'pv', 'pv_upstream', 'maintainer', 'noupgradereason'])

    recipelines = []
    for m in maintainers:
        for recipe in recipes.keys():
            recipe_maintainer = recipes[recipe]['maintainer']
            recipe_upstream = recipes[recipe]['upstream']

            if m.id == recipe_maintainer.maintainer.id:
                pn_max_len = 20
                pv_max_len = 20
                name_max_len = 20
                reason_max_len = 30

                recipelines.append(
                    RecipeUpgradeLine(recipe.pn, recipe.pv,
                                      recipe_upstream.version, m.name,
                                      recipe_upstream.no_update_reason))

                upgradable_count = upgradable_count + 1
                if recipe_upstream.no_update_reason:
                    no_upgradable_count = no_upgradable_count + 1

    commits = []
    fetchdir = settings.LAYER_FETCH_DIR
    for item in maintplan.maintenanceplanlayerbranch_set.all():
        layerbranch = item.layerbranch
        layer = layerbranch.layer
        urldir = layer.get_fetch_dir()
        repodir = os.path.join(fetchdir, urldir)
        # FIXME this assumes the correct branch is checked out
        topcommitdesc = utils.runcmd("git log -1 --oneline", repodir).strip()
        commits.append('%s: %s' % (layerbranch.layer.name, topcommitdesc))

    # Render the subject as a template (to allow a bit of flexibility)
    subject = options.subject or maintplan.email_subject
    subject_template = Template(subject)
    current_site = Site.objects.get_current()
    d = Context({
        'maintplan': maintplan,
        'site': current_site,
    })
    subject_content = subject_template.render(d)

    from_email = options._from or maintplan.email_from
    if options.to:
        to_email_list = options.to.split(';')
    elif maintplan.email_to:
        to_email_list = maintplan.email_to.split(';')
    else:
        to_email_list = []

    if not subject:
        logger.error(
            'No subject specified in maintenance plan %s and none specified on command line'
            % maintplan.name)
        sys.exit(1)
    if not from_email:
        logger.error(
            'No sender email address specified in maintenance plan %s and none specified on command line'
            % maintplan.name)
        sys.exit(1)
    if not to_email_list:
        logger.error(
            'No recipient email address specified in maintenance plan %s and none specified on command line'
            % maintplan.name)
        sys.exit(1)

    plaintext = get_template('rrs/report_email.txt')
    site_url = 'http://' + current_site.domain + reverse('rrs_frontpage')
    d = {
        'maintplan': maintplan,
        'site': current_site,
        'site_url': site_url,
        'upgradable_count': (upgradable_count - no_upgradable_count),
        'total_upgradable_count': upgradable_count,
        'commits': commits,
        'recipelines': recipelines,
    }
    text_content = plaintext.render(d)

    msg = EmailMessage(subject_content, text_content, from_email,
                       to_email_list)
    msg.send()
Пример #12
0
def upgrade_history(options, logger):
    from rrs.models import MaintenancePlan, RecipeUpgrade, Release, Milestone, RecipeUpgradeGroupRule

    logger.debug('=== BEGIN; cmdline: %s' % (' '.join(sys.argv)))

    if options.plan:
        maintplans = MaintenancePlan.objects.filter(id=int(options.plan))
        if not maintplans.exists():
            logger.error('No maintenance plan with ID %s found' % options.plan)
            sys.exit(1)
    else:
        maintplans = MaintenancePlan.objects.filter(updates_enabled=True)
        if not maintplans.exists():
            logger.error('No enabled maintenance plans found')
            sys.exit(1)

    if options.regroup:
        for maintplan in maintplans:
            for maintplanbranch in maintplan.maintenanceplanlayerbranch_set.all(
            ):
                layerbranch = maintplanbranch.layerbranch
                if RecipeUpgradeGroupRule.objects.filter(
                        layerbranch=layerbranch).exists():
                    for ru in RecipeUpgrade.objects.filter(
                            recipesymbol__layerbranch=layerbranch):
                        if ru.regroup():
                            ru.save()
        sys.exit(0)

    lockfn = os.path.join(fetchdir, "layerindex.lock")
    lockfile = utils.lock_file(lockfn)
    if not lockfile:
        logger.error("Layer index lock timeout expired")
        sys.exit(1)
    try:
        for maintplan in maintplans:
            # Check releases and milestones
            current = date.today()
            current_release = Release.get_by_date(maintplan, current)
            if current_release:
                current_milestone = Milestone.get_by_release_and_date(
                    current_release, current)
                if not current_milestone:
                    logger.warning(
                        '%s: there is no milestone defined in the latest release (%s) that covers the current date, so up-to-date data will not be visible in the web interface.'
                        % (maintplan, current_release))
            else:
                logger.warning(
                    '%s: there is no release defined that covers the current date, so up-to-date data will not be visible in the web interface.'
                    % maintplan)

            for maintplanbranch in maintplan.maintenanceplanlayerbranch_set.all(
            ):
                layerbranch = maintplanbranch.layerbranch
                if options.fullreload and not options.dry_run:
                    logger.debug('fullreload: deleting upgrade objects')
                    if options.filter_files:
                        RecipeUpgrade.objects.filter(
                            recipesymbol__layerbranch=layerbranch,
                            filepath__startswith=options.filter_files).delete(
                            )
                    else:
                        RecipeUpgrade.objects.filter(
                            recipesymbol__layerbranch=layerbranch).delete()
                layer = layerbranch.layer
                urldir = layer.get_fetch_dir()
                repodir = os.path.join(fetchdir, urldir)
                layerdir = os.path.join(repodir, layerbranch.vcs_subdir)

                if options.commit:
                    initial = False
                    since = options.commit
                    since_option = [
                        '%s^..%s' % (options.commit, options.commit)
                    ]
                elif maintplanbranch.upgrade_rev and not options.fullreload:
                    initial = False
                    since = maintplanbranch.upgrade_date
                    since_option = [
                        '%s..origin/master' % maintplanbranch.upgrade_rev
                    ]
                else:
                    initial = True
                    since = options.since
                    since_option = ['--since=%s' % since, 'origin/master']

                repo = git.Repo(repodir)
                if repo.bare:
                    logger.error('Repository %s is bare, not supported' %
                                 repodir)
                    continue

                commits = utils.runcmd(['git', 'log'] + since_option +
                                       ['--format=%H %ct', '--reverse'],
                                       repodir,
                                       logger=logger)
                commit_list = commits.split('\n')

                bitbake_map = {}

                def remap_range(start, end, replacewith=None):
                    if replacewith is None:
                        replacewith = end
                    bitbake_commits = utils.runcmd(
                        ['git', 'rev-list',
                         '%s^..%s^' % (start, end)],
                        bitbakepath,
                        logger=logger)
                    bitbake_commit_list = bitbake_commits.splitlines()
                    for commit in bitbake_commit_list:
                        bitbake_map[commit] = replacewith

                # Filter out some bad commits
                remap_range('fef18b445c0cb6b266cd939b9c78d7cbce38663f',
                            '39780b1ccbd76579db0fc6fb9369c848a3bafa9d')
                remap_range('5796ed550d127853808f38257f8dcc8c1cf59342',
                            '547128731e62b36d2271c4390b3fee2b16c535dc')

                if options.stop_commit and (options.stop_commit not in [
                        x.split()[0] for x in commit_list
                ]):
                    logger.error('Stop commit %s is not in repository %s' %
                                 (options.stop_commit, repodir))
                    sys.exit(1)

                if initial:
                    logger.debug("Adding initial upgrade history ....")

                    ct, ctepoch = commit_list.pop(0).split()
                    ctdate = datetime.fromtimestamp(int(ctepoch))
                    run_internal(maintplanbranch,
                                 ct,
                                 ctdate,
                                 options,
                                 logger,
                                 bitbake_map,
                                 initial=True)

                if layerbranch.vcs_subdir:
                    layersubdir_start = layerbranch.vcs_subdir
                    if not layersubdir_start.endswith('/'):
                        layersubdir_start += '/'
                else:
                    layersubdir_start = ''
                logger.debug("Adding upgrade history from %s to %s ..." %
                             (since, datetime.today().strftime("%Y-%m-%d")))
                for item in commit_list:
                    if item:
                        ct, ctepoch = item.split()
                        if ct == options.stop_commit:
                            logger.debug('Stopping at requested commit %s' %
                                         ct)
                            break
                        ctdate = datetime.fromtimestamp(int(ctepoch))
                        commitobj = repo.commit(ct)
                        touches_recipe = False
                        for parent in commitobj.parents:
                            diff = parent.diff(commitobj)
                            for diffitem in diff:
                                if layersubdir_start and not (
                                        diffitem.a_path.startswith(
                                            layersubdir_start) or diffitem.
                                        b_path.startswith(layersubdir_start)):
                                    # Not in this layer, skip it
                                    continue
                                if options.filter_files and not (
                                        diffitem.a_path.startswith(
                                            options.filter_files)
                                        or diffitem.b_path.startswith(
                                            options.filter_files)):
                                    # Doesn't match path filter
                                    continue
                                if diffitem.a_path.endswith(
                                    ('.bb',
                                     '.inc')) or diffitem.b_path.endswith(
                                         ('.bb', '.inc')):
                                    # We need to look at this commit
                                    touches_recipe = True
                                    break
                            if touches_recipe:
                                break
                        if not touches_recipe:
                            # No recipes in the layer changed in this commit
                            # NOTE: Whilst it's possible that a change to a class might alter what's
                            # in the recipe, we can ignore that since we are only concerned with actual
                            # upgrades which would always require some sort of change to the recipe
                            # or an include file, so we can safely skip commits that don't do that
                            logger.debug("Skipping commit %s" % ct)
                            continue
                        logger.debug("Analysing commit %s ..." % ct)
                        run_internal(maintplanbranch, ct, ctdate, options,
                                     logger, bitbake_map)
                        if not (options.dry_run or options.filter_files):
                            maintplanbranch.upgrade_rev = ct
                            maintplanbranch.upgrade_date = ctdate
                            maintplanbranch.save()
    finally:
        utils.unlock_file(lockfile)
def generate_history(options, layerbranch_id, commit, logger):
    from layerindex.models import LayerBranch
    from rrs.models import Release, RecipeUpgrade
    layerbranch = LayerBranch.objects.get(id=layerbranch_id)

    fetchdir = settings.LAYER_FETCH_DIR
    if not fetchdir:
        logger.error("Please set LAYER_FETCH_DIR in settings.py")
        sys.exit(1)

    layer = layerbranch.layer
    urldir = str(layer.get_fetch_dir())
    repodir = os.path.join(fetchdir, urldir)
    layerdir = os.path.join(repodir, str(layerbranch.vcs_subdir))

    if layerbranch.vcs_subdir:
        layersubdir_start = layerbranch.vcs_subdir
        if not layersubdir_start.endswith('/'):
            layersubdir_start += '/'
    else:
        layersubdir_start = ''

    repo = git.Repo(repodir)
    if repo.bare:
        logger.error('Repository %s is bare, not supported' % repodir)
        sys.exit(1)

    commitdate = checkout_layer_deps(layerbranch, commit, fetchdir, logger)

    if options.initial:
        fns = None
        deleted = []
        moved = []
    else:
        if options.filter_files:
            filepath_start = options.filter_files
        else:
            filepath_start = layersubdir_start
        fns, deleted, moved = _get_recipes_filenames(commit, repo, repodir, filepath_start, logger)
        if not (fns or deleted or moved):
            return

    # setup bitbake
    bitbakepath = os.path.join(fetchdir, 'bitbake')
    if options.bitbake_rev:
        bitbake_rev = options.bitbake_rev
        if not re.match('^[0-9a-f]{40}$', bitbake_rev):
            # Branch name, need to check out detached
            bitbake_rev = 'origin/%s' % bitbake_rev
    else:
        bitbake_rev = utils.runcmd(['git', 'rev-list', '-1', '--before=%s' % commitdate, 'origin/master'], bitbakepath, logger=logger).strip()
    utils.checkout_repo(bitbakepath, bitbake_rev, logger)
    sys.path.insert(0, os.path.join(bitbakepath, 'lib'))

    (tinfoil, d, recipes, tempdir) = load_recipes(layerbranch, bitbakepath,
                        fetchdir, settings, logger, recipe_files=fns,
                        nocheckout=True)
    try:

        if options.initial:
            title = options.initial
            info = 'No maintainer;;' + utils.runcmd(['git', 'log', '--format=%ad;%cd', '--date=rfc', '-n', '1', commit], destdir=repodir, logger=logger)
            recordcommit = ''
        else:
            title = utils.runcmd(['git', 'log', '--format=%s', '-n', '1', commit],
                                            repodir, logger=logger)
            info = utils.runcmd(['git', 'log', '--format=%an;%ae;%ad;%cd', '--date=rfc', '-n', '1', commit], destdir=repodir, logger=logger)
            recordcommit = commit

        fn_data = {}
        for recipe_data in recipes:
            fn = os.path.relpath(recipe_data.getVar('FILE', True), repodir)
            fn_data[fn] = recipe_data

        seen_pns = []
        try:
            with transaction.atomic():
                # Handle recipes where PN has changed
                for a, b in moved:
                    logger.debug('Move %s -> %s' % (a,b))
                    rus = RecipeUpgrade.objects.filter(recipesymbol__layerbranch=layerbranch, filepath=a).order_by('-commit_date', '-id')
                    recipe_data = fn_data.get(b, None)
                    if recipe_data:
                        pn = recipe_data.getVar('PN', True)
                        ru = rus.first()
                        if ru and ru.recipesymbol.pn != pn:
                            # PN has been changed! We need to mark the old record as deleted
                            logger.debug('PN changed (with move): %s -> %s' % (ru.recipesymbol.pn, pn))
                            if a not in deleted:
                                deleted.append(a)
                    else:
                        logger.warning('Unable to find parsed data for recipe %s' % b)

                # Handle recipes that exist at this point in time (which may have upgraded)
                for recipe_data in recipes:
                    pn = recipe_data.getVar('PN', True)
                    filepath = os.path.relpath(recipe_data.getVar('FILE', True), repodir)
                    # Check if PN has changed internally
                    rus = RecipeUpgrade.objects.filter(recipesymbol__layerbranch=layerbranch, filepath=filepath).order_by('-commit_date', '-id')
                    deleted_pns = rus.filter(upgrade_type__in=['R', 'N']).values_list('recipesymbol__pn', flat=True).distinct()
                    for ru in rus:
                        if ru.recipesymbol.pn != pn and ru.recipesymbol.pn not in deleted_pns and ru.upgrade_type not in ['R', 'N']:
                            # PN changed (set within recipe), we need to mark the old recipe as deleted
                            logger.debug('PN changed (without move): %s -> %s' % (ru.recipesymbol.pn, pn))
                            _save_upgrade(ru.recipesymbol, layerbranch, ru.version, ru.srcrev, ru.license, recordcommit, title, info, ru.filepath, logger, upgrade_type='R')
                    orig_filepath = None
                    for a, b in moved:
                        if b == filepath:
                            orig_filepath = a
                            break
                    _create_upgrade(recipe_data, layerbranch, recordcommit, title,
                            info, filepath, logger, initial=options.initial, orig_filepath=orig_filepath)
                    seen_pns.append(pn)

                # Handle recipes that have been moved without it being an upgrade/delete
                for a, b in moved:
                    if a not in deleted:
                        rus = RecipeUpgrade.objects.filter(recipesymbol__layerbranch=layerbranch, filepath=a).order_by('-commit_date', '-id')
                        if rus:
                            ru = rus.first()
                            if not RecipeUpgrade.objects.filter(recipesymbol=ru.recipesymbol, filepath=b).exists():
                                # Need to record the move, otherwise we won't be able to
                                # find the record if we need to mark the recipe as deleted later
                                _save_upgrade(ru.recipesymbol, layerbranch, ru.version, ru.srcrev, ru.license, recordcommit, title, info, b, logger, upgrade_type='M', orig_filepath=a)

                # Handle deleted recipes
                for df in deleted:
                    rus = RecipeUpgrade.objects.filter(recipesymbol__layerbranch=layerbranch, filepath=df).order_by('-commit_date', '-id')
                    for ru in rus:
                        other_rus = RecipeUpgrade.objects.filter(recipesymbol=ru.recipesymbol, commit_date__gte=ru.commit_date).exclude(filepath=df).order_by('-commit_date', '-id')
                        # We make a distinction between deleting just one version and the entire recipe being deleted
                        upgrade_type = 'R'
                        for other_ru in other_rus:
                            if other_ru.upgrade_type == 'R':
                                logger.debug('There is a delete: %s' % other_ru)
                                upgrade_type = ''
                                break
                            if os.path.exists(os.path.join(repodir, other_ru.filepath)):
                                upgrade_type = 'N'
                        if not upgrade_type:
                            continue
                        if ru.upgrade_type != upgrade_type and ru.recipesymbol.pn not in seen_pns:
                            if upgrade_type == 'R':
                                finalmsg = ' [FINAL]'
                            else:
                                finalmsg = ''
                            logger.debug("%s: marking as deleted%s (%s)" % (ru.recipesymbol.pn, finalmsg, ru.filepath))
                            _save_upgrade(ru.recipesymbol, layerbranch, ru.version, ru.srcrev, ru.license, recordcommit, title, info, df, logger, upgrade_type=upgrade_type)
                            break

                if options.dry_run:
                    raise DryRunRollbackException
        except DryRunRollbackException:
            pass

    finally:
        if tinfoil and hasattr(tinfoil, 'shutdown') and (LooseVersion(bb.__version__) > LooseVersion("1.27")):
            tinfoil.shutdown()
        utils.rmtree_force(tempdir)