git_reflow 0.8.9 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/multi-ruby-tests.yml +33 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +2 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +1 -6
  7. data/CHANGELOG.md +466 -348
  8. data/Gemfile.lock +99 -72
  9. data/LICENSE +20 -20
  10. data/README.md +481 -0
  11. data/Rakefile +15 -8
  12. data/Workflow +3 -0
  13. data/_config.yml +1 -0
  14. data/bin/console +7 -7
  15. data/bin/setup +6 -6
  16. data/exe/git-reflow +20 -36
  17. data/git_reflow.gemspec +26 -30
  18. data/lib/git_reflow.rb +3 -15
  19. data/lib/git_reflow/config.rb +48 -13
  20. data/lib/git_reflow/git_helpers.rb +69 -22
  21. data/lib/git_reflow/git_server.rb +63 -63
  22. data/lib/git_reflow/git_server/base.rb +68 -68
  23. data/lib/git_reflow/git_server/bit_bucket.rb +101 -101
  24. data/lib/git_reflow/git_server/bit_bucket/pull_request.rb +84 -84
  25. data/lib/git_reflow/git_server/git_hub.rb +53 -41
  26. data/lib/git_reflow/git_server/git_hub/pull_request.rb +16 -14
  27. data/lib/git_reflow/git_server/pull_request.rb +4 -2
  28. data/lib/git_reflow/merge_error.rb +9 -9
  29. data/lib/git_reflow/rspec.rb +3 -2
  30. data/lib/git_reflow/rspec/command_line_helpers.rb +23 -6
  31. data/lib/git_reflow/rspec/stub_helpers.rb +13 -13
  32. data/lib/git_reflow/rspec/workflow_helpers.rb +18 -0
  33. data/lib/git_reflow/sandbox.rb +16 -6
  34. data/lib/git_reflow/version.rb +1 -1
  35. data/lib/git_reflow/workflow.rb +304 -9
  36. data/lib/git_reflow/workflows/FlatMergeWorkflow +38 -0
  37. data/lib/git_reflow/workflows/core.rb +364 -238
  38. data/spec/fixtures/authentication_failure.json +3 -0
  39. data/spec/fixtures/awesome_workflow.rb +3 -7
  40. data/spec/fixtures/git/git_config +7 -7
  41. data/spec/fixtures/issues/comment.json.erb +27 -27
  42. data/spec/fixtures/issues/comments.json +29 -29
  43. data/spec/fixtures/issues/comments.json.erb +15 -15
  44. data/spec/fixtures/pull_requests/comment.json.erb +45 -45
  45. data/spec/fixtures/pull_requests/comments.json +47 -47
  46. data/spec/fixtures/pull_requests/comments.json.erb +15 -15
  47. data/spec/fixtures/pull_requests/commits.json +29 -29
  48. data/spec/fixtures/pull_requests/external_pull_request.json +145 -145
  49. data/spec/fixtures/pull_requests/pull_request.json +142 -142
  50. data/spec/fixtures/pull_requests/pull_request.json.erb +142 -142
  51. data/spec/fixtures/pull_requests/pull_request_branch_nonexistent_error.json +32 -0
  52. data/spec/fixtures/pull_requests/pull_request_exists_error.json +32 -32
  53. data/spec/fixtures/pull_requests/pull_requests.json +136 -136
  54. data/spec/fixtures/repositories/commit.json +53 -53
  55. data/spec/fixtures/repositories/commit.json.erb +53 -53
  56. data/spec/fixtures/repositories/commits.json.erb +13 -13
  57. data/spec/fixtures/repositories/statuses.json +31 -31
  58. data/spec/fixtures/users/user.json +32 -0
  59. data/spec/lib/git_reflow/git_helpers_spec.rb +115 -12
  60. data/spec/lib/git_reflow/git_server/bit_bucket_spec.rb +81 -81
  61. data/spec/lib/git_reflow/git_server/git_hub/pull_request_spec.rb +10 -10
  62. data/spec/lib/git_reflow/git_server/git_hub_spec.rb +77 -3
  63. data/spec/lib/git_reflow/git_server/pull_request_spec.rb +9 -3
  64. data/spec/lib/git_reflow/git_server_spec.rb +101 -101
  65. data/spec/lib/git_reflow/sandbox_spec.rb +1 -1
  66. data/spec/lib/git_reflow/workflow_spec.rb +304 -59
  67. data/spec/lib/git_reflow/workflows/core_spec.rb +225 -67
  68. data/spec/lib/git_reflow/workflows/flat_merge_spec.rb +71 -59
  69. data/spec/lib/git_reflow_spec.rb +2 -25
  70. data/spec/spec_helper.rb +3 -0
  71. data/spec/support/fixtures.rb +54 -54
  72. data/spec/support/github_helpers.rb +99 -109
  73. data/spec/support/mock_pull_request.rb +17 -17
  74. data/spec/support/web_mocks.rb +39 -39
  75. metadata +51 -74
  76. data/README.rdoc +0 -461
  77. data/circle.yml +0 -26
  78. data/lib/git_reflow/commands/deliver.rb +0 -10
  79. data/lib/git_reflow/commands/refresh.rb +0 -20
  80. data/lib/git_reflow/commands/review.rb +0 -13
  81. data/lib/git_reflow/commands/setup.rb +0 -11
  82. data/lib/git_reflow/commands/stage.rb +0 -9
  83. data/lib/git_reflow/commands/start.rb +0 -18
  84. data/lib/git_reflow/commands/status.rb +0 -7
  85. data/lib/git_reflow/os_detector.rb +0 -23
  86. data/lib/git_reflow/workflows/flat_merge.rb +0 -10
  87. data/spec/fixtures/workflow_with_super.rb +0 -8
