git_workflow 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/README.markdown +77 -0
  2. data/Rakefile +33 -0
  3. data/bin/git-finish +4 -0
  4. data/bin/git-start +4 -0
  5. data/bin/git-workflow-setup +4 -0
  6. data/features/core_functionality/4049578_need_the_ability_to_start_a_new_branch.feature +15 -0
  7. data/features/core_functionality/4049611_need_the_ability_to_finish_a_specified_branch.feature +17 -0
  8. data/features/core_functionality/4056359_start_with_local_branch_already_present.feature +16 -0
  9. data/features/core_functionality/4056363_finish_the_current_branch.feature +17 -0
  10. data/features/core_functionality/4056389_changes_to_pt_story_name_cause_branch_issues.feature +25 -0
  11. data/features/core_functionality/4135658_control_output_verbosity_using_v_switch.feature +32 -0
  12. data/features/core_functionality/4468313_get_config_value_for_is_producing_an_error_log.feature +43 -0
  13. data/features/extensions/4058326_display_story_description_on_start.feature +14 -0
  14. data/features/git_branching/4135501_allow_branch_start_point_to_be_specified_from_command_line.feature +27 -0
  15. data/features/hooks/4069174_investigate_the_effort_required_to_add_pre_amp_post_hooks.feature +24 -0
  16. data/features/hooks/4199841_need_hooks_for_my_workflow.feature +51 -0
  17. data/features/hooks/4199845_need_hooks_for_sanger_workflow.feature +66 -0
  18. data/features/hooks/4205913_output_from_rake_tests_should_be_seen.feature +19 -0
  19. data/features/hooks/4424828_git_start_is_missing_pt_integration_for_my_hooks.feature +15 -0
  20. data/features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature +15 -0
  21. data/features/release/4132264_fix_the_libxml_warnings_from_nokogiri.feature +20 -0
  22. data/features/step_definitions/aruba_extensions.rb +4 -0
  23. data/features/step_definitions/configuration_steps.rb +38 -0
  24. data/features/step_definitions/git_steps.rb +102 -0
  25. data/features/step_definitions/misc_steps.rb +3 -0
  26. data/features/step_definitions/pivotal_tracker_steps.rb +43 -0
  27. data/features/step_definitions/project_code_steps.rb +35 -0
  28. data/features/support/aruba.rb +1 -0
  29. data/features/support/hooks/configuration.rb +4 -0
  30. data/features/support/hooks/environment.rb +49 -0
  31. data/features/support/hooks/pivotal_tracker.rb +170 -0
  32. data/lib/git_workflow.rb +5 -0
  33. data/lib/git_workflow/callbacks.rb +16 -0
  34. data/lib/git_workflow/callbacks/pivotal_tracker_support.rb +64 -0
  35. data/lib/git_workflow/callbacks/remote_git_branch_support.rb +9 -0
  36. data/lib/git_workflow/callbacks/styles/debug.rb +58 -0
  37. data/lib/git_workflow/callbacks/styles/default.rb +43 -0
  38. data/lib/git_workflow/callbacks/styles/mine.rb +52 -0
  39. data/lib/git_workflow/callbacks/styles/sanger.rb +48 -0
  40. data/lib/git_workflow/callbacks/test_code_support.rb +29 -0
  41. data/lib/git_workflow/command_line.rb +46 -0
  42. data/lib/git_workflow/commands.rb +4 -0
  43. data/lib/git_workflow/commands/base.rb +31 -0
  44. data/lib/git_workflow/commands/finish.rb +36 -0
  45. data/lib/git_workflow/commands/setup.rb +157 -0
  46. data/lib/git_workflow/commands/start.rb +30 -0
  47. data/lib/git_workflow/configuration.rb +93 -0
  48. data/lib/git_workflow/core_ext.rb +106 -0
  49. data/lib/git_workflow/git.rb +143 -0
  50. data/lib/git_workflow/logger.yaml +27 -0
  51. data/lib/git_workflow/logging.rb +77 -0
  52. data/lib/git_workflow/story.rb +96 -0
  53. data/spec/core_functionality/4056539_support_http_proxy_spec.rb +68 -0
  54. data/spec/core_functionality/4058365_bad_request_on_story_update_spec.rb +13 -0
  55. data/spec/core_functionality/4058394_make_commands_more_verbose_spec.rb +41 -0
  56. data/spec/core_functionality/4058719_error_if_none_of_the_required_configuration_values_are_set_spec.rb +21 -0
  57. data/spec/core_functionality/4058861_git_config_returns_empty_strings_which_should_be_nil_spec.rb +59 -0
  58. data/spec/core_functionality/4172431_add_git_workflow_setup_command_to_make_setup_easier_spec.rb +148 -0
  59. data/spec/core_functionality/4199841_need_callbacks_for_my_workflow_spec.rb +97 -0
  60. data/spec/extensions/4199841_need_callbacks_for_my_workflow_spec.rb +167 -0
  61. data/spec/extensions/4205913_output_from_rake_tests_should_be_seen_spec.rb +34 -0
  62. data/spec/git_branching/4058723_branches_end_in_if_the_last_character_is_invalid_spec.rb +40 -0
  63. data/spec/git_branching/4059824_code_is_not_using_workflow_localbranchconvention_to_decode_branch_names_on_finish_spec.rb +115 -0
  64. data/spec/git_branching/4216087_support_pushing_branches_spec.rb +44 -0
  65. data/spec/hooks/4199845_need_hooks_for_sanger_workflow_spec.rb +27 -0
  66. data/spec/pivotal_tracker_api/4056381_pt_api_token_not_being_passed_in_headers_spec.rb +40 -0
  67. data/spec/pivotal_tracker_api/4056638_library_code_using_localhost_spec.rb +16 -0
  68. data/spec/pivotal_tracker_api/4056661_content_type_header_should_be_text_xml_on_updates_spec.rb +16 -0
  69. data/spec/pivotal_tracker_api/4058718_if_pt_username_is_not_set_use_user_name_spec.rb +26 -0
  70. data/spec/pivotal_tracker_api/4146016_decode_the_xml_encoded_text_of_description_and_name_spec.rb +63 -0
  71. data/spec/shared_examples/configuration.rb +10 -0
  72. data/spec/shared_examples/story.rb +17 -0
  73. data/spec/spec_helper.rb +18 -0
  74. metadata +220 -0
