| @@ -110,3 +110,16 @@ http://localhost:3000/ | |||
| - 里程碑 | |||
|  | |||
| ### API | |||
| - [API](api_document.md) | |||
| ## 贡献代码 | |||
| 1. Fork 项目 | |||
| 2. 创建本地分支(git checkout -b my-new-feature) | |||
| 3. 提交更改 (git commit -am 'Add some feature') | |||
| 4. 推送到分支 (git push origin my-new-feature) | |||
| 5. 向源项目的 **develop** 分支发起 Pull Request | |||
| ## License | |||
| @@ -0,0 +1,43 @@ | |||
| class ProtectedBranchesController < ApplicationController | |||
| include OperateProjectAbilityAble | |||
| before_action :require_login | |||
| before_action :load_repository | |||
| before_action :authorizate_user_can_edit_project! | |||
| def index | |||
| scope = ProtectedBranch.all | |||
| @total_count = scope.size | |||
| @protected_branches = paginate(scope) | |||
| end | |||
| def create | |||
| @protected_branch = ProtectedBranches::CreateService.call(@repository, @owner, params) | |||
| render_protected_branch_json | |||
| end | |||
| def update | |||
| @protected_branch = ProtectedBranches::UpdateService.call(@repository, @owner, params) | |||
| end | |||
| def destroy | |||
| ProtectedBranches::DestroyService.call(@repository, @owner, params[:branch_name]) | |||
| render_ok | |||
| end | |||
| def show | |||
| @protected_branch = ProtectedBranches::GetService.call(@repository, @owner, params) | |||
| end | |||
| def edit | |||
| @branch, @protected_branch = ProtectedBranches::EditService.call(@repository, @owner, params[:branch_name]) | |||
| end | |||
| private | |||
| def render_protected_branch_json | |||
| @protected_branch.persisted? ? @protected_branch : render_error('创建失败!') | |||
| end | |||
| end | |||
| @@ -0,0 +1,19 @@ | |||
| class ProtectedBranches::CreateForm < BaseForm | |||
| attr_accessor :repository, :branch_name, :can_push, :enable_whitelist, :whitelist_user_i_ds, | |||
| :whitelist_team_i_ds, :enable_merge_whitelist, :whitelist_deploy_keys, :merge_whitelist_user_i_ds, | |||
| :merge_whitelist_team_i_ds, :enable_status_check, :status_check_contexts, :approvals_whitelist_user_i_ds, | |||
| :approvals_whitelist_team_i_ds, :required_approvals, :enable_approvals_whitelist, :block_on_rejected_reviews, | |||
| :dismiss_stale_approvals, :require_signed_commits, :protected_file_patterns, :block_on_outdated_branch | |||
| validates :repo_id, :branch_name, presence: true | |||
| validate do | |||
| check_branch_name! | |||
| end | |||
| def check_branch_name! | |||
| protected_branch_exists = repository.protected_branches.exists?(branch_name) | |||
| raise "Protected branch '#{branch_name}' already exists" if protected_branch_exists | |||
| end | |||
| end | |||
| @@ -0,0 +1,2 @@ | |||
| module ProtectedBranchesHelper | |||
| end | |||
| @@ -0,0 +1,44 @@ | |||
| module Repositories::ProtectedBranches | |||
| class DeleteInteractor | |||
| def self.call(user, identifier, filepath, **args) | |||
| interactor = new(user, identifier, filepath, **args) | |||
| interactor.run | |||
| interactor | |||
| end | |||
| attr_reader :error, :result | |||
| def initialize(user, identifier, filepath, **args) | |||
| @user = user | |||
| @identifier = identifier | |||
| @filepath = filepath | |||
| @args = args | |||
| end | |||
| def success? | |||
| @error.nil? | |||
| end | |||
| def result | |||
| end | |||
| def run | |||
| rescue Exception => exception | |||
| fail!(exception.message) | |||
| end | |||
| private | |||
| attr_reader :user, :identifier, :filepath, :args | |||
| def fail!(error) | |||
| @error = error | |||
| end | |||
| def render_result(response) | |||
| @result = response | |||
| end | |||
| end | |||
| end | |||
| @@ -7,6 +7,7 @@ module ProjectOperable | |||
| has_many :managers, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member' | |||
| has_many :developers, -> { joins(:roles).where(roles: { name: 'Developer' }) }, class_name: 'Member' | |||
| has_many :reporters, -> { joins(:roles).where(roles: { name: 'Reporter' }) }, class_name: 'Member' | |||
| has_many :writable_members, -> { joins(:roles).where.not(roles: {name: 'Reporter'}) }, class_name: 'Member' | |||
| end | |||
| def add_member!(user_id, role_name='Developer') | |||
| @@ -0,0 +1,65 @@ | |||
| # == Schema Information | |||
| # | |||
| # Table name: protected_branches | |||
| # | |||
| # id :integer not null, primary key | |||
| # repo_id :integer | |||
| # branch_name :string(255) default("") | |||
| # can_push :boolean default("0"), not null | |||
| # enable_whitelist :boolean default("0") | |||
| # whitelist_user_i_ds :text(65535) | |||
| # whitelist_team_i_ds :text(65535) | |||
| # enable_merge_whitelist :boolean default("0"), not null | |||
| # whitelist_deploy_keys :boolean default("0"), not null | |||
| # merge_whitelist_user_i_ds :text(65535) | |||
| # merge_whitelist_team_i_ds :text(65535) | |||
| # enable_status_check :boolean default("0"), not null | |||
| # status_check_contexts :text(65535) | |||
| # approvals_whitelist_user_i_ds :text(65535) | |||
| # approvals_whitelist_team_i_ds :text(65535) | |||
| # required_approvals :integer default("0") | |||
| # enable_approvals_whitelist :boolean default("0"), not null | |||
| # block_on_rejected_reviews :boolean default("0"), not null | |||
| # dismiss_stale_approvals :boolean default("0"), not null | |||
| # require_signed_commits :boolean default("0"), not null | |||
| # protected_file_patterns :text(65535) | |||
| # block_on_outdated_branch :boolean default("0"), not null | |||
| # created_at :datetime not null | |||
| # updated_at :datetime not null | |||
| # | |||
| # Indexes | |||
| # | |||
| # index_protected_branches_on_repo_id (repo_id) | |||
| # | |||
| class ProtectedBranch < ApplicationRecord | |||
| serialize :whitelist_user_i_ds, Array | |||
| serialize :merge_whitelist_user_i_ds, Array | |||
| serialize :approvals_whitelist_user_i_ds, Array | |||
| belongs_to :repo, class_name: 'Repository', foreign_key: :repo_id | |||
| validates :branch_name, presence: true | |||
| validates :repo, presence: true | |||
| def to_param | |||
| self.branch_name.parameterize | |||
| end | |||
| def push_whitelist_usernames | |||
| get_logins_by_ids(whitelist_user_i_ds) | |||
| end | |||
| def merge_whitelist_usernames | |||
| get_logins_by_ids(merge_whitelist_user_i_ds) | |||
| end | |||
| def approvals_whitelist_usernames | |||
| get_logins_by_ids(approvals_whitelist_user_i_ds) | |||
| end | |||
| def get_logins_by_ids(ids) | |||
| User.where(id: ids).map(&:login) | |||
| end | |||
| end | |||
| @@ -37,6 +37,7 @@ class Repository < ApplicationRecord | |||
| has_one :mirror, foreign_key: :repo_id | |||
| has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', foreign_key: :repo_id | |||
| has_many :version_releases, dependent: :destroy | |||
| has_many :protected_branches, class_name: 'ProtectedBranch', foreign_key: :repo_id, dependent: :destroy | |||
| validates :identifier, presence: true | |||
| @@ -14,4 +14,8 @@ class ApplicationService | |||
| def strip(str) | |||
| str.to_s.strip.presence | |||
| end | |||
| end | |||
| def str_to_boolean str | |||
| ActiveModel::Type::Boolean.new.cast str | |||
| end | |||
| end | |||
| @@ -21,11 +21,10 @@ class Gitea::ClientService < ApplicationService | |||
| def post(url, params={}) | |||
| puts "[gitea] request params: #{params}" | |||
| auth_token = authen_params(params[:token]) | |||
| response = conn(auth_token).post do |req| | |||
| conn(auth_token).post do |req| | |||
| req.url full_url(url) | |||
| req.body = params[:data].to_json | |||
| end | |||
| render_status(response) | |||
| end | |||
| def get(url, params={}) | |||
| @@ -164,4 +163,102 @@ class Gitea::ClientService < ApplicationService | |||
| nil | |||
| end | |||
| end | |||
| def render_response(response) | |||
| status = response.status | |||
| body = response&.body | |||
| log_error(status, body) | |||
| body, message = get_body_by_status(status, body) | |||
| [status, message, body] | |||
| end | |||
| def get_body_by_status(status, body) | |||
| body, message = | |||
| case status | |||
| when 404 then [nil, "404"] | |||
| when 403 then [nil, "403"] | |||
| else | |||
| if body.present? | |||
| body = JSON.parse(body) | |||
| fix_body(body) | |||
| else | |||
| nil | |||
| end | |||
| end | |||
| [body, message] | |||
| end | |||
| def log_error(status, body) | |||
| puts "[gitea] status: #{status}" | |||
| puts "[gitea] body: #{body}" | |||
| end | |||
| def fix_body(body) | |||
| return [body, nil] if body.is_a? Array | |||
| body['message'].blank? ? [body, nil] : [nil, body['message']] | |||
| end | |||
| def render_json_data(status, message, body, success=true) | |||
| if success | |||
| success(body) | |||
| else | |||
| error(message, status) | |||
| end | |||
| end | |||
| def error(message, http_status = nil) | |||
| result = { | |||
| message: message, | |||
| status: :error | |||
| } | |||
| result[:http_status] = http_status if http_status | |||
| result | |||
| end | |||
| def success(body=nil) | |||
| { | |||
| status: :success, | |||
| body: body | |||
| } | |||
| end | |||
| def render_body(body) | |||
| success(body)[:body] | |||
| end | |||
| def render_200_response(response) | |||
| extract_statuses(response) | |||
| end | |||
| def render_200_no_body(response) | |||
| response.status | |||
| case response.status | |||
| when 200 | |||
| {status: 200} | |||
| else | |||
| end | |||
| end | |||
| def render_201_response(response) | |||
| extract_statuses(response) | |||
| end | |||
| def render_202_response(response) | |||
| extract_statuses(response) | |||
| end | |||
| def extract_statuses(response) | |||
| success_statuses = [200, 201, 202, 204] | |||
| status, message, body = render_response(response) | |||
| return error(message, status) unless success_statuses.include? status | |||
| render_body(body) | |||
| end | |||
| end | |||
| @@ -24,7 +24,8 @@ class Gitea::Hooks::CreateService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, params) | |||
| response = post(url, params) | |||
| render_201_response(response) | |||
| end | |||
| private | |||
| @@ -27,7 +27,8 @@ class Gitea::Oauth2::CreateService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, request_params) | |||
| response = post(url, request_params) | |||
| render_201_response(response) | |||
| end | |||
| private | |||
| @@ -13,7 +13,7 @@ class Gitea::Repository::Branches::GetService < Gitea::ClientService | |||
| def call | |||
| response = get(url, params) | |||
| render_data(response) | |||
| render_200_response(response) | |||
| end | |||
| private | |||
| @@ -8,7 +8,7 @@ class Gitea::Repository::Branches::ListService < Gitea::ClientService | |||
| def call | |||
| response = get(url, params) | |||
| render_data(response) | |||
| render_200_response(response) | |||
| end | |||
| private | |||
| @@ -18,7 +18,8 @@ class Gitea::Repository::CreateService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, request_params) | |||
| response = post(url, request_params) | |||
| render_201_response(response) | |||
| end | |||
| private | |||
| @@ -29,7 +29,9 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, params) | |||
| response = post(url, params) | |||
| render_201_response(response) | |||
| end | |||
| private | |||
| @@ -14,7 +14,9 @@ class Gitea::Repository::ForkService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, request_params) | |||
| response = post(url, request_params) | |||
| render_202_response(response) | |||
| end | |||
| private | |||
| @@ -31,7 +31,9 @@ class Gitea::Repository::MigrateService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, request_params) | |||
| response = post(url, request_params) | |||
| render_201_response(response) | |||
| end | |||
| private | |||
| @@ -0,0 +1,76 @@ | |||
| # Create a branch protections for a repository | |||
| class Gitea::Repository::ProtectedBranches::CreateService < Gitea::ClientService | |||
| attr_reader :owner, :repo, :body, :token | |||
| # owner: owner of the repo | |||
| # repo: name of the repo | |||
| # body: | |||
| # { | |||
| # "approvals_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "approvals_whitelist_username": [ | |||
| # "string" | |||
| # ], | |||
| # "block_on_outdated_branch": true, | |||
| # "block_on_rejected_reviews": true, | |||
| # "branch_name": "string", | |||
| # "dismiss_stale_approvals": true, | |||
| # "enable_approvals_whitelist": true, | |||
| # "enable_merge_whitelist": true, | |||
| # "enable_push": true, | |||
| # "enable_push_whitelist": true, | |||
| # "enable_status_check": true, | |||
| # "merge_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "merge_whitelist_usernames": [ | |||
| # "string" | |||
| # ], | |||
| # "protected_file_patterns": "string", | |||
| # "push_whitelist_deploy_keys": true, | |||
| # "push_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "push_whitelist_usernames": [ | |||
| # "string" | |||
| # ], | |||
| # "require_signed_commits": true, | |||
| # "required_approvals": 0, | |||
| # "status_check_contexts": [ | |||
| # "string" | |||
| # ] | |||
| # } | |||
| def initialize(owner, repo, body={}, token=nil) | |||
| @owner = owner | |||
| @repo = repo | |||
| @body = body | |||
| @token = token | |||
| end | |||
| def call | |||
| response = post(url, params) | |||
| status, message, body = render_response(response) | |||
| json_format(status, message, body) | |||
| end | |||
| private | |||
| def params | |||
| Hash.new.merge(token: token, data: body) | |||
| end | |||
| def url | |||
| "/repos/#{owner}/#{repo}/branch_protections".freeze | |||
| end | |||
| def json_format(status, message, body) | |||
| case status | |||
| when 201 then success(body) | |||
| else | |||
| error(message, status) | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,39 @@ | |||
| # Delete a specific branch protection for the repository | |||
| class Gitea::Repository::ProtectedBranches::DestroyService < Gitea::ClientService | |||
| attr_reader :owner, :repo, :name, :token | |||
| # owner: owner of the repo | |||
| # repo: name of the repo | |||
| # name: name of protected branch | |||
| # eg: | |||
| # Gitea::Repository::ProtectedBranches::DestroyService.call(user.login, repo.identifier, branch_name, user.gitea_token) | |||
| def initialize(owner, repo, name, token=nil) | |||
| @owner = owner | |||
| @repo = repo | |||
| @name = name | |||
| @token = token | |||
| end | |||
| def call | |||
| response = delete(url, params) | |||
| status, message = render_response(response) | |||
| json_format(status, message) | |||
| end | |||
| private | |||
| def params | |||
| Hash.new.merge(token: token, data: name) | |||
| end | |||
| def url | |||
| "/repos/#{owner}/#{repo}/branch_protections/#{name}".freeze | |||
| end | |||
| def json_format(status, message) | |||
| case status | |||
| when 204 then success | |||
| when 404 then error(message, 404) | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,78 @@ | |||
| # Edit a branch protections for a repository. Only fields that are set will be changed | |||
| class Gitea::Repository::ProtectedBranches::UpdateService < Gitea::ClientService | |||
| attr_reader :owner, :repo, :name, :body, :token | |||
| # owner: owner of the repo | |||
| # repo: name of the repo | |||
| # nmae: name of protected branch | |||
| # body: | |||
| # { | |||
| # "approvals_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "approvals_whitelist_username": [ | |||
| # "string" | |||
| # ], | |||
| # "block_on_outdated_branch": true, | |||
| # "block_on_rejected_reviews": true, | |||
| # "branch_name": "string", | |||
| # "dismiss_stale_approvals": true, | |||
| # "enable_approvals_whitelist": true, | |||
| # "enable_merge_whitelist": true, | |||
| # "enable_push": true, | |||
| # "enable_push_whitelist": true, | |||
| # "enable_status_check": true, | |||
| # "merge_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "merge_whitelist_usernames": [ | |||
| # "string" | |||
| # ], | |||
| # "protected_file_patterns": "string", | |||
| # "push_whitelist_deploy_keys": true, | |||
| # "push_whitelist_teams": [ | |||
| # "string" | |||
| # ], | |||
| # "push_whitelist_usernames": [ | |||
| # "string" | |||
| # ], | |||
| # "require_signed_commits": true, | |||
| # "required_approvals": 0, | |||
| # "status_check_contexts": [ | |||
| # "string" | |||
| # ] | |||
| # } | |||
| # eq: | |||
| # Gitea::Repository::ProtectedBranches::UpdateService.call(user.login, repo.identifier, branch_name, body, user.gitea_token) | |||
| def initialize(owner, repo, name, body, token=nil) | |||
| @owner = owner | |||
| @repo = repo | |||
| @name = name | |||
| @body = body | |||
| @token = token | |||
| end | |||
| def call | |||
| response = patch(url, params) | |||
| status, message, body = render_response(response) | |||
| json_format(status, message, body) | |||
| end | |||
| private | |||
| def params | |||
| Hash.new.merge(token: token, data: body) | |||
| end | |||
| def url | |||
| "/repos/#{owner}/#{repo}/branch_protections/#{name}".freeze | |||
| end | |||
| def json_format(status, message, body) | |||
| case status | |||
| when 200 then success(body) | |||
| else | |||
| error(message, status) | |||
| end | |||
| end | |||
| end | |||
| @@ -15,7 +15,9 @@ class Gitea::Repository::SyncMirroredService < Gitea::ClientService | |||
| end | |||
| def call | |||
| post(url, request_params) | |||
| response = post(url, request_params) | |||
| render_200_no_body(response) | |||
| end | |||
| private | |||
| @@ -0,0 +1,276 @@ | |||
| module ProtectedBranches | |||
| class BaseService < ApplicationService | |||
| Error = Class.new(StandardError) | |||
| attr_accessor :repository, :owner, :params | |||
| def initialize(repository, user = nil, params = {}) | |||
| @repository, @owner, @params = repository, user, params.dup | |||
| end | |||
| # delegate :repository, to: :project | |||
| def protected_branch_params | |||
| # { | |||
| # "approvals_whitelist_teams": [ | |||
| # "string" | |||
| # ], //批准团队(或审查团队)白名单 | |||
| # "approvals_whitelist_username": [ | |||
| # "string" | |||
| # ], // 批准用户(或审查者)白名单 | |||
| # "block_on_outdated_branch": true, // 如果拉取过时,阻止合并 | |||
| # "block_on_rejected_reviews": true, // 拒绝审核,阻止合并请求 | |||
| # "branch_name": "string", //分支名称 | |||
| # "dismiss_stale_approvals": true, // 取消过时的批准 | |||
| # "enable_approvals_whitelist": true, //是否批准仅限列入白名单的用户或者团队, 主要用户pr的审核批准计数功能 | |||
| # "enable_merge_whitelist": true, // 是否启用合并请求白名单 | |||
| # "enable_push": true, //启用、禁止推送 | |||
| # "enable_push_whitelist": true, // 是否启动推送白名单 | |||
| # "enable_status_check": true, //是否启用状态检查 | |||
| # "merge_whitelist_teams": [ | |||
| # "string" | |||
| # ], // 合并请求团队白名单 | |||
| # "merge_whitelist_usernames": [ | |||
| # "string" | |||
| # ], // 合并请求用户白名单 | |||
| # "protected_file_patterns": "string", //保护文件模式 | |||
| # "push_whitelist_deploy_keys": true, // 具有推送权限的部署密钥白名单 | |||
| # "push_whitelist_teams": [ | |||
| # "string" | |||
| # ], //推送团队白名单 | |||
| # "push_whitelist_usernames": [ | |||
| # "string" | |||
| # ], //推送用户白名单 | |||
| # "require_signed_commits": true, //是否需要签名提交 | |||
| # "required_approvals": 0, // 所需批准数 | |||
| # "status_check_contexts": [ | |||
| # "string" | |||
| # ] // 状态检查规则 | |||
| # } | |||
| # branch_name :string(255) default("") | |||
| # can_push :boolean default("0"), not null | |||
| # enable_whitelist :boolean default("0") | |||
| # whitelist_user_i_ds :text(65535) | |||
| # whitelist_team_i_ds :text(65535) | |||
| # enable_merge_whitelist :boolean default("0"), not null | |||
| # whitelist_deploy_keys :boolean default("0"), not null | |||
| # merge_whitelist_user_i_ds :text(65535) | |||
| # merge_whitelist_team_i_ds :text(65535) | |||
| # enable_status_check :boolean default("0"), not null | |||
| # status_check_contexts :text(65535) | |||
| # approvals_whitelist_user_i_ds :text(65535) | |||
| # approvals_whitelist_team_i_ds :text(65535) | |||
| # required_approvals :integer default("0") | |||
| # enable_approvals_whitelist :boolean default("0"), not null | |||
| # block_on_rejected_reviews :boolean default("0"), not null | |||
| # dismiss_stale_approvals :boolean default("0"), not null | |||
| # require_signed_commits :boolean default("0"), not null | |||
| # protected_file_patterns :text(65535) | |||
| # block_on_outdated_branch :boolean default("0"), not null | |||
| { | |||
| branch_name: params[:branch_name], | |||
| can_push: can_push_params, | |||
| enable_whitelist: enable_whitelist_params, | |||
| whitelist_user_i_ds: whitelist_user_i_ds_params, | |||
| # whitelist_team_i_ds: whitelist_team_i_ds_params, | |||
| enable_merge_whitelist: enable_merge_whitelist_params, | |||
| merge_whitelist_user_i_ds: merge_whitelist_user_i_ds_params, | |||
| # merge_whitelist_team_i_ds: merge_whitelist_team_i_ds_params, | |||
| enable_status_check: enable_status_check_params, | |||
| required_approvals: params[:required_approvals] || 0, | |||
| enable_approvals_whitelist: enable_approvals_whitelist_params, | |||
| approvals_whitelist_user_i_ds: approvals_whitelist_user_i_ds_params, | |||
| # approvals_whitelist_team_i_ds: approvals_whitelist_team_i_ds_params, | |||
| block_on_rejected_reviews: block_on_rejected_reviews_params, | |||
| dismiss_stale_approvals: dismiss_stale_approvals_params, | |||
| require_signed_commits: require_signed_commits_params, | |||
| block_on_outdated_branch: block_on_outdated_branch_params | |||
| } | |||
| end | |||
| def enable_status_check_params | |||
| str_to_boolean(params[:enable_status_check] || false) | |||
| end | |||
| def enable_approvals_whitelist_params | |||
| str_to_boolean(params[:enable_approvals_whitelist] || false) | |||
| end | |||
| def block_on_rejected_reviews_params | |||
| str_to_boolean(params[:block_on_rejected_reviews] || false) | |||
| end | |||
| def dismiss_stale_approvals_params | |||
| str_to_boolean(params[:dismiss_stale_approvals] || false) | |||
| end | |||
| def require_signed_commits_params | |||
| str_to_boolean(params[:require_signed_commits] || false) | |||
| end | |||
| def block_on_outdated_branch_params | |||
| str_to_boolean(params[:block_on_outdated_branch] || false) | |||
| end | |||
| def can_push_params | |||
| return false if !can_push? | |||
| return true if enable_whitelist? | |||
| str_to_boolean(params[:enable_push]) | |||
| end | |||
| def enable_whitelist_params | |||
| return false if !can_push? | |||
| str_to_boolean(params[:enable_push_whitelist]) | |||
| end | |||
| def whitelist_user_i_ds_params | |||
| return [] if !can_push? | |||
| user_ids(get_push_whitelist_usernames) | |||
| end | |||
| def whitelist_team_i_ds_params | |||
| # params[:push_whitelist_usernames] | |||
| end | |||
| def enable_merge_whitelist_params | |||
| str_to_boolean(params[:enable_merge_whitelist] || false) | |||
| end | |||
| def merge_whitelist_user_i_ds_params | |||
| returtn [] if !enable_merge_whitelist? | |||
| user_ids(get_merge_whitelist_usernames) | |||
| end | |||
| def merge_whitelist_team_i_ds_params | |||
| params[:merge_whitelist_teams] | |||
| end | |||
| def approvals_whitelist_user_i_ds_params | |||
| return [] if !enable_approvals_whitelist? | |||
| user_ids(get_approvals_whitelist_usernames) | |||
| end | |||
| def approvals_whitelist_team_i_ds_params | |||
| params[:approvals_whitelist_teams] | |||
| end | |||
| def user_ids(names) | |||
| member_ids & names_by_params(names) | |||
| end | |||
| def member_ids | |||
| @repository.project.writable_members.map(&:user_id) | |||
| end | |||
| def names_by_params(names) | |||
| User.where(login: names.to_a).ids | |||
| end | |||
| def get_push_whitelist_usernames | |||
| return [] if !can_push? || !enable_whitelist? | |||
| filter_empty_element Array(params[:push_whitelist_usernames]) | |||
| end | |||
| def get_merge_whitelist_usernames | |||
| return [] if !enable_merge_whitelist? | |||
| filter_empty_element Array(params[:merge_whitelist_usernames]) | |||
| end | |||
| def get_approvals_whitelist_usernames | |||
| return [] if !enable_approvals_whitelist? | |||
| filter_empty_element Array(params[:approvals_whitelist_usernames]) | |||
| end | |||
| def check_users!(names) | |||
| names.each {|name| | |||
| check_user!(name) | |||
| break | |||
| } | |||
| end | |||
| def check_user!(name) | |||
| user_exist = User.exists?(login: name) | |||
| raise Error, "user '#{name}' does not exist" if !user_exist | |||
| end | |||
| def can_push? | |||
| str_to_boolean(params[:enable_push]) === true | |||
| end | |||
| def enable_whitelist? | |||
| str_to_boolean(params[:enable_push_whitelist]) === true | |||
| end | |||
| def enable_merge_whitelist? | |||
| str_to_boolean(params[:enable_merge_whitelist]) === true | |||
| end | |||
| def enable_approvals_whitelist? | |||
| str_to_boolean(params[:enable_approvals_whitelist]) === true | |||
| end | |||
| def filter_empty_element(array) | |||
| array.reject { |e| e.to_s.empty? } | |||
| end | |||
| def save_gitea_protected_branch! | |||
| @gitea_protected_branch ||= Gitea::Repository::ProtectedBranches::CreateService.call(@owner.login, | |||
| @repository.identifier,gitea_protected_branch_params, @owner.gitea_token) | |||
| raise Error, @gitea_protected_branch[:message] if @gitea_protected_branch[:status] != :success | |||
| end | |||
| def gitea_protected_branch_saved? | |||
| @gitea_protected_branch[:status] === success | |||
| end | |||
| def gitea_protected_branch | |||
| @gitea_protected_branch[:body] | |||
| end | |||
| def gitea_protected_branch_params | |||
| { | |||
| approvals_whitelist_username: get_approvals_whitelist_usernames, | |||
| branch_name: params[:branch_name], | |||
| enable_approvals_whitelist: enable_approvals_whitelist_params, | |||
| enable_merge_whitelist: enable_merge_whitelist_params, | |||
| enable_push: can_push_params, | |||
| enable_push_whitelist: enable_whitelist_params, | |||
| enable_status_check: enable_status_check_params, | |||
| # merge_whitelist_teams: [], | |||
| merge_whitelist_usernames: get_merge_whitelist_usernames, | |||
| # protected_file_patterns: string, | |||
| # push_whitelist_deploy_keys: true, | |||
| # push_whitelist_teams: [], | |||
| push_whitelist_usernames: get_push_whitelist_usernames, | |||
| block_on_rejected_reviews: block_on_rejected_reviews_params, | |||
| dismiss_stale_approvals: dismiss_stale_approvals_params, | |||
| require_signed_commits: require_signed_commits_params, | |||
| block_on_outdated_branch: block_on_outdated_branch_params | |||
| } | |||
| end | |||
| def validate! | |||
| protected_branch_exists = repository.protected_branches.exists?(params[:branch_name]) | |||
| raise Error, "Protected branch '#{branch_name}' already exists" if protected_branch_exists | |||
| check_users!(get_push_whitelist_usernames) if get_push_whitelist_usernames.any? | |||
| check_users!(get_merge_whitelist_usernames) if get_merge_whitelist_usernames.any? | |||
| check_users!(get_approvals_whitelist_usernames) if get_approvals_whitelist_usernames.any? | |||
| raise Error, '分支名称不能为空' if params[:branch_name].blank? | |||
| end | |||
| end | |||
| def error(errors, award: nil, status: nil) | |||
| errors = Array.wrap(errors) | |||
| super(errors.to_sentence.presence, status).merge({ | |||
| award: award, | |||
| errors: errors | |||
| }) | |||
| end | |||
| end | |||
| @@ -0,0 +1,23 @@ | |||
| module ProtectedBranches | |||
| class CreateService < ProtectedBranches::BaseService | |||
| def call | |||
| validate! | |||
| ProtectedBranch.transaction do | |||
| save_gitea_protected_branch! | |||
| save_protected_branch! | |||
| end | |||
| protected_branch | |||
| end | |||
| private | |||
| def protected_branch | |||
| @protected_branch ||= repository.protected_branches.new(protected_branch_params) | |||
| end | |||
| def save_protected_branch! | |||
| protected_branch.save | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,26 @@ | |||
| module ProtectedBranches | |||
| class DestroyService < ProtectedBranches::BaseService | |||
| def call | |||
| protected_branch.destroy! if success? | |||
| rescue ActiveRecord::RecordNotFound | |||
| raise Error, '404' | |||
| rescue => ex | |||
| Rails.logger.info ex | |||
| raise Error, ex | |||
| end | |||
| private | |||
| def protected_branch | |||
| @protected_branch ||= @repository.protected_branches.find_by!(branch_name: @params) | |||
| end | |||
| def success? | |||
| result = Gitea::Repository::ProtectedBranches::DestroyService.call(@owner.login, | |||
| @repository.identifier, protected_branch.branch_name, @owner.gitea_token) | |||
| return true if result[:status] === :success | |||
| raise Error, result[:message] | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,35 @@ | |||
| module ProtectedBranches | |||
| class EditService < ProtectedBranches::BaseService | |||
| def call | |||
| validate_branch_name! | |||
| protected_branch | |||
| rescue => ex | |||
| Rails.logger.info ex | |||
| raise Error, ex | |||
| end | |||
| private | |||
| def protected_branch | |||
| branch = get_common_branch | |||
| protected_branch ||= @repository.protected_branches.find_by(branch_name: branch_name) | |||
| [branch, protected_branch] | |||
| end | |||
| def get_common_branch | |||
| result = Gitea::Repository::Branches::GetService.call(@owner.login, | |||
| @repository.identifier, branch_name, @owner.gitea_token) | |||
| raise Error, '404' if result[:status] == :error | |||
| result | |||
| end | |||
| def validate_branch_name! | |||
| raise Error, '分支名称不能为空' if branch_name.blank? | |||
| end | |||
| def branch_name | |||
| params | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,23 @@ | |||
| module ProtectedBranches | |||
| class GetService < ProtectedBranches::BaseService | |||
| def call | |||
| validate_branch_name! | |||
| protected_branch | |||
| rescue ActiveRecord::RecordNotFound | |||
| raise Error, '404' | |||
| rescue => ex | |||
| Rails.logger.info ex | |||
| raise Error, ex | |||
| end | |||
| private | |||
| def protected_branch | |||
| @protected_branch ||= @repository.protected_branches.find_by!(branch_name: params[:branch_name]) | |||
| end | |||
| def validate_branch_name! | |||
| raise Error, '分支名称不能为空' if params[:branch_name].blank? | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,31 @@ | |||
| module ProtectedBranches | |||
| class UpdateService < ProtectedBranches::BaseService | |||
| def call | |||
| validate! | |||
| protected_branch.update(protected_branch_params) if success? | |||
| protected_branch | |||
| rescue ActiveRecord::RecordNotFound | |||
| raise Error, '404' | |||
| rescue => ex | |||
| Rails.logger.info ex | |||
| raise Error, ex | |||
| end | |||
| private | |||
| def protected_branch | |||
| @protected_branch ||= @repository.protected_branches.find_by!(branch_name: params[:branch_name]) | |||
| end | |||
| def success? | |||
| result = Gitea::Repository::ProtectedBranches::UpdateService.call(@owner.login, @repository.identifier, | |||
| protected_branch.branch_name, gitea_protected_branch_params, @owner.gitea_token) | |||
| return true if result[:status] === :success | |||
| raise Error, result[:message] | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,16 @@ | |||
| json.branch_name protected_branch.branch_name | |||
| json.enable_push protected_branch.can_push | |||
| json.required_approvals protected_branch.required_approvals | |||
| json.enable_status_check protected_branch.enable_status_check | |||
| json.enable_push_whitelist protected_branch.enable_whitelist | |||
| json.enable_merge_whitelist protected_branch.enable_merge_whitelist | |||
| json.enable_approvals_whitelist protected_branch.enable_approvals_whitelist | |||
| json.dismiss_stale_approvals protected_branch.dismiss_stale_approvals | |||
| json.block_on_rejected_reviews protected_branch.block_on_rejected_reviews | |||
| json.block_on_outdated_branch protected_branch.block_on_outdated_branch | |||
| json.require_signed_commits protected_branch.require_signed_commits | |||
| json.merge_whitelist_usernames protected_branch.merge_whitelist_usernames | |||
| json.push_whitelist_usernames protected_branch.push_whitelist_usernames | |||
| json.approvals_whitelist_usernames protected_branch.approvals_whitelist_usernames | |||
| json.created_at protected_branch.created_at.strftime("%Y-%m-%d %H:%M") | |||
| json.updated_at protected_branch.updated_at.strftime("%Y-%m-%d %H:%M") | |||
| @@ -0,0 +1 @@ | |||
| json.partial! @protected_branch, as: :protected_branch | |||
| @@ -0,0 +1,9 @@ | |||
| json.branch_name @branch['name'] | |||
| json.protected @branch['protected'] | |||
| json.protected_branch do | |||
| if @protected_branch | |||
| json.partial! @protected_branch, as: :protected_branch | |||
| else | |||
| json.nil! | |||
| end | |||
| end | |||
| @@ -0,0 +1,2 @@ | |||
| json.total_count @total_count | |||
| json.protected_branches @protected_branches, partial: 'protected_branches/protected_branch', as: :protected_branch | |||
| @@ -0,0 +1 @@ | |||
| json.partial! @protected_branch, as: :protected_branch | |||
| @@ -0,0 +1 @@ | |||
| json.partial! @protected_branch, as: :protected_branch | |||
| @@ -19,6 +19,8 @@ module Educoderplus | |||
| # | |||
| # config.educoder = config_for(:configuration) | |||
| # Custom directories with classes and modules you want to be autoloadable. | |||
| config.active_record.default_timezone = :utc | |||
| config.time_zone = 'Beijing' | |||
| @@ -323,6 +323,34 @@ Rails.application.routes.draw do | |||
| end | |||
| end | |||
| # protected_branches | |||
| scope do | |||
| get( | |||
| '/protected_branches/', | |||
| to: 'protected_branches#index' | |||
| ) | |||
| get( | |||
| '/protected_branches/:branch_name', | |||
| to: 'protected_branches#show' | |||
| ) | |||
| get( | |||
| '/protected_branches/:branch_name/edit', | |||
| to: 'protected_branches#edit' | |||
| ) | |||
| delete( | |||
| '/protected_branches/:branch_name', | |||
| to: 'protected_branches#destroy' | |||
| ) | |||
| post( | |||
| '/protected_branches', | |||
| to: 'protected_branches#create' | |||
| ) | |||
| patch( | |||
| '/protected_branches/:branch_name', | |||
| to: 'protected_branches#update' | |||
| ) | |||
| end | |||
| resources :issues do | |||
| collection do | |||
| get :commit_issues | |||
| @@ -0,0 +1,31 @@ | |||
| class CreateProtectedBranches < ActiveRecord::Migration[5.2] | |||
| def change | |||
| create_table :protected_branches do |t| | |||
| t.integer :repo_id | |||
| t.string :branch_name, default: "" | |||
| t.boolean :can_push, default: false, null: false | |||
| t.boolean :enable_whitelist, default: false | |||
| t.text :whitelist_user_i_ds | |||
| t.text :whitelist_team_i_ds | |||
| t.boolean :enable_merge_whitelist, default: false, null: false | |||
| t.boolean :whitelist_deploy_keys, default: false, null: false | |||
| t.text :merge_whitelist_user_i_ds | |||
| t.text :merge_whitelist_team_i_ds | |||
| t.boolean :enable_status_check, default: false, null: false | |||
| t.text :status_check_contexts | |||
| t.text :approvals_whitelist_user_i_ds | |||
| t.text :approvals_whitelist_team_i_ds | |||
| t.integer :required_approvals, default: 0 | |||
| t.boolean :enable_approvals_whitelist, default: false, null: false | |||
| t.boolean :block_on_rejected_reviews, default: false, null: false | |||
| t.boolean :dismiss_stale_approvals, default: false, null: false | |||
| t.boolean :require_signed_commits, default: false, null: false | |||
| t.text :protected_file_patterns | |||
| t.boolean :block_on_outdated_branch, default: false, null: false | |||
| t.timestamps | |||
| end | |||
| add_index :protected_branches, :repo_id | |||
| end | |||
| end | |||
| @@ -0,0 +1,40 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe ProtectedBranchesController, type: :controller do | |||
| describe "GET #index" do | |||
| it "returns http success" do | |||
| get :index | |||
| expect(response).to have_http_status(:success) | |||
| end | |||
| end | |||
| describe "GET #create" do | |||
| it "returns http success" do | |||
| get :create | |||
| expect(response).to have_http_status(:success) | |||
| end | |||
| end | |||
| describe "GET #edit" do | |||
| it "returns http success" do | |||
| get :edit | |||
| expect(response).to have_http_status(:success) | |||
| end | |||
| end | |||
| describe "GET #update" do | |||
| it "returns http success" do | |||
| get :update | |||
| expect(response).to have_http_status(:success) | |||
| end | |||
| end | |||
| describe "GET #destroy" do | |||
| it "returns http success" do | |||
| get :destroy | |||
| expect(response).to have_http_status(:success) | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,15 @@ | |||
| require 'rails_helper' | |||
| # Specs in this file have access to a helper object that includes | |||
| # the ProtectedBranchesHelper. For example: | |||
| # | |||
| # describe ProtectedBranchesHelper do | |||
| # describe "string concat" do | |||
| # it "concats two strings with spaces" do | |||
| # expect(helper.concat_strings("this","that")).to eq("this that") | |||
| # end | |||
| # end | |||
| # end | |||
| RSpec.describe ProtectedBranchesHelper, type: :helper do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe ProtectedBranch, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe "protected_branches/create.html.erb", type: :view do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe "protected_branches/destroy.html.erb", type: :view do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe "protected_branches/edit.html.erb", type: :view do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe "protected_branches/index.html.erb", type: :view do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe "protected_branches/update.html.erb", type: :view do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||