@@ -1,238 +1,364 @@
1
- $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../..')
2
- require 'git_reflow/workflow'
3
-
4
- module GitReflow
5
- module Workflows
6
- # This class contains the core workflow for git-reflow. Going forward, this
7
- # will act as the base class for customizing and extending git-reflow.
8
- class Core
9
- include GitReflow::Workflow
10
-
11
- # Sets up the required git configurations that git-reflow depends on.
12
- #
13
- # @param local [Boolean] whether to configure git-reflow specific to the current project
14
- # @param enterprise [Boolean] whether to configure git-reflow for use with Github Enterprise
15
- command(:setup, defaults: {local: false, enterprise: false}) do |**params|
16
- reflow_options = { project_only: params[:local], enterprise: params[:enterprise] }
17
- existing_git_include_paths = GitReflow::Config.get('include.path', all: true).split("\n")
18
-
19
- unless File.exist?(GitReflow::Config::CONFIG_FILE_PATH) or existing_git_include_paths.include?(GitReflow::Config::CONFIG_FILE_PATH)
20
- GitReflow.say "We'll walk you through setting up git-reflow's defaults for all your projects.", :notice
21
- GitReflow.say "In the future, you can run \`git-reflow setup\` from the root of any project you want to setup differently.", :notice
22
- GitReflow.say "To adjust these settings globally, you can run \`git-reflow setup --global\`.", :notice
23
- GitReflow.run "touch #{GitReflow::Config::CONFIG_FILE_PATH}"
24
- GitReflow.say "Created #{GitReflow::Config::CONFIG_FILE_PATH} for git-reflow specific configurations.", :notice
25
- GitReflow::Config.add "include.path", GitReflow::Config::CONFIG_FILE_PATH, global: true
26
- GitReflow.say "Added #{GitReflow::Config::CONFIG_FILE_PATH} to include.path in $HOME/.gitconfig.", :notice
27
- end
28
-
29
- choose do |menu|
30
- menu.header = "Available remote Git Server services"
31
- menu.prompt = "Which service would you like to use for this project? "
32
-
33
- menu.choice('GitHub') { GitReflow::GitServer.connect reflow_options.merge({ provider: 'GitHub', silent: false }) }
34
- menu.choice('BitBucket (team-owned repos only)') { GitReflow::GitServer.connect reflow_options.merge({ provider: 'BitBucket', silent: false }) }
35
- end
36
-
37
- GitReflow::Config.set "constants.minimumApprovals", ask("Set the minimum number of approvals (leaving blank will require approval from all commenters): "), local: reflow_options[:project_only]
38
- GitReflow::Config.set "constants.approvalRegex", GitReflow::GitServer::PullRequest::DEFAULT_APPROVAL_REGEX, local: reflow_options[:project_only]
39
-
40
- if GitReflow::Config.get('core.editor').length <= 0
41
- GitReflow::Config.set('core.editor', GitReflow.default_editor, local: reflow_options[:project_only])
42
- GitReflow.say "Updated git's editor (via git config key 'core.editor') to: #{GitReflow.default_editor}.", :notice
43
- end
44
- end
45
-
46
- # Start a new feature branch
47
- #
48
- # @param feature_branch [String] the name of the branch to create your feature on
49
- # @option base [String] the name of the base branch you want to checkout your feature from
50
- command(:start, defaults: {base: 'master'}) do |**params|
51
- base_branch = params[:base]
52
- feature_branch = params[:feature_branch]
53
-
54
- if feature_branch.nil? or feature_branch.length <= 0
55
- GitReflow.say "usage: git-reflow start [new-branch-name]", :error
56
- else
57
- GitReflow.run_command_with_label "git checkout #{base_branch}"
58
- GitReflow.run_command_with_label "git pull origin #{base_branch}"
59
- GitReflow.run_command_with_label "git push origin #{base_branch}:refs/heads/#{feature_branch}"
60
- GitReflow.run_command_with_label "git checkout --track -b #{feature_branch} origin/#{feature_branch}"
61
- end
62
- end
63
-
64
- # Submit a feature branch for review
65
- #
66
- # @option base [String] the name of the base branch you want to merge your feature into
67
- # @option title [String] the title of your pull request
68
- # @option body [String] the body of your pull request
69
- command(:review, defaults: {base: 'master'}) do |**params|
70
- base_branch = params[:base]
71
- create_pull_request = true
72
-
73
- GitReflow.fetch_destination base_branch
74
- begin
75
- GitReflow.push_current_branch
76
-
77
- existing_pull_request = GitReflow.git_server.find_open_pull_request( from: GitReflow.current_branch, to: base_branch )
78
- if existing_pull_request
79
- say "A pull request already exists for these branches:", :notice
80
- existing_pull_request.display_pull_request_summary
81
- else
82
- unless params[:title] || params[:body]
83
- pull_request_msg_file = "#{GitReflow.git_root_dir}/.git/GIT_REFLOW_PR_MSG"
84
-
85
- File.open(pull_request_msg_file, 'w') do |file|
86
- file.write(params[:title] || GitReflow.pull_request_template || GitReflow.current_branch)
87
- end
88
-
89
- GitReflow.run("#{GitReflow.git_editor_command} #{pull_request_msg_file}", with_system: true)
90
-
91
- pr_msg = File.read(pull_request_msg_file).split(/[\r\n]|\r\n/).map(&:strip)
92
- title = pr_msg.shift
93
-
94
- File.delete(pull_request_msg_file)
95
-
96
- unless pr_msg.empty?
97
- pr_msg.shift if pr_msg.first.empty?
98
- end
99
-
100
- params[:title] = title
101
- params[:body] = "#{pr_msg.join("\n")}\n"
102
-
103
- say "\nReview your PR:\n"
104
- say "--------\n"
105
- say "Title:\n#{params[:title]}\n\n"
106
- say "Body:\n#{params[:body]}\n"
107
- say "--------\n"
108
-
109
- create_pull_request = ask("Submit pull request? (Y)") =~ /y/i
110
- end
111
-
112
- if create_pull_request
113
- pull_request = GitReflow.git_server.create_pull_request(title: params[:title] || params[:body],
114
- body: params[:body],
115
- head: "#{GitReflow.remote_user}:#{GitReflow.current_branch}",
116
- base: params[:base])
117
-
118
- say "Successfully created pull request ##{pull_request.number}: #{pull_request.title}\nPull Request URL: #{pull_request.html_url}\n", :success
119
- else
120
- say "Review aborted. No pull request has been created.", :review_halted
121
- end
122
- end
123
- rescue Github::Error::UnprocessableEntity => e
124
- say "Github Error: #{e.to_s}", :error
125
- rescue StandardError => e
126
- say "\nError: #{e.inspect}", :error
127
- end
128
- end
129
-
130
- # Checks the status of an existing pull request
131
- #
132
- # @option destination_branch [String] the branch you're merging your feature into ('master' is default)
133
- command(:status, defaults: {destination_branch: 'master'}) do |**params|
134
- pull_request = GitReflow.git_server.find_open_pull_request( :from => GitReflow.current_branch, :to => params[:destination_branch] )
135
-
136
- if pull_request.nil?
137
- say "No pull request exists for #{GitReflow.current_branch} -> #{params[:destination_branch]}", :notice
138
- say "Run 'git reflow review #{params[:destination_branch]}' to start the review process", :notice
139
- else
140
- say "Here's the status of your review:"
141
- pull_request.display_pull_request_summary
142
- end
143
- end
144
-
145
- command(:deploy) do |**params|
146
- destination_server = params[:destination_server] || 'default'
147
- deploy_command = GitReflow::Config.get("reflow.deploy-to-#{destination_server}-command", local: true)
148
-
149
- # first check is to allow for automated setup
150
- if deploy_command.empty?
151
- deploy_command = ask("Enter the command you use to deploy to #{destination_server} (leaving blank will skip deployment)")
152
- end
153
-
154
- # second check is to see if the user wants to skip
155
- if deploy_command.empty?
156
- say "Skipping deployment..."
157
- false
158
- else
159
- GitReflow::Config.set("reflow.deploy-to-#{destination_server}-command", deploy_command, local: true)
160
- run_command_with_label(deploy_command, with_system: true)
161
- end
162
- end
163
-
164
- # Merge and deploy a feature branch to a staging branch
165
- command(:stage) do |**params|
166
- feature_branch_name = GitReflow.current_branch
167
- staging_branch_name = GitReflow::Config.get('reflow.staging-branch', local: true)
168
-
169
- if staging_branch_name.empty?
170
- staging_branch_name = GitReflow.ask("What's the name of your staging branch? (default: 'staging') ")
171
- staging_branch_name = 'staging' if staging_branch_name.strip == ''
172
- GitReflow::Config.set('reflow.staging-branch', staging_branch_name, local: true)
173
- end
174
-
175
- GitReflow.run_command_with_label "git checkout #{staging_branch_name}"
176
- GitReflow.run_command_with_label "git pull origin #{staging_branch_name}"
177
-
178
- if GitReflow.run_command_with_label "git merge #{feature_branch_name}", with_system: true
179
- GitReflow.run_command_with_label "git push origin #{staging_branch_name}"
180
-
181
- staged = self.deploy(destination_server: :staging)
182
-
183
- if staged
184
- GitReflow.say "Deployed to Staging.", :success
185
- else
186
- GitReflow.say "There were issues deploying to staging.", :error
187
- end
188
- else
189
- GitReflow.say "There were issues merging your feature branch to staging.", :error
190
- end
191
- end
192
-
193
- # Deliver a feature branch to a base branch
194
- #
195
- # @option base [String] base branch to merge your feature branch into
196
- # @option force [Boolean] whether to force-deliver the feature branch, ignoring any QA checks
197
- command(:deliver, defaults: {base: 'master'}) do |**params|
198
- begin
199
- existing_pull_request = GitReflow.git_server.find_open_pull_request( from: GitReflow.current_branch, to: params[:base] )
200
-
201
- if existing_pull_request.nil?
202
- say "No pull request exists for #{GitReflow.remote_user}:#{GitReflow.current_branch}\nPlease submit your branch for review first with \`git reflow review\`", :deliver_halted
203
- else
204
-
205
- if existing_pull_request.good_to_merge?(force: params[:force])
206
- # displays current status and prompts user for confirmation
207
- self.status destination_branch: params[:base]
208
- # TODO: change name of this in the merge! method
209
- params[:skip_lgtm] = params[:force] if params[:force]
210
- existing_pull_request.merge!(params)
211
- else
212
- say existing_pull_request.rejection_message, :deliver_halted
213
- end
214
-
215
- end
216
-
217
- rescue Github::Error::UnprocessableEntity => e
218
- say "Github Error: #{e.inspect}", :error
219
- end
220
- end
221
-
222
-
223
- # Updates and synchronizes your base branch and feature branch.
224
- #
225
- # Performs the following:
226
- # $ git checkout <base_branch>
227
- # $ git pull <remote_location> <base_branch>
228
- # $ git checkout <current_branch>
229
- # $ git pull origin <current_branch>
230
- # $ git merge <base_branch>
231
- # @param remote [String] the name of the remote repository to fetch updates from (origin by default)
232
- # @param base [String] the branch that you want to fetch updates from (master by default)
233
- command(:refresh, defaults: {remote: 'origin', base: 'master'}) do |**params|
234
- GitReflow.update_feature_branch(params)
235
- end
236
- end
237
- end
238
- end
1
+ $LOAD_PATH << File.expand_path(File.realpath(__dir__) + '/../..')
2
+ require 'git_reflow/workflow'
3
+
4
+ module GitReflow
5
+ module Workflows
6
+ # This class contains the core workflow for git-reflow. Going forward, this
7
+ # will act as the base class for customizing and extending git-reflow.
8
+ class Core
9
+ include ::GitReflow::Workflow
10
+
11
+ # Reads and evaluates the provided file in the context of this class
12
+ #
13
+ # @param workflow_path [String] the path of the Workflow file to eval
14
+ def self.load_workflow(workflow_path)
15
+ return unless workflow_path.length > 0 and File.exists?(workflow_path)
16
+ ::GitReflow.logger.debug "Using workflow: #{workflow_path}"
17
+ self.load_raw_workflow(File.read(workflow_path))
18
+ end
19
+
20
+ # Evaluates the provided string in the context of this class
21
+ #
22
+ # @param workflow_string [String] the contents of a Workflow file to eval
23
+ def self.load_raw_workflow(workflow_string)
24
+ return if workflow_string.strip.empty?
25
+ ::GitReflow.logger.debug "Evaluating workflow..."
26
+ binding.eval(workflow_string)
27
+ end
28
+
29
+ # Sets up the required git configurations that git-reflow depends on.
30
+ #
31
+ # @param [Hash] options the options to run setup with
32
+ # @option options [Boolean] :local (false) whether to configure git-reflow specific to the current project
33
+ # @option options [Boolean] :enterprise (false) whether to configure git-reflow for use with Github Enterprise
34
+ command(:setup, switches: { local: false, enterprise: false}) do |**params|
35
+ reflow_options = { project_only: params[:local], enterprise: params[:enterprise] }
36
+ existing_git_include_paths = git_config.get('include.path', all: true).split("\n")
37
+
38
+ unless File.exist?(git_config::CONFIG_FILE_PATH) or existing_git_include_paths.include?(git_config::CONFIG_FILE_PATH)
39
+ say "We'll walk you through setting up git-reflow's defaults for all your projects.", :notice
40
+ say "In the future, you can run \`git-reflow setup --local\` from the root of any project you want to setup differently.", :notice
41
+ run "touch #{git_config::CONFIG_FILE_PATH}"
42
+ say "Created #{git_config::CONFIG_FILE_PATH} for git-reflow specific configurations.", :notice
43
+ git_config.add "include.path", git_config::CONFIG_FILE_PATH, global: true
44
+ say "Added #{git_config::CONFIG_FILE_PATH} to include.path in $HOME/.gitconfig.", :notice
45
+ end
46
+
47
+ choose do |menu|
48
+ menu.header = "Available remote Git Server services"
49
+ menu.prompt = "Which service would you like to use for this project? "
50
+
51
+ menu.choice('GitHub') { ::GitReflow::GitServer.connect reflow_options.merge({ provider: 'GitHub', silent: false }) }
52
+ menu.choice('BitBucket (team-owned repos only)') { ::GitReflow::GitServer.connect reflow_options.merge({ provider: 'BitBucket', silent: false }) }
53
+ end
54
+
55
+ git_config.set "constants.minimumApprovals", ask("Set the minimum number of approvals (leaving blank will require approval from all commenters): "), local: reflow_options[:project_only]
56
+ git_config.set "constants.approvalRegex", ::GitReflow::GitServer::PullRequest::DEFAULT_APPROVAL_REGEX, local: reflow_options[:project_only]
57
+
58
+ if git_config.get('core.editor').length <= 0
59
+ git_config.set('core.editor', default_editor, local: reflow_options[:project_only])
60
+ say "Updated git's editor (via git config key 'core.editor') to: #{default_editor}.", :notice
61
+ end
62
+ end
63
+ command_help(
64
+ :setup,
65
+ summary: "Connect your GitServer (e.g. GitHub) account to git-reflow",
66
+ switches: {
67
+ local: "setup GitReflow for the current project only",
68
+ enterprise: "setup GitReflow with a Github Enterprise account",
69
+ }
70
+ )
71
+
72
+ # Start a new feature branch
73
+ #
74
+ # @param [Hash] options the options to run start with
75
+ # @option options [String] :base ("master") the name of the base branch you want to checkout your feature from
76
+ # @option options [String] :feature_branch the name of the base branch you want to checkout your feature from
77
+ command(:start, arguments: { feature_branch: nil }, flags: { base: nil }) do |**params|
78
+ base_branch = params[:base] || default_base_branch
79
+ feature_branch = params[:feature_branch]
80
+
81
+ if feature_branch.nil? or feature_branch.length <= 0
82
+ say "usage: git-reflow start [new-branch-name]", :error
83
+ else
84
+ run_command_with_label "git checkout #{base_branch}"
85
+ run_command_with_label "git pull origin #{base_branch}"
86
+ run_command_with_label "git push origin #{base_branch}:refs/heads/#{feature_branch}"
87
+ run_command_with_label "git checkout --track -b #{feature_branch} origin/#{feature_branch}"
88
+ end
89
+ end
90
+ command_help(
91
+ :start,
92
+ summary: "This will create a new feature branch and setup remote tracking",
93
+ arguments: { new_feature_branch: "name of the new feature branch" },
94
+ flags: { base: "name of a branch you want to branch off of" },
95
+ description: <<LONGTIME
96
+ Performs the following:\n
97
+ \t$ git checkout <base_branch>\n
98
+ \t$ git pull origin <base_branch>\n
99
+ \t$ git push origin <base_branch>:refs/heads/[new_feature_branch]\n
100
+ \t$ git checkout --track -b [new_feature_branch] origin/[new_feature_branch]\n
101
+ LONGTIME
102
+ )
103
+
104
+ # Submit a feature branch for review
105
+ #
106
+ # @param [Hash] options the options to run review with
107
+ # @option options [String] :base (GitReflow::Config.get('reflow.base-branch') or "master") the name of the base branch you want to merge your feature into
108
+ # @option options [String] :title (<current-branch-name>) the title of your pull request
109
+ # @option options [String] :message ("") the body of your pull request
110
+ command(:review, arguments: { base: nil }, flags: { title: nil, message: nil }) do |**params|
111
+ base_branch = params[:base] || default_base_branch
112
+ create_pull_request = true
113
+
114
+ fetch_destination base_branch
115
+ begin
116
+ push_current_branch
117
+
118
+ existing_pull_request = git_server.find_open_pull_request( from: current_branch, to: base_branch )
119
+ if existing_pull_request
120
+ say "A pull request already exists for these branches:", :notice
121
+ existing_pull_request.display_pull_request_summary
122
+ else
123
+ unless params[:title] || params[:message]
124
+ pull_request_msg_file = "#{git_root_dir}/.git/GIT_REFLOW_PR_MSG"
125
+
126
+ File.open(pull_request_msg_file, 'w') do |file|
127
+ begin
128
+ pr_message = params[:title] || pull_request_template || current_branch
129
+ file.write(pr_message)
130
+ rescue StandardError => e
131
+ logger.error "Unable to parse PR template (#{pull_request_msg_file}): #{e.inspect}"
132
+ file.write(params[:title] || current_branch)
133
+ end
134
+ end
135
+
136
+ run("#{git_editor_command} #{pull_request_msg_file}", with_system: true)
137
+
138
+ pr_msg = File.read(pull_request_msg_file).split(/[\r\n]|\r\n/).map(&:strip)
139
+ title = pr_msg.shift
140
+
141
+ File.delete(pull_request_msg_file)
142
+
143
+ unless pr_msg.empty?
144
+ pr_msg.shift if pr_msg.first.empty?
145
+ end
146
+
147
+ params[:title] = title
148
+ params[:message] = "#{pr_msg.join("\n")}\n"
149
+
150
+ say "\nReview your PR:\n"
151
+ say "--------\n"
152
+ say "Title:\n#{params[:title]}\n\n"
153
+ say "Body:\n#{params[:message]}\n"
154
+ say "--------\n"
155
+
156
+ create_pull_request = ask("Submit pull request? (Y)") =~ /y/i
157
+ end
158
+
159
+ if create_pull_request
160
+ begin
161
+ retries ||= 0
162
+ pull_request = git_server.create_pull_request(
163
+ title: params[:title] || params[:message],
164
+ body: params[:message],
165
+ head: "#{remote_user}:#{current_branch}",
166
+ base: base_branch
167
+ )
168
+ rescue Github::Error::UnprocessableEntity
169
+ retry if (retries += 1) < 3
170
+ raise
171
+ end
172
+
173
+ say "Successfully created pull request ##{pull_request.number}: #{pull_request.title}\nPull Request URL: #{pull_request.html_url}\n", :success
174
+ else
175
+ say "Review aborted. No pull request has been created.", :review_halted
176
+ end
177
+ end
178
+ rescue Github::Error::UnprocessableEntity => e
179
+ say "Github Error: #{e.to_s}", :error
180
+ rescue StandardError => e
181
+ say "\nError: #{e.inspect}", :error
182
+ end
183
+ end
184
+ command_help(
185
+ :review,
186
+ summary: "Pushes your latest feature branch changes to your remote repo and creates a pull request",
187
+ arguments: {
188
+ base: "the branch you want to merge your feature branch into"
189
+ },
190
+ flags: {
191
+ title: "the title of the Pull Request we'll create",
192
+ message: "the body of the Pull Request we'll create"
193
+ }
194
+ )
195
+
196
+ # Checks the status of an existing pull request
197
+ #
198
+ # @param [Hash] options the options to run review with
199
+ # @option options [String] :destination_branch ("master") the branch you're merging your feature into
200
+ command(:status, arguments: { destination_branch: nil }) do |**params|
201
+ base_branch = params[:destination_branch] || default_base_branch
202
+ pull_request = git_server.find_open_pull_request( :from => current_branch, :to => base_branch )
203
+
204
+ if pull_request.nil?
205
+ say "No pull request exists for #{current_branch} -> #{base_branch}", :notice
206
+ say "Run 'git reflow review #{base_branch}' to start the review process", :notice
207
+ else
208
+ say "Here's the status of your review:"
209
+ pull_request.display_pull_request_summary
210
+ end
211
+ end
212
+ command_help(
213
+ :status,
214
+ summary: "Display information about the status of your feature in the review process",
215
+ arguments: {
216
+ destination_branch: "the branch to merge your feature into"
217
+ }
218
+ )
219
+
220
+ # Deploys the current branch to a specified server
221
+ #
222
+ # @param [Hash] options the options to run review with
223
+ # @option options [String] :destination_server ("default") the environment server to deploy to (pulled from `git config "reflow.deploy-to-#{destination_server}-command")
224
+ command(:deploy, arguments: { destination_server: "default" }) do |**params|
225
+ destination_server = params[:destination_server] || "default"
226
+ deploy_command = git_config.get("reflow.deploy-to-#{destination_server}-command", local: true)
227
+
228
+ # first check is to allow for automated setup
229
+ if deploy_command.empty?
230
+ deploy_command = ask("Enter the command you use to deploy to #{destination_server} (leaving blank will skip deployment)")
231
+ end
232
+
233
+ # second check is to see if the user wants to skip
234
+ if deploy_command.empty?
235
+ say "Skipping deployment..."
236
+ false
237
+ else
238
+ git_config.set("reflow.deploy-to-#{destination_server}-command", deploy_command, local: true)
239
+ run_command_with_label(deploy_command, with_system: true)
240
+ end
241
+ end
242
+ command_help(
243
+ :deploy,
244
+ summary: "Deploys the current branch to a specified server",
245
+ arguments: {
246
+ destination_server: 'the environment to deploy to (from: `git config "reflow.deploy-to-#{destination_server}-command"`)'
247
+ }
248
+ )
249
+
250
+ # Merge and deploy a feature branch to a staging branch
251
+ command(:stage) do |**params|
252
+ feature_branch_name = current_branch
253
+ staging_branch_name = git_config.get('reflow.staging-branch', local: true)
254
+
255
+ if staging_branch_name.empty?
256
+ staging_branch_name = ask("What's the name of your staging branch? (default: 'staging') ")
257
+ staging_branch_name = 'staging' if staging_branch_name.strip == ''
258
+ git_config.set('reflow.staging-branch', staging_branch_name, local: true)
259
+ end
260
+
261
+ run_command_with_label "git checkout #{staging_branch_name}"
262
+ run_command_with_label "git pull origin #{staging_branch_name}"
263
+
264
+ if run_command_with_label "git merge #{feature_branch_name}", with_system: true
265
+ run_command_with_label "git push origin #{staging_branch_name}"
266
+
267
+ staged = self.deploy(destination_server: :staging)
268
+
269
+ if staged
270
+ say "Deployed to Staging.", :success
271
+ else
272
+ say "There were issues deploying to staging.", :error
273
+ end
274
+ else
275
+ say "There were issues merging your feature branch to staging.", :error
276
+ end
277
+ end
278
+ command_help(
279
+ :stage,
280
+ summary: "Merge and deploy a feature branch to a staging branch"
281
+ )
282
+
283
+ # Deliver a feature branch to a base branch
284
+ #
285
+ # @param [Hash] options the options to run review with
286
+ # @option options [String] :base ("master") the base branch to merge your feature branch into
287
+ # @option options [String] :force (false) whether to force-deliver the feature branch, ignoring any QA checks
288
+ command(:deliver, arguments: { base: nil }, flags: { merge_method: "squash" }, switches: { force: false, skip_lgtm: false }) do |**params|
289
+ params[:force] = params[:force] || params[:skip_lgtm]
290
+ params[:base] ||= default_base_branch
291
+
292
+ begin
293
+ existing_pull_request = git_server.find_open_pull_request( from: current_branch, to: params[:base] )
294
+
295
+ if existing_pull_request.nil?
296
+ say "No pull request exists for #{remote_user}:#{current_branch}\nPlease submit your branch for review first with \`git reflow review\`", :deliver_halted
297
+ else
298
+
299
+ if existing_pull_request.good_to_merge?(force: params[:force])
300
+ # displays current status and prompts user for confirmation
301
+ self.status destination_branch: params[:base]
302
+ existing_pull_request.merge!(params)
303
+ else
304
+ say existing_pull_request.rejection_message, :deliver_halted
305
+ end
306
+
307
+ end
308
+
309
+ rescue Github::Error::UnprocessableEntity => e
310
+ say "Github Error: #{e.inspect}", :error
311
+ end
312
+ end
313
+ command_help(
314
+ :deliver,
315
+ summary: "deliver your feature branch",
316
+ arguments: {
317
+ base: "the branch to merge this feature into"
318
+ },
319
+ flags: {
320
+ merge_method: "how you want your feature branch merged ('squash', 'merge', 'rebase')"
321
+ },
322
+ switches: {
323
+ force: "skip the lgtm checks and deliver your feature branch",
324
+ skip_lgtm: "skip the lgtm checks and deliver your feature branch"
325
+ },
326
+ description: "merge your feature branch down to your base branch, and cleanup your feature branch"
327
+ )
328
+
329
+
330
+ # Updates and synchronizes your base branch and feature branch.
331
+ #
332
+ # Performs the following:
333
+ # $ git checkout <base_branch>
334
+ # $ git pull <remote_location> <base_branch>
335
+ # $ git checkout <current_branch>
336
+ # $ git pull origin <current_branch>
337
+ # $ git merge <base_branch>
338
+ #
339
+ # @param [Hash] options the options to run review with
340
+ # @option options [String] :remote ("origin") the name of the remote repository to fetch updates from
341
+ # @option options [String] :base ("master") the branch that you want to fetch updates from
342
+ command(:refresh, flags: { remote: 'origin', base: nil}) do |**params|
343
+ params[:base] ||= default_base_branch
344
+ update_feature_branch(params)
345
+ end
346
+ command_help(
347
+ :refresh,
348
+ summary: "Updates and synchronizes your base branch and feature branch.",
349
+ flags: {
350
+ base: "branch to merge into",
351
+ remote: "remote repository name to fetch updates from",
352
+ },
353
+ description: <<LONGTIME
354
+ Performs the following:\n
355
+ \t$ git checkout <base_branch>\n
356
+ \t$ git pull <remote_location> <base_branch>\n
357
+ \t$ git checkout <current_branch>\n
358
+ \t$ git pull origin <current_branch>\n
359
+ \t$ git merge <base_branch>\n
360
+ LONGTIME
361
+ )
362
+ end
363
+ end
364
+ end