def new_project(): """ Form to create a new project. """ form = pagure.forms.ProjectForm() if form.validate_on_submit(): name = form.name.data description = form.description.data try: message = pagure.lib.new_project( SESSION, name=name, description=description, user=flask.g.fas_user.username, blacklist=APP.config['BLACKLISTED_PROJECTS'], gitfolder=APP.config['GIT_FOLDER'], docfolder=APP.config['DOCS_FOLDER'], ticketfolder=APP.config['TICKETS_FOLDER'], requestfolder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() generate_gitolite_acls() flask.flash(message) return flask.redirect(flask.url_for('view_repo', repo=name)) except pagure.exceptions.PagureException, err: flask.flash(str(err), 'error') except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error')
def update_project(repo, username=None): """ Update the description of a project. """ if admin_session_timedout(): flask.flash('Action canceled, try it again', 'error') url = flask.url_for( 'view_settings', username=username, repo=repo.name) return flask.redirect( flask.url_for('auth_login', next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to change the settings for this project') form = pagure.forms.ProjectFormSimplified() if form.validate_on_submit(): try: repo.description = form.description.data repo.avatar_email = form.avatar_email.data.strip() repo.url = form.url.data.strip() SESSION.add(repo) SESSION.commit() flask.flash('Project updated') except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error')
def new_repo_hook_token(repo, username=None): """ Re-generate a hook token for the present project. """ if admin_session_timedout(): flask.flash('Action canceled, try it again', 'error') url = flask.url_for( 'view_settings', username=username, repo=repo) return flask.redirect( flask.url_for('auth_login', next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to change the settings for this project') form = pagure.forms.ConfirmationForm() if not form.validate_on_submit(): flask.abort(400, 'Invalid request') try: repo.hook_token = pagure.lib.login.id_generator(40) SESSION.commit() flask.flash('New hook token generated') except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Could not generate a new token for this project', 'error')
def delete_repo(repo, username=None): """ Delete the present project. """ if not pagure.APP.config.get('ENABLE_DEL_PROJECTS', True): flask.abort(404) if admin_session_timedout(): flask.flash('Action canceled, try it again', 'error') url = flask.url_for( 'view_settings', username=username, repo=repo) return flask.redirect( flask.url_for('auth_login', next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to change the settings for this project') try: for issue in repo.issues: for comment in issue.comments: SESSION.delete(comment) SESSION.commit() SESSION.delete(issue) SESSION.delete(repo) SESSION.commit() except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Could not delete the project', 'error')
def reset_password(token): """ Method to allow a user to reset his/her password. """ form = forms.ResetPasswordForm() user_obj = pagure.lib.search_user(SESSION, token=token) if not user_obj: flask.flash("No user associated with this token.", "error") return flask.redirect(flask.url_for("auth_login")) elif not user_obj.token: flask.flash("Invalid user, this user never asked for a password change", "error") return flask.redirect(flask.url_for("auth_login")) if form.validate_on_submit(): password = "******" % (form.password.data, APP.config.get("PASSWORD_SEED", None)) user_obj.password = hashlib.sha512(password).hexdigest() user_obj.token = None SESSION.add(user_obj) try: SESSION.commit() flask.flash("Password changed") except SQLAlchemyError as err: SESSION.rollback() flask.flash("Could not set the new password.", "error") APP.logger.debug("Password lost change - Error setting password.") APP.logger.exception(err) return flask.redirect(flask.url_for("auth_login")) return flask.render_template("login/password_reset.html", form=form, token=token)
def add_user_email(): """ Add a new email for the logged in user. """ if admin_session_timedout(): return flask.redirect( flask.url_for('auth_login', next=flask.request.url)) user = pagure.lib.search_user( SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, 'User not found') form = pagure.forms.UserEmailForm( emails=[mail.email for mail in user.emails]) if form.validate_on_submit(): email = form.email.data try: pagure.lib.add_user_pending_email(SESSION, user, email) SESSION.commit() flask.flash('Email pending validation') return flask.redirect(flask.url_for('.user_settings')) except pagure.exceptions.PagureException as err: flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Email could not be added', 'error') return flask.render_template( 'user_emails.html', user=user, form=form, )
def set_default_email(): """ Set the default email address of the user. """ if admin_session_timedout(): return flask.redirect(flask.url_for("auth_login", next=flask.request.url)) user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, "User not found") form = pagure.forms.UserEmailForm() if form.validate_on_submit(): email = form.email.data useremails = [mail.email for mail in user.emails] if email not in useremails: flask.flash("You do not have the email: %s, nothing to set" % email, "error") return flask.redirect(flask.url_for(".user_settings")) user.default_email = email try: SESSION.commit() flask.flash("Default email set to: %s" % email) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash("Default email could not be set", "error") return flask.redirect(flask.url_for(".user_settings"))
def add_user_email(): """ Add a new email for the logged in user. """ if admin_session_timedout(): return flask.redirect(flask.url_for("auth_login", next=flask.request.url)) user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, "User not found") form = pagure.forms.UserEmailForm() if form.validate_on_submit(): email = form.email.data useremails = [mail.email for mail in user.emails] if email in useremails: flask.flash("The email: %s is already associated to you" % email, "error") return flask.redirect(flask.url_for(".user_settings")) try: pagure.lib.add_user_pending_email(SESSION, user, email) SESSION.commit() flask.flash("Email pending validation") return flask.redirect(flask.url_for(".user_settings")) except pagure.exceptions.PagureException, err: flask.flash(str(err), "error") except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash("Email could not be added", "error")
def new_repo_hook_token(repo, username=None): """ Re-generate a hook token for the present project. """ if not pagure.APP.config.get("WEBHOOK", False): flask.abort(404) if admin_session_timedout(): flask.flash("Action canceled, try it again", "error") url = flask.url_for("view_settings", username=username, repo=repo) return flask.redirect(flask.url_for("auth_login", next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, "Project not found") if not is_repo_admin(repo): flask.abort(403, "You are not allowed to change the settings for this project") form = pagure.forms.ConfirmationForm() if not form.validate_on_submit(): flask.abort(400, "Invalid request") try: repo.hook_token = pagure.lib.login.id_generator(40) SESSION.commit() flask.flash("New hook token generated") except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash("Could not generate a new token for this project", "error") return flask.redirect(flask.url_for("view_settings", repo=repo.name, username=username))
def user_settings(): """ Update the user settings. """ if admin_session_timedout(): return flask.redirect(flask.url_for("auth_login", next=flask.request.url)) user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, "User not found") form = pagure.forms.UserSettingsForm() if form.validate_on_submit(): ssh_key = form.ssh_key.data try: message = "Nothing to update" if user.public_ssh_key != ssh_key: pagure.lib.update_user_ssh( SESSION, user=user, ssh_key=ssh_key, keydir=APP.config.get("GITOLITE_KEYDIR", None) ) SESSION.commit() message = "Public ssh key updated" flask.flash(message) return flask.redirect(flask.url_for("view_user", username=user.user)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), "error")
def edit_tag(repo, tag, username=None): """ Edit the specified tag of a project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to edt tags of this project') form = pagure.forms.AddIssueTagForm() if form.validate_on_submit(): new_tag = form.tag.data msgs = pagure.lib.edit_issue_tags( SESSION, repo, tag, new_tag, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ) try: SESSION.commit() for msg in msgs: flask.flash(msg) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() LOG.error(err) flask.flash('Could not edit tag: %s' % tag, 'error') return flask.redirect(flask.url_for( '.view_settings', repo=repo.name, username=username))
def reconfirm_email(): """ Re-send the email address of the user. """ if admin_session_timedout(): return flask.redirect( flask.url_for('auth_login', next=flask.request.url)) user = pagure.lib.search_user( SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, 'User not found') form = pagure.forms.UserEmailForm() if form.validate_on_submit(): email = form.email.data try: pagure.lib.resend_pending_email(SESSION, user, email) SESSION.commit() flask.flash('Confirmation email re-sent') except pagure.exceptions.PagureException as err: flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Confirmation email could not be re-sent', 'error') return flask.redirect(flask.url_for('.user_settings'))
def edit_comment_issue(repo, issueid, commentid, username=None): """Edit comment of an issue """ is_js = flask.request.args.get('js', False) project = pagure.lib.get_project(SESSION, repo, user=username) if not project: flask.abort(404, 'Project not found') if not project.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, project, issueid=issueid) if issue is None or issue.project != project: flask.abort(404, 'Issue not found') comment = pagure.lib.get_issue_comment( SESSION, issue.uid, commentid) if comment is None or comment.parent.project != project: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not is_repo_admin(project): flask.abort(403, 'You are not allowed to edit this comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): updated_comment = form.update_comment.data try: message = pagure.lib.edit_comment( SESSION, parent=issue, comment=comment, user=flask.g.fas_user.username, updated_comment=updated_comment, folder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if not is_js: flask.flash(message) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() LOG.error(err) if is_js: return 'error' flask.flash( 'Could not edit the comment: %s' % commentid, 'error') if is_js: return 'ok' return flask.redirect(flask.url_for( 'view_issue', username=username, repo=project.name, issueid=issueid))
def user_settings(): """ Update the user settings. """ if admin_session_timedout(): return flask.redirect( flask.url_for('auth_login', next=flask.request.url)) user = pagure.lib.search_user( SESSION, username=flask.g.fas_user.username) if not user: flask.abort(404, 'User not found') form = pagure.forms.UserSettingsForm() if form.validate_on_submit(): ssh_key = form.ssh_key.data try: message = pagure.lib.update_user_ssh( SESSION, user=user, ssh_key=ssh_key, ) if message != 'Nothing to update': generate_gitolite_key(user.user, ssh_key) generate_authorized_key_file() SESSION.commit() flask.flash(message) return flask.redirect( flask.url_for('view_user', username=user.user)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error')
def add_token(repo, username=None): """ Add a token to a specified project. """ if admin_session_timedout(): if flask.request.method == "POST": flask.flash("Action canceled, try it again", "error") return flask.redirect(flask.url_for("auth_login", next=flask.request.url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, "Project not found") if not is_repo_admin(repo): flask.abort(403, "You are not allowed to change the settings for this project") acls = pagure.lib.get_acls(SESSION) form = pagure.forms.NewTokenForm(acls=acls) if form.validate_on_submit(): try: msg = pagure.lib.add_token_to_user(SESSION, repo, acls=form.acls.data, username=flask.g.fas_user.username) SESSION.commit() flask.flash(msg) return flask.redirect(flask.url_for(".view_settings", repo=repo.name, username=username)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash("User could not be added", "error") return flask.render_template("add_token.html", form=form, acls=acls, username=username, repo=repo)
def revoke_api_token(repo, token_id, username=None): """ Revokie a token to a specified project. """ if admin_session_timedout(): flask.flash("Action canceled, try it again", "error") url = flask.url_for("view_settings", username=username, repo=repo) return flask.redirect(flask.url_for("auth_login", next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, "Project not found") if not is_repo_admin(repo): flask.abort(403, "You are not allowed to change the settings for this project") token = pagure.lib.get_api_token(SESSION, token_id) if not token or token.project.fullname != repo.fullname or token.user.username != flask.g.fas_user.username: flask.abort(404, "Token not found") form = pagure.forms.ConfirmationForm() if form.validate_on_submit(): try: token.expiration = datetime.datetime.utcnow() SESSION.commit() flask.flash("Token revoked") except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash("Token could not be revoked, please contact an admin", "error") return flask.redirect(flask.url_for(".view_settings", repo=repo.name, username=username))
def remove_tag(repo, username=None): """ Remove the specified tag from the project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to remove tags of this project') form = pagure.forms.AddIssueTagForm() if form.validate_on_submit(): tags = form.tag.data tags = [tag.strip() for tag in tags.split(',')] msgs = pagure.lib.remove_tags( SESSION, repo, tags, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ) try: SESSION.commit() for msg in msgs: flask.flash(msg) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() LOG.error(err) flask.flash( 'Could not remove tag: %s' % ','.join(tags), 'error')
def api_pull_request_close(repo, requestid, username=None): """ Close a pull-request -------------------- Instruct Pagure to close a pull request. :: POST /api/0/<repo>/pull-request/<request id>/close :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/close Sample response ^^^^^^^^^^^^^^^ :: { "message": "Pull-request closed!" } """ output = {} repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get("pull_requests", True): raise pagure.exceptions.APIError(404, error_code=APIERROR.EPULLREQUESTSDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) if not is_repo_admin(repo): raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE) try: pagure.lib.close_pull_request( SESSION, request, flask.g.fas_user.username, requestfolder=APP.config["REQUESTS_FOLDER"], merged=False ) SESSION.commit() output["message"] = "Pull-request closed!" except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def request_pull_patch(repo, requestid, username=None): """ Returns the commits from the specified pull-request as patches. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests( SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') repo_from = request.project_from repopath = pagure.get_repo_path(repo_from) repo_obj = pygit2.Repository(repopath) parentpath = _get_parent_repo_path(repo_from) orig_repo = pygit2.Repository(parentpath) branch = repo_obj.lookup_branch(request.branch_from) commitid = None if branch: commitid = branch.get_object().hex diff_commits = [] if request.status != 'Open': commitid = request.commit_stop for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME): diff_commits.append(commit) if commit.oid.hex == request.commit_start: break else: try: diff_commits, diff = pagure.lib.git.diff_pull_request( SESSION, request, repo_obj, orig_repo, requestfolder=APP.config['REQUESTS_FOLDER'], with_diff=False) except pagure.exceptions.PagureException as err: flask.flash(err.message, 'error') return flask.redirect(flask.url_for( 'view_repo', username=username, repo=repo.name)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash( 'Could not update this pull-request in the database', 'error') diff_commits.reverse() patch = pagure.lib.git.commit_to_patch(repo_obj, diff_commits) return flask.Response(patch, content_type="text/plain;charset=UTF-8")
def pull_request_add_comment( repo, requestid, commit=None, filename=None, row=None, username=None): """ Add a comment to a commit in a pull-request. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests( SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') is_js = flask.request.args.get('js', False) repo = request.project_from form = pagure.forms.AddPullRequestCommentForm() form.commit.data = commit form.filename.data = filename form.requestid.data = requestid form.row.data = row if form.validate_on_submit(): comment = form.comment.data try: message = pagure.lib.add_pull_request_comment( SESSION, request=request, commit=commit, filename=filename, row=row, comment=comment, user=flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], redis=REDIS, ) SESSION.commit() flask.flash(message) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash(str(err), 'error') if is_js: return 'error' if is_js: return 'ok' return flask.redirect(flask.url_for( 'request_pull', username=username, repo=repo.name, requestid=requestid))
def new_user(): """ Create a new user. """ form = forms.NewUserForm() if form.validate_on_submit(): username = form.user.data if pagure.lib.search_user(SESSION, username=username): flask.flash('Username already taken.', 'error') return flask.redirect(flask.request.url) email = form.email_address.data if pagure.lib.search_user(SESSION, email=email): flask.flash('Email address already taken.', 'error') return flask.redirect(flask.request.url) password = '******' % ( form.password.data, APP.config.get('PASSWORD_SEED', None)) form.password.data = hashlib.sha512(password).hexdigest() token = pagure.lib.login.id_generator(40) user = model.User() user.token = token form.populate_obj(obj=user) user.default_email = form.email_address.data SESSION.add(user) SESSION.flush() emails = [email.email for email in user.emails] if form.email_address.data not in emails: useremail = model.UserEmail( user_id=user.id, email=form.email_address.data) SESSION.add(useremail) SESSION.flush() try: SESSION.flush() send_confirmation_email(user) flask.flash( 'User created, please check your email to activate the ' 'account') except SQLAlchemyError as err: SESSION.rollback() flask.flash('Could not create user.') APP.logger.debug('Could not create user.') APP.logger.exception(err) SESSION.commit() return flask.redirect(flask.url_for('auth_login')) return flask.render_template( 'login/user_new.html', form=form, )
def view_settings(repo, username=None): """ Presents the settings of the project. """ if admin_session_timedout(): if flask.request.method == 'POST': flask.flash('Action canceled, try it again', 'error') return flask.redirect( flask.url_for('auth_login', next=flask.request.url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') repo_admin = is_repo_admin(repo) if not repo_admin: flask.abort( 403, 'You are not allowed to change the settings for this project') reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) plugins = pagure.ui.plugins.get_plugin_names( APP.config.get('DISABLED_PLUGINS')) tags = pagure.lib.get_tags_of_project(SESSION, repo) form = pagure.forms.ConfirmationForm() tag_form = pagure.forms.AddIssueTagForm() branches = repo_obj.listall_branches() branches_form = pagure.forms.DefaultBranchForm(branches=branches) if form.validate_on_submit(): settings = {} for key in flask.request.form: if key == 'csrf_token': continue settings[key] = flask.request.form[key] try: message = pagure.lib.update_project_settings( SESSION, repo=repo, settings=settings, user=flask.g.fas_user.username, ) SESSION.commit() flask.flash(message) return flask.redirect(flask.url_for( 'view_repo', username=username, repo=repo.name)) except pagure.exceptions.PagureException as msg: SESSION.rollback() flask.flash(msg, 'error') except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error')
def pull_request_drop_comment(repo, requestid, username=None): """ Delete a comment of a pull-request. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests( SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') if flask.request.form.get('edit_comment'): commentid = flask.request.form.get('edit_comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): return pull_request_edit_comment( repo.name, requestid, commentid, username=username) form = pagure.forms.ConfirmationForm() if form.validate_on_submit(): if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_request_comment( SESSION, request.uid, commentid) if comment is None or comment.pull_request.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status is False) \ and not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') SESSION.delete(comment) try: SESSION.commit() flask.flash('Comment removed') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() LOG.error(err) flask.flash( 'Could not remove the comment: %s' % commentid, 'error') return flask.redirect(flask.url_for( 'request_pull', username=username, repo=repo.name, requestid=requestid))
def pull_request_edit_comment(repo, requestid, commentid, username=None): """Edit comment of a pull request """ is_js = flask.request.args.get("js", False) project = pagure.lib.get_project(SESSION, repo, user=username) if not project: flask.abort(404, "Project not found") if not project.settings.get("pull_requests", True): flask.abort(404, "No pull-requests found for this project") request = pagure.lib.search_pull_requests(SESSION, project_id=project.id, requestid=requestid) if not request: flask.abort(404, "Pull-request not found") comment = pagure.lib.get_request_comment(SESSION, request.uid, commentid) if comment is None or comment.parent.project != project: flask.abort(404, "Comment not found") if (flask.g.fas_user.username != comment.user.username or comment.parent.status != "Open") and not is_repo_admin( project ): flask.abort(403, "You are not allowed to edit the comment") form = pagure.forms.EditCommentForm() if form.validate_on_submit(): updated_comment = form.update_comment.data try: message = pagure.lib.edit_comment( SESSION, parent=request, comment=comment, user=flask.g.fas_user.username, updated_comment=updated_comment, folder=APP.config["REQUESTS_FOLDER"], ) SESSION.commit() if not is_js: flask.flash(message) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() LOG.error(err) if is_js: return "error" flask.flash("Could not edit the comment: %s" % commentid, "error") if is_js: return "ok" return flask.redirect(flask.url_for("request_pull", username=username, repo=project.name, requestid=requestid))
def delete_repo(repo, username=None): """ Delete the present project. """ if not pagure.APP.config.get('ENABLE_DEL_PROJECTS', True): flask.abort(404) if admin_session_timedout(): flask.flash('Action canceled, try it again', 'error') url = flask.url_for( 'view_settings', username=username, repo=repo) return flask.redirect( flask.url_for('auth_login', next=url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to change the settings for this project') try: for issue in repo.issues: for comment in issue.comments: SESSION.delete(comment) SESSION.commit() SESSION.delete(issue) SESSION.delete(repo) SESSION.commit() except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Could not delete the project', 'error') repopath = os.path.join(APP.config['GIT_FOLDER'], repo.path) if repo.is_fork: repopath = os.path.join(APP.config['FORK_FOLDER'], repo.path) docpath = os.path.join(APP.config['DOCS_FOLDER'], repo.path) ticketpath = os.path.join(APP.config['TICKETS_FOLDER'], repo.path) requestpath = os.path.join(APP.config['REQUESTS_FOLDER'], repo.path) try: shutil.rmtree(repopath) shutil.rmtree(docpath) shutil.rmtree(ticketpath) shutil.rmtree(requestpath) except (OSError, IOError) as err: APP.logger.exception(err) flask.flash( 'Could not delete all the repos from the system', 'error') return flask.redirect( flask.url_for('view_user', username=flask.g.fas_user.username))
def request_pull_edit(repo, requestid, username=None): """ Edit the title of a pull-request. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests( SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') if request.status != 'Open': flask.abort(400, 'Pull-request is already closed') if not is_repo_admin(repo) \ and flask.g.fas_user.username != request.user.username: flask.abort(403, 'You are not allowed to edit this pull-request') form = pagure.forms.RequestPullForm() if form.validate_on_submit(): request.title = form.title.data.strip() request.initial_comment = form.initial_comment.data.strip() SESSION.add(request) try: SESSION.commit() flask.flash('Request pull edited!') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash( 'Could not edit this pull-request in the database', 'error') return flask.redirect(flask.url_for( 'request_pull', username=username, repo=repo.name, requestid=requestid)) elif flask.request.method == 'GET': form.title.data = request.title form.initial_comment.data = request.initial_comment return flask.render_template( 'pull_request_title.html', select='requests', request=request, repo=repo, username=username, form=form, )
def upload_issue(repo, issueid, username=None, namespace=None): ''' Upload a file to a ticket. ''' repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') try: user_obj = pagure.lib.get_user( SESSION, flask.g.fas_user.username) except pagure.exceptions.PagureException: flask.abort( 404, 'No such user found in the database: %s' % ( flask.g.fas_user.username)) form = pagure.forms.UploadFileForm() if form.validate_on_submit(): filestream = flask.request.files['filestream'] try: new_filename = pagure.lib.git.add_file_to_git( repo=repo, issue=issue, ticketfolder=APP.config['TICKETS_FOLDER'], user=user_obj, filename=filestream.filename, filestream=filestream.stream, ) except filelock.Timeout as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash( 'We could not save all the info, please try again', 'error') return flask.jsonify({ 'output': 'ok', 'filename': new_filename.split('-', 1)[1], 'filelocation': flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, namespace=repo.namespace, filename=new_filename, ) }) else: return flask.jsonify({'output': 'notok'})
def add_group_project(repo, username=None): """ Add the specified group from the project. """ if not pagure.APP.config.get('ENABLE_USER_MNGT', True): flask.abort(404) if admin_session_timedout(): if flask.request.method == 'POST': flask.flash('Action canceled, try it again', 'error') return flask.redirect( flask.url_for('auth_login', next=flask.request.url)) repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to add groups to this project') form = pagure.forms.AddGroupForm() if form.validate_on_submit(): try: msg = pagure.lib.add_group_to_project( SESSION, repo, new_group=form.group.data, user=flask.g.fas_user.username, ) SESSION.commit() pagure.lib.git.generate_gitolite_acls() flask.flash(msg) return flask.redirect( flask.url_for( '.view_settings', repo=repo.name, username=username) ) except pagure.exceptions.PagureException as msg: SESSION.rollback() flask.flash(msg, 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Group could not be added', 'error') return flask.render_template( 'add_group_project.html', form=form, username=username, repo=repo, )
def edit_tag(repo, tag, username=None): """ Edit the specified tag associated with the issues of a project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to edit tags associated with the issues of \ this project') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') tags = pagure.lib.get_tags_of_project(SESSION, repo) if not tags or tag not in [t.tag for t in tags]: flask.abort(404, 'Tag %s not found in this project' % tag ) form = pagure.forms.AddIssueTagForm() if form.validate_on_submit(): new_tag = form.tag.data msgs = pagure.lib.edit_issue_tags( SESSION, repo, tag, new_tag, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ) try: SESSION.commit() for msg in msgs: flask.flash(msg) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() LOG.error(err) flask.flash('Could not edit tag: %s' % tag, 'error') return flask.redirect(flask.url_for( '.view_settings', repo=repo.name, username=username)) return flask.render_template( 'edit_tag.html', form=form, username=username, repo=repo, edit_tag=tag, )
def change_password(): """ Method to change the password for local auth users. """ form = forms.ChangePasswordForm() user_obj = pagure.lib.search_user( SESSION, username=flask.g.fas_user.username) if not user_obj: flask.abort(404, 'User not found') if form.validate_on_submit(): try: password_checks = check_password( form.old_password.data, user_obj.password, seed=APP.config.get('PASSWORD_SEED', None)) except pagure.exceptions.PagureException as err: APP.logger.exception(err) flask.flash( 'Could not update your password, either user or password ' 'could not be checked', 'error') return flask.redirect(flask.url_for('auth_login')) if password_checks: user_obj.password = generate_hashed_value(form.password.data) SESSION.add(user_obj) else: flask.flash( 'Could not update your password, either user or password ' 'could not be checked', 'error') return flask.redirect(flask.url_for('auth_login')) try: SESSION.commit() flask.flash( 'Password changed') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() flask.flash('Could not set the new password.', 'error') APP.logger.debug( 'Password change - Error setting new password.') APP.logger.exception(err) return flask.redirect(flask.url_for('auth_login')) return flask.render_template( 'login/password_recover.html', form=form, )
def new_issue(repo, username=None): """ Create a new issue """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.IssueForm(status=status) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data private = form.private.data try: issue = pagure.lib.new_issue( SESSION, repo=repo, title=title, content=content, private=private or False, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() # If there is a file attached, attach it. filestream = flask.request.files.get('filestream') if filestream and '<!!image>' in issue.content: new_filename = pagure.lib.git.add_file_to_git( repo=repo, issue=issue, ticketfolder=APP.config['TICKETS_FOLDER'], user=flask.g.fas_user, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, filename=new_filename, ) new_filename = new_filename.split('-', 1)[1] url = '[![%s](%s)](%s)' % (new_filename, filelocation, filelocation) issue.content = issue.content.replace('<!!image>', url) SESSION.add(issue) SESSION.commit() flask.flash('Issue created') return flask.redirect( flask.url_for('.view_issue', username=username, repo=repo.name, issueid=issue.id)) except pagure.exceptions.PagureException, err: flask.flash(str(err), 'error') except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error')
def pull_request_edit_comment(repo, requestid, commentid, username=None): """Edit comment of a pull request """ is_js = flask.request.args.get('js', False) project = pagure.lib.get_project(SESSION, repo, user=username) if not project: flask.abort(404, 'Project not found') if not project.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests(SESSION, project_id=project.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') comment = pagure.lib.get_request_comment(SESSION, request.uid, commentid) if comment is None or comment.parent.project != project: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not is_repo_admin(project): flask.abort(403, 'You are not allowed to edit the comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): updated_comment = form.update_comment.data try: message = pagure.lib.edit_comment( SESSION, parent=request, comment=comment, user=flask.g.fas_user.username, updated_comment=updated_comment, folder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() if not is_js: flask.flash(message) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() LOG.error(err) if is_js: return 'error' flask.flash('Could not edit the comment: %s' % commentid, 'error') if is_js: return 'ok' return flask.redirect( flask.url_for('request_pull', username=username, repo=project.name, requestid=requestid))
def api_update_custom_fields(repo, issueid, username=None, namespace=None): """ Update custom fields -------------------- Update or reset the content of a collection of custom fields associated to an issue. :: POST /api/0/<repo>/issue/<issue id>/custom POST /api/0/<namespace>/<repo>/issue/<issue id>/custom :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/custom POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/custom Input ^^^^^ +------------------+---------+--------------+-----------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+=============================+ | ``myfields`` | dict | Mandatory | A dictionary with the fields| | | | | name as key and the value | +------------------+---------+--------------+-----------------------------+ Sample payload ^^^^^^^^^^^^^^ :: { "myField": "to do", "myField_1": "test", "myField_2": "done", } Sample response ^^^^^^^^^^^^^^^ :: { "messages": [ { "myField" : "Custom field myField adjusted to to do" }, { "myField_1": "Custom field myField_1 adjusted test (was: to do)" }, { "myField_2": "Custom field myField_1 adjusted to done (was: test)" } ] } """ # noqa output = {'messages': []} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_ticket_access(issue) fields = flask.request.form if not fields: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) repo_fields = {k.name: k for k in repo.issue_keys} if not all(key in repo_fields.keys() for key in fields.keys()): raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDISSUEFIELD) for field in fields: key = repo_fields[field] value = fields.get(key.name) if value: _check_link_custom_field(key, value) try: message = pagure.lib.set_custom_key_value(SESSION, issue, key, value) SESSION.commit() if message: output['messages'].append({key.name: message}) pagure.lib.add_metadata_update_notif( session=SESSION, obj=issue, messages=message, user=flask.g.fas_user.username, gitfolder=APP.config['TICKETS_FOLDER']) else: output['messages'].append({key.name: 'No changes'}) except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover print(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_subscribe_issue(repo, issueid, username=None, namespace=None): """ Subscribe to an issue --------------------- Allows someone to subscribe to or unsubscribe from the notifications related to an issue. :: POST /api/0/<repo>/issue/<issue id>/subscribe POST /api/0/<namespace>/<repo>/issue/<issue id>/subscribe :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | The intended subscription | | | | | status. ``true`` for | | | | | subscribing, ``false`` | | | | | for unsubscribing. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed" } """ # noqa output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_private_issue_access(issue) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = str(form.status.data).strip().lower() in ['1', 'true'] try: # Toggle subscribtion message = pagure.lib.set_watch_obj(SESSION, user=flask.g.fas_user.username, obj=issue, watch_status=status) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def api_comment_issue(repo, issueid, username=None, namespace=None): """ Comment to an issue ------------------- Add a comment to an issue. :: POST /api/0/<repo>/issue/<issue id>/comment POST /api/0/<namespace>/<repo>/issue/<issue id>/comment :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/comment POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/comment Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``comment`` | string | Mandatory | | The comment to add to | | | | | the issue | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Comment added" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) issue = _get_issue(repo, issueid) _check_private_issue_access(issue) form = pagure.forms.CommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data try: # New comment message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def api_change_status_issue(repo, issueid, username=None, namespace=None): """ Change issue status ------------------- Change the status of an issue. :: POST /api/0/<repo>/issue/<issue id>/status POST /api/0/<namespace>/<repo>/issue/<issue id>/status :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/status POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/status Input ^^^^^ +------------------+---------+--------------+------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+========================+ | ``close_status`` | string | Optional | The close status of | | | | | the issue | +------------------+---------+--------------+------------------------+ | ``status`` | string | Mandatory | The new status of the | | | | | issue, can be 'Open' or| | | | | 'Closed' | +------------------+---------+--------------+------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Successfully edited issue #1" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) issue = _get_issue(repo, issueid) _check_ticket_access(issue) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, close_status=repo.close_status, csrf_enabled=False) if not pagure.is_repo_user(repo) \ and flask.g.fas_user.username != issue.user.user: raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) close_status = None if form.close_status.raw_data: close_status = form.close_status.data new_status = form.status.data.strip() if new_status in repo.close_status and not close_status: close_status = new_status new_status = 'Closed' form.status.data = new_status if form.validate_on_submit(): try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, close_status=close_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' if message: pagure.lib.add_metadata_update_notif( session=SESSION, obj=issue, messages=message, user=flask.g.fas_user.username, gitfolder=APP.config['TICKETS_FOLDER']) except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException, err: is_js = False SESSION.rollback() if not is_js: flask.flash(err.message, 'error') except SQLAlchemyError, err: # pragma: no cover is_js = False SESSION.rollback() APP.logger.exception(err) if not is_js: flask.flash(str(err), 'error') if is_js: return 'ok' else: return flask.redirect( flask.url_for('view_issue', username=username, repo=repo.name, issueid=issueid)) @APP.route('/<repo>/tag/<tag>/edit/', methods=('GET', 'POST'))
def request_pull(repo, requestid, username=None): """ Request pulling the changes from the fork into the project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') if request.remote: repopath = pagure.get_remote_repo_path(request.remote_git, request.branch_from) parentpath = pagure.get_repo_path(request.project) else: repo_from = request.project_from repopath = pagure.get_repo_path(repo_from) parentpath = _get_parent_repo_path(repo_from) repo_obj = pygit2.Repository(repopath) orig_repo = pygit2.Repository(parentpath) diff_commits = [] diff = None # Closed pull-request if request.status != 'Open': commitid = request.commit_stop try: for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME): diff_commits.append(commit) if commit.oid.hex == request.commit_start: break except KeyError: # This happens when repo.walk() cannot find commitid pass if diff_commits: diff = repo_obj.diff( repo_obj.revparse_single(diff_commits[-1].parents[0].oid.hex), repo_obj.revparse_single(diff_commits[0].oid.hex)) else: try: diff_commits, diff = pagure.lib.git.diff_pull_request( SESSION, request, repo_obj, orig_repo, requestfolder=APP.config['REQUESTS_FOLDER']) except pagure.exceptions.PagureException as err: flask.flash(err.message, 'error') return flask.redirect( flask.url_for('view_repo', username=username, repo=repo.name)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Could not update this pull-request in the database', 'error') if diff: diff.find_similar() form = pagure.forms.ConfirmationForm() return flask.render_template( 'pull_request.html', select='requests', requestid=requestid, repo=repo, username=username, pull_request=request, repo_admin=is_repo_admin(request.project), diff_commits=diff_commits, diff=diff, mergeform=form, )
def api_new_issue(repo, username=None): """ Create a new issue ------------------ Open a new issue on a project. :: POST /api/0/<repo>/new_issue :: POST /api/0/fork/<username>/<repo>/new_issue Input ^^^^^ +--------------+----------+--------------+-----------------------------+ | Key | Type | Optionality | Description | +==============+==========+==============+=============================+ | ``title`` | string | Mandatory | The title of the issue | +--------------+----------+--------------+-----------------------------+ | ``content`` | string | Mandatory | | The description of the | | | | | issue | +--------------+----------+--------------+-----------------------------+ | ``private`` | boolean | Optional | | Include this key if | | | | | you want a private issue | | | | | to be created | +--------------+----------+--------------+-----------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Issue created" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) form = pagure.forms.IssueFormSimplied(csrf_enabled=False) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data private = form.private.data try: issue = pagure.lib.new_issue( SESSION, repo=repo, title=title, content=content, private=private or False, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.flush() # If there is a file attached, attach it. filestream = flask.request.files.get('filestream') if filestream and '<!!image>' in issue.content: new_filename = pagure.lib.git.add_file_to_git( repo=repo, issue=issue, ticketfolder=APP.config['TICKETS_FOLDER'], user=flask.g.fas_user, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, filename=new_filename, ) new_filename = new_filename.split('-', 1)[1] url = '[![%s](%s)](%s)' % (new_filename, filelocation, filelocation) issue.content = issue.content.replace('<!!image>', url) SESSION.add(issue) SESSION.flush() SESSION.commit() output['message'] = 'Issue created' except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_change_status_issue(repo, issueid, username=None): """ Change issue status ------------------- Change the status of an issue. :: POST /api/0/<repo>/issue/<issue id>/status :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/status Input ^^^^^ +-------------+---------+--------------+------------------------------+ | Key | Type | Optionality | Description | +=============+=========+==============+==============================+ | ``status`` | string | Mandatory | The new status of the issue | +-------------+---------+--------------+------------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Successfully edited issue #1" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, csrf_enabled=False) if form.validate_on_submit(): new_status = form.status.data try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException, err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_pull_request_add_flag(repo, requestid, username=None): """ Flag a pull-request ------------------- Add or edit flags on a pull-request. :: POST /api/0/<repo>/pull-request/<request id>/flag :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/flag Input ^^^^^ +---------------+---------+--------------+-----------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+=============================+ | ``username`` | string | Mandatory | | The name of the | | | | | application to be | | | | | presented to users | | | | | on the pull request page | +---------------+---------+--------------+-----------------------------+ | ``percent`` | int | Mandatory | | A percentage of | | | | | completion compared to | | | | | the goal. The percentage | | | | | also determine the | | | | | background color of the | | | | | flag on the pull-request | | | | | page | +---------------+---------+--------------+-----------------------------+ | ``comment`` | string | Mandatory | | A short message | | | | | summarizing the | | | | | presented results | +---------------+---------+--------------+-----------------------------+ | ``url`` | string | Mandatory | | A URL to the result | | | | | of this flag | +---------------+---------+--------------+-----------------------------+ | ``uid`` | string | Optional | | A unique identifier used | | | | | to identify a flag on a | | | | | pull-request. If the | | | | | provided UID matches an | | | | | existing one, then the | | | | | API call will update the | | | | | existing one rather than | | | | | create a new one. | | | | | Maximum Length: 32 | | | | | characters. Default: an | | | | | auto generated UID | +---------------+---------+--------------+-----------------------------+ | ``commit`` | string | Optional | | The hash of the commit | | | | | you use | +---------------+---------+--------------+-----------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Flag added" } :: { "message": "Flag updated" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('pull_requests', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) form = pagure.forms.AddPullRequestFlagForm(csrf_enabled=False) if form.validate_on_submit(): username = form.username.data percent = form.percent.data comment = form.comment.data.strip() url = form.url.data.strip() uid = form.uid.data.strip() if form.uid.data else None try: # New Flag message = pagure.lib.add_pull_request_flag( SESSION, request=request, username=username, percent=percent, comment=comment, url=url, uid=uid, user=flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() output['message'] = message except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover APP.logger.exception(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_pull_request_add_comment(repo, requestid, username=None): """ Comment on a pull-request ------------------------- Add comment to a pull request. :: POST /api/0/<repo>/pull-request/<request id>/comment :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/comment Input ^^^^^ +---------------+---------+--------------+-----------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+=============================+ | ``comment`` | string | Mandatory | | The comment to add | | | | | to the pull request | +---------------+---------+--------------+-----------------------------+ | ``commit`` | string | Optional | | The hash of the specific | | | | | commit you wish to | | | | | comment on | +---------------+---------+--------------+-----------------------------+ | ``filename`` | string | Optional | | The filename of the | | | | | specific file you wish | | | | | to comment on | +---------------+---------+--------------+-----------------------------+ | ``row`` | int | Optional | | Used in combination | | | | | with filename to comment | | | | | on a specific row | | | | | of a file | +---------------+---------+--------------+-----------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Comment added" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('pull_requests', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) form = pagure.forms.AddPullRequestCommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data commit = form.commit.data or None filename = form.filename.data or None row = form.row.data or None try: # New comment message = pagure.lib.add_pull_request_comment( SESSION, request=request, commit=commit, filename=filename, row=row, comment=comment, user=flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() output['message'] = message except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover APP.logger.exception(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_pull_request_close(repo, requestid, username=None): """ Close a pull-request -------------------- Instruct Pagure to close a pull request. :: POST /api/0/<repo>/pull-request/<request id>/close :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/close Sample response ^^^^^^^^^^^^^^^ :: { "message": "Pull-request closed!" } """ output = {} repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('pull_requests', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) if not is_repo_admin(repo): raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE) try: pagure.lib.close_pull_request( SESSION, request, flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], merged=False) SESSION.commit() output['message'] = 'Pull-request closed!' except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def pull_request_add_comment(repo, requestid, commit=None, filename=None, row=None, username=None): """ Add a comment to a commit in a pull-request. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') is_js = flask.request.args.get('js', False) form = pagure.forms.AddPullRequestCommentForm() form.commit.data = commit form.filename.data = filename form.requestid.data = requestid form.row.data = row if form.validate_on_submit(): comment = form.comment.data try: message = pagure.lib.add_pull_request_comment( SESSION, request=request, commit=commit, filename=filename, row=row, comment=comment, user=flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() if not is_js: flask.flash(message) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash(str(err), 'error') if is_js: return 'error' if is_js: return 'ok' return flask.redirect( flask.url_for('request_pull', username=username, repo=repo.name, requestid=requestid)) if is_js and flask.request.method == 'POST': return 'failed' return flask.render_template( 'pull_request_comment.html', select='requests', requestid=requestid, repo=repo, username=username, commit=commit, filename=filename, row=row, form=form, )
def api_fork_project(): """ Fork a project -------------------- Fork a project on this pagure instance. :: POST /api/0/<repo>/fork Input ^^^^^ +------------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+===========================+ | ``repo`` | string | Mandatory | | The name of the project | | | | | to fork. | +------------------+---------+--------------+---------------------------+ | ``username`` | string | Optional | | The username of the user| | | | | of the fork. | +------------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": 'Repo "test" cloned to "pingou/test"' } """ output = {} form = pagure.forms.ForkRepoForm(csrf_enabled=False) if form.validate_on_submit(): repo = form.repo.data username = form.username.data or None repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) try: message = pagure.lib.fork_project( SESSION, user=flask.g.fas_user.username, repo=repo, gitfolder=APP.config['GIT_FOLDER'], docfolder=APP.config['DOCS_FOLDER'], ticketfolder=APP.config['TICKETS_FOLDER'], requestfolder=APP.config['REQUESTS_FOLDER'], ) SESSION.commit() pagure.lib.git.generate_gitolite_acls() output['message'] = message except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover APP.logger.exception(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def request_pull_patch(repo, requestid, username=None): """ Returns the commits from the specified pull-request as patches. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: flask.abort(404, 'Pull-request not found') if request.remote: repopath = pagure.get_remote_repo_path(request.remote_git, request.branch_from) parentpath = pagure.get_repo_path(request.project) else: repo_from = request.project_from repopath = pagure.get_repo_path(repo_from) parentpath = _get_parent_repo_path(repo_from) repo_obj = pygit2.Repository(repopath) orig_repo = pygit2.Repository(parentpath) branch = repo_obj.lookup_branch(request.branch_from) commitid = None if branch: commitid = branch.get_object().hex diff_commits = [] if request.status != 'Open': commitid = request.commit_stop try: for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME): diff_commits.append(commit) if commit.oid.hex == request.commit_start: break except KeyError: # This happens when repo.walk() cannot find commitid pass else: try: diff_commits = pagure.lib.git.diff_pull_request( SESSION, request, repo_obj, orig_repo, requestfolder=APP.config['REQUESTS_FOLDER'], with_diff=False)[0] except pagure.exceptions.PagureException as err: flask.flash(err.message, 'error') return flask.redirect( flask.url_for('view_repo', username=username, repo=repo.name)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash('Could not update this pull-request in the database', 'error') diff_commits.reverse() patch = pagure.lib.git.commit_to_patch(repo_obj, diff_commits) return flask.Response(patch, content_type="text/plain;charset=UTF-8")
def api_new_project(): """ Create a new project -------------------- Create a new project on this pagure instance. :: POST /api/0/<repo>/new Input ^^^^^ +------------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+===========================+ | ``name`` | string | Mandatory | | The name of the new | | | | | project. | +------------------+---------+--------------+---------------------------+ | ``description`` | string | Mandatory | | A short description of | | | | | the new project. | +------------------+---------+--------------+---------------------------+ | ``url`` | string | Optional | | An url providing more | | | | | information about the | | | | | project. | +------------------+---------+--------------+---------------------------+ | ``avatar_email`` | string | Optional | | An email address for the| | | | | avatar of the project. | +------------------+---------+--------------+---------------------------+ | ``create_readme``| boolean | Optional | | A boolean to specify if | | | | | there should be a readme| | | | | added to the project on | | | | | creation. | +------------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { 'message': 'Project "foo" created' } """ user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) output = {} if not pagure.APP.config.get('ENABLE_NEW_PROJECTS', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ENEWPROJECTDISABLED) form = pagure.forms.ProjectForm(csrf_enabled=False) if form.validate_on_submit(): name = form.name.data description = form.description.data url = form.url.data avatar_email = form.avatar_email.data create_readme = form.create_readme.data try: message = pagure.lib.new_project( SESSION, name=name, description=description, url=url, avatar_email=avatar_email, user=flask.g.fas_user.username, blacklist=APP.config['BLACKLISTED_PROJECTS'], allowed_prefix=APP.config['ALLOWED_PREFIX'], gitfolder=APP.config['GIT_FOLDER'], docfolder=APP.config['DOCS_FOLDER'], ticketfolder=APP.config['TICKETS_FOLDER'], requestfolder=APP.config['REQUESTS_FOLDER'], add_readme=create_readme, userobj=user, prevent_40_chars=APP.config.get('OLD_VIEW_COMMIT_ENABLED', False), ) SESSION.commit() pagure.lib.git.generate_gitolite_acls() output['message'] = message except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover APP.logger.exception(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def api_assign_issue(repo, issueid, username=None, namespace=None): """ Assign an issue --------------- Assign an issue to someone. :: POST /api/0/<repo>/issue/<issue id>/assign POST /api/0/<namespace>/<repo>/issue/<issue id>/assign :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/assign POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/assign Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``assignee`` | string | Mandatory | | The username of the user| | | | | to assign the issue to. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Issue assigned" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_ticket_access(issue, assignee=True) form = pagure.forms.AssignIssueForm(csrf_enabled=False) if form.validate_on_submit(): assignee = form.assignee.data or None # Create our metadata comment object try: # New comment message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: pagure.lib.add_metadata_update_notif( session=SESSION, obj=issue, messages=message, user=flask.g.fas_user.username, gitfolder=APP.config['TICKETS_FOLDER']) output['message'] = message else: output['message'] = 'Nothing to change' except pagure.exceptions.PagureException as err: # pragma: no cover raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def view_plugin(repo, plugin, username=None, full=True): """ Presents the settings of the project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) if not repo: flask.abort(404, 'Project not found') if not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to change the settings for this project') if plugin in APP.config.get('DISABLED_PLUGINS', []): flask.abort(404, 'Plugin disabled') plugin = get_plugin(plugin) fields = [] new = True dbobj = plugin.db_object() if hasattr(repo, plugin.backref): dbobj = getattr(repo, plugin.backref) # There should always be only one, but let's double check if dbobj and len(dbobj) > 0: dbobj = dbobj[0] new = False else: dbobj = plugin.db_object() form = plugin.form(obj=dbobj) for field in plugin.form_fields: fields.append(getattr(form, field)) if form.validate_on_submit(): form.populate_obj(obj=dbobj) if new: dbobj.project_id = repo.id SESSION.add(dbobj) try: SESSION.flush() except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.debug('Could not add plugin %s', plugin.name) APP.logger.exception(err) flask.flash( 'Could not add plugin %s, please contact an admin' % plugin.name) return flask.render_template( 'plugin.html', select='settings', full=full, repo=repo, username=username, plugin=plugin, form=form, fields=fields) if form.active.data: # Set up the main script if necessary plugin.set_up(repo) # Install the plugin itself plugin.install(repo, dbobj) flask.flash('Hook %s activated' % plugin.name) else: plugin.remove(repo) flask.flash('Hook %s inactived' % plugin.name) SESSION.commit() return flask.redirect(flask.url_for( 'view_settings', repo=repo.name, username=username)) return flask.render_template( 'plugin.html', select='settings', full=full, repo=repo, username=username, plugin=plugin, form=form, fields=fields)
def api_change_milestone_issue(repo, issueid, username=None, namespace=None): """ Change issue milestone ---------------------- Change the milestone of an issue. :: POST /api/0/<repo>/issue/<issue id>/milestone POST /api/0/<namespace>/<repo>/issue/<issue id>/milestone :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/milestone POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/milestone Input ^^^^^ +------------------+---------+--------------+------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+========================+ | ``milestone`` | string | Optional | The new milestone of | | | | | the issue, can be any | | | | | of defined milestones | | | | | or empty to unset the | | | | | milestone | +------------------+---------+--------------+------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Successfully edited issue #1" } """ # noqa output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_ticket_access(issue) form = pagure.forms.MilestoneForm(milestones=repo.milestones.keys(), csrf_enabled=False) if form.validate_on_submit(): new_milestone = form.milestone.data if new_milestone == '': new_milestone = None # unset milestone try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, milestone=new_milestone, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' if message: pagure.lib.add_metadata_update_notif( session=SESSION, obj=issue, messages=message, user=flask.g.fas_user.username, gitfolder=APP.config['TICKETS_FOLDER']) except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def edit_issue(repo, issueid, username=None): """ Edit the specified issue """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if not (is_repo_admin(repo) or flask.g.fas_user.username == issue.user.username): flask.abort(403, 'You are not allowed to edit issues for this project') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.IssueForm(status=status) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data status = form.status.data private = form.private.data try: message = pagure.lib.edit_issue( SESSION, issue=issue, title=title, content=content, status=status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], private=private, ) SESSION.commit() # If there is a file attached, attach it. filestream = flask.request.files.get('filestream') if filestream and '<!!image>' in issue.content: new_filename = pagure.lib.git.add_file_to_git( repo=repo, issue=issue, ticketfolder=APP.config['TICKETS_FOLDER'], user=flask.g.fas_user, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, filename=new_filename, ) new_filename = new_filename.split('-', 1)[1] url = '[![%s](%s)](%s)' % (new_filename, filelocation, filelocation) issue.content = issue.content.replace('<!!image>', url) SESSION.add(issue) SESSION.commit() flask.flash(message) url = flask.url_for('view_issue', username=username, repo=repo.name, issueid=issueid) return flask.redirect(url) except pagure.exceptions.PagureException as err: flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error') elif flask.request.method == 'GET': form.title.data = issue.title form.issue_content.data = issue.content form.status.data = issue.status form.private.data = issue.private return flask.render_template( 'new_issue.html', select='issues', type='edit', form=form, repo=repo, username=username, issue=issue, issueid=issueid, repo_admin=is_repo_admin(repo), )
def api_new_issue(repo, username=None, namespace=None): """ Create a new issue ------------------ Open a new issue on a project. :: POST /api/0/<repo>/new_issue POST /api/0/<namespace>/<repo>/new_issue :: POST /api/0/fork/<username>/<repo>/new_issue POST /api/0/fork/<username>/<namespace>/<repo>/new_issue Input ^^^^^ +-------------------+--------+-------------+---------------------------+ | Key | Type | Optionality | Description | +===================+========+=============+===========================+ | ``title`` | string | Mandatory | The title of the issue | +-------------------+--------+-------------+---------------------------+ | ``issue_content`` | string | Mandatory | | The description of the | | | | | issue | +-------------------+--------+-------------+---------------------------+ | ``private`` | boolean| Optional | | Include this key if | | | | | you want a private issue| | | | | to be created | +-------------------+--------+-------------+---------------------------+ | ``priority`` | string | Optional | | The priority to set to | | | | | this ticket from the | | | | | list of priorities set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``milestone`` | string | Optional | | The milestone to assign | | | | | to this ticket from the | | | | | list of milestones set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``tag`` | string | Optional | | Comma separated list of | | | | | tags to link to this | | | | | ticket from the list of | | | | | tags in the project | +-------------------+--------+-------------+---------------------------+ | ``assignee`` | string | Optional | | The username of the user| | | | | to assign this ticket to| +-------------------+--------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "issue": { "assignee": null, "blocks": [], "close_status": null, "closed_at": null, "comments": [], "content": "This issue needs attention", "custom_fields": [], "date_created": "1479458613", "depends": [], "id": 1, "milestone": null, "priority": null, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } }, "message": "Issue created" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) user_obj = pagure.lib.get_user(SESSION, flask.g.fas_user.username) if not user_obj: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOUSER) form = pagure.forms.IssueFormSimplied(priorities=repo.priorities, milestones=repo.milestones, csrf_enabled=False) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data milestone = form.milestone.data or None private = str(form.private.data).lower() in ['true', '1'] priority = form.priority.data or None assignee = flask.request.form.get('assignee', '').strip() or None tags = [ tag.strip() for tag in flask.request.form.get('tag', '').split(',') if tag.strip() ] try: issue = pagure.lib.new_issue( SESSION, repo=repo, title=title, content=content, private=private, assignee=assignee, milestone=milestone, priority=priority, tags=tags, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.flush() # If there is a file attached, attach it. filestream = flask.request.files.get('filestream') if filestream and '<!!image>' in issue.content: new_filename = pagure.lib.add_attachment( repo=repo, issue=issue, attachmentfolder=APP.config['ATTACHMENTS_FOLDER'], user=user_obj, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, filename=new_filename, ) new_filename = new_filename.split('-', 1)[1] url = '[![%s](%s)](%s)' % (new_filename, filelocation, filelocation) issue.content = issue.content.replace('<!!image>', url) SESSION.add(issue) SESSION.flush() SESSION.commit() output['message'] = 'Issue created' output['issue'] = issue.to_json(public=True) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def new_issue(repo, username=None): """ Create a new issue """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') form = pagure.forms.IssueFormSimplied() if form.validate_on_submit(): title = form.title.data content = form.issue_content.data private = form.private.data try: issue = pagure.lib.new_issue( SESSION, repo=repo, title=title, content=content, private=private or False, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() # If there is a file attached, attach it. filestream = flask.request.files.get('filestream') if filestream and '<!!image>' in issue.content: new_filename = pagure.lib.git.add_file_to_git( repo=repo, issue=issue, ticketfolder=APP.config['TICKETS_FOLDER'], user=flask.g.fas_user, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( 'view_issue_raw_file', repo=repo.name, username=username, filename=new_filename, ) new_filename = new_filename.split('-', 1)[1] url = '[![%s](%s)](%s)' % (new_filename, filelocation, filelocation) issue.content = issue.content.replace('<!!image>', url) SESSION.add(issue) SESSION.commit() return flask.redirect( flask.url_for('.view_issue', username=username, repo=repo.name, issueid=issue.id)) except pagure.exceptions.PagureException as err: flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error') types = None default = None ticketrepopath = os.path.join(APP.config['TICKETS_FOLDER'], repo.path) if os.path.exists(ticketrepopath): ticketrepo = pygit2.Repository(ticketrepopath) if not ticketrepo.is_empty and not ticketrepo.head_is_unborn: commit = ticketrepo[ticketrepo.head.target] # Get the different ticket types files = __get_file_in_tree(ticketrepo, commit.tree, ['templates'], bail_on_tree=True) if files: types = [f.name.rstrip('.md') for f in files] # Get the default template default_file = __get_file_in_tree(ticketrepo, commit.tree, ['templates', 'default.md'], bail_on_tree=True) if default_file: default, _ = pagure.doc_utils.convert_readme( default_file.data, 'md') return flask.render_template( 'new_issue.html', select='issues', form=form, repo=repo, username=username, repo_admin=is_repo_admin(repo), types=types, default=default, )
def api_update_custom_field(repo, issueid, field, username=None, namespace=None): """ Update custom field ------------------- Update or reset the content of a custom field associated to an issue. :: POST /api/0/<repo>/issue/<issue id>/custom/<field> POST /api/0/<namespace>/<repo>/issue/<issue id>/custom/<field> :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/custom/<field> POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/custom/<field> Input ^^^^^ +------------------+---------+--------------+-------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+=========================+ | ``value`` | string | Optional | The new value of the | | | | | custom field of interest| +------------------+---------+--------------+-------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Custom field adjusted" } """ # noqa output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_ticket_access(issue) fields = {k.name: k for k in repo.issue_keys} if field not in fields: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDISSUEFIELD) key = fields[field] value = flask.request.form.get('value') if value: _check_link_custom_field(key, value) try: message = pagure.lib.set_custom_key_value(SESSION, issue, key, value) SESSION.commit() if message: output['message'] = message pagure.lib.add_metadata_update_notif( session=SESSION, obj=issue, messages=message, user=flask.g.fas_user.username, gitfolder=APP.config['TICKETS_FOLDER']) else: output['message'] = 'No changes' except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover print(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def new_remote_request_pull(repo, username=None): """ Request pulling the changes from a remote fork into the project. """ repo = pagure.lib.get_project(SESSION, repo, user=username) confirm = flask.request.values.get('confirm', False) if not repo: flask.abort(404) if not repo.settings.get('pull_requests', True): flask.abort(404, 'No pull-requests found for this project') parentpath = pagure.get_repo_path(repo) orig_repo = pygit2.Repository(parentpath) repo_admin = is_repo_admin(repo) form = pagure.forms.RemoteRequestPullForm() if form.validate_on_submit(): branch_from = form.branch_from.data.strip() branch_to = form.branch_to.data.strip() remote_git = form.git_repo.data.strip() repopath = pagure.get_remote_repo_path(remote_git, branch_from) repo_obj = pygit2.Repository(repopath) try: diff, diff_commits, orig_commit = _get_pr_info( repo_obj, orig_repo, branch_from, branch_to) except pagure.exceptions.PagureException as err: flask.flash(err.message, 'error') return flask.redirect( flask.url_for('view_repo', username=username, repo=repo.name)) if not confirm: return flask.render_template( 'pull_request.html', select='requests', repo=repo, username=username, repo_obj=repo_obj, orig_repo=orig_repo, diff_commits=diff_commits, diff=diff, form=form, branches=sorted(orig_repo.listall_branches()), branch_to=branch_to, branch_from=branch_from, repo_admin=repo_admin, remote_git=remote_git, ) try: if repo.settings.get('Enforce_signed-off_commits_in_pull-request', False): for commit in diff_commits: if 'signed-off-by' not in commit.message.lower(): raise pagure.exceptions.PagureException( 'This repo enforces that all commits are ' 'signed off by their author. ') if orig_commit: orig_commit = orig_commit.oid.hex parent = repo if repo.parent: parent = repo.parent request = pagure.lib.new_pull_request( SESSION, repo_to=parent, branch_to=branch_to, branch_from=branch_from, repo_from=None, remote_git=remote_git, title=form.title.data, user=flask.g.fas_user.username, requestfolder=APP.config['REQUESTS_FOLDER'], ) try: SESSION.commit() flask.flash('Request created') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) flask.flash( 'Could not register this pull-request in ' 'the database', 'error') if not parent.is_fork: url = flask.url_for('request_pull', requestid=request.id, username=None, repo=parent.name) else: url = flask.url_for('request_pull', requestid=request.id, username=parent.user, repo=parent.name) return flask.redirect(url) except pagure.exceptions.PagureException as err: # pragma: no cover # There could be a PagureException thrown if the # flask.g.fas_user wasn't in the DB but then it shouldn't # be recognized as a repo admin and thus, if we ever are # here, we are in trouble. flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() flask.flash(str(err), 'error') return flask.render_template( 'remote_pull_request.html', select='requests', repo=repo, username=username, form=form, branches=sorted(orig_repo.listall_branches()), repo_admin=repo_admin, branch_to=orig_repo.head.shorthand, )
def api_comment_issue(repo, issueid, username=None): """ Comment to an issue ------------------- Add a comment to an issue. :: POST /api/0/<repo>/issue/<issue id>/comment :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/comment Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``comment`` | string | Mandatory | | The comment to add to | | | | | the issue | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Comment added" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.CommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data try: # New comment message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def update_issue(repo, issueid, username=None): ''' Add a comment to an issue. ''' is_js = flask.request.args.get('js', False) repo = pagure.lib.get_project(SESSION, repo, user=username) if flask.request.method == 'GET': if not is_js: flask.flash('Invalid method: GET', 'error') return flask.redirect( flask.url_for('view_issue', username=username, repo=repo.name, issueid=issueid)) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm(status=status) if form.validate_on_submit(): repo_admin = is_repo_admin(repo) if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_issue_comment(SESSION, issue.uid, commentid) if comment is None or comment.issue.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') SESSION.delete(comment) try: SESSION.commit() if not is_js: flask.flash('Comment removed') except SQLAlchemyError, err: # pragma: no cover is_js = False SESSION.rollback() LOG.error(err) if not is_js: flask.flash('Could not remove the comment: %s' % commentid, 'error') comment = form.comment.data depends = [] for depend in form.depends.data.split(','): if depend.strip(): try: depends.append(int(depend.strip())) except ValueError: pass blocks = [] for block in form.blocks.data.split(','): if block.strip(): try: blocks.append(int(block.strip())) except ValueError: pass assignee = form.assignee.data new_status = form.status.data tags = [tag.strip() for tag in form.tag.data.split(',') if tag.strip()] try: # New comment if comment: message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) SESSION.commit() if message and not is_js: flask.flash(message) if repo_admin: # Adjust (add/remove) tags messages = pagure.lib.update_tags( SESSION, issue, tags, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS) if not is_js: for message in messages: flask.flash(message) # Assign or update assignee of the ticket message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee or None, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if message and not is_js: SESSION.commit() flask.flash(message) if repo_admin: # Update status if new_status in status: message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) SESSION.commit() if message: flask.flash(message) # Update ticket this one depends on messages = pagure.lib.update_dependency_issue( SESSION, repo, issue, depends, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if not is_js: for message in messages: flask.flash(message) # Update ticket(s) depending on this one messages = pagure.lib.update_blocked_issue( SESSION, repo, issue, blocks, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException, err: is_js = False SESSION.rollback() if not is_js: flask.flash(err.message, 'error')