def run_lintian_fixer( # noqa: C901 local_tree: WorkingTree, fixer: Fixer, committer: Optional[str] = None, update_changelog: Union[bool, Callable[[], bool]] = True, compat_release: Optional[str] = None, minimum_certainty: Optional[str] = None, trust_package: bool = False, allow_reformatting: bool = False, dirty_tracker=None, subpath: str = "", net_access: bool = True, opinionated: Optional[bool] = None, diligence: int = 0, timestamp: Optional[datetime] = None, basis_tree: Optional[Tree] = None, changes_by: str = "lintian-brush", ): """Run a lintian fixer on a tree. Args: local_tree: WorkingTree object basis_tree: Tree fixer: Fixer object to apply committer: Optional committer (name and email) update_changelog: Whether to add a new entry to the changelog compat_release: Minimum release that the package should be usable on (e.g. 'stable' or 'unstable') minimum_certainty: How certain the fixer should be about its changes. trust_package: Whether to run code from the package if necessary allow_reformatting: Whether to allow reformatting of changed files dirty_tracker: Optional object that can be used to tell if the tree has been changed. subpath: Path in tree to operate on net_access: Whether to allow accessing external services opinionated: Whether to be opinionated diligence: Level of diligence Returns: tuple with set of FixerResult, summary of the changes """ if basis_tree is None: basis_tree = local_tree.basis_tree() changelog_path = os.path.join(subpath, "debian/changelog") try: with local_tree.get_file(changelog_path) as f: cl = Changelog(f, max_blocks=1) except NoSuchFile: raise NotDebianPackage(local_tree, subpath) package = cl.package if cl.distributions == "UNRELEASED": current_version = cl.version else: current_version = cl.version increment_version(current_version) if compat_release is None: compat_release = "sid" if minimum_certainty is None: minimum_certainty = DEFAULT_MINIMUM_CERTAINTY logger.debug('Running fixer %r', fixer) try: result = fixer.run( local_tree.abspath(subpath), package=package, current_version=current_version, compat_release=compat_release, minimum_certainty=minimum_certainty, trust_package=trust_package, allow_reformatting=allow_reformatting, net_access=net_access, opinionated=opinionated, diligence=diligence, ) except BaseException: reset_tree(local_tree, basis_tree, subpath, dirty_tracker=dirty_tracker) raise if not certainty_sufficient(result.certainty, minimum_certainty): reset_tree(local_tree, basis_tree, subpath, dirty_tracker=dirty_tracker) raise NotCertainEnough( fixer, result.certainty, minimum_certainty, overridden_lintian_issues=result.overridden_lintian_issues) specific_files: Optional[List[str]] if dirty_tracker: relpaths = dirty_tracker.relpaths() # Sort paths so that directories get added before the files they # contain (on VCSes where it matters) local_tree.add( [ p for p in sorted(relpaths) if local_tree.has_filename(p) and not local_tree.is_ignored(p) ] ) specific_files = [p for p in relpaths if local_tree.is_versioned(p)] if not specific_files: raise NoChanges( fixer, "Script didn't make any changes", result.overridden_lintian_issues) else: local_tree.smart_add([local_tree.abspath(subpath)]) specific_files = [subpath] if subpath else None if local_tree.supports_setting_file_ids(): RenameMap.guess_renames(basis_tree, local_tree, dry_run=False) changes = list( local_tree.iter_changes( basis_tree, specific_files=specific_files, want_unversioned=False, require_versioned=True, ) ) if len(local_tree.get_parent_ids()) <= 1 and not changes: raise NoChanges( fixer, "Script didn't make any changes", result.overridden_lintian_issues) if not result.description: raise DescriptionMissing(fixer) lines = result.description.splitlines() summary = lines[0] details = list(itertools.takewhile(lambda line: line, lines[1:])) # If there are upstream changes in a non-native package, perhaps # export them to debian/patches if has_non_debian_changes(changes, subpath) and current_version.debian_revision: try: patch_name, specific_files = _upstream_changes_to_patch( local_tree, basis_tree, dirty_tracker, subpath, result.patch_name or fixer.name, result.description, timestamp=timestamp, ) except BaseException: reset_tree(local_tree, basis_tree, subpath, dirty_tracker=dirty_tracker) raise summary = "Add patch %s: %s" % (patch_name, summary) if only_changes_last_changelog_block( local_tree, basis_tree, changelog_path, changes ): # If the script only changed the last entry in the changelog, # don't update the changelog update_changelog = False if callable(update_changelog): update_changelog = update_changelog() if update_changelog: from .changelog import add_changelog_entry add_changelog_entry(local_tree, changelog_path, [summary] + details) if specific_files: specific_files.append(changelog_path) description = result.description + "\n" description += "\n" description += "Changes-By: %s\n" % changes_by for tag in result.fixed_lintian_tags: description += "Fixes: lintian: %s\n" % tag description += "See-also: https://lintian.debian.org/tags/%s.html\n" % tag if committer is None: committer = get_committer(local_tree) revid = local_tree.commit( description, allow_pointless=False, reporter=NullCommitReporter(), committer=committer, specific_files=specific_files, ) result.revision_id = revid # TODO(jelmer): Support running sbuild & verify lintian warning is gone? return result, summary
def run_lintian_fixers( # noqa: C901 local_tree: WorkingTree, fixers: List[Fixer], update_changelog: bool = True, verbose: bool = False, committer: Optional[str] = None, compat_release: Optional[str] = None, minimum_certainty: Optional[str] = None, trust_package: bool = False, allow_reformatting: bool = False, use_inotify: Optional[bool] = None, subpath: str = "", net_access: bool = True, opinionated: Optional[bool] = None, diligence: int = 0, ): """Run a set of lintian fixers on a tree. Args: local_tree: WorkingTree object fixers: A set of Fixer objects update_changelog: Whether to add an entry to the changelog verbose: Whether to be verbose committer: Optional committer (name and email) compat_release: Minimum release that the package should be usable on (e.g. 'sid' or 'stretch') minimum_certainty: How certain the fixer should be about its changes. trust_package: Whether to run code from the package if necessary allow_reformatting: Whether to allow reformatting of changed files use_inotify: Use inotify to watch changes (significantly improves performance). Defaults to None (automatic) subpath: Subpath in the tree in which the package lives net_access: Whether to allow network access opinionated: Whether to be opinionated diligence: Level of diligence Returns: Tuple with two lists: 1. list of tuples with (lintian-tag, certainty, description) of fixers that ran 2. dictionary mapping fixer names for fixers that failed to run to the error that occurred """ basis_tree = local_tree.basis_tree() check_clean_tree(local_tree, basis_tree=basis_tree, subpath=subpath) fixers = list(fixers) dirty_tracker = get_dirty_tracker( local_tree, subpath=subpath, use_inotify=use_inotify ) # If we don't know whether to update the changelog, then find out *once* if update_changelog is None: changelog_behaviour = None def update_changelog(): nonlocal update_changelog, changelog_behaviour changelog_behaviour = determine_update_changelog( local_tree, os.path.join(subpath, "debian")) update_changelog = changelog_behaviour.update_changelog return update_changelog else: changelog_behaviour = None ret = ManyResult() with trange(len(fixers), leave=False, disable=None) as t: for i, fixer in enumerate(fixers): t.set_description("Running fixer %s" % fixer) t.update() start = time.time() if dirty_tracker: dirty_tracker.mark_clean() try: result, summary = run_lintian_fixer( local_tree, fixer, update_changelog=update_changelog, committer=committer, compat_release=compat_release, minimum_certainty=minimum_certainty, trust_package=trust_package, allow_reformatting=allow_reformatting, dirty_tracker=dirty_tracker, subpath=subpath, net_access=net_access, opinionated=opinionated, diligence=diligence, basis_tree=basis_tree, ) except FormattingUnpreservable as e: ret.formatting_unpreservable[fixer.name] = e.path if verbose: logging.info( "Fixer %r was unable to preserve formatting of %s.", fixer.name, e.path, ) except FixerFailed as e: ret.failed_fixers[fixer.name] = e if verbose: logging.info("Fixer %r failed to run.", fixer.name) sys.stderr.write(str(e)) except MemoryError as e: ret.failed_fixers[fixer.name] = e if verbose: logging.info("Run out of memory while running fixer %r.", fixer.name) except NotCertainEnough as e: if verbose: logging.info( "Fixer %r made changes but not high enough " "certainty (was %r, needed %r). (took: %.2fs)", fixer.name, e.certainty, e.minimum_certainty, time.time() - start, ) except FailedPatchManipulation as e: if verbose: logging.info("Unable to manipulate upstream patches: %s", e.args[2]) ret.failed_fixers[fixer.name] = e except NoChanges as e: if verbose: logging.info( "Fixer %r made no changes. (took: %.2fs)", fixer.name, time.time() - start, ) ret.overridden_lintian_issues.extend( e.overridden_lintian_issues) else: if verbose: logging.info( "Fixer %r made changes. (took %.2fs)", fixer.name, time.time() - start, ) ret.success.append((result, summary)) basis_tree = local_tree.basis_tree() if changelog_behaviour: ret.changelog_behaviour = changelog_behaviour return ret