diff options
Diffstat (limited to 'testing/mozharness/mozharness/mozilla/repo_manipulation.py')
-rw-r--r-- | testing/mozharness/mozharness/mozilla/repo_manipulation.py | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/testing/mozharness/mozharness/mozilla/repo_manipulation.py b/testing/mozharness/mozharness/mozilla/repo_manipulation.py new file mode 100644 index 0000000000..3a5712fadb --- /dev/null +++ b/testing/mozharness/mozharness/mozilla/repo_manipulation.py @@ -0,0 +1,222 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import six + +# pylint --py3k: W1648 +if six.PY2: + from ConfigParser import ConfigParser +else: + from configparser import ConfigParser + +import json +import os + +from mozharness.base.errors import HgErrorList +from mozharness.base.log import FATAL, INFO +from mozharness.base.vcs.mercurial import MercurialVCS + + +class MercurialRepoManipulationMixin(object): + def get_version(self, repo_root, version_file="browser/config/version.txt"): + version_path = os.path.join(repo_root, version_file) + contents = self.read_from_file(version_path, error_level=FATAL) + lines = [l for l in contents.splitlines() if l and not l.startswith("#")] + return lines[-1].split(".") + + def replace(self, file_name, from_, to_): + """Replace text in a file.""" + text = self.read_from_file(file_name, error_level=FATAL) + new_text = text.replace(from_, to_) + if text == new_text: + self.fatal("Cannot replace '%s' to '%s' in '%s'" % (from_, to_, file_name)) + self.write_to_file(file_name, new_text, error_level=FATAL) + + def query_hg_revision(self, path): + """Avoid making 'pull' a required action every run, by being able + to fall back to figuring out the revision from the cloned repo + """ + m = MercurialVCS(log_obj=self.log_obj, config=self.config) + revision = m.get_revision_from_path(path) + return revision + + def hg_commit(self, cwd, message, user=None, ignore_no_changes=False): + """Commit changes to hg.""" + cmd = self.query_exe("hg", return_type="list") + ["commit", "-m", message] + if user: + cmd.extend(["-u", user]) + success_codes = [0] + if ignore_no_changes: + success_codes.append(1) + self.run_command( + cmd, + cwd=cwd, + error_list=HgErrorList, + halt_on_failure=True, + success_codes=success_codes, + ) + return self.query_hg_revision(cwd) + + def clean_repos(self): + """We may end up with contaminated local repos at some point, but + we don't want to have to clobber and reclone from scratch every + time. + + This is an attempt to clean up the local repos without needing a + clobber. + """ + dirs = self.query_abs_dirs() + hg = self.query_exe("hg", return_type="list") + hg_repos = self.query_repos() + hg_strip_error_list = [ + { + "substr": r"""abort: empty revision set""", + "level": INFO, + "explanation": "Nothing to clean up; we're good!", + } + ] + HgErrorList + for repo_config in hg_repos: + repo_name = repo_config["dest"] + repo_path = os.path.join(dirs["abs_work_dir"], repo_name) + if os.path.exists(repo_path): + # hg up -C to discard uncommitted changes + self.run_command( + hg + ["up", "-C", "-r", repo_config["branch"]], + cwd=repo_path, + error_list=HgErrorList, + halt_on_failure=True, + ) + # discard unpushed commits + status = self.retry( + self.run_command, + args=( + hg + + [ + "--config", + "extensions.mq=", + "strip", + "--no-backup", + "outgoing()", + ], + ), + kwargs={ + "cwd": repo_path, + "error_list": hg_strip_error_list, + "return_type": "num_errors", + "success_codes": (0, 255), + }, + ) + if status not in [0, 255]: + self.fatal("Issues stripping outgoing revisions!") + # 2nd hg up -C to make sure we're not on a stranded head + # which can happen when reverting debugsetparents + self.run_command( + hg + ["up", "-C", "-r", repo_config["branch"]], + cwd=repo_path, + error_list=HgErrorList, + halt_on_failure=True, + ) + + def commit_changes(self): + """Do the commit.""" + hg = self.query_exe("hg", return_type="list") + for cwd in self.query_commit_dirs(): + self.run_command(hg + ["diff"], cwd=cwd) + self.hg_commit( + cwd, + user=self.config["hg_user"], + message=self.query_commit_message(), + ignore_no_changes=self.config.get("ignore_no_changes", False), + ) + self.info( + "Now verify |hg out| and |hg out --patch| if you're paranoid, and --push" + ) + + def hg_tag( + self, + cwd, + tags, + user=None, + message=None, + revision=None, + force=None, + halt_on_failure=True, + ): + if isinstance(tags, six.string_types): + tags = [tags] + cmd = self.query_exe("hg", return_type="list") + ["tag"] + if not message: + message = "No bug - Tagging %s" % os.path.basename(cwd) + if revision: + message = "%s %s" % (message, revision) + message = "%s with %s" % (message, ", ".join(tags)) + message += " a=release DONTBUILD CLOSED TREE" + self.info(message) + cmd.extend(["-m", message]) + if user: + cmd.extend(["-u", user]) + if revision: + cmd.extend(["-r", revision]) + if force: + cmd.append("-f") + cmd.extend(tags) + return self.run_command( + cmd, cwd=cwd, halt_on_failure=halt_on_failure, error_list=HgErrorList + ) + + def query_existing_tags(self, cwd, halt_on_failure=True): + cmd = self.query_exe("hg", return_type="list") + ["tags"] + existing_tags = {} + output = self.get_output_from_command( + cmd, cwd=cwd, halt_on_failure=halt_on_failure + ) + for line in output.splitlines(): + parts = line.split(" ") + if len(parts) > 1: + # existing_tags = {TAG: REVISION, ...} + existing_tags[parts[0]] = parts[-1].split(":")[-1] + self.info( + "existing_tags:\n{}".format( + json.dumps(existing_tags, sort_keys=True, indent=4) + ) + ) + return existing_tags + + def push(self): + """""" + error_message = """Push failed! If there was a push race, try rerunning +the script (--clean-repos --pull --migrate). The second run will be faster.""" + hg = self.query_exe("hg", return_type="list") + for cwd in self.query_push_dirs(): + if not cwd: + self.warning("Skipping %s" % cwd) + continue + push_cmd = hg + ["push"] + self.query_push_args(cwd) + if self.config.get("push_dest"): + push_cmd.append(self.config["push_dest"]) + status = self.run_command( + push_cmd, + cwd=cwd, + error_list=HgErrorList, + success_codes=[0, 1], + ) + if status == 1: + self.warning("No changes for %s!" % cwd) + elif status: + self.fatal(error_message) + + def edit_repo_hg_rc(self, cwd, section, key, value): + hg_rc = self.read_repo_hg_rc(cwd) + hg_rc.set(section, key, value) + + with open(self._get_hg_rc_path(cwd), "wb") as f: + hg_rc.write(f) + + def read_repo_hg_rc(self, cwd): + hg_rc = ConfigParser() + hg_rc.read(self._get_hg_rc_path(cwd)) + return hg_rc + + def _get_hg_rc_path(self, cwd): + return os.path.join(cwd, ".hg", "hgrc") |