From 18d6d36e3b317e9bd5bb8c3e4d8db5203f6996b4 Mon Sep 17 00:00:00 2001 From: Simon Zhu Date: Mon, 18 Apr 2016 14:52:00 -0700 Subject: [PATCH] [Issue #149] Fix for PR ending as "Closed" instead of "Merged" - Calling Github API to Squash Merge the PR as opposed doing it manually --- README.rdoc | 119 +++++++++--------- lib/git_reflow.rb | 39 ++---- lib/git_reflow/commands/setup.rb | 4 + lib/git_reflow/git_helpers.rb | 19 --- .../git_server/bit_bucket/pull_request.rb | 2 +- .../git_server/git_hub/pull_request.rb | 43 +++++++ lib/git_reflow/git_server/pull_request.rb | 30 +++++ lib/git_reflow/merge_error.rb | 9 ++ spec/git_reflow_spec.rb | 99 +++++++-------- spec/lgtm_git_reflow_spec.rb | 112 +++++++---------- spec/lib/git_reflow/git_helpers_spec.rb | 60 --------- 11 files changed, 252 insertions(+), 284 deletions(-) create mode 100644 lib/git_reflow/merge_error.rb diff --git a/README.rdoc b/README.rdoc index c016ec9..b31b6c0 100644 --- a/README.rdoc +++ b/README.rdoc @@ -205,45 +205,32 @@ You kick butt. You've got your code reviewed and now you're ready to merge it do Reflow's +deliver+ requires you to have a pull request, so you'll be protected on those mornings when the coffee isn't working yet. We built this to get in your way and make you follow the process. If you don't like it, do it by hand. You already know what you're doing. -You'll be presented with a prefilled commit message based on the body of your pull request which includes the text Closes #XX that will automatically close the associated pull request on github when deliver completes. +You'll be presented with a prefilled commit message based on the body of your pull request with references to the pull request and reviewers. -Make a mistake and deliver too early? It happens. You'll be prompted after you edit your commit message if you want to push your updated +master+ to github. If you answer 'n', then you'll want to do git reset --hard origin/master and checkout your branch again. +Want to clean up your feature branch afterwards? You'll be prompted after you edit your commit message if you want to clean up your +feature_branch+ on github. If you answer 'n', then your feature_branch will exist for you to clean it up later. This is what it looks like: + $ git reflow deliver - From github.com:reenhanced/gitreflow - * branch master -> FETCH_HEAD - Merging pull request #36: 'Enforce at least one LGTM before delivery', from 'reenhanced:nh-fail-without-lgtm' into 'reenhanced:master' - Switched to branch 'master' - From github.com:reenhanced/gitreflow + Merging pull request #36: 'test-a-a', from 'simonzhu24:test-a-a' into 'simonzhu24:master' + # + + [success] Pull Request successfully merged. + Would you like to cleanup your feature branch? Y + git pull origin master + remote: Counting objects: 1, done. + remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 + Unpacking objects: 100% (1/1), done. + From https://github.com/simonzhu24/test * branch master -> FETCH_HEAD - Already up-to-date. - Switched to branch 'nh-fail-without-lgtm' - Switched to branch 'master' - Updating c2ec1b1..f90e111 - Fast-forward - Squash commit -- not updating HEAD - lib/git_reflow.rb | 71 +++++++++++++++++++++++++++---------------------------- - 1 file changed, 35 insertions(+), 36 deletions(-) - [master d1b4dd5] Enforces LGTM before deliver, even with no comments. - 1 file changed, 35 insertions(+), 36 deletions(-) - Merge complete! - Would you like to push this branch to your remote repo and cleanup your feature branch? y - Counting objects: 7, done. - Delta compression using up to 16 threads. - Compressing objects: 100% (4/4), done. - Writing objects: 100% (4/4), 1.38 KiB, done. - Total 4 (delta 2), reused 0 (delta 0) - To git@github.com:reenhanced/gitreflow.git - c2ec1b1..d1b4dd5 master -> master - - To git@github.com:reenhanced/gitreflow.git - - [deleted] nh-fail-without-lgtm - - Deleted branch nh-fail-without-lgtm (was f90e111). + a549abf..4f089a9 master -> origin/master + Merge made by the 'recursive' strategy. + git branch -D test-a-a + error: Cannot delete the branch 'test-a-a' which you are currently on. + Nice job buddy. - -This is what the default commit message looks like: + + This is what the default commit message looks like: Enforces LGTM before deliver, even with no comments. Removes the need to review the pull request yourself if you make a comment. @@ -274,29 +261,42 @@ This is what we do behind the scenes when you do +deliver+ If not, show a list of authors that need to provide a +LGTM+. -If the review is done, it's time to merge. Here's what happens: +* If the review is done, it's time to merge. Here's what happens: -First, update our local +master+ so we don't get conflicts - git checkout master - git pull origin master + First, we use the github_api gem to merge the pull request. -Next, squash merge our feature branch - git merge --squash nh-branch-name - -Now, we'll apply a little magic so we have a great commit message by default. Your editor will open and you'll see a nice default for your commit message based on the pull request body. + We call the public API for: -Once you've saved the commit, prompt the user to see if we should continue - Merge complete! - Would you like to push this branch to your remote repo and cleanup your feature branch? + github.pull_requests.merge 'user-name', 'repo-name', 'number', payload + + Please take a look at lib/git_reflow/git_server/git_hub/pull_request.rb:102-107 for an example of the call. + This call makes an HTTP request using the Github API to merge the available pull request passing in the user name, repository name, pull request number, and some additional data in the payload. + + The payload contains data in the following format: + + data = { + "commit_title", + "commit_message", + "sha", + "squash" + } + + Notice "squash" is set to true, meaning that we will do a squash-merge for each pull request. + + For more detailed documentation, please read: https://github.com/piotrmurach/github/blob/master/lib/github_api/client/pull_requests.rb#L197 + +* Next, we prompt you if you want to deploy and cleanup + Would you like cleanup your feature branch? -If 'y', then we'll push to +master+ and delete the feature branch - git push origin master - git push origin :nh-branch-name - git branch -D nh-branch-name + If 'y', then we'll update the +base+ and delete the feature branch + + git pull origin #{base_branch} + git push origin :#{feature_branch} + git branch -D #{feature_branch} -If 'n', then just stop here. The user can reset his local +master+. + If 'n', then just stop here. The user can clean up his branch locally. -And we're done. + And we're done. == Guiding principles: * Your workflow should resemble the following: @@ -321,22 +321,27 @@ http://reenhanced.com/images/reflow.png * If you make a new commit in your branch, you require another review. -* All participants in a pull request must approve the pull request. - If 2 people comment, you need 2 'LGTM's before the code is ready to merge. +* Depending on the minimumApprovals that you specify in your ~/.gitconfig.reflow (created upon reflow setup), you can have the following: + + "" : All participants in a pull request must approve the pull request. + "0": 0 approvals required for you to merge PR. + "1": You need a minimum of 1 LGTM and the last comment on your PR must be an LGTM. + "2": You need a minimum of 2 LGTM and the last comment on your PR must be an LGTM. + ... -* Once approved, your feature branch is squash merged to master. - This makes the history of the master branch extremely clean and easy to follow. +* Once approved, your feature branch is squash merged to your base_branch. + This makes the history of the base_branch branch extremely clean and easy to follow. * Git blame becomes your friend. You'll know who to blame and can see the full context of changes. - Squash commits to master mean every commit represents the whole feature, not a "typo fix". + Squash commits to base_branch mean every commit represents the whole feature, not a "typo fix". == Configuration In order to streamline delivery you can set the following git config to - always push the branch after merge and clean up the feature branch. + always clean up the feature branch after the PR is merged - git config --global --add "reflow.always-deploy-and-cleanup" "true" + git config --global --add "reflow.always-cleanup" "true" --- diff --git a/lib/git_reflow.rb b/lib/git_reflow.rb index 569ad4e..a5166a5 100644 --- a/lib/git_reflow.rb +++ b/lib/git_reflow.rb @@ -14,6 +14,7 @@ require 'git_reflow/os_detector' require 'git_reflow/sandbox' require 'git_reflow/git_helpers' +require 'git_reflow/merge_error' module GitReflow include Sandbox @@ -107,10 +108,6 @@ def deliver(options = {}) feature_branch = current_branch base_branch = options['base'] || 'master' - update_current_branch - fetch_destination(base_branch) - update_destination(feature_branch) - begin existing_pull_request = git_server.find_open_pull_request( :from => current_branch, :to => base_branch ) @@ -118,31 +115,21 @@ def deliver(options = {}) say "No pull request exists for #{remote_user}:#{current_branch}\nPlease submit your branch for review first with \`git reflow review\`", :deliver_halted else - commit_message = if "#{existing_pull_request.description}".length > 0 - existing_pull_request.description - else - "#{get_first_commit_message}" - end - if existing_pull_request.good_to_merge?(force: options['skip_lgtm']) puts "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.feature_branch_name}' into '#{existing_pull_request.base_branch_name}'" - update_destination(base_branch) - merge_feature_branch(feature_branch, - :destination_branch => base_branch, - :pull_request_number => existing_pull_request.number, - :lgtm_authors => existing_pull_request.approvals, - :message => commit_message) - committed = run_command_with_label 'git commit', with_system: true + begin + existing_pull_request.merge!(destination_branch: base_branch) - if committed - say "Merge complete!", :success + say "Pull Request successfully merged.", :success # check if user always wants to push and cleanup, otherwise ask - always_deploy_and_cleanup = GitReflow::Config.get('reflow.always-deploy-and-cleanup') == "true" - deploy_and_cleanup = always_deploy_and_cleanup || (ask "Would you like to push this branch to your remote repo and cleanup your feature branch? ") =~ /^y/i + always_cleanup = GitReflow::Config.get('reflow.always-cleanup') == "true" + cleanup = always_cleanup || (ask "Would you like to cleanup your feature branch? ") =~ /^y/i - if deploy_and_cleanup + if cleanup + # Pulls merged changes from remote base_branch + run_command_with_label "git pull origin #{base_branch}" run_command_with_label "git push origin #{base_branch}" run_command_with_label "git push origin :#{feature_branch}" run_command_with_label "git branch -D #{feature_branch}" @@ -151,9 +138,9 @@ def deliver(options = {}) puts "Cleanup halted. Local changes were not pushed to remote repo.".colorize(:red) puts "To reset and go back to your branch run \`git reset --hard origin/#{base_branch} && git checkout #{feature_branch}\`" end - else - say "There were problems commiting your feature... please check the errors above and try again.", :error - end + rescue GitReflow::GitServer::MergeError => error + say error.inspect, :deliver_halted + end else say existing_pull_request.rejection_message, :deliver_halted end @@ -185,7 +172,7 @@ def deploy(destination_server) end def git_server - @git_server ||= GitServer.connect provider: GitReflow::Config.get('reflow.git-server').strip, silent: true + @git_server ||= GitServer.connect provider: "#{GitReflow::Config.get('reflow.git-server')}".strip, silent: true end end diff --git a/lib/git_reflow/commands/setup.rb b/lib/git_reflow/commands/setup.rb index deb6990..dc44354 100644 --- a/lib/git_reflow/commands/setup.rb +++ b/lib/git_reflow/commands/setup.rb @@ -25,5 +25,9 @@ GitReflow::Config.add "constants.minimumApprovals", ask("Set the minimum number of approvals (leaving blank will require approval from all commenters): "), local: reflow_options[:project_only] GitReflow::Config.add "constants.approvalRegex", GitReflow::GitServer::PullRequest::DEFAULT_APPROVAL_REGEX, local: reflow_options[:project_only] + say("Enter the follow information associated with your remote repository: (The information is used to run 'git reflow deliver'.") + # Sets the user's github auth token + GitReflow::Config.set "github.oauth-token", ask("Set the your local github authorization token: (You cannot run 'git reflow deliver' without it!) "), local: reflow_options[:project_only] + say("Thanks! Your settings are saved to #{GitReflow::Config::CONFIG_FILE_PATH}.") end end diff --git a/lib/git_reflow/git_helpers.rb b/lib/git_reflow/git_helpers.rb index 9b0c98c..30f2d05 100644 --- a/lib/git_reflow/git_helpers.rb +++ b/lib/git_reflow/git_helpers.rb @@ -61,25 +61,6 @@ def update_feature_branch(options = {}) run_command_with_label "git merge #{base_branch}" end - def merge_feature_branch(feature_branch_name, options = {}) - options[:destination_branch] ||= 'master' - - message = "#{options[:message]}" - - if "#{options[:pull_request_number]}".length > 0 - message << "\nCloses ##{options[:pull_request_number]}\n" - end - - if lgtm_authors = Array(options[:lgtm_authors]) and lgtm_authors.any? - message << "\nLGTM given by: @#{lgtm_authors.join(', @')}\n" - end - - run_command_with_label "git checkout #{options[:destination_branch]}" - run_command_with_label "git merge --squash #{feature_branch_name}" - - append_to_squashed_commit_message(message) if message.length > 0 - end - def append_to_squashed_commit_message(message = '') tmp_squash_message_path = "#{git_root_dir}/.git/tmp_squash_msg" squash_message_path = "#{git_root_dir}/.git/SQUASH_MSG" diff --git a/lib/git_reflow/git_server/bit_bucket/pull_request.rb b/lib/git_reflow/git_server/bit_bucket/pull_request.rb index 50b7f25..964941d 100644 --- a/lib/git_reflow/git_server/bit_bucket/pull_request.rb +++ b/lib/git_reflow/git_server/bit_bucket/pull_request.rb @@ -77,7 +77,7 @@ def approvals approved end - + end end end diff --git a/lib/git_reflow/git_server/git_hub/pull_request.rb b/lib/git_reflow/git_server/git_hub/pull_request.rb index 00c10e0..73a2d33 100644 --- a/lib/git_reflow/git_server/git_hub/pull_request.rb +++ b/lib/git_reflow/git_server/git_hub/pull_request.rb @@ -78,6 +78,49 @@ def approved? end end + def merge!(options = {}) + options[:destination_branch] ||= 'master' + feature_branch_name = GitReflow.current_branch + + commit_message = if "#{self.description}".length > 0 + self.description + else + "#{GitReflow.get_first_commit_message}" + end + + if "#{self.number}".length > 0 + commit_message << "\nMerges ##{self.number}\n" + end + + if lgtm_authors = Array(self.approvals) and lgtm_authors.any? + commit_message << "\nLGTM given by: @#{lgtm_authors.join(', @')}\n" + end + + data = { + "commit_title" => "#{self.title}", + "commit_message" => "#{commit_message}", + "sha" => "#{self.head.sha}", + "squash" => true + } + + merge_response = GitReflow::GitServer::GitHub.connection.pull_requests.merge( + "#{GitReflow.git_server.class.remote_user}", + "#{GitReflow.git_server.class.remote_repo_name}", + "#{self.number}", + data + ) + + if merge_response.success? + append_to_squashed_commit_message(commit_message) if commit_message.length > 0 + else + if merge_response.has_key?(:message) + raise MergeError.new(merge_response.message) + else + raise MergeError.new(merge_response.to_s) + end + end + end + def build github_build_status = GitReflow.git_server.get_build_status(self.head.sha) build_status_object = Struct.new(:state, :description, :url) diff --git a/lib/git_reflow/git_server/pull_request.rb b/lib/git_reflow/git_server/pull_request.rb index bf7da52..834a17e 100644 --- a/lib/git_reflow/git_server/pull_request.rb +++ b/lib/git_reflow/git_server/pull_request.rb @@ -140,6 +140,36 @@ def method_missing(method_sym, *arguments, &block) super end end + + def merge!(options = {}) + + destination_branch = options[:destination_branch] || 'master' + feature_branch_name = GitReflow.current_branch + + update_current_branch + fetch_destination(destination_branch) + update_destination(feature_branch_name) + update_destination(destination_branch) + + commit_message = if "#{self.description}".length > 0 + self.description + else + "#{get_first_commit_message}" + end + + if "#{options[:pull_request_number]}".length > 0 + commit_message << "\nMerges ##{options[:pull_request_number]}\n" + end + + if lgtm_authors = Array(options[:lgtm_authors]) and lgtm_authors.any? + commit_message << "\nLGTM given by: @#{lgtm_authors.join(', @')}\n" + end + + run_command_with_label "git checkout #{destination_branch}" + run_command_with_label "git merge --squash #{feature_branch_name}" + + append_to_squashed_commit_message(commit_message) if commit_r.length > 0 + end end end end diff --git a/lib/git_reflow/merge_error.rb b/lib/git_reflow/merge_error.rb new file mode 100644 index 0000000..292fefd --- /dev/null +++ b/lib/git_reflow/merge_error.rb @@ -0,0 +1,9 @@ +module GitReflow + module GitServer + class MergeError < StandardError + def initialize(msg="My default message") + super(msg) + end + end + end +end diff --git a/spec/git_reflow_spec.rb b/spec/git_reflow_spec.rb index b29a10d..7572563 100644 --- a/spec/git_reflow_spec.rb +++ b/spec/git_reflow_spec.rb @@ -22,6 +22,8 @@ # Stubbing out numlgtm value to test all reviewers in gitconfig file allow(GitReflow::Config).to receive(:get).with("constants.minimumApprovals").and_return('') allow(GitReflow::Config).to receive(:get).and_call_original + allow(GitReflow::GitServer::GitHub).to receive_message_chain(:connection, :pull_requests, :merge).and_return({}) + allow_any_instance_of(Hash).to receive(:success?).and_return("200") allow_any_instance_of(HighLine).to receive(:ask) do |terminal, question| values = { @@ -29,7 +31,7 @@ "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes', + "Would you like to cleanup your feature branch? " => 'yes', "Would you like to open it in your browser?" => 'n' } return_value = values[question] || values[terminal] @@ -174,7 +176,12 @@ context :deliver do let(:branch) { 'new-feature' } - let(:inputs) { {} } + let(:inputs) { + { + "title" => "new-feature", + "head" => "reenhanced:new-feature", + } + } let!(:github) do allow_any_instance_of(GitReflow::GitServer::GitHub::PullRequest).to receive(:build).and_return(Struct.new(:state, :description, :url).new) stub_github_with({ @@ -188,7 +195,7 @@ before do - allow(GitReflow).to receive(:append_to_squashed_commit_message).and_return(true) + allow(existing_pull_request).to receive(:append_to_squashed_commit_message).and_return(true) module Kernel def system(cmd) @@ -199,16 +206,6 @@ def system(cmd) subject { GitReflow.deliver inputs } - it "fetches the latest changes to the current branch" do - expect(GitReflow).to receive(:update_current_branch) - subject - end - - it "fetches the latest changes to the destination branch" do - expect(GitReflow).to receive(:fetch_destination).with('master') - subject - end - it "looks for a pull request matching the feature branch and destination branch" do expect(github).to receive(:find_open_pull_request).with(from: branch, to: 'master') subject @@ -219,6 +216,7 @@ def system(cmd) allow(github).to receive(:build_status).and_return(build_status) allow(github).to receive(:find_open_pull_request).and_return(existing_pull_request) allow(existing_pull_request).to receive(:has_comments?).and_return(true) + allow(github).to receive(:reviewers).and_return(['codenamev']) end @@ -237,19 +235,19 @@ def system(cmd) context 'and build status is nil' do let(:build_status) { nil } - let(:inputs) {{ skip_lgtm: true }} + let(:inputs) {{ 'skip_lgtm' => false }} before do # stubbing unrelated results so we can just test that it made it insdide the conditional block allow(existing_pull_request).to receive(:has_comments?).and_return(true) allow(existing_pull_request).to receive(:reviewers).and_return([]) - allow(GitReflow).to receive(:update_destination).and_return(true) - allow(GitReflow).to receive(:merge_feature_branch).and_return(true) - allow(GitReflow).to receive(:append_to_squashed_commit_message).and_return(true) + allow(existing_pull_request).to receive(:reviewers_pending_response).and_return([]) + allow(existing_pull_request).to receive(:approvals).and_return(['simonzhu24']) + allow(existing_pull_request).to receive(:append_to_squashed_commit_message).and_return(true) end it "ignores build status when not setup" do - expect { subject }.to have_said "Merge complete!", :success + expect { subject }.to have_said "Pull Request successfully merged.", :success end end @@ -269,8 +267,8 @@ def system(cmd) end it "includes the pull request body in the commit message" do - squash_message = "#{existing_pull_request.body}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).with(squash_message) + squash_message = "#{existing_pull_request.body}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).with(squash_message) subject end @@ -298,7 +296,7 @@ def system(cmd) end it "commits the changes if the build status is nil but has comments/approvals and no pending response" do - expect{ subject }.to have_said 'Merge complete!', :success + expect{ subject }.to have_said 'Pull Request successfully merged.', :success end end @@ -313,30 +311,23 @@ def system(cmd) end it "includes the first commit message for the new branch in the commit message of the merge" do - squash_message = "#{first_commit_message}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).with(squash_message) + squash_message = "#{first_commit_message}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).with(squash_message) subject end end it "notifies user of the merge and performs it" do - expect(GitReflow).to receive(:merge_feature_branch).with('new-feature', { - destination_branch: 'master', - pull_request_number: existing_pull_request.number, - lgtm_authors: ['nhance'], - message: existing_pull_request.body - }) + expect(existing_pull_request).to receive(:merge!).with({destination_branch: 'master'}).and_return({}) - expect { subject }.to have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'" - end + # Stubs out the http response to github api + allow_any_instance_of(Hash).to receive(:success?).and_return(true) - it "updates the destination brnach" do - expect(GitReflow).to receive(:update_destination).with('master') - subject + expect { subject }.to have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'" end it "commits the changes for the squash merge" do - expect{ subject }.to have_said 'Merge complete!', :success + expect{ subject }.to have_said 'Pull Request successfully merged.', :success end context "and cleaning up feature branch" do @@ -347,7 +338,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes', + "Would you like to cleanup your feature branch? " => 'yes', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -358,12 +349,12 @@ def system(cmd) context "not always" do before do - allow(GitReflow::Config).to receive(:get).with("reflow.always-deploy-and-cleanup").and_return("false") + allow(GitReflow::Config).to receive(:get).with("reflow.always-cleanup").and_return("false") allow(GitReflow::Config).to receive(:get).and_call_original end - it "pushes local squash merged base branch to remote repo" do - expect { subject }.to have_run_command("git push origin master") + it "pulls changes from remote repo to local branch" do + expect { subject }.to have_run_command("git pull origin master") end it "deletes the remote feature branch" do @@ -377,12 +368,12 @@ def system(cmd) context "always" do before do - allow(GitReflow::Config).to receive(:get).with("reflow.always-deploy-and-cleanup").and_return("true") + allow(GitReflow::Config).to receive(:get).with("reflow.always-cleanup").and_return("true") allow(GitReflow::Config).to receive(:get).and_call_original end - it "pushes local squash merged base branch to remote repo" do - expect { subject }.to have_run_command("git push origin master") + it "pulls changes from remote repo to local branch" do + expect { subject }.to have_run_command("git pull origin master") end it "deletes the remote feature branch" do @@ -404,7 +395,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'no', + "Would you like to cleanup your feature branch? " => 'no', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -429,14 +420,6 @@ def system(cmd) expect { subject }.to have_output("To reset and go back to your branch run \`git reset --hard origin/master && git checkout new-feature\`") end end - - context "and there were issues commiting the squash merge to the base branch" do - before { stub_with_fallback(GitReflow, :run_command_with_label).with('git commit', {with_system: true}).and_return false } - it "notifies user of issues commiting the squash merge of the feature branch" do - expect { subject }.to have_said("There were problems commiting your feature... please check the errors above and try again.", :error) - end - end - end context 'but there are still unaddressed comments' do @@ -461,16 +444,18 @@ def system(cmd) end it "successfully finds a pull request for the current feature branch" do + allow(existing_pull_request).to receive(:good_to_merge?).and_return(true) + allow(existing_pull_request).to receive(:approvals).and_return(["Simon"]) + allow(existing_pull_request).to receive(:title).and_return(inputs['title']) expect { subject }.to have_output "Merging pull request #1: 'new-feature', from 'new-feature' into 'master'" end - it "checks out the destination branch and updates any remote changes" do - expect(GitReflow).to receive(:update_destination) - subject - end - it "merges and squashes the feature branch into the master branch" do - expect(GitReflow).to receive(:merge_feature_branch) + allow(existing_pull_request).to receive(:good_to_merge?).and_return(true) + allow(existing_pull_request).to receive(:approvals).and_return(["Simon"]) + allow(existing_pull_request).to receive(:title).and_return(inputs['title']) + expect(existing_pull_request).to receive(:merge!).and_return({}) + allow_any_instance_of(Hash).to receive(:success?).and_return(true) subject end end diff --git a/spec/lgtm_git_reflow_spec.rb b/spec/lgtm_git_reflow_spec.rb index adf8bbd..75f784a 100644 --- a/spec/lgtm_git_reflow_spec.rb +++ b/spec/lgtm_git_reflow_spec.rb @@ -29,7 +29,7 @@ "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes', + "Would you like to cleanup your feature branch? " => 'yes', "Would you like to open it in your browser?" => 'n' } return_value = values[question] || values[terminal] @@ -40,7 +40,11 @@ context :deliver do let(:branch) { 'new-feature' } - let(:inputs) { {} } + let(:inputs) { + { "title" => "new-feature", + "head" => "reenhanced:new-feature" + } + } let!(:github) do allow_any_instance_of(GitReflow::GitServer::GitHub::PullRequest).to receive(:build).and_return(Struct.new(:state, :description, :url).new) stub_github_with({ @@ -54,7 +58,11 @@ before do - allow(GitReflow).to receive(:append_to_squashed_commit_message).and_return(true) + allow(existing_pull_request).to receive(:append_to_squashed_commit_message).and_return(true) + + # Stubs out the http response to github api + allow(GitReflow::GitServer::GitHub).to receive_message_chain(:connection, :pull_requests, :merge).and_return({}) + allow_any_instance_of(Hash).to receive(:success?).and_return(true); module Kernel def system(cmd) @@ -65,11 +73,6 @@ def system(cmd) subject { GitReflow.deliver inputs } - it "fetches the latest changes to the destination branch" do - expect(GitReflow).to receive(:fetch_destination).with('master') - subject - end - it "looks for a pull request matching the feature branch and destination branch" do expect(github).to receive(:find_open_pull_request).with(from: branch, to: 'master') subject @@ -81,7 +84,6 @@ def system(cmd) expect(github).to receive(:find_open_pull_request).and_return(existing_pull_request) allow(existing_pull_request).to receive(:has_comments?).and_return(true) allow(github).to receive(:reviewers).and_return(['codenamev']) - allow(existing_pull_request).to receive(:approvals).and_return(["Simon", "John"]) allow(existing_pull_request).to receive_message_chain(:last_comment, :match).and_return(true) end @@ -101,19 +103,19 @@ def system(cmd) context 'and build status is nil' do let(:build_status) { nil } - let(:inputs) {{ 'skip_lgtm' => true }} + let(:inputs) {{ 'skip_lgtm' => false }} before do # stubbing unrelated results so we can just test that it made it insdide the conditional block allow(existing_pull_request).to receive(:has_comments?).and_return(true) allow(existing_pull_request).to receive(:reviewers).and_return([]) - allow(GitReflow).to receive(:update_destination).and_return(true) - allow(GitReflow).to receive(:merge_feature_branch).and_return(true) - allow(GitReflow).to receive(:append_to_squashed_commit_message).and_return(true) + allow(existing_pull_request).to receive(:reviewers_pending_response).and_return([]) + allow(existing_pull_request).to receive(:approvals).and_return(['simonzhu24']) + allow(existing_pull_request).to receive(:append_to_squashed_commit_message).and_return(true) end it "ignores build status when not setup" do - expect { subject }.to have_said "Merge complete!", :success + expect { subject }.to have_said "Pull Request successfully merged.", :success end end @@ -136,8 +138,8 @@ def system(cmd) end it "doesn't include the pull request body in the commit message" do - squash_message = "#{existing_pull_request.body}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).never.with(squash_message) + squash_message = "#{existing_pull_request.body}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).never.with(squash_message) subject end @@ -152,19 +154,14 @@ def system(cmd) end it "doesn't include the first commit message for the new branch in the commit message of the merge" do - squash_message = "#{first_commit_message}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).never.with(squash_message) + squash_message = "#{first_commit_message}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).never.with(squash_message) subject end end it "doesn't notify user of the merge and performs it" do - expect(GitReflow).to receive(:merge_feature_branch).never.with('new-feature', { - destination_branch: 'master', - pull_request_number: existing_pull_request.number, - lgtm_authors: ['nhance', 'Simon'], - message: existing_pull_request.body - }) + expect(existing_pull_request).to receive(:merge!).never.with({destination_branch: 'master'}) expect { subject }.to_not have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'" end @@ -182,7 +179,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes', + "Would you like to cleanup your feature branch?" => 'yes', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -237,7 +234,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'no', + "Would you like to cleanup your feature branch? " => 'no', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -280,8 +277,8 @@ def system(cmd) end it "includes the pull request body in the commit message" do - squash_message = "#{existing_pull_request.body}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).with(squash_message) + squash_message = "#{existing_pull_request.body}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).with(squash_message) subject end @@ -309,7 +306,7 @@ def system(cmd) end it "commits the changes if the build status is nil but has comments/approvals and no pending response" do - expect{ subject }.to have_said 'Merge complete!', :success + expect{ subject }.to have_said 'Pull Request successfully merged.', :success end end @@ -324,30 +321,23 @@ def system(cmd) end it "includes the first commit message for the new branch in the commit message of the merge" do - squash_message = "#{first_commit_message}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" - expect(GitReflow).to receive(:append_to_squashed_commit_message).with(squash_message) + squash_message = "#{first_commit_message}\nMerges ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n" + expect(existing_pull_request).to receive(:append_to_squashed_commit_message).with(squash_message) subject end end it "notifies user of the merge and performs it" do - expect(GitReflow).to receive(:merge_feature_branch).with('new-feature', { - destination_branch: 'master', - pull_request_number: existing_pull_request.number, - lgtm_authors: ['nhance', 'Simon'], - message: existing_pull_request.body - }) + expect(existing_pull_request).to receive(:merge!).with({destination_branch: 'master'}).and_return({}) - expect { subject }.to have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'" - end + # Stubs out the http response to github api + allow_any_instance_of(Hash).to receive(:success?).and_return(true); - it "updates the destination branch" do - expect(GitReflow).to receive(:update_destination).with('master') - subject + expect { subject }.to have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'" end it "commits the changes for the squash merge" do - expect{ subject }.to have_said 'Merge complete!', :success + expect{ subject }.to have_said 'Pull Request successfully merged.', :success end context "and cleaning up feature branch" do @@ -358,7 +348,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes', + "Would you like to cleanup your feature branch? " => 'yes', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -372,8 +362,8 @@ def system(cmd) allow(GitReflow::Config).to receive(:get) { "false" } end - it "pushes local squash merged base branch to remote repo" do - expect { subject }.to have_run_command("git push origin master") + it "pulls changes from remote repo to local branch" do + expect { subject }.to have_run_command("git pull origin master") end it "deletes the remote feature branch" do @@ -390,8 +380,8 @@ def system(cmd) allow(GitReflow::Config).to receive(:get) { "true" } end - it "pushes local squash merged base branch to remote repo" do - expect { subject }.to have_run_command("git push origin master") + it "pulls changes from remote repo to local branch" do + expect { subject }.to have_run_command("git pull origin master") end it "deletes the remote feature branch" do @@ -412,7 +402,7 @@ def system(cmd) "Please enter your GitHub password (we do NOT store this): " => password, "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site, "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api, - "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'no', + "Would you like to cleanup your feature branch? " => 'no', "Would you like to open it in your browser?" => 'no' } return_value = values[question] || values[terminal] @@ -437,14 +427,6 @@ def system(cmd) expect { subject }.to have_output("To reset and go back to your branch run \`git reset --hard origin/master && git checkout new-feature\`") end end - - context "and there were issues commiting the squash merge to the base branch" do - before { stub_with_fallback(GitReflow, :run_command_with_label).with('git commit', {with_system: true}).and_return false } - it "notifies user of issues commiting the squash merge of the feature branch" do - expect { subject }.to have_said("There were problems commiting your feature... please check the errors above and try again.", :error) - end - end - end context 'but there are still unaddressed comments' do @@ -465,21 +447,23 @@ def system(cmd) end it "notifies the user to get their code reviewed" do - expect { subject }.to have_said "Merge complete!", :success + expect { subject }.to have_said "Pull Request successfully merged.", :success end end it "successfully finds a pull request for the current feature branch" do + allow(existing_pull_request).to receive(:good_to_merge?).and_return(true) + allow(existing_pull_request).to receive(:approvals).and_return(["Simon"]) + allow(existing_pull_request).to receive(:title).and_return(inputs['title']) expect { subject }.to have_output "Merging pull request #1: 'new-feature', from 'new-feature' into 'master'" end - it "checks out the destination branch and updates any remote changes" do - expect(GitReflow).to receive(:update_destination) - subject - end - it "merges and squashes the feature branch into the master branch" do - expect(GitReflow).to receive(:merge_feature_branch) + allow(existing_pull_request).to receive(:good_to_merge?).and_return(true) + allow(existing_pull_request).to receive(:approvals).and_return(["Simon"]) + allow(existing_pull_request).to receive(:title).and_return(inputs['title']) + expect(existing_pull_request).to receive(:merge!).and_return({}) + allow_any_instance_of(Hash).to receive(:success?).and_return(true) subject end end diff --git a/spec/lib/git_reflow/git_helpers_spec.rb b/spec/lib/git_reflow/git_helpers_spec.rb index fb57c87..8047a67 100644 --- a/spec/lib/git_reflow/git_helpers_spec.rb +++ b/spec/lib/git_reflow/git_helpers_spec.rb @@ -116,66 +116,6 @@ module Gitacular end end - describe ".merge_feature_branch(options)" do - let(:destination_branch) { 'monkey-business' } - let(:feature_branch) { 'bananas' } - let(:merge_options) { {} } - - subject { Gitacular.merge_feature_branch(feature_branch, merge_options) } - - it 'checks out master as the default destination branch and squash merges the feature branch' do - expect { subject }.to have_run_commands_in_order [ - 'git checkout master', - "git merge --squash #{feature_branch}" - ] - end - - context "providing a destination branch" do - let(:merge_options) {{ destination_branch: destination_branch }} - it { expect{ subject }.to have_run_command "git checkout #{destination_branch}" } - end - - context "with a message" do - let(:merge_options) {{ message: "don't throw doo doo" }} - it "appends the message to the squashed commit message" do - expect(Gitacular).to receive(:append_to_squashed_commit_message).with("don't throw doo doo") - subject - end - - context 'and a pull reuqest number' do - before { merge_options.merge!(pull_request_number: 3) } - it "appends the message to the squashed commit message" do - expect(Gitacular).to receive(:append_to_squashed_commit_message).with("don't throw doo doo\nCloses #3\n") - subject - end - end - end - - context "with a pull request number" do - let(:merge_options) {{ pull_request_number: 3 }} - it "appends the message to the squashed commit message" do - expect(Gitacular).to receive(:append_to_squashed_commit_message).with("\nCloses #3\n") - subject - end - end - - context "with one LGTM author" do - let(:merge_options) {{ lgtm_authors: 'codenamev' }} - it "appends the message to the squashed commit message" do - expect(Gitacular).to receive(:append_to_squashed_commit_message).with("\nLGTM given by: @#{merge_options[:lgtm_authors]}\n") - subject - end - end - - context "with LGTM authors" do - let(:merge_options) {{ lgtm_authors: ['codenamev', 'nhance'] }} - it "appends the message to the squashed commit message" do - expect(Gitacular).to receive(:append_to_squashed_commit_message).with("\nLGTM given by: @#{merge_options[:lgtm_authors].join(', @')}\n") - subject - end - end - end - describe ".append_to_squashed_commit_message(message)" do let(:original_squash_message) { "Oooooo, SQUASH IT" } let(:message) { "do do the voodoo that you do" }