| # -*- coding:utf-8 -*- |
| |
| from __future__ import print_function, unicode_literals |
| |
| import errno |
| import io |
| import logging |
| import platform |
| import re |
| import signal |
| import subprocess |
| import sys |
| import tempfile |
| from itertools import chain |
| |
| from _emerge.UserQuery import UserQuery |
| |
| import portage |
| from portage import cvstree |
| from portage import os |
| from portage import _encodings |
| from portage import _unicode_encode |
| from portage.output import ( |
| bold, create_color_func, green, red) |
| from portage.package.ebuild.digestgen import digestgen |
| from portage.process import find_binary, spawn |
| from portage.util import writemsg_level |
| |
| from repoman._subprocess import repoman_popen, repoman_getstatusoutput |
| from repoman.errors import err |
| from repoman.gpg import gpgsign, need_signature |
| from repoman import utilities |
| from repoman.vcs.vcs import git_supports_gpg_sign, vcs_files_to_cps |
| |
| bad = create_color_func("BAD") |
| |
| |
| class Actions(object): |
| '''Handles post check result output and performs |
| the various vcs activities for committing the results''' |
| |
| def __init__(self, repo_settings, options, scanner, vcs_settings): |
| self.repo_settings = repo_settings |
| self.options = options |
| self.scanner = scanner |
| self.vcs_settings = vcs_settings |
| self.repoman_settings = repo_settings.repoman_settings |
| self.suggest = { |
| 'ignore_masked': False, |
| 'include_dev': False, |
| } |
| if scanner.have['pmasked'] and not (options.without_mask or options.ignore_masked): |
| self.suggest['ignore_masked'] = True |
| if scanner.have['dev_keywords'] and not options.include_dev: |
| self.suggest['include_dev'] = True |
| |
| |
| def inform(self, can_force, result): |
| '''Inform the user of all the problems found''' |
| if ((self.suggest['ignore_masked'] or self.suggest['include_dev']) |
| and not self.options.quiet): |
| self._suggest() |
| if self.options.mode != 'commit': |
| self._non_commit(result) |
| return False |
| else: |
| self._fail(result, can_force) |
| if self.options.pretend: |
| utilities.repoman_sez( |
| "\"So, you want to play it safe. Good call.\"\n") |
| return True |
| |
| |
| def perform(self, qa_output): |
| myunadded, mydeleted = self._vcs_unadded() |
| |
| myautoadd = self._vcs_autoadd(myunadded) |
| |
| self._vcs_deleted(mydeleted) |
| |
| changes = self.get_vcs_changed(mydeleted) |
| |
| mynew, mychanged, myremoved, no_expansion, expansion = changes |
| |
| # Manifests need to be regenerated after all other commits, so don't commit |
| # them now even if they have changed. |
| mymanifests = set() |
| myupdates = set() |
| for f in mychanged + mynew: |
| if "Manifest" == os.path.basename(f): |
| mymanifests.add(f) |
| else: |
| myupdates.add(f) |
| myupdates.difference_update(myremoved) |
| myupdates = list(myupdates) |
| mymanifests = list(mymanifests) |
| myheaders = [] |
| |
| commitmessage = self.options.commitmsg |
| if self.options.commitmsgfile: |
| try: |
| f = io.open( |
| _unicode_encode( |
| self.options.commitmsgfile, |
| encoding=_encodings['fs'], errors='strict'), |
| mode='r', encoding=_encodings['content'], errors='replace') |
| commitmessage = f.read() |
| f.close() |
| del f |
| except (IOError, OSError) as e: |
| if e.errno == errno.ENOENT: |
| portage.writemsg( |
| "!!! File Not Found:" |
| " --commitmsgfile='%s'\n" % self.options.commitmsgfile) |
| else: |
| raise |
| if not commitmessage or not commitmessage.strip(): |
| commitmessage = self.get_new_commit_message(qa_output) |
| |
| commitmessage = commitmessage.rstrip() |
| |
| myupdates, broken_changelog_manifests = self.changelogs( |
| myupdates, mymanifests, myremoved, mychanged, myautoadd, |
| mynew, commitmessage) |
| |
| commit_footer = self.get_commit_footer() |
| commitmessage += commit_footer |
| |
| print("* %s files being committed..." % green(str(len(myupdates))), end=' ') |
| |
| if self.vcs_settings.vcs not in ('cvs', 'svn'): |
| # With git, bzr and hg, there's never any keyword expansion, so |
| # there's no need to regenerate manifests and all files will be |
| # committed in one big commit at the end. |
| print() |
| elif not self.repo_settings.repo_config.thin_manifest: |
| self.thick_manifest(myupdates, myheaders, no_expansion, expansion) |
| |
| logging.info("myupdates: %s", myupdates) |
| logging.info("myheaders: %s", myheaders) |
| |
| uq = UserQuery(self.options) |
| if self.options.ask and uq.query('Commit changes?', True) != 'Yes': |
| print("* aborting commit.") |
| sys.exit(128 + signal.SIGINT) |
| |
| # Handle the case where committed files have keywords which |
| # will change and need a priming commit before the Manifest |
| # can be committed. |
| if (myupdates or myremoved) and myheaders: |
| self.priming_commit(myupdates, myremoved, commitmessage) |
| |
| # When files are removed and re-added, the cvs server will put /Attic/ |
| # inside the $Header path. This code detects the problem and corrects it |
| # so that the Manifest will generate correctly. See bug #169500. |
| # Use binary mode in order to avoid potential character encoding issues. |
| self.clear_attic(myheaders) |
| |
| if self.scanner.repolevel == 1: |
| utilities.repoman_sez( |
| "\"You're rather crazy... " |
| "doing the entire repository.\"\n") |
| |
| if self.vcs_settings.vcs in ('cvs', 'svn') and (myupdates or myremoved): |
| for x in sorted(vcs_files_to_cps( |
| chain(myupdates, myremoved, mymanifests), |
| self.scanner.repolevel, self.scanner.reposplit, self.scanner.categories)): |
| self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) |
| digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) |
| |
| elif broken_changelog_manifests: |
| for x in broken_changelog_manifests: |
| self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) |
| digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) |
| |
| if self.repo_settings.sign_manifests: |
| self.sign_manifest(myupdates, myremoved, mymanifests) |
| |
| if self.vcs_settings.vcs == 'git': |
| # It's not safe to use the git commit -a option since there might |
| # be some modified files elsewhere in the working tree that the |
| # user doesn't want to commit. Therefore, call git update-index |
| # in order to ensure that the index is updated with the latest |
| # versions of all new and modified files in the relevant portion |
| # of the working tree. |
| myfiles = mymanifests + myupdates |
| myfiles.sort() |
| update_index_cmd = ["git", "update-index"] |
| update_index_cmd.extend(f.lstrip("./") for f in myfiles) |
| if self.options.pretend: |
| print("(%s)" % (" ".join(update_index_cmd),)) |
| else: |
| retval = spawn(update_index_cmd, env=os.environ) |
| if retval != os.EX_OK: |
| writemsg_level( |
| "!!! Exiting on %s (shell) " |
| "error code: %s\n" % (self.vcs_settings.vcs, retval), |
| level=logging.ERROR, noiselevel=-1) |
| sys.exit(retval) |
| |
| self.add_manifest(mymanifests, myheaders, myupdates, myremoved, commitmessage) |
| |
| if self.options.quiet: |
| return |
| print() |
| if self.vcs_settings.vcs: |
| print("Commit complete.") |
| else: |
| print( |
| "repoman was too scared" |
| " by not seeing any familiar version control file" |
| " that he forgot to commit anything") |
| utilities.repoman_sez( |
| "\"If everyone were like you, I'd be out of business!\"\n") |
| return |
| |
| |
| def _suggest(self): |
| print() |
| if self.suggest['ignore_masked']: |
| print(bold( |
| "Note: use --without-mask to check " |
| "KEYWORDS on dependencies of masked packages")) |
| |
| if self.suggest['include_dev']: |
| print(bold( |
| "Note: use --include-dev (-d) to check " |
| "dependencies for 'dev' profiles")) |
| print() |
| |
| |
| def _non_commit(self, result): |
| if result['full']: |
| print(bold("Note: type \"repoman full\" for a complete listing.")) |
| if result['warn'] and not result['fail']: |
| if self.options.quiet: |
| print(bold("Non-Fatal QA errors found")) |
| else: |
| utilities.repoman_sez( |
| "\"You're only giving me a partial QA payment?\n" |
| " I'll take it this time, but I'm not happy.\"" |
| ) |
| elif not result['fail']: |
| if self.options.quiet: |
| print("No QA issues found") |
| else: |
| utilities.repoman_sez( |
| "\"If everyone were like you, I'd be out of business!\"") |
| elif result['fail']: |
| print(bad("Please fix these important QA issues first.")) |
| if not self.options.quiet: |
| utilities.repoman_sez( |
| "\"Make your QA payment on time" |
| " and you'll never see the likes of me.\"\n") |
| sys.exit(1) |
| |
| |
| def _fail(self, result, can_force): |
| if result['fail'] and can_force and self.options.force and not self.options.pretend: |
| utilities.repoman_sez( |
| " \"You want to commit even with these QA issues?\n" |
| " I'll take it this time, but I'm not happy.\"\n") |
| elif result['fail']: |
| if self.options.force and not can_force: |
| print(bad( |
| "The --force option has been disabled" |
| " due to extraordinary issues.")) |
| print(bad("Please fix these important QA issues first.")) |
| utilities.repoman_sez( |
| "\"Make your QA payment on time" |
| " and you'll never see the likes of me.\"\n") |
| sys.exit(1) |
| |
| |
| def _vcs_unadded(self): |
| myunadded = [] |
| mydeleted = [] |
| if self.vcs_settings.vcs == "cvs": |
| try: |
| myvcstree = portage.cvstree.getentries("./", recursive=1) |
| myunadded = portage.cvstree.findunadded( |
| myvcstree, recursive=1, basedir="./") |
| except SystemExit: |
| raise # TODO propagate this |
| except: |
| err("Error retrieving CVS tree; exiting.") |
| if self.vcs_settings.vcs == "svn": |
| try: |
| with repoman_popen("svn status --no-ignore") as f: |
| svnstatus = f.readlines() |
| myunadded = [ |
| "./" + elem.rstrip().split()[1] |
| for elem in svnstatus |
| if elem.startswith("?") or elem.startswith("I")] |
| except SystemExit: |
| raise # TODO propagate this |
| except: |
| err("Error retrieving SVN info; exiting.") |
| if self.vcs_settings.vcs == "git": |
| # get list of files not under version control or missing |
| myf = repoman_popen("git ls-files --others") |
| myunadded = ["./" + elem[:-1] for elem in myf] |
| myf.close() |
| if self.vcs_settings.vcs == "bzr": |
| try: |
| with repoman_popen("bzr status -S .") as f: |
| bzrstatus = f.readlines() |
| myunadded = [ |
| "./" + elem.rstrip().split()[1].split('/')[-1:][0] |
| for elem in bzrstatus |
| if elem.startswith("?") or elem[0:2] == " D"] |
| except SystemExit: |
| raise # TODO propagate this |
| except: |
| err("Error retrieving bzr info; exiting.") |
| if self.vcs_settings.vcs == "hg": |
| with repoman_popen("hg status --no-status --unknown .") as f: |
| myunadded = f.readlines() |
| myunadded = ["./" + elem.rstrip() for elem in myunadded] |
| |
| # Mercurial doesn't handle manually deleted files as removed from |
| # the repository, so the user need to remove them before commit, |
| # using "hg remove [FILES]" |
| with repoman_popen("hg status --no-status --deleted .") as f: |
| mydeleted = f.readlines() |
| mydeleted = ["./" + elem.rstrip() for elem in mydeleted] |
| return myunadded, mydeleted |
| |
| |
| def _vcs_autoadd(self, myunadded): |
| myautoadd = [] |
| if myunadded: |
| for x in range(len(myunadded) - 1, -1, -1): |
| xs = myunadded[x].split("/") |
| if self.repo_settings.repo_config.find_invalid_path_char(myunadded[x]) != -1: |
| # The Manifest excludes this file, |
| # so it's safe to ignore. |
| del myunadded[x] |
| elif xs[-1] == "files": |
| print("!!! files dir is not added! Please correct this.") |
| sys.exit(-1) |
| elif xs[-1] == "Manifest": |
| # It's a manifest... auto add |
| myautoadd += [myunadded[x]] |
| del myunadded[x] |
| |
| if myunadded: |
| print(red( |
| "!!! The following files are in your local tree" |
| " but are not added to the master")) |
| print(red( |
| "!!! tree. Please remove them from the local tree" |
| " or add them to the master tree.")) |
| for x in myunadded: |
| print(" ", x) |
| print() |
| print() |
| sys.exit(1) |
| return myautoadd |
| |
| |
| def _vcs_deleted(self, mydeleted): |
| if self.vcs_settings.vcs == "hg" and mydeleted: |
| print(red( |
| "!!! The following files are removed manually" |
| " from your local tree but are not")) |
| print(red( |
| "!!! removed from the repository." |
| " Please remove them, using \"hg remove [FILES]\".")) |
| for x in mydeleted: |
| print(" ", x) |
| print() |
| print() |
| sys.exit(1) |
| |
| |
| def get_vcs_changed(self, mydeleted): |
| '''Holding function which calls the approriate VCS module for the data''' |
| changed = ([], [], [], [], []) |
| if self.vcs_settings.vcs: |
| vcs_module = getattr(self, '_get_changed_%s_' % self.vcs_settings.vcs) |
| changed = vcs_module(mydeleted) |
| mynew, mychanged, myremoved, no_expansion, expansion = changed |
| |
| a_file_is_changed = mychanged or mynew or myremoved |
| a_file_is_deleted_hg = self.vcs_settings.vcs == "hg" and mydeleted |
| |
| if not (a_file_is_changed or a_file_is_deleted_hg): |
| utilities.repoman_sez( |
| "\"Doing nothing is not always good for QA.\"") |
| print() |
| print("(Didn't find any changed files...)") |
| print() |
| sys.exit(1) |
| return changed |
| |
| |
| def _get_changed_cvs_(self, mydeleted): |
| mycvstree = cvstree.getentries("./", recursive=1) |
| mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./") |
| mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./") |
| myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./") |
| bin_blob_pattern = re.compile("^-kb$") |
| no_expansion = set(portage.cvstree.findoption( |
| mycvstree, bin_blob_pattern, recursive=1, basedir="./")) |
| expansion = {} |
| return (mynew, mychanged, myremoved, no_expansion, expansion) |
| |
| def _get_changed_svn_(self, mydeleted): |
| with repoman_popen("svn status") as f: |
| svnstatus = f.readlines() |
| mychanged = [ |
| "./" + elem.split()[-1:][0] |
| for elem in svnstatus |
| if (elem[:1] in "MR" or elem[1:2] in "M")] |
| mynew = [ |
| "./" + elem.split()[-1:][0] |
| for elem in svnstatus |
| if elem.startswith("A")] |
| myremoved = [ |
| "./" + elem.split()[-1:][0] |
| for elem in svnstatus |
| if elem.startswith("D")] |
| # Subversion expands keywords specified in svn:keywords properties. |
| with repoman_popen("svn propget -R svn:keywords") as f: |
| props = f.readlines() |
| expansion = dict( |
| ("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) |
| for prop in props if " - " in prop) |
| no_expansion = set() |
| return (mynew, mychanged, myremoved, no_expansion, expansion) |
| |
| def _get_changed_git_(self, mydeleted): |
| with repoman_popen( |
| "git diff-index --name-only " |
| "--relative --diff-filter=M HEAD") as f: |
| mychanged = f.readlines() |
| mychanged = ["./" + elem[:-1] for elem in mychanged] |
| with repoman_popen( |
| "git diff-index --name-only " |
| "--relative --diff-filter=A HEAD") as f: |
| mynew = f.readlines() |
| mynew = ["./" + elem[:-1] for elem in mynew] |
| with repoman_popen( |
| "git diff-index --name-only " |
| "--relative --diff-filter=D HEAD") as f: |
| myremoved = f.readlines() |
| myremoved = ["./" + elem[:-1] for elem in myremoved] |
| no_expansion = set() |
| expansion = {} |
| return (mynew, mychanged, myremoved, no_expansion, expansion) |
| |
| def _get_changed_bzr_(self, mydeleted): |
| with repoman_popen("bzr status -S .") as f: |
| bzrstatus = f.readlines() |
| mychanged = [ |
| "./" + elem.split()[-1:][0].split('/')[-1:][0] |
| for elem in bzrstatus |
| if elem and elem[1:2] == "M"] |
| mynew = [ |
| "./" + elem.split()[-1:][0].split('/')[-1:][0] |
| for elem in bzrstatus |
| if elem and (elem[1:2] in "NK" or elem[0:1] == "R")] |
| myremoved = [ |
| "./" + elem.split()[-1:][0].split('/')[-1:][0] |
| for elem in bzrstatus |
| if elem.startswith("-")] |
| myremoved = [ |
| "./" + elem.split()[-3:-2][0].split('/')[-1:][0] |
| for elem in bzrstatus |
| if elem and (elem[1:2] == "K" or elem[0:1] == "R")] |
| # Bazaar expands nothing. |
| no_expansion = set() |
| expansion = {} |
| return (mynew, mychanged, myremoved, no_expansion, expansion) |
| |
| def _get_changed_hg_(self, mydeleted): |
| with repoman_popen("hg status --no-status --modified .") as f: |
| mychanged = f.readlines() |
| mychanged = ["./" + elem.rstrip() for elem in mychanged] |
| |
| with repoman_popen("hg status --no-status --added .") as f: |
| mynew = f.readlines() |
| mynew = ["./" + elem.rstrip() for elem in mynew] |
| |
| with repoman_popen("hg status --no-status --removed .") as f: |
| myremoved = f.readlines() |
| myremoved = ["./" + elem.rstrip() for elem in myremoved] |
| no_expansion = set() |
| expansion = {} |
| return (mynew, mychanged, myremoved, no_expansion, expansion) |
| |
| |
| def get_commit_footer(self): |
| portage_version = getattr(portage, "VERSION", None) |
| gpg_key = self.repoman_settings.get("PORTAGE_GPG_KEY", "") |
| dco_sob = self.repoman_settings.get("DCO_SIGNED_OFF_BY", "") |
| report_options = [] |
| if self.options.force: |
| report_options.append("--force") |
| if self.options.ignore_arches: |
| report_options.append("--ignore-arches") |
| if self.scanner.include_arches is not None: |
| report_options.append( |
| "--include-arches=\"%s\"" % |
| " ".join(sorted(self.scanner.include_arches))) |
| |
| if portage_version is None: |
| sys.stderr.write("Failed to insert portage version in message!\n") |
| sys.stderr.flush() |
| portage_version = "Unknown" |
| # Use new footer only for git (see bug #438364). |
| if self.vcs_settings.vcs in ["git"]: |
| commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version |
| if report_options: |
| commit_footer += "\nRepoMan-Options: " + " ".join(report_options) |
| if self.repo_settings.sign_manifests: |
| commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, ) |
| if dco_sob: |
| commit_footer += "\nSigned-off-by: %s" % (dco_sob, ) |
| else: |
| unameout = platform.system() + " " |
| if platform.system() in ["Darwin", "SunOS"]: |
| unameout += platform.processor() |
| else: |
| unameout += platform.machine() |
| commit_footer = "\n\n" |
| if dco_sob: |
| commit_footer += "Signed-off-by: %s\n" % (dco_sob, ) |
| commit_footer += "(Portage version: %s/%s/%s" % \ |
| (portage_version, self.vcs_settings.vcs, unameout) |
| if report_options: |
| commit_footer += ", RepoMan options: " + " ".join(report_options) |
| if self.repo_settings.sign_manifests: |
| commit_footer += ", signed Manifest commit with key %s" % \ |
| (gpg_key, ) |
| else: |
| commit_footer += ", unsigned Manifest commit" |
| commit_footer += ")" |
| return commit_footer |
| |
| |
| def changelogs(self, myupdates, mymanifests, myremoved, mychanged, myautoadd, |
| mynew, changelog_msg): |
| broken_changelog_manifests = [] |
| if self.options.echangelog in ('y', 'force'): |
| logging.info("checking for unmodified ChangeLog files") |
| committer_name = utilities.get_committer_name(env=self.repoman_settings) |
| for x in sorted(vcs_files_to_cps( |
| chain(myupdates, mymanifests, myremoved), |
| self.scanner.repolevel, self.scanner.reposplit, self.scanner.categories)): |
| catdir, pkgdir = x.split("/") |
| checkdir = self.repo_settings.repodir + "/" + x |
| checkdir_relative = "" |
| if self.scanner.repolevel < 3: |
| checkdir_relative = os.path.join(pkgdir, checkdir_relative) |
| if self.scanner.repolevel < 2: |
| checkdir_relative = os.path.join(catdir, checkdir_relative) |
| checkdir_relative = os.path.join(".", checkdir_relative) |
| |
| changelog_path = os.path.join(checkdir_relative, "ChangeLog") |
| changelog_modified = changelog_path in self.scanner.changed.changelogs |
| if changelog_modified and self.options.echangelog != 'force': |
| continue |
| |
| # get changes for this package |
| cdrlen = len(checkdir_relative) |
| check_relative = lambda e: e.startswith(checkdir_relative) |
| split_relative = lambda e: e[cdrlen:] |
| clnew = list(map(split_relative, filter(check_relative, mynew))) |
| clremoved = list(map(split_relative, filter(check_relative, myremoved))) |
| clchanged = list(map(split_relative, filter(check_relative, mychanged))) |
| |
| # Skip ChangeLog generation if only the Manifest was modified, |
| # as discussed in bug #398009. |
| nontrivial_cl_files = set() |
| nontrivial_cl_files.update(clnew, clremoved, clchanged) |
| nontrivial_cl_files.difference_update(['Manifest']) |
| if not nontrivial_cl_files and self.options.echangelog != 'force': |
| continue |
| |
| new_changelog = utilities.UpdateChangeLog( |
| checkdir_relative, committer_name, changelog_msg, |
| os.path.join(self.repo_settings.repodir, 'skel.ChangeLog'), |
| catdir, pkgdir, |
| new=clnew, removed=clremoved, changed=clchanged, |
| pretend=self.options.pretend) |
| if new_changelog is None: |
| writemsg_level( |
| "!!! Updating the ChangeLog failed\n", |
| level=logging.ERROR, noiselevel=-1) |
| sys.exit(1) |
| |
| # if the ChangeLog was just created, add it to vcs |
| if new_changelog: |
| myautoadd.append(changelog_path) |
| # myautoadd is appended to myupdates below |
| else: |
| myupdates.append(changelog_path) |
| |
| if self.options.ask and not self.options.pretend: |
| # regenerate Manifest for modified ChangeLog (bug #420735) |
| self.repoman_settings["O"] = checkdir |
| digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) |
| else: |
| broken_changelog_manifests.append(x) |
| |
| if myautoadd: |
| print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...") |
| add_cmd = [self.vcs_settings.vcs, "add"] |
| add_cmd += myautoadd |
| if self.options.pretend: |
| portage.writemsg_stdout( |
| "(%s)\n" % " ".join(add_cmd), |
| noiselevel=-1) |
| else: |
| |
| if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \ |
| not os.path.isabs(add_cmd[0]): |
| # Python 3.1 _execvp throws TypeError for non-absolute executable |
| # path passed as bytes (see http://bugs.python.org/issue8513). |
| fullname = find_binary(add_cmd[0]) |
| if fullname is None: |
| raise portage.exception.CommandNotFound(add_cmd[0]) |
| add_cmd[0] = fullname |
| |
| add_cmd = [_unicode_encode(arg) for arg in add_cmd] |
| retcode = subprocess.call(add_cmd) |
| if retcode != os.EX_OK: |
| logging.error( |
| "Exiting on %s error code: %s\n" % (self.vcs_settings.vcs, retcode)) |
| sys.exit(retcode) |
| |
| myupdates += myautoadd |
| return myupdates, broken_changelog_manifests |
| |
| |
| def add_manifest(self, mymanifests, myheaders, myupdates, myremoved, |
| commitmessage): |
| myfiles = mymanifests[:] |
| # If there are no header (SVN/CVS keywords) changes in |
| # the files, this Manifest commit must include the |
| # other (yet uncommitted) files. |
| if not myheaders: |
| myfiles += myupdates |
| myfiles += myremoved |
| myfiles.sort() |
| |
| fd, commitmessagefile = tempfile.mkstemp(".repoman.msg") |
| mymsg = os.fdopen(fd, "wb") |
| mymsg.write(_unicode_encode(commitmessage)) |
| mymsg.close() |
| |
| commit_cmd = [] |
| if self.options.pretend and self.vcs_settings.vcs is None: |
| # substitute a bogus value for pretend output |
| commit_cmd.append("cvs") |
| else: |
| commit_cmd.append(self.vcs_settings.vcs) |
| commit_cmd.extend(self.vcs_settings.vcs_global_opts) |
| commit_cmd.append("commit") |
| commit_cmd.extend(self.vcs_settings.vcs_local_opts) |
| if self.vcs_settings.vcs == "hg": |
| commit_cmd.extend(["--logfile", commitmessagefile]) |
| commit_cmd.extend(myfiles) |
| else: |
| commit_cmd.extend(["-F", commitmessagefile]) |
| commit_cmd.extend(f.lstrip("./") for f in myfiles) |
| |
| try: |
| if self.options.pretend: |
| print("(%s)" % (" ".join(commit_cmd),)) |
| else: |
| retval = spawn(commit_cmd, env=self.repo_settings.commit_env) |
| if retval != os.EX_OK: |
| if self.repo_settings.repo_config.sign_commit and self.vcs_settings.vcs == 'git' and \ |
| not git_supports_gpg_sign(): |
| # Inform user that newer git is needed (bug #403323). |
| logging.error( |
| "Git >=1.7.9 is required for signed commits!") |
| |
| writemsg_level( |
| "!!! Exiting on %s (shell) " |
| "error code: %s\n" % (self.vcs_settings.vcs, retval), |
| level=logging.ERROR, noiselevel=-1) |
| sys.exit(retval) |
| finally: |
| try: |
| os.unlink(commitmessagefile) |
| except OSError: |
| pass |
| |
| |
| def priming_commit(self, myupdates, myremoved, commitmessage): |
| myfiles = myupdates + myremoved |
| fd, commitmessagefile = tempfile.mkstemp(".repoman.msg") |
| mymsg = os.fdopen(fd, "wb") |
| mymsg.write(_unicode_encode(commitmessage)) |
| mymsg.close() |
| |
| separator = '-' * 78 |
| |
| print() |
| print(green("Using commit message:")) |
| print(green(separator)) |
| print(commitmessage) |
| print(green(separator)) |
| print() |
| |
| # Having a leading ./ prefix on file paths can trigger a bug in |
| # the cvs server when committing files to multiple directories, |
| # so strip the prefix. |
| myfiles = [f.lstrip("./") for f in myfiles] |
| |
| commit_cmd = [self.vcs_settings.vcs] |
| commit_cmd.extend(self.vcs_settings.vcs_global_opts) |
| commit_cmd.append("commit") |
| commit_cmd.extend(self.vcs_settings.vcs_local_opts) |
| commit_cmd.extend(["-F", commitmessagefile]) |
| commit_cmd.extend(myfiles) |
| |
| try: |
| if self.options.pretend: |
| print("(%s)" % (" ".join(commit_cmd),)) |
| else: |
| retval = spawn(commit_cmd, env=self.repo_settings.commit_env) |
| if retval != os.EX_OK: |
| writemsg_level( |
| "!!! Exiting on %s (shell) " |
| "error code: %s\n" % (self.vcs_settings.vcs, retval), |
| level=logging.ERROR, noiselevel=-1) |
| sys.exit(retval) |
| finally: |
| try: |
| os.unlink(commitmessagefile) |
| except OSError: |
| pass |
| |
| |
| def thick_manifest(self, myupdates, myheaders, no_expansion, expansion): |
| if self.vcs_settings.vcs == 'cvs': |
| headerstring = "'\$(Header|Id).*\$'" |
| elif self.vcs_settings.vcs == "svn": |
| svn_keywords = dict((k.lower(), k) for k in [ |
| "Rev", |
| "Revision", |
| "LastChangedRevision", |
| "Date", |
| "LastChangedDate", |
| "Author", |
| "LastChangedBy", |
| "URL", |
| "HeadURL", |
| "Id", |
| "Header", |
| ]) |
| |
| for myfile in myupdates: |
| |
| # for CVS, no_expansion contains files that are excluded from expansion |
| if self.vcs_settings.vcs == "cvs": |
| if myfile in no_expansion: |
| continue |
| |
| # for SVN, expansion contains files that are included in expansion |
| elif self.vcs_settings.vcs == "svn": |
| if myfile not in expansion: |
| continue |
| |
| # Subversion keywords are case-insensitive |
| # in svn:keywords properties, |
| # but case-sensitive in contents of files. |
| enabled_keywords = [] |
| for k in expansion[myfile]: |
| keyword = svn_keywords.get(k.lower()) |
| if keyword is not None: |
| enabled_keywords.append(keyword) |
| |
| headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords) |
| |
| myout = repoman_getstatusoutput( |
| "egrep -q %s %s" % (headerstring, portage._shell_quote(myfile))) |
| if myout[0] == 0: |
| myheaders.append(myfile) |
| |
| print("%s have headers that will change." % green(str(len(myheaders)))) |
| print( |
| "* Files with headers will" |
| " cause the manifests to be changed and committed separately.") |
| |
| |
| def clear_attic(self, myheaders): |
| cvs_header_re = re.compile(br'^#\s*\$Header.*\$$') |
| attic_str = b'/Attic/' |
| attic_replace = b'/' |
| for x in myheaders: |
| f = open( |
| _unicode_encode(x, encoding=_encodings['fs'], errors='strict'), |
| mode='rb') |
| mylines = f.readlines() |
| f.close() |
| modified = False |
| for i, line in enumerate(mylines): |
| if cvs_header_re.match(line) is not None and \ |
| attic_str in line: |
| mylines[i] = line.replace(attic_str, attic_replace) |
| modified = True |
| if modified: |
| portage.util.write_atomic(x, b''.join(mylines), mode='wb') |
| |
| |
| def sign_manifest(self, myupdates, myremoved, mymanifests): |
| try: |
| for x in sorted(vcs_files_to_cps( |
| chain(myupdates, myremoved, mymanifests), |
| self.scanner.repolevel, self.scanner.reposplit, self.scanner.categories)): |
| self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) |
| manifest_path = os.path.join(self.repoman_settings["O"], "Manifest") |
| if not need_signature(manifest_path): |
| continue |
| gpgsign(manifest_path, self.repoman_settings, self.options) |
| except portage.exception.PortageException as e: |
| portage.writemsg("!!! %s\n" % str(e)) |
| portage.writemsg("!!! Disabled FEATURES='sign'\n") |
| self.repo_settings.sign_manifests = False |
| |
| |
| def get_new_commit_message(self, qa_output): |
| msg_prefix = "" |
| if self.scanner.repolevel > 1: |
| msg_prefix = "/".join(self.scanner.reposplit[1:]) + ": " |
| |
| try: |
| editor = os.environ.get("EDITOR") |
| if editor and utilities.editor_is_executable(editor): |
| commitmessage = utilities.get_commit_message_with_editor( |
| editor, message=qa_output, prefix=msg_prefix) |
| else: |
| commitmessage = utilities.get_commit_message_with_stdin() |
| except KeyboardInterrupt: |
| logging.fatal("Interrupted; exiting...") |
| sys.exit(1) |
| if (not commitmessage or not commitmessage.strip() |
| or commitmessage.strip() == msg_prefix): |
| print("* no commit message? aborting commit.") |
| sys.exit(1) |
| return commitmessage |