|
| 1 | +import os |
| 2 | + |
| 3 | + |
| 4 | +class StagingPullRequestWrapper: |
| 5 | + BACKLOG_LABEL = "staging/Backlog" |
| 6 | + INPROGRESS_LABEL = "staging/In Progress" |
| 7 | + |
| 8 | + def __init__(self, conn, owner: str, repo: str, number: int, *, topdir: str): |
| 9 | + from . import PullRequest |
| 10 | + |
| 11 | + self.conn = conn |
| 12 | + self.owner = owner |
| 13 | + self.repo = repo |
| 14 | + self.number = number |
| 15 | + self._topdir = topdir |
| 16 | + |
| 17 | + self.pr_obj = PullRequest.get(conn, owner, repo, number) |
| 18 | + self.git = None |
| 19 | + self.submodules_by_owner_repo = {} |
| 20 | + self.package_pr_map = {} |
| 21 | + |
| 22 | + self.base_git = None |
| 23 | + self.base_submodules_by_owner_repo = {} |
| 24 | + |
| 25 | + def clone(self): |
| 26 | + from . import Git |
| 27 | + from . import Repo |
| 28 | + |
| 29 | + path = os.path.join(self._topdir, f"{self.owner}_{self.repo}_{self.number}") |
| 30 | + Repo.clone_or_update( |
| 31 | + self.conn, |
| 32 | + self.owner, |
| 33 | + self.repo, |
| 34 | + pr_number=self.number, |
| 35 | + commit=self.pr_obj.head_commit, |
| 36 | + directory=path, |
| 37 | + ssh_private_key_path=self.conn.login.ssh_key, |
| 38 | + ) |
| 39 | + self.git = Git(path) |
| 40 | + # git fetch --all to have all commits from all remotes available |
| 41 | + self.git.fetch() |
| 42 | + |
| 43 | + submodules = self.git.get_submodules() |
| 44 | + self.submodules_by_owner_repo = dict([((i["owner"], i["repo"]), i) for i in submodules.values()]) |
| 45 | + |
| 46 | + for pkg_owner, pkg_repo, pkg_number in self.pr_obj.parse_pr_references(): |
| 47 | + pkg_pr_obj = self.__class__(self.conn, pkg_owner, pkg_repo, pkg_number, topdir=self._topdir) |
| 48 | + self.package_pr_map[(pkg_owner, pkg_repo, pkg_number)] = pkg_pr_obj |
| 49 | + # FIXME: doesn't work when the commits are padded with zeros |
| 50 | + # assert self.submodules_by_owner_repo[(pkg_owner, pkg_repo)]["commit"] == pkg_pr_obj.pr_obj.head_commit |
| 51 | + |
| 52 | + def clone_base(self): |
| 53 | + from . import Git |
| 54 | + from . import Repo |
| 55 | + |
| 56 | + path = os.path.join(self._topdir, f"{self.owner}_{self.repo}_{self.number}_base") |
| 57 | + Repo.clone_or_update( |
| 58 | + self.conn, |
| 59 | + self.owner, |
| 60 | + self.repo, |
| 61 | + branch=self.pr_obj.base_branch, |
| 62 | + commit=self.pr_obj.base_commit, |
| 63 | + directory=path, |
| 64 | + ssh_private_key_path=self.conn.login.ssh_key, |
| 65 | + ) |
| 66 | + self.base_git = Git(path) |
| 67 | + # git fetch --all to have all commits from all remotes available |
| 68 | + self.base_git.fetch() |
| 69 | + |
| 70 | + submodules = self.base_git.get_submodules() |
| 71 | + self.base_submodules_by_owner_repo = dict([((i["owner"], i["repo"]), i) for i in submodules.values()]) |
| 72 | + |
| 73 | + def merge(self, other): |
| 74 | + """ |
| 75 | + Merge ``other`` pull request into ``self`` by MOVING all ``PR: <pr_id>`` references. |
| 76 | + It is crucial to remove the references from the self.package_pr_maporiginal ``other`` request, otherwise the bots might get confused and close the pull request with reparented package pull request. |
| 77 | + """ |
| 78 | + from . import Git |
| 79 | + from . import PullRequest |
| 80 | + |
| 81 | + self.pr_obj._data["body"] = PullRequest.add_pr_references(self.pr_obj.body, other.package_pr_map.keys()) |
| 82 | + other.pr_obj._data["body"] = PullRequest.remove_pr_references(other.pr_obj.body, other.package_pr_map.keys()) |
| 83 | + |
| 84 | + submodule_paths = [] |
| 85 | + |
| 86 | + for pkg_owner, pkg_repo, pkg_number in other.package_pr_map: |
| 87 | + other_submodule = other.submodules_by_owner_repo[(pkg_owner, pkg_repo)] |
| 88 | + self_submodule = self.submodules_by_owner_repo.get((pkg_owner, pkg_repo), None) |
| 89 | + |
| 90 | + if self_submodule: |
| 91 | + # use an existing path if the submodule already exists |
| 92 | + submodule_path = self_submodule["path"] |
| 93 | + submodule_branch = self_submodule["branch"] |
| 94 | + else: |
| 95 | + # use submodule path from the ``other`` pull request |
| 96 | + submodule_path = other_submodule["path"] |
| 97 | + submodule_branch = other_submodule["branch"] |
| 98 | + |
| 99 | + # add a submodule if missing |
| 100 | + if not self_submodule: |
| 101 | + self.git._run_git(["submodule", "add", "-b", submodule_branch, f"../../{pkg_owner}/{pkg_repo}", submodule_path]) |
| 102 | + |
| 103 | + # init the submodule |
| 104 | + self.git._run_git(["submodule", "update", "--init", submodule_path]) |
| 105 | + |
| 106 | + submodule_git = Git(os.path.join(self.git.abspath, submodule_path)) |
| 107 | + submodule_git.fetch() |
| 108 | + submodule_git._run_git(["fetch", "origin", f"pull/{pkg_number}/head:{submodule_branch}", "--force", "--update-head-ok"]) |
| 109 | + submodule_paths.append(submodule_path) |
| 110 | + |
| 111 | + self.git.add(submodule_paths) |
| 112 | + self.git.commit(f"Merge package submodules from {other.pr_obj.base_owner}/{other.pr_obj.base_repo}!{other.pr_obj.number}") |
| 113 | + |
| 114 | + def remove(self, package_pr): |
| 115 | + """ |
| 116 | + Remove a package submodule from this project. |
| 117 | + """ |
| 118 | + from . import Git |
| 119 | + from . import GitObsRuntimeError |
| 120 | + from . import PullRequest |
| 121 | + |
| 122 | + self.pr_obj._data["body"] = PullRequest.remove_pr_references(self.pr_obj.body, [(package_pr.owner, package_pr.repo, package_pr.number)]) |
| 123 | + |
| 124 | + submodule = self.submodules_by_owner_repo.get((package_pr.owner, package_pr.repo), None) |
| 125 | + base_submodule = self.base_submodules_by_owner_repo.get((package_pr.owner, package_pr.repo), None) |
| 126 | + |
| 127 | + if not submodule: |
| 128 | + raise GitObsRuntimeError(f"Unable to find a submodule for pull request {package_pr.owner}/{package_pr.repo}!{package_pr.number}") |
| 129 | + |
| 130 | + submodule_path = submodule["path"] |
| 131 | + |
| 132 | + if base_submodule: |
| 133 | + # we're reverting the submodule to an older commit |
| 134 | + self.git._run_git(["submodule", "update", "--init", "--remote", submodule_path]) |
| 135 | + submodule_git = Git(os.path.join(self.git.abspath, submodule_path)) |
| 136 | + submodule_git.fetch() |
| 137 | + submodule_git.reset(commit=base_submodule["commit"], hard=True) |
| 138 | + self.git.add([submodule_path]) |
| 139 | + else: |
| 140 | + # we're removing the submodule completely |
| 141 | + self.git._run_git(["rm", submodule["path"]]) |
| 142 | + |
| 143 | + self.git.commit(f"Remove package pull request {package_pr.pr_obj.base_owner}/{package_pr.pr_obj.base_repo}!{package_pr.pr_obj.number}") |
0 commit comments