@@ -0,0 +1,157 @@
1
+ require 'highline'
2
+
3
+ class HighLine
4
+ [ :ask, :choose ].each do |name|
5
+ eval <<-END_OF_METHOD_ALIASING
6
+ def #{ name }_with_newline(*args, &block)
7
+ #{ name }_without_newline(*args, &block)
8
+ ensure
9
+ $stdout.puts
10
+ end
11
+ alias_method_chain(:#{ name }, :newline)
12
+ END_OF_METHOD_ALIASING
13
+ end
14
+ end
15
+
16
+ class HighLine::Menu
17
+ def menu_option(menu_text, result)
18
+ choice(menu_text) { result }
19
+ end
20
+ end
21
+
22
+ module GitWorkflow
23
+ module Commands
24
+ class Setup < Base
25
+ def execute
26
+ [ :name, :token, :project_id, :workflow, :branches ].each do |step|
27
+ send(:"enquire_about_#{ step }")
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ ALIGNMENT_PADDING = ' ' * 10
34
+
35
+ def usage_info(options)
36
+ options.banner = 'Usage: git workflow-setup'
37
+ end
38
+
39
+ def highline
40
+ @highline ||= HighLine.new
41
+ end
42
+
43
+ delegate :get_config_value_for, :to => 'GitWorkflow::Configuration.instance'
44
+ delegate :set_config_value, :to => 'GitWorkflow::Configuration.instance'
45
+
46
+ def set_value_through(method, setting, *args, &block)
47
+ options = args.last.is_a?(Hash) ? args.pop : {}
48
+ optional = options.delete(:optional)
49
+
50
+ # Only update the value if it is set or is optional and blank.
51
+ default = get_config_value_for(setting)
52
+ value = highline.send(method, *args) do |question|
53
+ question.default = default
54
+ block.call(question)
55
+ end
56
+ set_config_value(setting, value) unless optional and value.blank?
57
+ end
58
+
59
+ def ask(setting, options = {}, &block)
60
+ set_value_through(:ask, setting, 'Dummy', options, &block)
61
+ end
62
+
63
+ def choose(setting, options = {}, &block)
64
+ set_value_through(:choose, setting, options, &block)
65
+ end
66
+
67
+ def enquire_about_name
68
+ username = get_config_value_for('user.name')
69
+ ask('pt.username', :optional => true) do |question|
70
+ text = %Q{
71
+ When marking Pivotal Tracker (http://pivotaltracker.com/) stories as started or finished,
72
+ this git workflow needs to know your name. You have to enter it as it appears on
73
+ Pivotal Tracker and it is your name, not your email address. For instance, I appear
74
+ as 'Matthew Denner'.
75
+ }
76
+ text << %Q{
77
+ If the name you have on Pivotal Tracker is the same as the current git 'user.name' then
78
+ you can leave this blank. Your git 'user.name' is currently: #{ username }
79
+ } unless username.blank?
80
+ text << %Q{
81
+ Please enter your Pivotal Tracker name:
82
+ }
83
+
84
+ question.question = text.align(ALIGNMENT_PADDING)
85
+ question.default = ''
86
+ end
87
+ end
88
+
89
+ def enquire_about_token
90
+ ask('pt.token') do |question|
91
+ question.validate = /^[A-Fa-f0-9]{32}$/
92
+ question.question = %Q{
93
+ When the workflow starts or finishes a story it needs to be able to interact with Pivotal
94
+ Tracker (http://pivotaltracker.com/) and to do so you must have an account. Any updates
95
+ this git workflow makes to Pivotal Tracker need to be done through the API and so it needs
96
+ to know your individual API token. This can be found under 'API Token' on your profile
97
+ page (https://www.pivotaltracker.com/profile).
98
+
99
+ Please enter your Pivotal Tracker API token:
100
+ }.align(ALIGNMENT_PADDING)
101
+ end
102
+ end
103
+
104
+ def enquire_about_project_id
105
+ ask('pt.projectid') do |question|
106
+ question.answer_type = Integer
107
+ question.question = %Q{
108
+ To interact with Pivotal Tracker (http://pivotaltracker.com/) this git workflow needs to
109
+ know the ID for your project. This can be found in the URL you get when looking at your
110
+ project and is the numeric valid after 'http://www.pivotaltracker.com/projects'.
111
+
112
+ Please enter your Pivotal Tracker project ID:
113
+ }.align(ALIGNMENT_PADDING)
114
+ end
115
+ end
116
+
117
+ def enquire_about_workflow
118
+ ask('workflow.callbacks') do |question|
119
+ question.default = 'default'
120
+ question.question = %Q{
121
+ The standard behaviour of this git workflow is to create a new branch and mark a
122
+ Pivotal Tracker (http://pivotaltracker.com/) story as started when you run 'git start',
123
+ and to mark a Pivotal Tracker story as finished when you run 'git finish'. You can alter
124
+ this behaviour by choosing a different workflow.
125
+
126
+ Please enter the name of your workflow:
127
+ }.align(ALIGNMENT_PADDING)
128
+ end
129
+ end
130
+
131
+ def enquire_about_branches
132
+ choose_branch_convention(:local)
133
+ choose_branch_convention(:remote)
134
+ end
135
+
136
+ def choose_branch_convention(location)
137
+ choose("workflow.#{ location }branchconvention") do |convention|
138
+ convention.header = %Q{
139
+ Git branches are created locally and can be pushed remotely. The naming convention for these
140
+ can be different and can be setup to your personal preference. It's easier to explain their
141
+ format with an example:
142
+
143
+ Imagine you have story 12345 which has a title of 'Do something useful'. If you choose to have
144
+ a branch convention of "PT story number, then PT story title" the branch created will be
145
+ '12345_do_something_useful'. If you select "PT story title, then PT story number" you will
146
+ get the branch 'do_something_useful_12345'.
147
+ }.align(ALIGNMENT_PADDING)
148
+
149
+ convention.menu_option('PT story number, then PT story title', '${number}_${name}')
150
+ convention.menu_option('PT story title, then PT story number', '${name}_${number}')
151
+
152
+ convention.prompt = "Choose your #{ location } branch naming convention:"
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,30 @@
1
+ require 'git_workflow/commands/base'
2
+
3
+ module GitWorkflow
4
+ module Commands
5
+ class Start < Base
6
+ def initialize(command_line_arguments)
7
+ super(command_line_arguments) do |remaining_arguments|
8
+ @story_id, @parent_branch = remaining_arguments
9
+ raise InvalidCommandLine, 'The command line is invalid' if @story_id.nil?
10
+ end
11
+ end
12
+
13
+ def execute
14
+ story(@story_id) do |story|
15
+ start(story, @parent_branch)
16
+ start_story_on_pivotal_tracker!(story)
17
+
18
+ $stdout.puts "Story #{ story.story_id }: #{ story.name }"
19
+ $stdout.puts story.description
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def usage_info(options)
26
+ options.banner = 'Usage: git start <PT story number> [<parent branch>]'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,93 @@
1
+ require 'singleton'
2
+ require 'git_workflow/git'
3
+
4
+ module GitWorkflow
5
+ class Configuration
6
+ include Singleton
7
+ include GitWorkflow::Git
8
+
9
+ class Convention
10
+ include GitWorkflow::Git
11
+
12
+ REGEXP_STORY_ID = /\$\{\s*number\s*\}/
13
+ REGEXP_EVALUATION = /\$\{(number|name)\}/
14
+
15
+ def initialize(convention)
16
+ raise StandardError, "Convention '#{ convention }' has no story ID" unless convention =~ REGEXP_STORY_ID
17
+ @to_convention = convention.gsub(REGEXP_EVALUATION, '#{\1}')
18
+ @from_convention = Regexp.new(convention.sub(REGEXP_STORY_ID, '(\d+)').gsub(REGEXP_EVALUATION, '.+'))
19
+ end
20
+
21
+ def to(story)
22
+ use_existing_branch_for(story) || generate_new_branch_name_for(story)
23
+ end
24
+
25
+ def from(branch)
26
+ match = branch.match(@from_convention)
27
+ raise StandardError, "Branch '#{ branch }' does not appear to conform to local convention" if match.nil?
28
+ match[ 1 ].to_i
29
+ end
30
+
31
+ private
32
+
33
+ def use_existing_branch_for(story)
34
+ repository.branches.detect do |branch|
35
+ begin
36
+ from(branch) == story.story_id
37
+ rescue StandardError => exception
38
+ # Ignore, as this is definitely not the branch!
39
+ false
40
+ end
41
+ end
42
+ end
43
+
44
+ def generate_new_branch_name_for(story)
45
+ StoryWrapper.new(story).__instance_eval__(%Q{"#{ @to_convention }"}).downcase.gsub(/[^a-z0-9]+/, '_').sub(/_+$/, '')
46
+ end
47
+
48
+ class StoryWrapper
49
+ alias_method :__instance_eval__, :instance_eval
50
+ instance_methods.each do |method|
51
+ undef_method(method) unless method.to_s =~ /^__(send|id|instance_eval)__$/
52
+ end
53
+
54
+ def initialize(story)
55
+ @story = story
56
+ end
57
+
58
+ def number
59
+ @story.story_id
60
+ end
61
+
62
+ def name
63
+ @story.name
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ def username
70
+ @username ||= get_config_value_for('pt.username') || get_config_value_for!('user.name')
71
+ end
72
+
73
+ def local_branch_convention
74
+ @local_branch_convention ||= Convention.new(get_config_value_for!('workflow.localbranchconvention'))
75
+ end
76
+
77
+ def remote_branch_convention
78
+ @remote_branch_convention ||= Convention.new(get_config_value_for!('workflow.remotebranchconvention'))
79
+ end
80
+
81
+ def project_id
82
+ @project_id ||= get_config_value_for!('pt.projectid')
83
+ end
84
+
85
+ def api_token
86
+ @api_token ||= get_config_value_for!('pt.token')
87
+ end
88
+
89
+ def ignore_git_global?
90
+ ENV['IGNORE_GIT_GLOBAL']
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,106 @@
1
+ class Class
2
+ def delegates_to_class(method)
3
+ class_eval do
4
+ linenumber = __LINE__ + 1
5
+ eval(%Q{
6
+ def #{ method }(*args, &block)
7
+ self.class.#{ method }(*args, &block)
8
+ end
9
+ }, binding, __FILE__, linenumber)
10
+ end
11
+ end
12
+ end
13
+
14
+ class NilClass
15
+ def blank?
16
+ true
17
+ end
18
+ end
19
+
20
+ class String
21
+ def blank?
22
+ self =~ /^\s*$/
23
+ end
24
+
25
+ def underscore
26
+ self.split('::').map { |v| v.gsub(/(.)([A-Z])/, '\1_\2') }.join('/').downcase
27
+ end
28
+
29
+ def camelize
30
+ self.split('/').map { |v| v.gsub(/(?:^|_)(.)/) { $1.upcase } }.join('::')
31
+ end
32
+
33
+ def constantize
34
+ self.camelize.split('::').inject(Object) do |current,constant|
35
+ current.const_get(constant) or current.const_missing(constant)
36
+ end
37
+ end
38
+
39
+ def align(padding = '')
40
+ self.gsub(/^\s+([^\s])/, "#{ padding }\\1")
41
+ end
42
+ end
43
+
44
+ require 'popen4'
45
+
46
+ module Execution
47
+ class CommandFailure < StandardError
48
+ attr_reader :result
49
+
50
+ def initialize(command, result)
51
+ super("Command '#{ command }' failed")
52
+ @result = result
53
+ end
54
+
55
+ def raise_if_required!
56
+ raise self unless @result.exitstatus == 0
57
+ end
58
+ end
59
+
60
+ def execute_command(command)
61
+ command_output = nil
62
+ execute_command_with_output_handling(command) { |stdout, _, _| command_output = stdout.read }
63
+ command_output
64
+ end
65
+
66
+ def execute_command_with_output_handling(command, &block)
67
+ debug("Executing '#{ command }' ...") do
68
+ exit_status = POpen4.popen4(command) do |stdout, stderr, stdin, pid|
69
+ yield(stdout, stderr, stdin)
70
+ end
71
+ CommandFailure.new(command, exit_status).raise_if_required!
72
+ end
73
+ end
74
+ end
75
+
76
+ class Module
77
+ def chain_methods(method, chain, &block)
78
+ match = method.to_s.match(/^([^!\?]+)([!\?])?$/) or raise StandardError, "Cannot match '#{ method }' as a method"
79
+ method_core, extension = match[ 1 ], match[ 2 ]
80
+ with_chain_method = :"#{ method_core }_with_#{ chain }#{ extension }"
81
+ without_chain_method = :"#{ method_core }_without_#{ chain }#{ extension }"
82
+ yield(with_chain_method, without_chain_method)
83
+ end
84
+
85
+ def alias_method_chain(method, chain)
86
+ chain_methods(method, chain) do |with_chain_method, without_chain_method|
87
+ alias_method(without_chain_method, method)
88
+ alias_method(method, with_chain_method)
89
+ end
90
+ end
91
+
92
+ def unalias_method_chain(method, chain)
93
+ chain_methods(method, chain) do |with_chain_method, without_chain_method|
94
+ alias_method(method, without_chain_method)
95
+ undef_method(without_chain_method)
96
+ end
97
+ end
98
+
99
+ def delegate(name, options)
100
+ class_eval <<-END_OF_DELEGATION
101
+ def #{ name }(*args, &block)
102
+ #{ options[ :to ] }.#{ name }(*args, &block)
103
+ end
104
+ END_OF_DELEGATION
105
+ end
106
+ end
@@ -0,0 +1,143 @@
1
+ require 'singleton'
2
+ require 'git_workflow/core_ext'
3
+
4
+ module GitWorkflow
5
+ module Git
6
+ class Repository
7
+ include Singleton
8
+ include Execution
9
+ include GitWorkflow::Logging
10
+
11
+ RepositoryError = Class.new(StandardError)
12
+ BranchError = Class.new(RepositoryError)
13
+ CheckoutError = Class.new(RepositoryError)
14
+ ConfigError = Class.new(RepositoryError)
15
+
16
+ def current_branch
17
+ return @current_branch unless @current_branch.nil?
18
+
19
+ match = execute_command('git branch').match(/\*\s+([^\s]+)/)
20
+ raise BranchError, 'Could not determine current branch' if match.nil?
21
+ @current_branch = match[ 1 ]
22
+ end
23
+
24
+ def checkout(branch)
25
+ maintain_current_branch(branch) do
26
+ begin
27
+ info("Checking out branch '#{ branch }'") do
28
+ execute_command("git checkout #{ branch }")
29
+ end
30
+ rescue Execution::CommandFailure => exception
31
+ raise CheckoutError, "Unable to checkout branch '#{ branch }'"
32
+ end
33
+ end
34
+ end
35
+
36
+ def create(branch, source = nil)
37
+ maintain_current_branch(branch) do
38
+ begin
39
+ info("Creating branch '#{ branch }'") do
40
+ command = "git checkout -b #{ branch }"
41
+ command << " #{ source }" unless source.nil?
42
+ execute_command(command)
43
+ end
44
+ rescue Execution::CommandFailure => exception
45
+ raise CheckoutError, "Unable to create branch '#{ branch }'"
46
+ end
47
+ end
48
+ end
49
+
50
+ def merge(branch)
51
+ execute_command("git merge #{ branch }")
52
+ rescue Execution::CommandFailure => exception
53
+ raise BranchError, "Could not merge #{ branch } into current branch"
54
+ end
55
+
56
+ def branches
57
+ execute_command('git branch').split("\n").map { |b| b.sub(/^(\*)?\s+/, '') }
58
+ end
59
+
60
+ def does_branch_exist?(branch)
61
+ execute_command('git branch') =~ /\s+#{ branch }(\s+|$)/
62
+ end
63
+
64
+ def config_get(key)
65
+ command = [ 'git config' ]
66
+ command << '--file .git/config' if GitWorkflow::Configuration.instance.ignore_git_global?
67
+ command << key
68
+ value = execute_command(command.join(' ')).strip
69
+ value.empty? ? nil : value
70
+ rescue Execution::CommandFailure => exception
71
+ raise ConfigError, "Could not retrieve '#{ key }' configuration setting"
72
+ end
73
+
74
+ def config_set(key, value)
75
+ raise ConfigError, 'No key has been specified for configuration setting' if key.blank?
76
+ execute_command(%Q{git config '#{ key }' '#{ value }'})
77
+ rescue Execution::CommandFailure => exception
78
+ raise ConfigError, "Could not set '#{ key }' configuration setting (value: #{ value })"
79
+ end
80
+
81
+ def push(branch)
82
+ execute_command("git push origin #{ branch }")
83
+ rescue Execution::CommandFailure => exception
84
+ raise BranchError, "Unable to push branch '#{ branch }'"
85
+ end
86
+
87
+ private
88
+
89
+ def maintain_current_branch(branch, &block)
90
+ yield
91
+ @current_branch = branch
92
+ end
93
+ end
94
+
95
+ def repository
96
+ Repository.instance
97
+ end
98
+
99
+ def in_git_branch(target_branch, &block)
100
+ current_branch = repository.current_branch
101
+ if current_branch == target_branch
102
+ yield
103
+ else
104
+ info("Temporarily switching to branch '#{ target_branch }'")
105
+
106
+ repository.checkout(target_branch)
107
+ yield
108
+ repository.checkout(current_branch)
109
+
110
+ info("Switched back to '#{ current_branch }'")
111
+ end
112
+ end
113
+
114
+ def merge_branch(source_branch, target_branch)
115
+ info("Merging branch '#{ source_branch }' into '#{ target_branch }'") do
116
+ repository.checkout(target_branch)
117
+ repository.merge(source_branch)
118
+ end
119
+ end
120
+
121
+ def checkout_or_create_branch(branch, source = nil)
122
+ if repository.does_branch_exist?(branch)
123
+ repository.checkout(branch)
124
+ else
125
+ repository.create(branch, source)
126
+ end
127
+ end
128
+
129
+ def get_config_value_for(key, default = nil)
130
+ repository.config_get(key)
131
+ rescue Repository::ConfigError => exception
132
+ default
133
+ end
134
+
135
+ def get_config_value_for!(key)
136
+ get_config_value_for(key) or raise StandardError, "Required configuration setting '#{ key }' is unset"
137
+ end
138
+
139
+ def set_config_value(key, value)
140
+ repository.config_set(key, value)
141
+ end
142
+ end
143
+ end