git_reflow 0.8.10 → 0.9.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -1
- data/Appraisals +1 -6
- data/CHANGELOG.md +426 -348
- data/Gemfile.lock +15 -19
- data/LICENSE +20 -20
- data/README.md +27 -7
- data/Rakefile +8 -8
- data/bin/console +7 -7
- data/bin/setup +6 -6
- data/circle.yml +5 -5
- data/exe/git-reflow +9 -30
- data/git_reflow.gemspec +1 -2
- data/lib/git_reflow/config.rb +22 -13
- data/lib/git_reflow/git_helpers.rb +69 -22
- data/lib/git_reflow/git_server/base.rb +68 -68
- data/lib/git_reflow/git_server/git_hub/pull_request.rb +15 -13
- data/lib/git_reflow/git_server/pull_request.rb +4 -2
- data/lib/git_reflow/merge_error.rb +9 -9
- data/lib/git_reflow/rspec/command_line_helpers.rb +9 -1
- data/lib/git_reflow/rspec/stub_helpers.rb +13 -13
- data/lib/git_reflow/rspec/workflow_helpers.rb +18 -0
- data/lib/git_reflow/rspec.rb +1 -0
- data/lib/git_reflow/sandbox.rb +1 -0
- data/lib/git_reflow/version.rb +1 -1
- data/lib/git_reflow/workflow.rb +277 -9
- data/lib/git_reflow/workflows/FlatMergeWorkflow +38 -0
- data/lib/git_reflow/workflows/core.rb +208 -79
- data/lib/git_reflow.rb +3 -14
- data/spec/fixtures/awesome_workflow.rb +2 -6
- data/spec/fixtures/git/git_config +7 -7
- data/spec/fixtures/issues/comment.json.erb +27 -27
- data/spec/fixtures/issues/comments.json +29 -29
- data/spec/fixtures/issues/comments.json.erb +15 -15
- data/spec/fixtures/pull_requests/comment.json.erb +45 -45
- data/spec/fixtures/pull_requests/comments.json +47 -47
- data/spec/fixtures/pull_requests/comments.json.erb +15 -15
- data/spec/fixtures/pull_requests/commits.json +29 -29
- data/spec/fixtures/pull_requests/external_pull_request.json +145 -145
- data/spec/fixtures/pull_requests/pull_request.json +142 -142
- data/spec/fixtures/pull_requests/pull_request.json.erb +142 -142
- data/spec/fixtures/pull_requests/pull_request_branch_nonexistent_error.json +32 -0
- data/spec/fixtures/pull_requests/pull_request_exists_error.json +32 -32
- data/spec/fixtures/pull_requests/pull_requests.json +136 -136
- data/spec/fixtures/repositories/commit.json +53 -53
- data/spec/fixtures/repositories/commit.json.erb +53 -53
- data/spec/fixtures/repositories/commits.json.erb +13 -13
- data/spec/fixtures/repositories/statuses.json +31 -31
- data/spec/lib/git_reflow/git_helpers_spec.rb +115 -12
- data/spec/lib/git_reflow/git_server/git_hub/pull_request_spec.rb +6 -6
- data/spec/lib/git_reflow/git_server/pull_request_spec.rb +9 -3
- data/spec/lib/git_reflow/workflow_spec.rb +190 -11
- data/spec/lib/git_reflow/workflows/core_spec.rb +224 -65
- data/spec/lib/git_reflow/workflows/flat_merge_spec.rb +17 -6
- data/spec/lib/git_reflow_spec.rb +2 -25
- data/spec/spec_helper.rb +3 -0
- data/spec/support/github_helpers.rb +1 -1
- data/spec/support/mock_pull_request.rb +17 -17
- data/spec/support/web_mocks.rb +39 -39
- metadata +9 -28
- data/lib/git_reflow/commands/deliver.rb +0 -10
- data/lib/git_reflow/commands/refresh.rb +0 -20
- data/lib/git_reflow/commands/review.rb +0 -13
- data/lib/git_reflow/commands/setup.rb +0 -11
- data/lib/git_reflow/commands/stage.rb +0 -9
- data/lib/git_reflow/commands/start.rb +0 -18
- data/lib/git_reflow/commands/status.rb +0 -7
- data/lib/git_reflow/workflows/flat_merge.rb +0 -10
- data/spec/fixtures/workflow_with_super.rb +0 -8
@@ -1,68 +1,68 @@
|
|
1
|
-
require 'git_reflow/config'
|
2
|
-
|
3
|
-
module GitReflow
|
4
|
-
class GitServer::Base
|
5
|
-
extend GitHelpers
|
6
|
-
|
7
|
-
@@connection = nil
|
8
|
-
|
9
|
-
def initialize(options)
|
10
|
-
site_url = self.class.site_url
|
11
|
-
api_endpoint = self.class.api_endpoint
|
12
|
-
|
13
|
-
self.class.site_url = site_url
|
14
|
-
self.class.api_endpoint = api_endpoint
|
15
|
-
|
16
|
-
authenticate
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.connection
|
20
|
-
raise "#{self.class.to_s}.connection method must be implemented"
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.user
|
24
|
-
raise "#{self.class.to_s}.user method must be implemented"
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.api_endpoint
|
28
|
-
raise "#{self.class.to_s}.api_endpoint method must be implemented"
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.api_endpoint=(api_endpoint, options = {local: false})
|
32
|
-
raise "#{self.class.to_s}.api_endpoint= method must be implemented"
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.site_url
|
36
|
-
raise "#{self.class.to_s}.site_url method must be implemented"
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.site_url=(site_url, options = {local: false})
|
40
|
-
raise "#{self.class.to_s}.site_url= method must be implemented"
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.project_only?
|
44
|
-
GitReflow::Config.get("reflow.local-projects", all: true).include? "#{remote_user}/#{remote_repo_name}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def connection
|
48
|
-
@connection ||= self.class.connection
|
49
|
-
end
|
50
|
-
|
51
|
-
def authenticate
|
52
|
-
raise "#{self.class.to_s}#authenticate method must be implemented"
|
53
|
-
end
|
54
|
-
|
55
|
-
def find_open_pull_request(options)
|
56
|
-
raise "#{self.class.to_s}#find_open_pull_request(options) method must be implemented"
|
57
|
-
end
|
58
|
-
|
59
|
-
def get_build_status sha
|
60
|
-
raise "#{self.class.to_s}#get_build_status(sha) method must be implemented"
|
61
|
-
end
|
62
|
-
|
63
|
-
def colorized_build_description status
|
64
|
-
raise "#{self.class.to_s}#colorized_build_description(status) method must be implemented"
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
1
|
+
require 'git_reflow/config'
|
2
|
+
|
3
|
+
module GitReflow
|
4
|
+
class GitServer::Base
|
5
|
+
extend GitHelpers
|
6
|
+
|
7
|
+
@@connection = nil
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
site_url = self.class.site_url
|
11
|
+
api_endpoint = self.class.api_endpoint
|
12
|
+
|
13
|
+
self.class.site_url = site_url
|
14
|
+
self.class.api_endpoint = api_endpoint
|
15
|
+
|
16
|
+
authenticate
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.connection
|
20
|
+
raise "#{self.class.to_s}.connection method must be implemented"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.user
|
24
|
+
raise "#{self.class.to_s}.user method must be implemented"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.api_endpoint
|
28
|
+
raise "#{self.class.to_s}.api_endpoint method must be implemented"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.api_endpoint=(api_endpoint, options = {local: false})
|
32
|
+
raise "#{self.class.to_s}.api_endpoint= method must be implemented"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.site_url
|
36
|
+
raise "#{self.class.to_s}.site_url method must be implemented"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.site_url=(site_url, options = {local: false})
|
40
|
+
raise "#{self.class.to_s}.site_url= method must be implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.project_only?
|
44
|
+
GitReflow::Config.get("reflow.local-projects", all: true).include? "#{remote_user}/#{remote_repo_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def connection
|
48
|
+
@connection ||= self.class.connection
|
49
|
+
end
|
50
|
+
|
51
|
+
def authenticate
|
52
|
+
raise "#{self.class.to_s}#authenticate method must be implemented"
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_open_pull_request(options)
|
56
|
+
raise "#{self.class.to_s}#find_open_pull_request(options) method must be implemented"
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_build_status sha
|
60
|
+
raise "#{self.class.to_s}#get_build_status(sha) method must be implemented"
|
61
|
+
end
|
62
|
+
|
63
|
+
def colorized_build_description status
|
64
|
+
raise "#{self.class.to_s}#colorized_build_description(status) method must be implemented"
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -38,7 +38,7 @@ module GitReflow
|
|
38
38
|
|
39
39
|
def commit_author
|
40
40
|
begin
|
41
|
-
username,
|
41
|
+
username, _ = base.label.split(':')
|
42
42
|
first_commit = GitReflow.git_server.connection.pull_requests.commits(username, GitReflow.git_server.class.remote_repo_name, number.to_s).first
|
43
43
|
"#{first_commit.commit.author.name} <#{first_commit.commit.author.email}>".strip
|
44
44
|
rescue Github::Error::NotFound
|
@@ -47,7 +47,7 @@ module GitReflow
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def reviewers
|
50
|
-
(comment_authors + pull_request_reviews.map(&:user).map(&:login)).uniq
|
50
|
+
(comment_authors + pull_request_reviews.map(&:user).map(&:login)).uniq - [user.login]
|
51
51
|
end
|
52
52
|
|
53
53
|
def approvals
|
@@ -65,7 +65,7 @@ module GitReflow
|
|
65
65
|
comments = GitReflow.git_server.connection.issues.comments.all GitReflow.remote_user, GitReflow.remote_repo_name, number: self.number
|
66
66
|
review_comments = GitReflow.git_server.connection.pull_requests.comments.all GitReflow.remote_user, GitReflow.remote_repo_name, number: self.number
|
67
67
|
|
68
|
-
review_comments.to_a + comments.to_a
|
68
|
+
(review_comments.to_a + comments.to_a).select { |c| c.user.login != user.login }
|
69
69
|
end
|
70
70
|
|
71
71
|
def last_comment
|
@@ -80,7 +80,10 @@ module GitReflow
|
|
80
80
|
if self.class.minimum_approvals.to_i == 0
|
81
81
|
super
|
82
82
|
else
|
83
|
-
approvals.size >= self.class.minimum_approvals.to_i and
|
83
|
+
approvals.size >= self.class.minimum_approvals.to_i and (
|
84
|
+
last_comment.empty? ||
|
85
|
+
!last_comment.match(self.class.approval_regex).nil?
|
86
|
+
)
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
@@ -93,20 +96,22 @@ module GitReflow
|
|
93
96
|
if deliver?
|
94
97
|
GitReflow.say "Merging pull request ##{self.number}: '#{self.title}', from '#{self.feature_branch_name}' into '#{self.base_branch_name}'", :notice
|
95
98
|
|
99
|
+
merge_method = options[:merge_method] || GitReflow::Config.get("reflow.merge-method")
|
100
|
+
merge_method = "squash" if "#{merge_method}".length < 1
|
101
|
+
merge_message_file = GitReflow.merge_message_path(merge_method: merge_method)
|
102
|
+
|
96
103
|
unless options[:title] || options[:message]
|
97
104
|
# prompts user for commit_title and commit_message
|
98
|
-
|
99
|
-
|
100
|
-
File.open(squash_merge_message_file, 'w') do |file|
|
105
|
+
File.open(merge_message_file, 'w') do |file|
|
101
106
|
file.write("#{self.title}\n#{self.commit_message_for_merge}\n")
|
102
107
|
end
|
103
108
|
|
104
|
-
GitReflow.run("#{GitReflow.git_editor_command} #{
|
105
|
-
merge_message = File.read(
|
109
|
+
GitReflow.run("#{GitReflow.git_editor_command} #{merge_message_file}", with_system: true)
|
110
|
+
merge_message = File.read(merge_message_file).split(/[\r\n]|\r\n/).map(&:strip)
|
106
111
|
|
107
112
|
title = merge_message.shift
|
108
113
|
|
109
|
-
File.delete(
|
114
|
+
File.delete(merge_message_file)
|
110
115
|
|
111
116
|
unless merge_message.empty?
|
112
117
|
merge_message.shift if merge_message.first.empty?
|
@@ -124,9 +129,6 @@ module GitReflow
|
|
124
129
|
|
125
130
|
options[:body] = "#{options[:message]}\n" if options[:body].nil? and "#{options[:message]}".length > 0
|
126
131
|
|
127
|
-
merge_method = options[:merge_method] || GitReflow::Config.get("reflow.merge-method")
|
128
|
-
merge_method = "squash" if "#{merge_method}".length < 1
|
129
|
-
|
130
132
|
merge_response = GitReflow::GitServer::GitHub.connection.pull_requests.merge(
|
131
133
|
"#{GitReflow.git_server.class.remote_user}",
|
132
134
|
"#{GitReflow.git_server.class.remote_repo_name}",
|
@@ -158,6 +158,8 @@ module GitReflow
|
|
158
158
|
end
|
159
159
|
|
160
160
|
def commit_message_for_merge
|
161
|
+
return GitReflow.merge_commit_template unless GitReflow.merge_commit_template.nil?
|
162
|
+
|
161
163
|
message = ""
|
162
164
|
|
163
165
|
if "#{self.description}".length > 0
|
@@ -204,14 +206,14 @@ module GitReflow
|
|
204
206
|
GitReflow.run_command_with_label "git checkout #{self.base_branch_name}"
|
205
207
|
GitReflow.run_command_with_label "git pull origin #{self.base_branch_name}"
|
206
208
|
|
207
|
-
case merge_method
|
209
|
+
case merge_method.to_s
|
208
210
|
when /squash/i
|
209
211
|
GitReflow.run_command_with_label "git merge --squash #{self.feature_branch_name}"
|
210
212
|
else
|
211
213
|
GitReflow.run_command_with_label "git merge #{self.feature_branch_name}"
|
212
214
|
end
|
213
215
|
|
214
|
-
GitReflow.
|
216
|
+
GitReflow.append_to_merge_commit_message(message) if message.length > 0
|
215
217
|
|
216
218
|
if GitReflow.run_command_with_label 'git commit', with_system: true
|
217
219
|
GitReflow.say "Pull request ##{self.number} successfully merged.", :success
|
@@ -1,9 +1,9 @@
|
|
1
|
-
module GitReflow
|
2
|
-
module GitServer
|
3
|
-
class MergeError < StandardError
|
4
|
-
def initialize(msg="Merge failed")
|
5
|
-
super(msg)
|
6
|
-
end
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
1
|
+
module GitReflow
|
2
|
+
module GitServer
|
3
|
+
class MergeError < StandardError
|
4
|
+
def initialize(msg="Merge failed")
|
5
|
+
super(msg)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -2,8 +2,8 @@ require "highline"
|
|
2
2
|
|
3
3
|
module GitReflow
|
4
4
|
module RSpec
|
5
|
+
# @nodoc
|
5
6
|
module CommandLineHelpers
|
6
|
-
|
7
7
|
def stub_command_line
|
8
8
|
$commands_ran = []
|
9
9
|
$stubbed_commands = {}
|
@@ -57,6 +57,14 @@ module GitReflow
|
|
57
57
|
allow(GitReflow::Sandbox).to receive(:run).with(command).and_return(return_value)
|
58
58
|
end
|
59
59
|
|
60
|
+
def stub_command_line_inputs_for(module_to_stub, inputs)
|
61
|
+
allow(module_to_stub).to receive(:ask) do |terminal, question|
|
62
|
+
return_value = inputs[question]
|
63
|
+
question = ""
|
64
|
+
return_value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
60
68
|
def stub_command_line_inputs(inputs)
|
61
69
|
allow_any_instance_of(HighLine).to receive(:ask) do |terminal, question|
|
62
70
|
return_value = inputs[question]
|
@@ -1,13 +1,13 @@
|
|
1
|
-
module GitReflow
|
2
|
-
module RSpec
|
3
|
-
module StubHelpers
|
4
|
-
|
5
|
-
def stub_with_fallback(obj, method)
|
6
|
-
original_method = obj.method(method)
|
7
|
-
allow(obj).to receive(method).with(anything()) { |*args| original_method.call(*args) }
|
8
|
-
return allow(obj).to receive(method)
|
9
|
-
end
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
1
|
+
module GitReflow
|
2
|
+
module RSpec
|
3
|
+
module StubHelpers
|
4
|
+
|
5
|
+
def stub_with_fallback(obj, method)
|
6
|
+
original_method = obj.method(method)
|
7
|
+
allow(obj).to receive(method).with(anything()) { |*args| original_method.call(*args) }
|
8
|
+
return allow(obj).to receive(method)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GitReflow
|
2
|
+
module RSpec
|
3
|
+
# @nodoc
|
4
|
+
module WorkflowHelpers
|
5
|
+
def use_workflow(path)
|
6
|
+
allow(GitReflow::Workflows::Core).to receive(:load_workflow).and_return(
|
7
|
+
GitReflow::Workflows::Core.load_raw_workflow(File.read(path))
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def suppress_loading_of_external_workflows
|
12
|
+
allow(GitReflow::Workflows::Core).to receive(:load__workflow).with("#{GitReflow.git_root_dir}/Workflow").and_return(false)
|
13
|
+
return if GitReflow::Config.get('reflow.workflow').to_s.empty?
|
14
|
+
allow(GitReflow::Workflows::Core).to receive(:load_workflow).with(GitReflow::Config.get('reflow.workflow')).and_return(false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/git_reflow/rspec.rb
CHANGED
data/lib/git_reflow/sandbox.rb
CHANGED
data/lib/git_reflow/version.rb
CHANGED
data/lib/git_reflow/workflow.rb
CHANGED
@@ -9,20 +9,102 @@ module GitReflow
|
|
9
9
|
|
10
10
|
# @nodoc
|
11
11
|
def self.current
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
return @current unless @current.nil?
|
13
|
+
# First look for a "Workflow" file in the current directory, then check
|
14
|
+
# for a global Workflow file stored in git-reflow git config.
|
15
|
+
loaded_local_workflow = GitReflow::Workflows::Core.load_workflow "#{GitReflow.git_root_dir}/Workflow"
|
16
|
+
loaded_global_workflow = false
|
17
|
+
|
18
|
+
unless loaded_local_workflow
|
19
|
+
loaded_global_workflow = GitReflow::Workflows::Core.load_workflow GitReflow::Config.get('reflow.workflow')
|
19
20
|
end
|
21
|
+
|
22
|
+
@current = GitReflow::Workflows::Core
|
23
|
+
end
|
24
|
+
|
25
|
+
# @nodoc
|
26
|
+
# This is primarily a helper method for tests. Due to the nature of how the
|
27
|
+
# tests load many different workflows, this helps start fresh and isolate
|
28
|
+
# the scenario at hand.
|
29
|
+
def self.reset!
|
30
|
+
GitReflow.logger.debug "Resetting GitReflow workflow..."
|
31
|
+
current.commands = {}
|
32
|
+
current.callbacks = { before: {}, after: {}}
|
33
|
+
@current = nil
|
34
|
+
# We'll need to reload the core class again in order to clear previously
|
35
|
+
# eval'd content in the context of the class
|
36
|
+
load File.expand_path('../workflows/core.rb', __FILE__)
|
20
37
|
end
|
21
38
|
|
22
39
|
module ClassMethods
|
23
40
|
include GitReflow::Sandbox
|
24
41
|
include GitReflow::GitHelpers
|
25
42
|
|
43
|
+
def commands
|
44
|
+
@commands ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def commands=(command_hash)
|
48
|
+
@commands = command_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def command_docs
|
52
|
+
@command_docs ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def command_docs=(command_doc_hash)
|
56
|
+
@command_docs = command_doc_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def callbacks
|
60
|
+
@callbacks ||= {
|
61
|
+
before: {},
|
62
|
+
after: {}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def callbacks=(callback_hash)
|
67
|
+
@callbacks = callback_hash
|
68
|
+
end
|
69
|
+
|
70
|
+
# Proxy our Config class so that it's available in workflow files
|
71
|
+
def git_config
|
72
|
+
GitReflow::Config
|
73
|
+
end
|
74
|
+
|
75
|
+
def git_server
|
76
|
+
GitReflow.git_server
|
77
|
+
end
|
78
|
+
|
79
|
+
def logger
|
80
|
+
GitReflow.logger
|
81
|
+
end
|
82
|
+
|
83
|
+
# Loads a pre-defined workflow (FlatMergeWorkflow) from within another
|
84
|
+
# Workflow file
|
85
|
+
#
|
86
|
+
# @param name [String] the name of the Workflow file to use as a basis
|
87
|
+
def use(workflow_name)
|
88
|
+
if workflows.key?(workflow_name)
|
89
|
+
GitReflow.logger.debug "Using Workflow: #{workflow_name}"
|
90
|
+
GitReflow::Workflows::Core.load_workflow(workflows[workflow_name])
|
91
|
+
else
|
92
|
+
GitReflow.logger.error "Tried to use non-existent Workflow: #{workflow_name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Keeps track of available workflows when using `.use(workflow_name)`
|
97
|
+
# Workflow file
|
98
|
+
#
|
99
|
+
# @return [Hash, nil] A hash with [workflow_name, workflow_path] as key/value pairs
|
100
|
+
def workflows
|
101
|
+
return @workflows if @workflows
|
102
|
+
workflow_paths = Dir["#{File.dirname(__FILE__)}/workflows/*Workflow"]
|
103
|
+
@workflows = {}
|
104
|
+
workflow_paths.each { |p| @workflows[File.basename(p)] = p }
|
105
|
+
@workflows
|
106
|
+
end
|
107
|
+
|
26
108
|
# Creates a singleton method on the inlcuded class
|
27
109
|
#
|
28
110
|
# This method will take any number of keyword parameters. If @defaults keyword is provided, and the given
|
@@ -34,11 +116,24 @@ module GitReflow
|
|
34
116
|
#
|
35
117
|
# @yield [a:, b:, c:, ...] Invokes the block with an arbitrary number of keyword arguments
|
36
118
|
def command(name, **params, &block)
|
37
|
-
|
119
|
+
params[:flags] ||= {}
|
120
|
+
params[:switches] ||= {}
|
121
|
+
params[:arguments] ||= {}
|
122
|
+
defaults ||= params[:arguments].merge(params[:flags]).merge(params[:switches])
|
123
|
+
|
124
|
+
# Ensure flags and switches use kebab-case
|
125
|
+
kebab_case_keys!(params[:flags])
|
126
|
+
kebab_case_keys!(params[:switches])
|
127
|
+
|
128
|
+
# Register the command with the workflow so that we can properly handle
|
129
|
+
# option parsing from the command line
|
130
|
+
self.commands[name] = params
|
131
|
+
self.command_docs[name] = params
|
132
|
+
|
38
133
|
self.define_singleton_method(name) do |**args|
|
39
134
|
args_with_defaults = {}
|
40
135
|
args.each do |name, value|
|
41
|
-
if "#{value}".length <= 0
|
136
|
+
if "#{value}".length <= 0 && !defaults[name].nil?
|
42
137
|
args_with_defaults[name] = defaults[name]
|
43
138
|
else
|
44
139
|
args_with_defaults[name] = value
|
@@ -51,9 +146,182 @@ module GitReflow
|
|
51
146
|
end
|
52
147
|
end
|
53
148
|
|
149
|
+
GitReflow.logger.debug "callbacks: #{callbacks.inspect}"
|
150
|
+
Array(callbacks[:before][name]).each do |block|
|
151
|
+
GitReflow.logger.debug "(before) callback running for `#{name}` command..."
|
152
|
+
argument_overrides = block.call(**args_with_defaults) || {}
|
153
|
+
args_with_defaults.merge!(argument_overrides) if argument_overrides.is_a?(Hash)
|
154
|
+
end
|
155
|
+
|
156
|
+
GitReflow.logger.debug "Running command `#{name}` with args: #{args_with_defaults.inspect}..."
|
54
157
|
block.call(**args_with_defaults)
|
158
|
+
|
159
|
+
Array(callbacks[:after][name]).each do |block|
|
160
|
+
GitReflow.logger.debug "(after) callback running for `#{name}` command..."
|
161
|
+
block.call(**args_with_defaults)
|
162
|
+
end
|
55
163
|
end
|
56
164
|
end
|
165
|
+
|
166
|
+
# Stores a Proc to be called once the command successfully finishes
|
167
|
+
#
|
168
|
+
# Procs declared with `before` are executed sequentially in the order they are defined in a custom Workflow
|
169
|
+
# file.
|
170
|
+
#
|
171
|
+
# @param name [Symbol] the name of the method to create
|
172
|
+
#
|
173
|
+
# @yield A block to be executed before the given command. These blocks
|
174
|
+
# are executed in the context of `GitReflow::Workflows::Core`
|
175
|
+
def before(name, &block)
|
176
|
+
name = name.to_sym
|
177
|
+
if commands[name].nil?
|
178
|
+
GitReflow.logger.error "Attempted to register (before) callback for non-existing command: #{name}"
|
179
|
+
else
|
180
|
+
GitReflow.logger.debug "(before) callback registered for: #{name}"
|
181
|
+
callbacks[:before][name] ||= []
|
182
|
+
callbacks[:before][name] << block
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Stores a Proc to be called once the command successfully finishes
|
187
|
+
#
|
188
|
+
# Procs declared with `after` are executed sequentially in the order they are defined in a custom Workflow
|
189
|
+
# file.
|
190
|
+
#
|
191
|
+
# @param name [Symbol] the name of the method to create
|
192
|
+
#
|
193
|
+
# @yield A block to be executed after the given command. These blocks
|
194
|
+
# are executed in the context of `GitReflow::Workflows::Core`
|
195
|
+
def after(name, &block)
|
196
|
+
name = name.to_sym
|
197
|
+
if commands[name].nil?
|
198
|
+
GitReflow.logger.error "Attempted to register (after) callback for non-existing command: #{name}"
|
199
|
+
else
|
200
|
+
GitReflow.logger.debug "(after) callback registered for: #{name}"
|
201
|
+
callbacks[:after][name] ||= []
|
202
|
+
callbacks[:after][name] << block
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Creates a singleton method on the inlcuded class
|
207
|
+
#
|
208
|
+
# This method updates the help text associated with the provided command.
|
209
|
+
#
|
210
|
+
# @param name [Symbol] the name of the command to add/update help text for
|
211
|
+
# @param defaults [Hash] keyword arguments to provide fallbacks for
|
212
|
+
def command_help(name, summary:, arguments: {}, flags: {}, switches: {}, description: "")
|
213
|
+
command_docs[name] = {
|
214
|
+
summary: summary,
|
215
|
+
description: description,
|
216
|
+
arguments: arguments,
|
217
|
+
flags: kebab_case_keys!(flags),
|
218
|
+
switches: kebab_case_keys!(switches)
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
# Outputs documentation for the provided command
|
223
|
+
#
|
224
|
+
# @param name [Symbol] the name of the command to output help text for
|
225
|
+
def documentation_for_command(name)
|
226
|
+
name = name.to_sym
|
227
|
+
docs = command_docs[name]
|
228
|
+
if !docs.nil?
|
229
|
+
GitReflow.say "USAGE"
|
230
|
+
GitReflow.say " git-reflow #{name} [command options] #{docs[:arguments].keys.map {|arg| "[#{arg}]" }.join(' ')}"
|
231
|
+
if docs[:arguments].any?
|
232
|
+
GitReflow.say "ARGUMENTS"
|
233
|
+
docs[:arguments].each do |arg_name, arg_desc|
|
234
|
+
default_text = commands[name][:arguments][arg_name].nil? ? "" : "(default: #{commands[name][:arguments][arg_name]}) "
|
235
|
+
GitReflow.say " #{arg_name} – #{default_text}#{arg_desc}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
if docs[:flags].any? || docs[:switches].any?
|
239
|
+
cmd = commands[name.to_sym]
|
240
|
+
GitReflow.say "COMMAND OPTIONS"
|
241
|
+
docs[:flags].each do |flag_name, flag_desc|
|
242
|
+
flag_names = ["-#{flag_name.to_s[0]}", "--#{flag_name}"]
|
243
|
+
flag_default = cmd[:flags][flag_name]
|
244
|
+
|
245
|
+
GitReflow.say " #{flag_names} – #{!flag_default.nil? ? "(default: #{flag_default}) " : ""}#{flag_desc}"
|
246
|
+
end
|
247
|
+
docs[:switches].each do |switch_name, switch_desc|
|
248
|
+
switch_names = [switch_name.to_s[0], "-#{switch_name}"].map {|s| "-#{s}" }.join(', ')
|
249
|
+
switch_default = cmd[:switches][switch_name]
|
250
|
+
|
251
|
+
GitReflow.say " #{switch_names} – #{!switch_default.nil? ? "(default: #{switch_default}) " : ""}#{switch_desc}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
else
|
255
|
+
help
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Outputs documentation for git-reflow
|
260
|
+
def help
|
261
|
+
GitReflow.say "NAME"
|
262
|
+
GitReflow.say " git-reflow – Git Reflow manages your git workflow."
|
263
|
+
GitReflow.say "VERSION"
|
264
|
+
GitReflow.say " #{GitReflow::VERSION}"
|
265
|
+
GitReflow.say "USAGE"
|
266
|
+
GitReflow.say " git-reflow command [command options] [arguments...]"
|
267
|
+
GitReflow.say "COMMANDS"
|
268
|
+
command_docs.each do |command_name, command_doc|
|
269
|
+
GitReflow.say " #{command_name}\t– #{command_doc[:summary]}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Parses ARGV for the provided git-reflow command name
|
274
|
+
#
|
275
|
+
# @param name [Symbol, String] the name of the git-reflow command to parse from ARGV
|
276
|
+
def parse_command_options!(name)
|
277
|
+
name = name.to_sym
|
278
|
+
options = {}
|
279
|
+
docs = command_docs[name]
|
280
|
+
OptionParser.new do |opts|
|
281
|
+
opts.banner = "USAGE:\n git-reflow #{name} [command options] #{docs[:arguments].keys.map {|arg| "[#{arg}]" }.join(' ')}"
|
282
|
+
opts.separator ""
|
283
|
+
opts.separator "COMMAND OPTIONS:" if docs[:flags].any? || docs[:switches].any?
|
284
|
+
|
285
|
+
self.commands[name][:flags].each do |flag_name, flag_default|
|
286
|
+
opts.on("-#{flag_name[0]}", "--#{flag_name} #{flag_name.upcase}", command_docs[name][:flags][flag_name]) do |f|
|
287
|
+
options[kebab_to_underscore(flag_name)] = f || flag_default
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
self.commands[name][:switches].each do |switch_name, switch_default|
|
292
|
+
opts.on("-#{switch_name[0]}", "--[no-]#{switch_name}", command_docs[name][:switches][switch_name]) do |s|
|
293
|
+
options[kebab_to_underscore(switch_name)] = s || switch_default
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end.parse!
|
297
|
+
|
298
|
+
# Add arguments to optiosn to pass to defined commands
|
299
|
+
commands[name][:arguments].each do |arg_name, arg_default|
|
300
|
+
options[arg_name] = ARGV.shift || arg_default
|
301
|
+
end
|
302
|
+
options
|
303
|
+
rescue OptionParser::InvalidOption
|
304
|
+
documentation_for_command(name)
|
305
|
+
exit 1
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def kebab_case_keys!(hsh)
|
311
|
+
hsh.keys.each do |key_to_update|
|
312
|
+
hsh[underscore_to_kebab(key_to_update)] = hsh.delete(key_to_update) if key_to_update =~ /_/
|
313
|
+
end
|
314
|
+
|
315
|
+
hsh
|
316
|
+
end
|
317
|
+
|
318
|
+
def kebab_to_underscore(sym_or_string)
|
319
|
+
sym_or_string.to_s.gsub('-', '_').to_sym
|
320
|
+
end
|
321
|
+
|
322
|
+
def underscore_to_kebab(sym_or_string)
|
323
|
+
sym_or_string.to_s.gsub('_', '-').to_sym
|
324
|
+
end
|
57
325
|
end
|
58
326
|
end
|
59
327
|
end
|