def dryrun(data): """API endpoint at /landings/dryrun. Returns a LandingAssessment for the given Revision ID. """ revision_id, diff_id = unmarshal_landing_request(data) phab = g.phabricator get_revision = lazy_get_revision(phab, revision_id) get_latest_diff = lazy_get_latest_diff(phab, get_revision) get_diff = lazy_get_diff(phab, diff_id, get_latest_diff) get_diff_author = lazy_get_diff_author(get_diff) get_latest_landed = lazy(Transplant.legacy_latest_landed)(revision_id) get_repository = lazy_get_repository(phab, get_revision) get_landing_repo = lazy_get_landing_repo( get_repository, current_app.config.get('ENVIRONMENT') ) get_open_parents = lazy_get_open_parents(phab, get_revision) get_reviewers = lazy_get_reviewers(get_revision) get_reviewer_info = lazy_reviewers_search(phab, get_reviewers) get_reviewers_extra_state = lazy_get_reviewers_extra_state( get_reviewers, get_diff ) get_revision_status = lazy_get_revision_status(get_revision) assessment = check_landing_conditions( g.auth0_user, revision_id, diff_id, get_revision, get_latest_diff, get_latest_landed, get_repository, get_landing_repo, get_diff, get_diff_author, get_open_parents, get_reviewers, get_reviewer_info, get_reviewers_extra_state, get_revision_status, ) return jsonify(assessment.to_dict())
def get(revision_id, diff_id=None): """Gets revision from Phabricator. Args: revision_id: (string) ID of the revision in 'D{number}' format diff_id: (integer) Id of the diff to return with the revision. By default the active diff will be returned. """ revision_id = revision_id_to_int(revision_id) phab = g.phabricator revision = phab.call_conduit( 'differential.revision.search', constraints={'ids': [revision_id]}, attachments={ 'reviewers': True, 'reviewers-extra': True, } ) 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' ) latest_diff = phab.single( phab.call_conduit( 'differential.diff.search', constraints={ 'phids': [phab.expect(revision, 'fields', 'diffPHID')] }, ), 'data' ) latest_diff_id = phab.expect(latest_diff, 'id') if diff_id is not None and diff_id != latest_diff_id: diff = phab.single( phab.call_conduit( 'differential.diff.search', constraints={'ids': [diff_id]} ), 'data', none_when_empty=True ) else: diff = latest_diff if diff is None: return problem( 404, 'Diff not found', 'The requested diff does not exist', type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404' ) revision_phid = phab.expect(revision, 'phid') if phab.expect(diff, 'fields', 'revisionPHID') != revision_phid: return problem( 400, 'Diff not related to the revision', 'The requested diff is not related to the requested revision.', type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400' ) # TODO: remove when commit author information is available in # the 'commits' attachment of 'differential.revision.search'. diff_id = phab.expect(diff, 'id') querydiffs_diff = phab.call_conduit( 'differential.querydiffs', ids=[diff_id] ) querydiffs_diff = phab.expect(querydiffs_diff, str(diff_id)) author_phid = phab.expect(revision, 'fields', 'authorPHID') # Immediately execute the lazy functions. reviewers = lazy_get_reviewers(revision)() users = lazy_user_search(phab, list(reviewers.keys()) + [author_phid])() projects = lazy_project_search(phab, list(reviewers.keys()))() accepted_reviewers = [ reviewer_identity(phid, users, projects).identifier for phid, r in reviewers.items() if r['status'] is ReviewerStatus.ACCEPTED ] title = phab.expect(revision, 'fields', 'title') summary = phab.expect(revision, 'fields', 'summary') bug_id = phab.expect(revision, 'fields').get('bugzilla.bug-id') bug_id = int(bug_id) if bug_id and not isinstance(bug_id, int) else None human_revision_id = 'D{}'.format(revision_id) revision_url = urllib.parse.urljoin( current_app.config['PHABRICATOR_URL'], human_revision_id ) commit_message_title, commit_message = format_commit_message( title, bug_id, accepted_reviewers, summary, revision_url ) reviewers_response = _render_reviewers_response( reviewers, users, projects, phab.expect(diff, 'phid') ) author_response = _render_author_response(author_phid, users) diff_response = _render_diff_response(querydiffs_diff) return { 'id': human_revision_id, 'phid': phab.expect(revision, 'phid'), 'bug_id': bug_id, 'title': title, 'url': revision_url, 'date_created': _epoch_to_isoformat_time( phab.expect(revision, 'fields', 'dateCreated') ), 'date_modified': _epoch_to_isoformat_time( phab.expect(revision, 'fields', 'dateModified') ), 'summary': summary, 'commit_message_title': commit_message_title, 'commit_message': commit_message, 'diff': diff_response, 'latest_diff_id': latest_diff_id, 'author': author_response, 'reviewers': reviewers_response, }, 200 # yapf: disable
def post(data): """API endpoint at POST /landings to land revision.""" logger.info( 'landing requested by user', extra={ 'path': request.path, 'method': request.method, 'data': data, } ) revision_id, diff_id = unmarshal_landing_request(data) confirmation_token = data.get('confirmation_token') or None phab = g.phabricator get_revision = lazy_get_revision(phab, revision_id) get_latest_diff = lazy_get_latest_diff(phab, get_revision) get_diff = lazy_get_diff(phab, diff_id, get_latest_diff) get_diff_author = lazy_get_diff_author(get_diff) get_latest_landed = lazy(Transplant.legacy_latest_landed)(revision_id) get_repository = lazy_get_repository(phab, get_revision) get_landing_repo = lazy_get_landing_repo( get_repository, current_app.config.get('ENVIRONMENT') ) get_open_parents = lazy_get_open_parents(phab, get_revision) get_reviewers = lazy_get_reviewers(get_revision) get_reviewer_info = lazy_reviewers_search(phab, get_reviewers) get_reviewers_extra_state = lazy_get_reviewers_extra_state( get_reviewers, get_diff ) get_revision_status = lazy_get_revision_status(get_revision) assessment = check_landing_conditions( g.auth0_user, revision_id, diff_id, get_revision, get_latest_diff, get_latest_landed, get_repository, get_landing_repo, get_diff, get_diff_author, get_open_parents, get_reviewers, get_reviewer_info, get_reviewers_extra_state, get_revision_status, short_circuit=True, ) assessment.raise_if_blocked_or_unacknowledged(confirmation_token) if assessment.warnings: # Log any warnings that were acknowledged, for auditing. logger.info( 'Landing with acknowledged warnings is being requested', extra={ 'revision_id': revision_id, 'warnings': [w.serialize() for w in assessment.warnings], } ) # These are guaranteed to return proper data since we're # running after checking_landing_conditions(). revision = get_revision() landing_repo = get_landing_repo() author_name, author_email = get_diff_author() # Collect the usernames of reviewers who have accepted. reviewers = get_reviewers() users, projects = get_reviewer_info() accepted_reviewers = [ reviewer_identity(phid, users, projects).identifier for phid, r in reviewers.items() if r['status'] is ReviewerStatus.ACCEPTED ] # Seconds since Unix Epoch, UTC. date_modified = phab.expect(revision, 'fields', 'dateModified') title = phab.expect(revision, 'fields', 'title') summary = phab.expect(revision, 'fields', 'summary') bug_id = get_bugzilla_bug(revision) human_revision_id = 'D{}'.format(revision_id) revision_url = urllib.parse.urljoin( current_app.config['PHABRICATOR_URL'], human_revision_id ) commit_message = format_commit_message( title, bug_id, accepted_reviewers, summary, revision_url ) # Construct the patch that will be sent to transplant. raw_diff = phab.call_conduit('differential.getrawdiff', diffID=diff_id) patch = build_patch_for_revision( raw_diff, author_name, author_email, commit_message[1], date_modified ) # Upload the patch to S3 patch_url = upload( revision_id, diff_id, patch, current_app.config['PATCH_BUCKET_NAME'], aws_access_key=current_app.config['AWS_ACCESS_KEY'], aws_secret_key=current_app.config['AWS_SECRET_KEY'], ) trans = TransplantClient( current_app.config['TRANSPLANT_URL'], current_app.config['TRANSPLANT_USERNAME'], current_app.config['TRANSPLANT_PASSWORD'], ) submitted_assessment = LandingAssessment( blockers=[ LandingInProgress( 'This revision was submitted for landing by another user at ' 'the same time.' ) ] ) ldap_username = g.auth0_user.email try: # WARNING: Entering critical section, do not add additional # code unless absolutely necessary. Acquires a lock on the # transplants table which gives exclusive write access and # prevents readers who are entering this critical section. # See https://www.postgresql.org/docs/9.3/static/explicit-locking.html # for more details on the specifics of the lock mode. with db.session.begin_nested(): db.session.execute( 'LOCK TABLE transplants IN SHARE ROW EXCLUSIVE MODE;' ) if Transplant.is_revision_submitted(revision_id): submitted_assessment.raise_if_blocked_or_unacknowledged(None) transplant_request_id = trans.land( revision_id=revision_id, ldap_username=ldap_username, patch_urls=[patch_url], tree=landing_repo.tree, pingback=current_app.config['PINGBACK_URL'], push_bookmark=landing_repo.push_bookmark ) transplant = Transplant( request_id=transplant_request_id, revision_to_diff_id={str(revision_id): diff_id}, revision_order=[str(revision_id)], requester_email=ldap_username, tree=landing_repo.tree, repository_url=landing_repo.url, status=TransplantStatus.submitted ) db.session.add(transplant) except TransplantError as exc: logger.info( 'error creating transplant', extra={'revision': revision_id}, exc_info=exc ) return problem( 502, 'Landing not created', 'The requested revision does exist, but landing failed.' 'Please retry your request at a later time.', type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502' ) # Transaction succeeded, commit the session. db.session.commit() logger.info( 'transplant created', extra={ 'revision_id': revision_id, 'transplant_id': transplant.id, } ) return {'id': transplant.id}, 202