diff options
author | Sean McGivern <sean@gitlab.com> | 2019-03-29 17:48:01 +0300 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2019-03-29 17:48:01 +0300 |
commit | bf48b071f9c19c6585eb3e589bb8bc2ab6a75723 (patch) | |
tree | 6e138cf7500f407a1294772bdd41f689ecb11ea6 /lib | |
parent | ece9e2707146fb6984b18ee91c399cf85aadda72 (diff) | |
parent | 4b9ff4d2fe2eb22e3abed32f56aef6b24d40aa31 (diff) |
Merge branch '50199-quick-actions-refactor' into 'master'
Extend quick actions dsl
Closes #50199
See merge request gitlab-org/gitlab-ce!26095
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gitlab/quick_actions/command_definition.rb | 8 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/commit_actions.rb | 31 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/common_actions.rb | 17 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/dsl.rb | 23 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/issuable_actions.rb | 221 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/issue_actions.rb | 136 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/issue_and_merge_request_actions.rb | 225 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/merge_request_actions.rb | 59 |
8 files changed, 717 insertions, 3 deletions
diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb index e7bfcb16582..93030fd454e 100644 --- a/lib/gitlab/quick_actions/command_definition.rb +++ b/lib/gitlab/quick_actions/command_definition.rb @@ -4,7 +4,7 @@ module Gitlab module QuickActions class CommandDefinition attr_accessor :name, :aliases, :description, :explanation, :params, - :condition_block, :parse_params_block, :action_block, :warning + :condition_block, :parse_params_block, :action_block, :warning, :types def initialize(name, attributes = {}) @name = name @@ -17,6 +17,7 @@ module Gitlab @condition_block = attributes[:condition_block] @parse_params_block = attributes[:parse_params_block] @action_block = attributes[:action_block] + @types = attributes[:types] || [] end def all_names @@ -28,6 +29,7 @@ module Gitlab end def available?(context) + return false unless valid_type?(context) return true unless condition_block context.instance_exec(&condition_block) @@ -96,6 +98,10 @@ module Gitlab context.instance_exec(arg, &parse_params_block) end + + def valid_type?(context) + types.blank? || types.any? { |type| context.quick_action_target.is_a?(type) } + end end end end diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb new file mode 100644 index 00000000000..62c0fbb5afd --- /dev/null +++ b/lib/gitlab/quick_actions/commit_actions.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module CommitActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Commit only quick actions definitions + desc 'Tag this commit.' + explanation do |tag_name, message| + with_message = %{ with "#{message}"} if message.present? + "Tags this commit to #{tag_name}#{with_message}." + end + params 'v1.2.3 <message>' + parse_params do |tag_name_and_message| + tag_name_and_message.split(' ', 2) + end + types Commit + condition do + current_user.can?(:push_code, project) + end + command :tag do |tag_name, message| + @updates[:tag_name] = tag_name + @updates[:tag_message] = message + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/common_actions.rb b/lib/gitlab/quick_actions/common_actions.rb new file mode 100644 index 00000000000..5d2732d4826 --- /dev/null +++ b/lib/gitlab/quick_actions/common_actions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module CommonActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # This is a dummy command, so that it appears in the autocomplete commands + desc 'CC' + params '@user' + command :cc + end + end + end +end diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb index a3aab92061b..ecb2169151e 100644 --- a/lib/gitlab/quick_actions/dsl.rb +++ b/lib/gitlab/quick_actions/dsl.rb @@ -24,7 +24,7 @@ module Gitlab # Example: # # desc do - # "This is a dynamic description for #{noteable.to_ability_name}" + # "This is a dynamic description for #{quick_action_target.to_ability_name}" # end # command :command_key do |arguments| # # Awesome code block @@ -66,6 +66,23 @@ module Gitlab @explanation = block_given? ? block : text end + # Allows to define type(s) that must be met in order for the command + # to be returned by `.command_names` & `.command_definitions`. + # + # It is being evaluated before the conditions block is being evaluated + # + # If no types are passed then any type is allowed as the check is simply skipped. + # + # Example: + # + # types Commit, Issue, MergeRequest + # command :command_key do |arguments| + # # Awesome code block + # end + def types(*types_list) + @types = types_list + end + # Allows to define conditions that must be met in order for the command # to be returned by `.command_names` & `.command_definitions`. # It accepts a block that will be evaluated with the context @@ -144,7 +161,8 @@ module Gitlab params: @params, condition_block: @condition_block, parse_params_block: @parse_params_block, - action_block: block + action_block: block, + types: @types ) self.command_definitions << definition @@ -159,6 +177,7 @@ module Gitlab @condition_block = nil @warning = nil @parse_params_block = nil + @types = nil end end end diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb new file mode 100644 index 00000000000..ad2e15d19fa --- /dev/null +++ b/lib/gitlab/quick_actions/issuable_actions.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssuableActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + SHRUG = '¯\\_(ツ)_/¯'.freeze + TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze + + included do + # Issue, MergeRequest, Epic: quick actions definitions + desc do + "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}" + end + explanation do + "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.open? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :close do + @updates[:state_event] = 'close' + end + + desc do + "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}" + end + explanation do + "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.closed? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :reopen do + @updates[:state_event] = 'reopen' + end + + desc 'Change title' + explanation do |title_param| + "Changes the title to \"#{title_param}\"." + end + params '<New title>' + types Issuable + condition do + quick_action_target.persisted? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :title do |title_param| + @updates[:title] = title_param + end + + desc 'Add label(s)' + explanation do |labels_param| + labels = find_label_references(labels_param) + + "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + end + params '~label1 ~"label 2"' + types Issuable + condition do + parent && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) && + find_labels.any? + end + command :label do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end + end + + desc 'Remove all or specific label(s)' + explanation do |labels_param = nil| + if labels_param.present? + labels = find_label_references(labels_param) + "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + else + 'Removes all labels.' + end + end + params '~label1 ~"label 2"' + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.labels.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) + end + command :unlabel do |labels_param = nil| + if labels_param.present? + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end + else + @updates[:label_ids] = [] + end + end + + desc 'Replace all label(s)' + explanation do |labels_param| + labels = find_label_references(labels_param) + "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + end + params '~label1 ~"label 2"' + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.labels.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) + end + command :relabel do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end + end + + desc 'Add a todo' + explanation 'Adds a todo.' + types Issuable + condition do + quick_action_target.persisted? && + !TodoService.new.todo_exist?(quick_action_target, current_user) + end + command :todo do + @updates[:todo_event] = 'add' + end + + desc 'Mark todo as done' + explanation 'Marks todo as done.' + types Issuable + condition do + quick_action_target.persisted? && + TodoService.new.todo_exist?(quick_action_target, current_user) + end + command :done do + @updates[:todo_event] = 'done' + end + + desc 'Subscribe' + explanation do + "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + !quick_action_target.subscribed?(current_user, project) + end + command :subscribe do + @updates[:subscription_event] = 'subscribe' + end + + desc 'Unsubscribe' + explanation do + "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.subscribed?(current_user, project) + end + command :unsubscribe do + @updates[:subscription_event] = 'unsubscribe' + end + + desc 'Toggle emoji award' + explanation do |name| + "Toggles :#{name}: emoji award." if name + end + params ':emoji:' + types Issuable + condition do + quick_action_target.persisted? + end + parse_params do |emoji_param| + match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern) + match[1] if match + end + command :award do |name| + if name && quick_action_target.user_can_award?(current_user) + @updates[:emoji_award] = name + end + end + + desc "Append the comment with #{SHRUG}" + params '<Comment>' + types Issuable + substitution :shrug do |comment| + "#{comment} #{SHRUG}" + end + + desc "Append the comment with #{TABLEFLIP}" + params '<Comment>' + types Issuable + substitution :tableflip do |comment| + "#{comment} #{TABLEFLIP}" + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb new file mode 100644 index 00000000000..1f08e8740a2 --- /dev/null +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssueActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Issue only quick actions definition + desc 'Set due date' + explanation do |due_date| + "Sets the due date to #{due_date.to_s(:medium)}." if due_date + end + params '<in 2 days | this Friday | December 31st>' + types Issue + condition do + quick_action_target.respond_to?(:due_date) && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |due_date_param| + Chronic.parse(due_date_param).try(:to_date) + end + command :due do |due_date| + @updates[:due_date] = due_date if due_date + end + + desc 'Remove due date' + explanation 'Removes the due date.' + types Issue + condition do + quick_action_target.persisted? && + quick_action_target.respond_to?(:due_date) && + quick_action_target.due_date? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_due_date do + @updates[:due_date] = nil + end + + desc 'Move issue from one column of the board to another' + explanation do |target_list_name| + label = find_label_references(target_list_name).first + "Moves issue to #{label} column in the board." if label + end + params '~"Target column"' + types Issue + condition do + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) && + quick_action_target.project.boards.count == 1 + end + # rubocop: disable CodeReuse/ActiveRecord + command :board_move do |target_list_name| + label_ids = find_label_ids(target_list_name) + + if label_ids.size == 1 + label_id = label_ids.first + + # Ensure this label corresponds to a list on the board + next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists? + + @updates[:remove_label_ids] = + quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id) + @updates[:add_label_ids] = [label_id] + end + end + # rubocop: enable CodeReuse/ActiveRecord + + desc 'Mark this issue as a duplicate of another issue' + explanation do |duplicate_reference| + "Marks this issue as a duplicate of #{duplicate_reference}." + end + params '#issue' + types Issue + condition do + quick_action_target.persisted? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :duplicate do |duplicate_param| + canonical_issue = extract_references(duplicate_param, :issue).first + + if canonical_issue.present? + @updates[:canonical_issue_id] = canonical_issue.id + end + end + + desc 'Move this issue to another project.' + explanation do |path_to_project| + "Moves this issue to #{path_to_project}." + end + params 'path/to/project' + types Issue + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :move do |target_project_path| + target_project = Project.find_by_full_path(target_project_path) + + if target_project.present? + @updates[:target_project] = target_project + end + end + + desc 'Make issue confidential.' + explanation do + 'Makes this issue confidential' + end + types Issue + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :confidential do + @updates[:confidential] = true + end + + desc 'Create a merge request.' + explanation do |branch_name = nil| + branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch' + "Creates #{branch_text} and a merge request to resolve this issue" + end + params "<branch name>" + types Issue + condition do + current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project) + end + command :create_merge_request do |branch_name = nil| + @updates[:create_merge_request] = { + branch_name: branch_name, + issue_iid: quick_action_target.iid + } + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb new file mode 100644 index 00000000000..08872eda410 --- /dev/null +++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssueAndMergeRequestActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Issue, MergeRequest: quick actions definitions + desc 'Assign' + # rubocop: disable CodeReuse/ActiveRecord + explanation do |users| + users = quick_action_target.allows_multiple_assignees? ? users : users.take(1) + "Assigns #{users.map(&:to_reference).to_sentence}." + end + # rubocop: enable CodeReuse/ActiveRecord + params do + quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user' + end + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |assignee_param| + extract_users(assignee_param) + end + command :assign do |users| + next if users.empty? + + if quick_action_target.allows_multiple_assignees? + @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id) + @updates[:assignee_ids] += users.map(&:id) + else + @updates[:assignee_ids] = [users.first.id] + end + end + + desc do + if quick_action_target.allows_multiple_assignees? + 'Remove all or specific assignee(s)' + else + 'Remove assignee' + end + end + explanation do |users = nil| + assignees = quick_action_target.assignees + assignees &= users if users.present? && quick_action_target.allows_multiple_assignees? + "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}." + end + params do + quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '' + end + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.assignees.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |unassign_param| + # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed + extract_users(unassign_param) if quick_action_target.allows_multiple_assignees? + end + command :unassign do |users = nil| + if quick_action_target.allows_multiple_assignees? && users&.any? + @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id) + @updates[:assignee_ids] -= users.map(&:id) + else + @updates[:assignee_ids] = [] + end + end + + desc 'Set milestone' + explanation do |milestone| + "Sets the milestone to #{milestone.to_reference}." if milestone + end + params '%"milestone"' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) && + find_milestones(project, state: 'active').any? + end + parse_params do |milestone_param| + extract_references(milestone_param, :milestone).first || + find_milestones(project, title: milestone_param.strip).first + end + command :milestone do |milestone| + @updates[:milestone_id] = milestone.id if milestone + end + + desc 'Remove milestone' + explanation do + "Removes #{quick_action_target.milestone.to_reference(format: :name)} milestone." + end + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.milestone_id? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_milestone do + @updates[:milestone_id] = nil + end + + desc 'Copy labels and milestone from other issue or merge request' + explanation do |source_issuable| + "Copy labels and milestone from #{source_issuable.to_reference}." + end + params '#issue | !merge_request' + types Issue, MergeRequest + condition do + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + parse_params do |issuable_param| + extract_references(issuable_param, :issue).first || + extract_references(issuable_param, :merge_request).first + end + command :copy_metadata do |source_issuable| + if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id + @updates[:add_label_ids] = source_issuable.labels.map(&:id) + @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone + end + end + + desc 'Set time estimate' + explanation do |time_estimate| + time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate) + + "Sets time estimate to #{time_estimate}." if time_estimate + end + params '<1w 3d 2h 14m>' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |raw_duration| + Gitlab::TimeTrackingFormatter.parse(raw_duration) + end + command :estimate do |time_estimate| + if time_estimate + @updates[:time_estimate] = time_estimate + end + end + + desc 'Add or subtract spent time' + explanation do |time_spent, time_spent_date| + if time_spent + if time_spent > 0 + verb = 'Adds' + value = time_spent + else + verb = 'Subtracts' + value = -time_spent + end + + "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time." + end + end + params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + parse_params do |raw_time_date| + Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute + end + command :spend do |time_spent, time_spent_date| + if time_spent + @updates[:spend_time] = { + duration: time_spent, + user_id: current_user.id, + spent_at: time_spent_date + } + end + end + + desc 'Remove time estimate' + explanation 'Removes time estimate.' + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_estimate do + @updates[:time_estimate] = 0 + end + + desc 'Remove spent time' + explanation 'Removes spent time.' + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + types Issue, MergeRequest + command :remove_time_spent do + @updates[:spend_time] = { duration: :reset, user_id: current_user.id } + end + + desc "Lock the discussion" + explanation "Locks the discussion" + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + !quick_action_target.discussion_locked? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :lock do + @updates[:discussion_locked] = true + end + + desc "Unlock the discussion" + explanation "Unlocks the discussion" + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.discussion_locked? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :unlock do + @updates[:discussion_locked] = false + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb new file mode 100644 index 00000000000..bade59182a1 --- /dev/null +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module MergeRequestActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # MergeRequest only quick actions definitions + desc 'Merge (when the pipeline succeeds)' + explanation 'Merges this merge request when the pipeline succeeds.' + types MergeRequest + condition do + last_diff_sha = params && params[:merge_request_diff_head_sha] + quick_action_target.persisted? && + quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) + end + command :merge do + @updates[:merge] = params[:merge_request_diff_head_sha] + end + + desc 'Toggle the Work In Progress status' + explanation do + verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks' + noun = quick_action_target.to_ability_name.humanize(capitalize: false) + "#{verb} this #{noun} as Work In Progress." + end + types MergeRequest + condition do + quick_action_target.respond_to?(:work_in_progress?) && + # Allow it to mark as WIP on MR creation page _or_ through MR notes. + (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)) + end + command :wip do + @updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip' + end + + desc 'Set target branch' + explanation do |branch_name| + "Sets target branch to #{branch_name}." + end + params '<Local branch name>' + types MergeRequest + condition do + quick_action_target.respond_to?(:target_branch) && + (current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) || + quick_action_target.new_record?) + end + parse_params do |target_branch_param| + target_branch_param.strip + end + command :target_branch do |branch_name| + @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name) + end + end + end + end +end |