def test_request_extended_revision_data_stacked_revisions(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() diff1 = phabdouble.diff(repo=repo) r1 = phabdouble.revision(diff=diff1, repo=repo) diff2 = phabdouble.diff(repo=repo) r2 = phabdouble.revision(depends_on=[r1], diff=diff2, repo=repo) data = request_extended_revision_data(phab, [r1['phid'], r2['phid']]) assert r1['phid'] in data.revisions assert r2['phid'] in data.revisions assert diff1['phid'] in data.diffs assert diff2['phid'] in data.diffs assert repo['phid'] in data.repositories data = request_extended_revision_data(phab, [r1['phid']]) assert r1['phid'] in data.revisions assert r2['phid'] not in data.revisions assert diff1['phid'] in data.diffs assert diff2['phid'] not in data.diffs assert repo['phid'] in data.repositories
def test_request_extended_revision_data_no_revisions(phabdouble): phab = phabdouble.get_phabricator_client() data = request_extended_revision_data(phab, []) assert not data.revisions assert not data.diffs assert not data.repositories
def test_calculate_landable_subgraphs_diverging_paths_merge(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() r1 = phabdouble.revision(repo=repo) r2 = phabdouble.revision(repo=repo, depends_on=[r1]) r3 = phabdouble.revision(repo=repo, depends_on=[r2]) r4 = phabdouble.revision(repo=repo, depends_on=[r1]) r5 = phabdouble.revision(repo=repo, depends_on=[r4]) r6 = phabdouble.revision(repo=repo, depends_on=[r1]) r7 = phabdouble.revision(repo=repo, depends_on=[r3, r5, r6]) nodes, edges = build_stack_graph(phab, r1['phid']) ext_data = request_extended_revision_data(phab, [ r1['phid'], r2['phid'], r3['phid'], r4['phid'], r5['phid'], r6['phid'], r7['phid'], ]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo['phid']}) assert len(landable) == 3 assert [r1['phid'], r2['phid'], r3['phid']] in landable assert [r1['phid'], r4['phid'], r5['phid']] in landable assert [r1['phid'], r6['phid']] in landable
def test_calculate_landable_subgraphs_extra_check(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() r1 = phabdouble.revision(repo=repo) r2 = phabdouble.revision(repo=repo, depends_on=[r1]) r3 = phabdouble.revision(repo=repo, depends_on=[r2]) r4 = phabdouble.revision(repo=repo, depends_on=[r3]) nodes, edges = build_stack_graph(phab, r1['phid']) ext_data = request_extended_revision_data( phab, [r1['phid'], r2['phid'], r3['phid'], r4['phid']]) REASON = "Blocked by custom check." def custom_check(*, revision, diff, repo): return REASON if revision['id'] == r3['id'] else None landable, blocked = calculate_landable_subgraphs( ext_data, edges, {repo['phid']}, other_checks=[custom_check]) assert landable == [ [r1['phid'], r2['phid']], ] assert r3['phid'] in blocked and r4['phid'] in blocked assert blocked[r3['phid']] == REASON
def test_calculate_landable_subgraphs_allows_distinct_repo_paths(phabdouble): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name='repo1') r1 = phabdouble.revision(repo=repo1) r2 = phabdouble.revision(repo=repo1, depends_on=[r1]) repo2 = phabdouble.repo(name='repo2') r3 = phabdouble.revision(repo=repo2) r4 = phabdouble.revision(repo=repo2, depends_on=[r3]) r5 = phabdouble.revision(repo=repo1, depends_on=[r2, r4]) nodes, edges = build_stack_graph(phab, r1['phid']) ext_data = request_extended_revision_data(phab, [ r1['phid'], r2['phid'], r3['phid'], r4['phid'], r5['phid'], ]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo1['phid'], repo2['phid']}) assert len(landable) == 2 assert [r1['phid'], r2['phid']] in landable assert [r3['phid'], r4['phid']] in landable
def _assess_transplant_request(phab, landing_path): nodes, edges = _find_stack_from_landing_path(phab, landing_path) stack_data = request_extended_revision_data(phab, [phid for phid in nodes]) landing_path = _convert_path_id_to_phid(landing_path, stack_data) supported_repos = get_repos_for_env(current_app.config.get('ENVIRONMENT')) landable_repos = get_landable_repos_for_revision_data( stack_data, supported_repos) landable, blocked = calculate_landable_subgraphs( stack_data, edges, landable_repos, other_checks=DEFAULT_OTHER_BLOCKER_CHECKS) assessment = check_landing_blockers( g.auth0_user, landing_path, stack_data, landable, landable_repos, ) if assessment.blocker is not None: return (assessment, None, None, None) # We have now verified that landable_path is valid and is indeed # landable (in the sense that it is a landable_subgraph, with no # revisions being blocked). Make this clear by using a different # value, and assume it going forward. valid_path = landing_path # Now that we know this is a valid path we can convert it into a list # of (revision, diff) tuples. to_land = [stack_data.revisions[r_phid] for r_phid, _ in valid_path] to_land = [ (r, stack_data.diffs[PhabricatorClient.expect(r, 'fields', 'diffPHID')]) for r in to_land ] # To be a landable path the entire path must have the same # repository, so we can get away with checking only one. repo = stack_data.repositories[to_land[0][0]['fields']['repositoryPHID']] landing_repo = landable_repos[repo['phid']] involved_phids = set() for revision, _ in to_land: involved_phids.update(gather_involved_phids(revision)) involved_phids = list(involved_phids) users = lazy_user_search(phab, involved_phids)() projects = lazy_project_search(phab, involved_phids)() reviewers = { revision['phid']: get_collated_reviewers(revision) for revision, _ in to_land } assessment = check_landing_warnings(g.auth0_user, to_land, repo, landing_repo, reviewers, users, projects) return (assessment, to_land, landing_repo, stack_data)
def test_calculate_landable_subgraphs_no_edges_closed(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() revision = phabdouble.revision(repo=repo, status=RevisionStatus.PUBLISHED) ext_data = request_extended_revision_data(phab, [revision['phid']]) landable, _ = calculate_landable_subgraphs(ext_data, [], {repo['phid']}) assert not landable
def test_request_extended_revision_data_single_revision_no_repo(phabdouble): phab = phabdouble.get_phabricator_client() diff = phabdouble.diff() revision = phabdouble.revision(diff=diff) data = request_extended_revision_data(phab, [revision['phid']]) assert revision['phid'] in data.revisions assert diff['phid'] in data.diffs assert not data.repositories
def test_calculate_landable_subgraphs_no_edges_open(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() revision = phabdouble.revision(repo=repo) ext_data = request_extended_revision_data(phab, [revision['phid']]) landable, _ = calculate_landable_subgraphs(ext_data, [], {repo['phid']}) assert len(landable) == 1 assert landable[0] == [revision['phid']]
def test_request_extended_revision_data_gets_latest_diff(phabdouble): phab = phabdouble.get_phabricator_client() first_diff = phabdouble.diff() revision = phabdouble.revision(diff=first_diff) latest_diff = phabdouble.diff(revision=revision) data = request_extended_revision_data(phab, [revision['phid']]) assert revision['phid'] in data.revisions assert first_diff['phid'] not in data.diffs assert latest_diff['phid'] in data.diffs
def test_request_extended_revision_data_single_revision_with_repo(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() diff = phabdouble.diff() revision = phabdouble.revision(diff=diff, repo=repo) data = request_extended_revision_data(phab, [revision["phid"]]) assert revision["phid"] in data.revisions assert diff["phid"] in data.diffs assert repo["phid"] in data.repositories
def test_calculate_landable_subgraphs_closed_root(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() r1 = phabdouble.revision(repo=repo, status=RevisionStatus.PUBLISHED) r2 = phabdouble.revision(repo=repo, depends_on=[r1]) nodes, edges = build_stack_graph(phab, r1["phid"]) ext_data = request_extended_revision_data(phab, [r1["phid"], r2["phid"]]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo["phid"]}) assert landable == [[r2["phid"]]]
def test_request_extended_revision_data_diff_and_revision_repo(phabdouble): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name='repo1') repo2 = phabdouble.repo(name='repo2') diff = phabdouble.diff(repo=repo1) revision = phabdouble.revision(diff=diff, repo=repo2) data = request_extended_revision_data(phab, [revision['phid']]) assert revision['phid'] in data.revisions assert diff['phid'] in data.diffs assert repo1['phid'] in data.repositories assert repo2['phid'] in data.repositories
def test_calculate_landable_subgraphs_stops_multiple_repo_paths(phabdouble): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name="repo1") repo2 = phabdouble.repo(name="repo2") r1 = phabdouble.revision(repo=repo1) r2 = phabdouble.revision(repo=repo1, depends_on=[r1]) r3 = phabdouble.revision(repo=repo2, depends_on=[r2]) nodes, edges = build_stack_graph(phab, r1["phid"]) ext_data = request_extended_revision_data( phab, [r1["phid"], r2["phid"], r3["phid"]]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo1["phid"], repo2["phid"]}) assert landable == [[r1["phid"], r2["phid"]]]
def test_get_landable_repos_for_revision_data(phabdouble, mocked_repo_config): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name='mozilla-central') repo2 = phabdouble.repo(name='not-mozilla-central') r1 = phabdouble.revision(repo=repo1) r2 = phabdouble.revision(repo=repo2, depends_on=[r1]) supported_repos = get_repos_for_env('test') revision_data = request_extended_revision_data(phab, [r1['phid'], r2['phid']]) landable_repos = get_landable_repos_for_revision_data( revision_data, supported_repos) assert repo1['phid'] in landable_repos assert repo2['phid'] not in landable_repos assert landable_repos[repo1['phid']].tree == 'mozilla-central'
def test_calculate_landable_subgraphs_different_repo_closed_parent(phabdouble): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name="repo1") r1 = phabdouble.revision(repo=repo1, status=RevisionStatus.PUBLISHED) repo2 = phabdouble.repo(name="repo2") r2 = phabdouble.revision(repo=repo2) r3 = phabdouble.revision(repo=repo2, depends_on=[r1, r2]) nodes, edges = build_stack_graph(phab, r1["phid"]) ext_data = request_extended_revision_data( phab, [r1["phid"], r2["phid"], r3["phid"]]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo1["phid"], repo2["phid"]}) assert len(landable) == 1 assert [r2["phid"], r3["phid"]] in landable
def test_request_extended_revision_data_unrelated_revisions(phabdouble): phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo(name='repo1') diff1 = phabdouble.diff(repo=repo1) r1 = phabdouble.revision(diff=diff1, repo=repo1) repo2 = phabdouble.repo(name='repo2') diff2 = phabdouble.diff(repo=repo2) r2 = phabdouble.revision(diff=diff2, repo=repo2) data = request_extended_revision_data(phab, [r1['phid'], r2['phid']]) assert r1['phid'] in data.revisions assert r2['phid'] in data.revisions assert diff1['phid'] in data.diffs assert diff2['phid'] in data.diffs assert repo1['phid'] in data.repositories assert repo2['phid'] in data.repositories
def test_calculate_landable_subgraphs_missing_repo(phabdouble): """Test to assert a missing repository for a revision is blocked with an appropriate error """ phab = phabdouble.get_phabricator_client() repo1 = phabdouble.repo() r1 = phabdouble.revision(repo=None) nodes, edges = build_stack_graph(phab, r1["phid"]) revision_data = request_extended_revision_data(phab, [r1["phid"]]) landable, blocked = calculate_landable_subgraphs(revision_data, edges, {repo1["phid"]}) repo_unset_warning = ("Revision's repository unset. Specify a target using" '"Edit revision" in Phabricator') assert not landable assert r1["phid"] in blocked assert blocked[r1["phid"]] == repo_unset_warning
def test_calculate_landable_subgraphs_closed_root_child_merges(phabdouble): phab = phabdouble.get_phabricator_client() repo = phabdouble.repo() r1 = phabdouble.revision(repo=repo) r2 = phabdouble.revision(repo=repo, depends_on=[r1]) r3 = phabdouble.revision(repo=repo, status=RevisionStatus.PUBLISHED) r4 = phabdouble.revision(repo=repo, depends_on=[r2, r3]) nodes, edges = build_stack_graph(phab, r1['phid']) ext_data = request_extended_revision_data(phab, [ r1['phid'], r2['phid'], r3['phid'], r4['phid'], ]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repo['phid']}) assert [r3['phid']] not in landable assert [r3['phid'], r4['phid']] not in landable assert [r4['phid']] not in landable assert landable == [[r1['phid'], r2['phid'], r4['phid']]]
def get(revision_id): """Get the stack a revision is part of. Args: revision_id: (string) ID of the revision in 'D{number}' format """ revision_id = revision_id_to_int(revision_id) phab = g.phabricator revision = phab.call_conduit("differential.revision.search", constraints={"ids": [revision_id]}) revision = phab.single(revision, "data", none_when_empty=True) if revision is None: return not_found_problem try: nodes, edges = build_stack_graph(phab, phab.expect(revision, "phid")) except PhabricatorAPIException: # If a revision within the stack causes an API exception, treat the whole stack # as not found. return not_found_problem stack_data = request_extended_revision_data(phab, [phid for phid in nodes]) supported_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT")) landable_repos = get_landable_repos_for_revision_data( stack_data, supported_repos) other_checks = get_blocker_checks( repositories=supported_repos, relman_group_phid=get_relman_group_phid(phab)) landable, blocked = calculate_landable_subgraphs(stack_data, edges, landable_repos, other_checks=other_checks) uplift_repos = [ name for name, repo in supported_repos.items() if repo.approval_required ] involved_phids = set() for revision in stack_data.revisions.values(): involved_phids.update(gather_involved_phids(revision)) involved_phids = list(involved_phids) users = user_search(phab, involved_phids) projects = project_search(phab, involved_phids) secure_project_phid = get_secure_project_phid(phab) sec_approval_project_phid = get_sec_approval_project_phid(phab) revisions_response = [] for _phid, revision in stack_data.revisions.items(): revision_phid = PhabricatorClient.expect(revision, "phid") fields = PhabricatorClient.expect(revision, "fields") diff_phid = PhabricatorClient.expect(fields, "diffPHID") diff = stack_data.diffs[diff_phid] human_revision_id = "D{}".format( PhabricatorClient.expect(revision, "id")) revision_url = urllib.parse.urljoin( current_app.config["PHABRICATOR_URL"], human_revision_id) secure = revision_is_secure(revision, secure_project_phid) commit_description = find_title_and_summary_for_display( phab, revision, secure) bug_id = get_bugzilla_bug(revision) reviewers = get_collated_reviewers(revision) accepted_reviewers = reviewers_for_commit_message( reviewers, users, projects, sec_approval_project_phid) commit_message_title, commit_message = format_commit_message( commit_description.title, bug_id, accepted_reviewers, commit_description.summary, revision_url, ) author_response = serialize_author(phab.expect(fields, "authorPHID"), users) revisions_response.append({ "id": human_revision_id, "phid": revision_phid, "status": serialize_status(revision), "blocked_reason": blocked.get(revision_phid, ""), "bug_id": bug_id, "title": commit_description.title, "url": revision_url, "date_created": PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, "fields", "dateCreated")).isoformat(), "date_modified": PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, "fields", "dateModified")).isoformat(), "summary": commit_description.summary, "commit_message_title": commit_message_title, "commit_message": commit_message, "repo_phid": PhabricatorClient.expect(fields, "repositoryPHID"), "diff": serialize_diff(diff), "author": author_response, "reviewers": serialize_reviewers(reviewers, users, projects, diff_phid), "is_secure": secure, "is_using_secure_commit_message": commit_description.sanitized, }) repositories = [] for phid in stack_data.repositories.keys(): short_name = PhabricatorClient.expect(stack_data.repositories[phid], "fields", "shortName") repo = supported_repos.get(short_name) landing_supported = repo is not None url = (repo.url if landing_supported else f"{current_app.config['PHABRICATOR_URL']}/source/{short_name}") repositories.append({ "approval_required": landing_supported and repo.approval_required, "commit_flags": repo.commit_flags if repo else [], "landing_supported": landing_supported, "phid": phid, "short_name": short_name, "url": url, }) return { "repositories": repositories, "revisions": revisions_response, "edges": [e for e in edges], "landable_paths": landable, "uplift_repositories": uplift_repos, }
def create_uplift_revision( phab: PhabricatorClient, source_revision: dict, target_repository: dict, form_content: str, ): """Create a new revision on a repository, cloning a diff from another repo""" # Check the target repository needs an approval repos = get_repos_for_env(current_app.config.get("ENVIRONMENT")) local_repo = repos.get(target_repository["fields"]["shortName"]) assert local_repo is not None, f"Unknown repository {target_repository}" assert ( local_repo.approval_required is True ), f"No approval required for {target_repository}" # Load release managers group for review release_managers = get_release_managers(phab) # Find the source diff on phabricator stack = request_extended_revision_data(phab, [source_revision["phid"]]) diff = stack.diffs[source_revision["fields"]["diffPHID"]] # Get raw diff raw_diff = phab.call_conduit("differential.getrawdiff", diffID=diff["id"]) if not raw_diff: raise Exception("Missing raw source diff, cannot uplift revision.") # Base revision hash is available on the diff fields refs = {ref["type"]: ref for ref in phab.expect(diff, "fields", "refs")} base_revision = refs["base"]["identifier"] if "base" in refs else None # The first commit in the attachment list is the current HEAD of stack # we can use the HEAD to mark the changes being created commits = phab.expect(diff, "attachments", "commits", "commits") head = commits[0] if commits else None # Upload it on target repo new_diff = phab.call_conduit( "differential.creatediff", changes=patch_to_changes(raw_diff, head["identifier"] if head else None), sourceMachine=local_repo.url, sourceControlSystem="hg", sourceControlPath="/", sourceControlBaseRevision=base_revision, creationMethod="lando-uplift", lintStatus="none", unitStatus="none", repositoryPHID=target_repository["phid"], sourcePath=None, # TODO ? Local path branch="HEAD", ) new_diff_id = phab.expect(new_diff, "diffid") new_diff_phid = phab.expect(new_diff, "phid") logger.info("Created new diff", extra={"id": new_diff_id, "phid": new_diff_phid}) # Attach commit information to setup the author (needed for landing) phab.call_conduit( "differential.setdiffproperty", diff_id=new_diff_id, name="local:commits", data=json.dumps( { commit["identifier"]: { "author": phab.expect(commit, "author", "name"), "authorEmail": phab.expect(commit, "author", "email"), "time": 0, "message": phab.expect(commit, "message"), "commit": phab.expect(commit, "identifier"), "tree": None, "parents": phab.expect(commit, "parents"), } for commit in commits } ), ) # Append an uplift mention to the summary summary = phab.expect(source_revision, "fields", "summary") summary += f"\nNOTE: Uplifted from D{source_revision['id']}" # Finally create the revision to link all the pieces new_rev = phab.call_conduit( "differential.revision.edit", transactions=[ {"type": "update", "value": new_diff_phid}, # Copy title & summary from source revision {"type": "title", "value": phab.expect(source_revision, "fields", "title")}, {"type": "summary", "value": summary}, # Set release managers as reviewers {"type": "reviewers.add", "value": [release_managers["phid"]]}, # Post the form as a comment on the revision {"type": "comment", "value": form_content}, # Copy Bugzilla id { "type": "bugzilla.bug-id", "value": phab.expect(source_revision, "fields", "bugzilla.bug-id"), }, ], ) new_rev_id = phab.expect(new_rev, "object", "id") new_rev_phid = phab.expect(new_rev, "object", "phid") logger.info( "Created new Phabricator revision", extra={"id": new_rev_id, "phid": new_rev_phid}, ) return { "mode": "uplift", "repository": phab.expect(target_repository, "fields", "shortName"), "url": f"{phab.url_base}/D{new_rev_id}", "revision_id": new_rev_id, "revision_phid": new_rev_phid, "diff_id": new_diff_id, "diff_phid": new_diff_phid, }
def get(revision_id): """Get the stack a revision is part of. Args: revision_id: (string) ID of the revision in 'D{number}' format """ revision_id = revision_id_to_int(revision_id) phab = g.phabricator revision = phab.call_conduit("differential.revision.search", constraints={"ids": [revision_id]}) revision = phab.single(revision, "data", none_when_empty=True) if revision is None: return problem( 404, "Revision not found", "The requested revision does not exist", type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404", ) # TODO: This assumes that all revisions and related objects in the stack # have uniform view permissions for the requesting user. Some revisions # being restricted could cause this to fail. nodes, edges = build_stack_graph(phab, phab.expect(revision, "phid")) stack_data = request_extended_revision_data(phab, [phid for phid in nodes]) supported_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT")) landable_repos = get_landable_repos_for_revision_data( stack_data, supported_repos) other_checks = get_blocker_checks( repositories=supported_repos, relman_group_phid=get_relman_group_phid(phab)) landable, blocked = calculate_landable_subgraphs(stack_data, edges, landable_repos, other_checks=other_checks) uplift_repos = [ name for name, repo in supported_repos.items() if repo.approval_required ] involved_phids = set() for revision in stack_data.revisions.values(): involved_phids.update(gather_involved_phids(revision)) involved_phids = list(involved_phids) users = user_search(phab, involved_phids) projects = project_search(phab, involved_phids) secure_project_phid = get_secure_project_phid(phab) sec_approval_project_phid = get_sec_approval_project_phid(phab) revisions_response = [] for _phid, revision in stack_data.revisions.items(): revision_phid = PhabricatorClient.expect(revision, "phid") fields = PhabricatorClient.expect(revision, "fields") diff_phid = PhabricatorClient.expect(fields, "diffPHID") diff = stack_data.diffs[diff_phid] human_revision_id = "D{}".format( PhabricatorClient.expect(revision, "id")) revision_url = urllib.parse.urljoin( current_app.config["PHABRICATOR_URL"], human_revision_id) secure = revision_is_secure(revision, secure_project_phid) commit_description = find_title_and_summary_for_display( phab, revision, secure) bug_id = get_bugzilla_bug(revision) reviewers = get_collated_reviewers(revision) accepted_reviewers = reviewers_for_commit_message( reviewers, users, projects, sec_approval_project_phid) commit_message_title, commit_message = format_commit_message( commit_description.title, bug_id, accepted_reviewers, commit_description.summary, revision_url, ) author_response = serialize_author(phab.expect(fields, "authorPHID"), users) revisions_response.append({ "id": human_revision_id, "phid": revision_phid, "status": serialize_status(revision), "blocked_reason": blocked.get(revision_phid, ""), "bug_id": bug_id, "title": commit_description.title, "url": revision_url, "date_created": PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, "fields", "dateCreated")).isoformat(), "date_modified": PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, "fields", "dateModified")).isoformat(), "summary": commit_description.summary, "commit_message_title": commit_message_title, "commit_message": commit_message, "repo_phid": PhabricatorClient.expect(fields, "repositoryPHID"), "diff": serialize_diff(diff), "author": author_response, "reviewers": serialize_reviewers(reviewers, users, projects, diff_phid), "is_secure": secure, "is_using_secure_commit_message": commit_description.sanitized, }) repositories = [] for phid in stack_data.repositories.keys(): short_name = PhabricatorClient.expect(stack_data.repositories[phid], "fields", "shortName") repo = supported_repos.get(short_name) if repo is None: landing_supported, approval_required = False, None else: landing_supported, approval_required = True, repo.approval_required url = ("{phabricator_url}/source/{short_name}".format( phabricator_url=current_app.config["PHABRICATOR_URL"], short_name=short_name, ) if not landing_supported else supported_repos[short_name].url) repositories.append({ "phid": phid, "short_name": short_name, "url": url, "landing_supported": landing_supported, "approval_required": approval_required, }) return { "repositories": repositories, "revisions": revisions_response, "edges": [e for e in edges], "landable_paths": landable, "uplift_repositories": uplift_repos, }
def test_calculate_landable_subgraphs_complex_graph(phabdouble): phab = phabdouble.get_phabricator_client() repoA = phabdouble.repo(name='repoA') repoB = phabdouble.repo(name='repoB') repoC = phabdouble.repo(name='repoC') # Revision stack to construct: # * rB4 # |\ # | * rB3 # * | rB2 (CLOSED) # | * rC1 # |/ # * rA10 # /|\ # * | | rB1 # | * rA9 (CLOSED) # * rA8 # | * rA7 # |/ # * rA6 # /| # | * rA5 # | * rA4 # * |\ rA3 (CLOSED) # | * | rA2 # \|/ # * rA1 (CLOSED) rA1 = phabdouble.revision(repo=repoA, status=RevisionStatus.PUBLISHED) rA2 = phabdouble.revision(repo=repoA, depends_on=[rA1]) rA3 = phabdouble.revision(repo=repoA, status=RevisionStatus.PUBLISHED, depends_on=[rA1]) rA4 = phabdouble.revision(repo=repoA, depends_on=[rA1, rA2]) rA5 = phabdouble.revision(repo=repoA, depends_on=[rA4]) rA6 = phabdouble.revision(repo=repoA, depends_on=[rA3, rA5]) rA7 = phabdouble.revision(repo=repoA, depends_on=[rA6]) rA8 = phabdouble.revision(repo=repoA, depends_on=[rA6]) rA9 = phabdouble.revision(repo=repoA, status=RevisionStatus.PUBLISHED) rB1 = phabdouble.revision(repo=repoB) rA10 = phabdouble.revision(repo=repoA, depends_on=[rA8, rA9, rB1]) rC1 = phabdouble.revision(repo=repoC, depends_on=[rA10]) rB2 = phabdouble.revision(repo=repoB, status=RevisionStatus.PUBLISHED) rB3 = phabdouble.revision(repo=repoB, depends_on=[rA10]) rB4 = phabdouble.revision(repo=repoB, depends_on=[rB2, rB3]) nodes, edges = build_stack_graph(phab, rA1['phid']) ext_data = request_extended_revision_data(phab, [ rA1['phid'], rA2['phid'], rA3['phid'], rA4['phid'], rA5['phid'], rA6['phid'], rA7['phid'], rA8['phid'], rA9['phid'], rA10['phid'], rB1['phid'], rB2['phid'], rB3['phid'], rB4['phid'], rC1['phid'], ]) landable, _ = calculate_landable_subgraphs(ext_data, edges, {repoA['phid'], repoB['phid']}) assert len(landable) == 3 assert [ rA2['phid'], rA4['phid'], rA5['phid'], rA6['phid'], rA7['phid'], ] in landable assert [ rA2['phid'], rA4['phid'], rA5['phid'], rA6['phid'], rA8['phid'], ] in landable assert [rB1['phid']] in landable
def get(revision_id): """Get the stack a revision is part of. Args: revision_id: (string) ID of the revision in 'D{number}' format """ revision_id = revision_id_to_int(revision_id) phab = g.phabricator revision = phab.call_conduit( 'differential.revision.search', constraints={'ids': [revision_id]}, ) revision = phab.single(revision, 'data', none_when_empty=True) if revision is None: return problem( 404, 'Revision not found', 'The requested revision does not exist', type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404' ) # TODO: This assumes that all revisions and related objects in the stack # have uniform view permissions for the requesting user. Some revisions # being restricted could cause this to fail. nodes, edges = build_stack_graph(phab, phab.expect(revision, 'phid')) stack_data = request_extended_revision_data(phab, [phid for phid in nodes]) supported_repos = get_repos_for_env(current_app.config.get('ENVIRONMENT')) landable_repos = get_landable_repos_for_revision_data( stack_data, supported_repos ) landable, blocked = calculate_landable_subgraphs( stack_data, edges, landable_repos, other_checks=DEFAULT_OTHER_BLOCKER_CHECKS ) involved_phids = set() for revision in stack_data.revisions.values(): involved_phids.update(gather_involved_phids(revision)) involved_phids = list(involved_phids) users = lazy_user_search(phab, involved_phids)() projects = lazy_project_search(phab, involved_phids)() revisions_response = [] for phid, revision in stack_data.revisions.items(): revision_phid = PhabricatorClient.expect(revision, 'phid') fields = PhabricatorClient.expect(revision, 'fields') diff_phid = PhabricatorClient.expect(fields, 'diffPHID') diff = stack_data.diffs[diff_phid] human_revision_id = 'D{}'.format( PhabricatorClient.expect(revision, 'id') ) revision_url = urllib.parse.urljoin( current_app.config['PHABRICATOR_URL'], human_revision_id ) title = PhabricatorClient.expect(fields, 'title') summary = PhabricatorClient.expect(fields, 'summary') bug_id = get_bugzilla_bug(revision) reviewers = get_collated_reviewers(revision) accepted_reviewers = [ reviewer_identity(phid, users, projects).identifier for phid, r in reviewers.items() if r['status'] is ReviewerStatus.ACCEPTED ] commit_message_title, commit_message = format_commit_message( title, bug_id, accepted_reviewers, summary, revision_url ) author_response = serialize_author( phab.expect(fields, 'authorPHID'), users ) revisions_response.append({ 'id': human_revision_id, 'phid': revision_phid, 'status': serialize_status(revision), 'blocked_reason': blocked.get(revision_phid, ''), 'bug_id': bug_id, 'title': title, 'url': revision_url, 'date_created': PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, 'fields', 'dateCreated') ).isoformat(), 'date_modified': PhabricatorClient.to_datetime( PhabricatorClient.expect(revision, 'fields', 'dateModified') ).isoformat(), 'summary': summary, 'commit_message_title': commit_message_title, 'commit_message': commit_message, 'diff': serialize_diff(diff), 'author': author_response, 'reviewers': serialize_reviewers( reviewers, users, projects, diff_phid ), }) # yapf: disable return { 'revisions': revisions_response, 'edges': [e for e in edges], 'landable_paths': landable, }