def _add_rq_to_prj_pseudometa(self, project, request_id, package): """ Records request as part of the project within metadata :param project: project to record into :param request_id: request id to record :param package: package the request is about """ data = self.get_prj_pseudometa(project) append = True for request in data['requests']: if request['package'] == package: # Only update if needed (to save calls to get_request) if request['id'] != request_id or not request.get('author'): request['id'] = request_id request['author'] = get_request( self.apiurl, str(request_id)).get_creator() append = False if append: author = get_request(self.apiurl, str(request_id)).get_creator() data['requests'].append({ 'id': request_id, 'package': package, 'author': author }) self.set_prj_pseudometa(project, data)
def perform(self, packages, cleanup, message): """ Remove request from staging project :param packages: packages/requests to delete from staging projects """ if cleanup: obsolete = self.api.project_status_requests( 'obsolete', self.filter_obsolete) if len(obsolete) > 0: print('Cleanup {} obsolete requests'.format(len(obsolete))) packages += tuple(obsolete) affected_projects = set() for request, request_project in RequestFinder.find_staged_sr( packages, self.api).items(): staging_project = request_project['staging'] affected_projects.add(staging_project) print('Unselecting "{}" from "{}"'.format(request, staging_project)) self.api.rm_from_prj(staging_project, request_id=request) req = get_request(self.api.apiurl, str(request)) if message: self.api.add_ignored_request(request, message) self.comment.add_comment(request_id=str(request), comment=message) elif req.state.name in ('new', 'review'): print( ' Consider marking the request ignored to let others know not to restage.' ) # Notify everybody about the changes for prj in affected_projects: self.api.update_status_or_deactivate(prj, 'unselect')
def repair(self, request): reviews = [] reqid = str(request) req = get_request(self.api.apiurl, reqid) if not req: raise oscerr.WrongArgs('Request {} not found'.format(reqid)) if req.state.name != 'review': print('Request "{}" is not in review state'.format(reqid)) return reviews = [ r.by_project for r in req.reviews if ':Staging:' in str(r.by_project) and r.state == 'new' ] if reviews: if len(reviews) > 1: raise oscerr.WrongArgs( 'Request {} had multiple review opened by different staging project' .format(reqid)) else: raise oscerr.WrongArgs( 'Request {} is not for staging project'.format(reqid)) staging_project = reviews[0] try: data = self.api.get_prj_pseudometa(staging_project) except urllib2.HTTPError, e: if e.code == 404: data = None
def add_review(self, request_id, by_project=None, by_group=None, msg=None): """ Adds review by project to the request :param request_id: request to add review to :param project: project to assign review to """ req = get_request(self.apiurl, str(request_id)) if not req: raise oscerr.WrongArgs('Request {} not found'.format(request_id)) for i in req.reviews: if by_project and i.by_project == by_project and i.state == 'new': return if by_group and i.by_group == by_group and i.state == 'new': return # don't try to change reviews if the request is dead if req.state.name not in ('new', 'review'): return query = {} if by_project: query['by_project'] = by_project if not msg: msg = 'Being evaluated by staging project "{}"' msg = msg.format(by_project) if by_group: query['by_group'] = by_group if not msg: msg = 'Being evaluated by group "{}"'.format(by_group) if not query: raise oscerr.WrongArgs('We need a group or a project') query['cmd'] = 'addreview' url = self.makeurl(['request', str(request_id)], query) http_POST(url, data=msg)
def perform(self, requests, cleanup=False): """ Unignore a request by removing from ignore list. """ requests_ignored = self.api.get_ignored_requests() length = len(requests_ignored) if len(requests) == 1 and requests[0] == 'all': requests_ignored = {} else: for request_id in RequestFinder.find_sr(requests, self.api): if request_id in requests_ignored.keys(): print('{}: unignored'.format(request_id)) del requests_ignored[request_id] self.api.del_ignored_request(request_id) self.comment.add_comment(request_id=str(request_id), comment=self.MESSAGE) if cleanup: now = datetime.now() for request_id in set(requests_ignored): request = get_request(self.api.apiurl, str(request_id)) if request.state.name not in ('new', 'review'): changed = dateutil.parser.parse(request.state.when) diff = now - changed if diff.days > 3: print('Removing {} which was {} {} days ago'.format( request_id, request.state.name, diff.days)) self.api.del_ignored_request(request_id) return True
def do_change_review_state(self, request_id, newstate, message=None, by_group=None, by_user=None, by_project=None): """ Change review state of the staging request :param request_id: id of the request :param newstate: state of the new request :param message: message for the review :param by_group, by_user, by_project: review type """ message = '' if not message else message req = get_request(self.apiurl, str(request_id)) if not req: raise oscerr.WrongArgs('Request {} not found'.format(request_id)) for review in req.reviews: if review.by_group == by_group and \ review.by_user == by_user and \ review.by_project == by_project and \ review.state == 'new': # call osc's function return change_review_state(self.apiurl, str(request_id), newstate, message=message, by_group=by_group, by_user=by_user, by_project=by_project) return False
def repair(self, request): reviews = [] reqid = str(request) req = get_request(self.api.apiurl, reqid) if not req: raise oscerr.WrongArgs('Request {} not found'.format(reqid)) if req.state.name != 'review': print('Request "{}" is not in review state'.format(reqid)) return reviews = [r.by_project for r in req.reviews if ':Staging:' in str(r.by_project) and r.state == 'new'] if reviews: if len(reviews) > 1: raise oscerr.WrongArgs('Request {} had multiple review opened by different staging project'.format(reqid)) else: raise oscerr.WrongArgs('Request {} is not for staging project'.format(reqid)) staging_project = reviews[0] data = self.api.get_prj_pseudometa(staging_project) for request in data['requests']: if request['id'] == reqid: print('Request "{}" had the good setup in "{}"'.format(reqid, staging_project)) return # a bad request setup found print('Repairing "{}"'.format(reqid)) change_review_state(self.api.apiurl, reqid, newstate='accepted', message='Re-evaluation needed', by_project=staging_project) self.api.add_review(reqid, by_group=self.api.cstaging_group, msg='Requesting new staging review')
def assertAnnotation(self, request_id, annotation): request = get_request(self.wf.apiurl, request_id) annotation_actual = origin_annotation_load(request, request.actions[0], self.bot_user) self.assertTrue(type(annotation_actual) is dict) self.assertEqual(annotation_actual, annotation)
def set_review(self, request_id, project, state='accepted', msg=None): """ Sets review for request done by project :param request_id: request to change review for :param project: project to do the review """ req = get_request(self.apiurl, str(request_id)) if not req: raise oscerr.WrongArgs('Request {} not found'.format(request_id)) # don't try to change reviews if the request is dead if req.state.name not in ('new', 'review'): return cont = False for i in req.reviews: if i.by_project == project and i.state == 'new': cont = True if not cont: return if not msg: msg = 'Reviewed by staging project "{}" with result: "{}"' msg = msg.format(project, state) self.do_change_review_state(request_id, state, by_project=project, message=msg)
def assertReview(self, rid, **kwargs): request = get_request(self.apiurl, rid) for review in request.reviews: for key, value in kwargs.items(): if hasattr(review, key) and getattr(review, key) == value[0]: self.assertEqual(review.state, value[1], '{}={} not {}'.format(key, value[0], value[1])) return self.fail('{} not found'.format(kwargs))
def repair(self, request): reviews = [] reqid = str(request) req = get_request(self.api.apiurl, reqid) if not req: raise oscerr.WrongArgs('Request {} not found'.format(reqid)) if req.state.name != 'review': print('Request "{}" is not in review state'.format(reqid)) return reviews = [ r.by_project for r in req.reviews if ':Staging:' in str(r.by_project) and r.state == 'new' ] if reviews: if len(reviews) > 1: raise oscerr.WrongArgs( 'Request {} had multiple review opened by different staging project' .format(reqid)) else: raise oscerr.WrongArgs( 'Request {} is not for staging project'.format(reqid)) staging_project = reviews[0] try: data = self.api.project_status(staging_project) except HTTPError as e: if e.code == 404: data = None # Pre-check and pre-setup if data is not None: for request in data.findall('staged_requests/requests'): if request.get('id') == reqid: print('Request "{}" had the good setup in "{}"'.format( reqid, staging_project)) return else: # this situation should only happen on adi staging print('Project is not exist, re-creating "{}"'.format( staging_project)) name = self.api.create_adi_project(staging_project) # a bad request setup found print('Repairing "{}"'.format(reqid)) change_review_state(self.api.apiurl, reqid, newstate='accepted', message='Re-evaluation needed', by_project=staging_project) self.api.add_review(reqid, by_group=self.api.cstaging_group, msg='Requesting new staging review')
def check_and_comment(self, request_id, message=None): request = get_request(self.api.apiurl, request_id) if not request: return 'not found' if request.actions[0].tgt_project != self.api.project: return 'not targeting {}'.format(self.api.project) if message: self.comment.add_comment(request_id=request_id, comment=message) return True
def assertReview(self, rid, **kwargs): request = get_request(self.apiurl, rid) for review in request.reviews: for key, value in kwargs.items(): if hasattr(review, key) and getattr(review, key) == value[0]: self.assertEqual( review.state, value[1], '{}={} not {}'.format(key, value[0], value[1])) return review self.fail('{} not found'.format(kwargs))
def test_accept_bugowners(self): wf = self.setup_wf(description="bugowner: group:factory-staging") self.assertEqual(True, AcceptCommand(wf.api).accept_all(['B'])) # we expect that the requests increase by 1 - to avoid a full search request = get_request(wf.apiurl, str(int(self.winerq.reqid) + 1)) # it's in review because this is a staging workflow self.assertEqual(request.state.name, 'review') exp = '<action type="set_bugowner">\n <target project="openSUSE:Factory" ' + \ 'package="wine" />\n <group name="factory-staging" />\n</action>' self.assertEqual(request.actions[0].to_str(), exp)
def _add_rq_to_prj_pseudometa(self, project, request_id, package): """ Records request as part of the project within metadata :param project: project to record into :param request_id: request id to record :param package: package the request is about """ data = self.get_prj_pseudometa(project) append = True for request in data['requests']: if request['package'] == package: # Only update if needed (to save calls to get_request) if request['id'] != request_id or not request.get('author'): request['id'] = request_id request['author'] = get_request(self.apiurl, str(request_id)).get_creator() append = False if append: author = get_request(self.apiurl, str(request_id)).get_creator() data['requests'].append({'id': request_id, 'package': package, 'author': author}) self.set_prj_pseudometa(project, data)
def repair(self, request): reviews = [] reqid = str(request) req = get_request(self.api.apiurl, reqid) if not req: raise oscerr.WrongArgs('Request {} not found'.format(reqid)) if req.state.name != 'review': print('Request "{}" is not in review state'.format(reqid)) return reviews = [ r.by_project for r in req.reviews if ':Staging:' in str(r.by_project) and r.state == 'new' ] if reviews: if len(reviews) > 1: raise oscerr.WrongArgs( 'Request {} had multiple review opened by different staging project' .format(reqid)) else: raise oscerr.WrongArgs( 'Request {} is not for staging project'.format(reqid)) staging_project = reviews[0] data = self.api.get_prj_pseudometa(staging_project) for request in data['requests']: if request['id'] == reqid: print('Request "{}" had the good setup in "{}"'.format( reqid, staging_project)) return # a bad request setup found print('Repairing "{}"'.format(reqid)) change_review_state(self.api.apiurl, reqid, newstate='accepted', message='Re-evaluation needed', by_project=staging_project) self.api.add_review(reqid, by_group=self.api.cstaging_group, msg='Requesting new staging review')
def assertReview(self, rid, **kwargs): """Asserts there is a review for the given request that is assigned to the given target (user, group or project) and that is in the expected state. For example, this asserts there is a new review for the user 'jdoe' in the request 20: ``assertReview(20, by_user=('jdoe', 'new'))`` :return: the found review, if the assertion succeeds :rtype: Review or None """ request = get_request(self.apiurl, rid) for review in request.reviews: for key, value in kwargs.items(): if hasattr(review, key) and getattr(review, key) == value[0]: self.assertEqual(review.state, value[1], '{}={} not {}'.format(key, value[0], value[1])) return review self.fail('{} not found'.format(kwargs))
def repair(self, request): reviews = [] reqid = str(request) req = get_request(self.api.apiurl, reqid) if not req: raise oscerr.WrongArgs('Request {} not found'.format(reqid)) if req.state.name != 'review': print('Request "{}" is not in review state'.format(reqid)) return reviews = [r.by_project for r in req.reviews if ':Staging:' in str(r.by_project) and r.state == 'new'] if reviews: if len(reviews) > 1: raise oscerr.WrongArgs('Request {} had multiple review opened by different staging project'.format(reqid)) else: raise oscerr.WrongArgs('Request {} is not for staging project'.format(reqid)) staging_project = reviews[0] try: data = self.api.get_prj_pseudometa(staging_project) except HTTPError as e: if e.code == 404: data = None # Pre-check and pre-setup if data: for request in data['requests']: if request['id'] == reqid: print('Request "{}" had the good setup in "{}"'.format(reqid, staging_project)) return else: # this situation should only happens on adi staging print('Project is not exist, re-creating "{}"'.format(staging_project)) name = self.api.create_adi_project(staging_project) # a bad request setup found print('Repairing "{}"'.format(reqid)) change_review_state(self.api.apiurl, reqid, newstate='accepted', message='Re-evaluation needed', by_project=staging_project) self.api.add_review(reqid, by_group=self.api.cstaging_group, msg='Requesting new staging review')
def update_status_comments(self, project, command): """ Refresh the status comments, used for notification purposes, based on the current list of requests. To ensure that all involved users (and nobody else) get notified, old status comments are deleted and a new one is created. :param project: project name :param command: name of the command to include in the message """ # TODO: we need to discuss the best way to keep track of status # comments. Right now they are marked with an initial markdown # comment. Maybe a cleaner approach would be to store something # like 'last_status_comment_id' in the pseudometa. But the current # OBS API for adding comments doesn't return the id of the created # comment. comment_api = CommentAPI(self.apiurl) comments = comment_api.get_comments(project_name=project) for comment in comments.values(): # TODO: update the comment removing the user mentions instead of # deleting the whole comment. But there is currently not call in # OBS API to update a comment if comment['comment'].startswith('<!--- osc staging'): comment_api.delete(comment['id']) break # There can be only one! (if we keep deleting them) meta = self.get_prj_pseudometa(project) lines = ['<!--- osc staging %s --->' % command] lines.append('The list of requests tracked in %s has changed:\n' % project) for req in meta['requests']: author = req.get('autor', None) if not author: # Old style metadata author = get_request(self.apiurl, str(req['id'])).get_creator() lines.append(' * Request#%s for package %s submitted by @%s' % (req['id'], req['package'], author)) msg = '\n'.join(lines) comment_api.add_comment(project_name=project, comment=msg)
def perform(self, packages, cleanup=False): """ Remove request from staging project :param packages: packages/requests to delete from staging projects """ if cleanup: obsolete = self.api.project_status_requests( 'obsolete', self.filter_obsolete) if len(obsolete) > 0: print('Cleanup {} obsolete requests'.format(len(obsolete))) packages += tuple(obsolete) ignored_requests = self.api.get_ignored_requests() affected_projects = set() for request, request_project in RequestFinder.find_staged_sr( packages, self.api).items(): staging_project = request_project['staging'] affected_projects.add(staging_project) msg = 'Unselecting "{}" from "{}"'.format(request, staging_project) print(msg) self.api.rm_from_prj( staging_project, request_id=request, msg='Removing from {}, re-evaluation needed'.format( staging_project)) self.api.add_review(request, by_group=self.api.cstaging_group, msg='Requesting new staging review') req = get_request(self.api.apiurl, str(request)) if req.state.name in ( 'new', 'review') and request not in ignored_requests: print( ' Consider marking the request ignored to let others know not to restage.' ) # Notify everybody about the changes for prj in affected_projects: self.api.update_status_or_deactivate(prj, 'unselect')
def rq_to_prj(self, request_id, project): """ Links request to project - delete or submit :param request_id: request to link :param project: project to link into """ # read info from sr tar_pkg = None req = get_request(self.apiurl, str(request_id)) if not req: raise oscerr.WrongArgs('Request {} not found'.format(request_id)) act = req.get_actions('submit') if act: tar_pkg = self.submit_to_prj(act[0], project) act = req.get_actions('delete') if act: tar_pkg = self.delete_to_prj(act[0], project) if not tar_pkg: msg = 'Request {} is not a submit or delete request' msg = msg.format(request_id) raise oscerr.WrongArgs(msg) # register the package name self._add_rq_to_prj_pseudometa(project, int(request_id), tar_pkg) # add review self.add_review(request_id, project) # now remove the staging checker self.do_change_review_state(request_id, 'accepted', by_group=self.cstaging_group, message='Picked {}'.format(project)) return True
def perform(self, requests, cleanup=False): """ Unignore a request by removing from ignore list. """ requests_ignored = self.api.get_ignored_requests() length = len(requests_ignored) if len(requests) == 1 and requests[0] == 'all': requests_ignored = {} else: for request_id in RequestFinder.find_sr(requests, self.api): if request_id in requests_ignored: print('{}: unignored'.format(request_id)) del requests_ignored[request_id] self.comment.add_comment(request_id=str(request_id), comment=self.MESSAGE) if cleanup: now = datetime.now() for request_id in set(requests_ignored): request = get_request(self.api.apiurl, str(request_id)) if request.state.name not in ('new', 'review'): changed = dateutil.parser.parse(request.state.when) diff = now - changed if diff.days > 3: print('Removing {} which was {} {} days ago' .format(request_id, request.state.name, diff.days)) del requests_ignored[request_id] diff = length - len(requests_ignored) if diff > 0: self.api.set_ignored_requests(requests_ignored) print('Unignored {} requests'.format(diff)) else: print('No requests to unignore') return True
def rq_to_prj(self, request_id, project): """ Links request to project - delete or submit :param request_id: request to link :param project: project to link into """ # read info from sr tar_pkg = None req = get_request(self.apiurl, str(request_id)) if not req: raise oscerr.WrongArgs('Request {} not found'.format(request_id)) act = req.get_actions('submit') if act: tar_pkg = self.submit_to_prj(act[0], project) act = req.get_actions('delete') if act: tar_pkg = self.delete_to_prj(act[0], project) if not tar_pkg: msg = 'Request {} is not a submit or delete request' msg = msg.format(request_id) raise oscerr.WrongArgs(msg) # register the package name self._add_rq_to_prj_pseudometa(project, int(request_id), tar_pkg) # add review self.add_review(request_id, project) # now remove the staging checker self.do_change_review_state(request_id, 'accepted', by_group='factory-staging', message='Picked {}'.format(project)) return True
def perform(self, request_ids, cleanup=False): """ Unignore a request by removing from ignore list. """ requests_ignored = self.api.get_ignored_requests() length = len(requests_ignored) if len(request_ids) == 1 and request_ids[0] == 'all': requests_ignored = {} else: for request_id in request_ids: request_id = int(request_id) if request_id in requests_ignored: print('Removing {}'.format(request_id)) del requests_ignored[request_id] if cleanup: now = datetime.now() for request_id in set(requests_ignored): request = get_request(self.api.apiurl, str(request_id)) if request.state.name not in ('new', 'review'): changed = dateutil.parser.parse(request.state.when) diff = now - changed if diff.days > 3: print('Removing {} which was {} {} days ago'.format( request_id, request.state.name, diff.days)) del requests_ignored[request_id] diff = length - len(requests_ignored) if diff > 0: print('Unignoring {} requests'.format(diff)) self.api.set_ignored_requests(requests_ignored) else: print('No requests to unignore') return True
try: reqdiff += diff.decode('utf-8') except UnicodeDecodeError: try: reqdiff += diff.decode('iso-8859-1') except UnicodeDecodeError: pass except urllib2.HTTPError, e: e.osc_msg = 'Diff not possible' return '' return reqdiff req = core.get_request(self.apiurl, reqid) try: req.reviews = [] reqinfo = unicode(req) except UnicodeEncodeError: reqinfo = u'' if show_detail: diff = gen_request_diff() if diff is None: diff = gen_server_diff(req) reqinfo += diff # the result, in unicode string return reqinfo
def get_request(self, wid, req_id, diff): # Copied from api.py req = core.get_request(self.obs.apiurl, req_id) return self.obs.req_to_dict(req, action_diff=diff)
def assertRequestState(self, rid, **kwargs): request = get_request(self.apiurl, rid) for key, value in kwargs.items(): self.assertEqual(getattr(request.state, key), value)
def devel_workflow(self, only_devel): self.remote_config_set_age_minimum() devel_project = self.randomString('devel') package = self.randomString('package') request = self.wf.create_submit_request(devel_project, package) attribute_value_save(self.wf.apiurl, devel_project, 'ApprovedRequestSource', '', 'OBS') if not only_devel: self.assertReviewBot(request.reqid, self.bot_user, 'new', 'new') comment = [ '<!-- OriginManager state=seen result=None -->', 'Source not found in allowed origins:', f'- {self.product_project}', f'Decision may be overridden via `@{self.bot_user} override`.', ] self.assertComment(request.reqid, comment) CommentAPI(self.wf.api.apiurl).add_comment( request_id=request.reqid, comment=f'@{self.bot_user} change_devel') comment = 'change_devel command by {}'.format('Admin') else: comment = 'only devel origin allowed' self.assertReviewBot(request.reqid, self.bot_user, 'new', 'accepted') self.assertAnnotation(request.reqid, { 'comment': comment, 'origin': devel_project, }) request.change_state('accepted') memoize_session_reset() self.osc_user(self.bot_user) request_future = origin_update(self.wf.apiurl, self.wf.project, package) self.assertNotEqual(request_future, False) if request_future: request_id_change_devel = request_future.print_and_create() # Ensure a second request is not triggered. request_future = origin_update(self.wf.apiurl, self.wf.project, package) self.assertEqual(request_future, False) self.osc_user_pop() memoize_session_reset() origin_info = origin_find(self.wf.apiurl, self.wf.project, package) self.assertEqual(origin_info, None) self.assertReviewBot(request_id_change_devel, self.bot_user, 'new', 'accepted') self.assertAnnotation(request_id_change_devel, { 'origin': devel_project, }) # Origin should change before request is accepted since it is properly # annotated and without fallback review. memoize_session_reset() origin_info = origin_find(self.wf.apiurl, self.wf.project, package) self.assertEqual(str(origin_info), devel_project) self.wf.projects[devel_project].packages[0].create_commit() self.osc_user(self.bot_user) request_future = origin_update(self.wf.apiurl, self.wf.project, package) self.assertNotEqual(request_future, False) if request_future: request_id_update = request_future.print_and_create() request_future = origin_update(self.wf.apiurl, self.wf.project, package) self.assertEqual(request_future, False) self.osc_user_pop() self.assertReviewBot(request_id_update, self.bot_user, 'new', 'accepted') self.assertAnnotation(request_id_update, { 'origin': devel_project, }) memoize_session_reset() devel_project_actual, _ = devel_project_get(self.wf.apiurl, self.wf.project, package) self.assertEqual(devel_project_actual, None) request = get_request(self.wf.apiurl, request_id_change_devel) request_state_change(self.wf.apiurl, request_id_change_devel, 'accepted') memoize_session_reset() devel_project_actual, devel_package_actual = devel_project_get( self.wf.apiurl, self.wf.project, package) self.assertEqual(devel_project_actual, devel_project) self.assertEqual(devel_package_actual, package) request = get_request(self.wf.apiurl, request_id_update) request_state_change(self.wf.apiurl, request_id_update, 'accepted') devel_project_new = self.randomString('develnew') self.wf.create_package(devel_project_new, package) attribute_value_save(self.wf.apiurl, devel_project_new, 'ApprovedRequestSource', '', 'OBS') copy_package(self.wf.apiurl, devel_project, package, self.wf.apiurl, devel_project_new, package) request_future = request_create_change_devel( self.wf.apiurl, devel_project_new, package, self.wf.project) self.assertNotEqual(request_future, False) if request_future: request_id_change_devel_new = request_future.print_and_create() self.assertReviewBot(request_id_change_devel_new, self.bot_user, 'new', 'accepted') self.assertAnnotation(request_id_change_devel_new, { 'origin': devel_project_new, 'origin_old': devel_project, }) self.accept_fallback_review(request_id_change_devel_new) request_state_change(self.wf.apiurl, request_id_change_devel_new, 'accepted') memoize_session_reset() origin_info = origin_find(self.wf.apiurl, self.wf.project, package) self.assertEqual(str(origin_info), devel_project_new)
time.sleep(max([0, change["time"] + 300 - time.time()])) package = change['package'] commitdate = datetime.datetime.utcfromtimestamp(int( change["time"])).strftime("%Y-%m-%dT%H:%M:%S") if 'rev' in change: info = 'Update ' + package + ' to rev ' + change['rev'] else: info = 'Delete ' + package if not 'user' in change and 'sender' in change: change['user'] = change['sender'] author = change['user'] if 'requestid' in change: info += ' via SR ' + change[ 'requestid'] + "\n\n" + obsbase + '/request/show/' + change[ 'requestid'] + "\n" rqobj = get_request(osc.conf.config['apiurl'], change['requestid']) commitdate = rqobj.state.when user2 = rqobj.creator if 'user' in change and user2 != change['user']: change['user'] = user2 + ' + ' + change['user'] author = user2 else: info += "\n\n" if 'rev' in change: info += obsbase + '/package/rdiff/' + change[ 'project'] + '/' + package + '?linkrev=base&rev=' + change[ 'rev'] + "\n" try: obsrev = get_source_rev(osc.conf.config['apiurl'], "openSUSE:Factory", package, change['rev'])