diff --git a/bin/git-backportpr b/bin/git-backportpr new file mode 100755 index 0000000..115fc06 --- /dev/null +++ b/bin/git-backportpr @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions', 'cli.rb') +Socialcast::Gitx::CLI.start (['backportpr'] + ARGV) diff --git a/lib/socialcast-git-extensions/cli.rb b/lib/socialcast-git-extensions/cli.rb index f9ba666..3fb902f 100644 --- a/lib/socialcast-git-extensions/cli.rb +++ b/lib/socialcast-git-extensions/cli.rb @@ -33,8 +33,6 @@ def initialize(*args) method_option :skip_additional_reviewers, :type => :string, :aliases => '-s', :desc => 'Skips adding additional reviewers' # @see http://developer.github.com/v3/pulls/ def reviewrequest(*additional_reviewers) - token = authorization_token - update review_mention = if buddy = socialcast_review_buddy(current_user) @@ -63,7 +61,7 @@ def reviewrequest(*additional_reviewers) description = options[:description] || editor_input(PULL_REQUEST_DESCRIPTION) branch = current_branch repo = current_repo - url = create_pull_request token, branch, repo, description, assignee + url = create_pull_request branch, repo, description, assignee say "Pull request created: #{url}" short_description = description.split("\n").first(5).join("\n") @@ -73,9 +71,8 @@ def reviewrequest(*additional_reviewers) desc "findpr", "Find pull requests including a given commit" def findpr(commit_hash) - token = authorization_token repo = current_repo - data = pull_requests_for_commit(token, repo, commit_hash) + data = pull_requests_for_commit(repo, commit_hash) if data['items'] data['items'].each do |entry| @@ -84,7 +81,38 @@ def findpr(commit_hash) else say "No results found", :yellow end + end + + desc "backportpr", "Backport a pull request" + def backportpr(pull_request_num, maintenance_branch) + original_base_branch = ENV['BASE_BRANCH'] + ENV['BASE_BRANCH'] = maintenance_branch + repo = current_repo + assignee = github_track_reviewer('Backport') + socialcast_reviewer = socialcast_track_reviewer('Backport') + + pull_request_data = github_api_request('GET', "repos/#{repo}/pulls/#{pull_request_num}") + commits_data = github_api_request('GET', pull_request_data['commits_url']) + non_merge_commits_data = commits_data.select { |commit_data| commit_data['parents'].length == 1 } + shas = non_merge_commits_data.map { |commit| commit['sha'] } + + backport_branch = "backport_#{pull_request_num}_to_#{maintenance_branch}" + backport_to(backport_branch, shas) + + maintenance_branch_url = "https://github.com/#{repo}/tree/#{maintenance_branch}" + description = "Backport ##{pull_request_num} to #{maintenance_branch_url}\n***\n#{pull_request_data['body']}" + + pull_request_url = create_pull_request(backport_branch, repo, description, assignee) + + review_message = ["#reviewrequest backport ##{pull_request_num} to #{maintenance_branch} #scgitx"] + if socialcast_reviewer + review_message << "/cc @#{socialcast_reviewer} for #backport track" + end + review_message << "/cc @SocialcastDevelopers" + post review_message.join("\n\n"), :url => pull_request_url, :message_type => 'review_request' + ensure + ENV['BASE_BRANCH'] = original_base_branch end # TODO: use --no-edit to skip merge messages diff --git a/lib/socialcast-git-extensions/git.rb b/lib/socialcast-git-extensions/git.rb index 221e29a..f22670d 100644 --- a/lib/socialcast-git-extensions/git.rb +++ b/lib/socialcast-git-extensions/git.rb @@ -28,6 +28,27 @@ def current_user `git config -z --global --get github.user`.strip end + def backport_to(branch, shas) + run_cmd "git checkout #{base_branch}" + run_cmd "git checkout -b #{branch}" + begin + run_cmd "git cherry-pick #{shas.join(' ')}" + rescue => e + while true + proceed = $terminal.ask "Error during cherry-pick. You can proceed by resolving the conflicts and using 'git cherry-pick --continue' to finish the cherry-pick in another terminal. Would you like to proceed (y/n)?" + if proceed.to_s.downcase == 'n' + run_cmd "git cherry-pick --abort" + exit 1 + elsif proceed.to_s.downcase == 'y' + break + else + say "Invalid response" + end + end + end + run_cmd "git push origin HEAD" + end + # retrieve a list of branches def branches(options = {}) branches = [] diff --git a/lib/socialcast-git-extensions/github.rb b/lib/socialcast-git-extensions/github.rb index 0001e94..4e56803 100644 --- a/lib/socialcast-git-extensions/github.rb +++ b/lib/socialcast-git-extensions/github.rb @@ -32,7 +32,7 @@ def authorization_token # returns the url of the created pull request # @see http://developer.github.com/v3/pulls/ - def create_pull_request(token, branch, repo, body, assignee) + def create_pull_request(branch, repo, body, assignee) payload = {:title => branch, :base => base_branch, :head => branch, :body => body}.to_json say "Creating pull request for " say "#{branch} ", :green @@ -40,41 +40,26 @@ def create_pull_request(token, branch, repo, body, assignee) say "#{base_branch} ", :green say "in " say repo, :green - response = RestClient::Request.new(:url => "https://api.github.com/repos/#{repo}/pulls", :method => "POST", :payload => payload, :headers => {:accept => :json, :content_type => :json, 'Authorization' => "token #{token}"}).execute - data = JSON.parse response.body - - assign_pull_request(token, branch, assignee, data) if assignee ## Unfortunately this needs to be done in a seperate request. + data = github_api_request("POST", "repos/#{repo}/pulls", payload) + assign_pull_request(branch, assignee, data) if assignee ## Unfortunately this needs to be done in a seperate request. url = data['html_url'] url - rescue RestClient::Exception => e - process_error e - throw e end # find the PRs matching the given commit hash # https://developer.github.com/v3/search/#search-issues - def pull_requests_for_commit(token, repo, commit_hash) + def pull_requests_for_commit(repo, commit_hash) query = "#{commit_hash}+type:pr+repo:#{repo}" say "Searching github pull requests for #{commit_hash}" - response = RestClient::Request.new(:url => "https://api.github.com/search/issues?q=#{query}", :method => "GET", :headers => {:accept => :json, :content_type => :json, 'Authorization' => "token #{token}"}).execute - JSON.parse response.body - rescue RestClient::Exception => e - process_error e - throw e + github_api_request "GET", "search/issues?q=#{query}" end - def assign_pull_request(token, branch, assignee, data) + def assign_pull_request(branch, assignee, data) issue_payload = { :title => branch, :assignee => assignee }.to_json - RestClient::Request.new(:url => data['issue_url'], :method => "PATCH", :payload => issue_payload, :headers => {:accept => :json, :content_type => :json, 'Authorization' => "token #{token}"}).execute - rescue RestClient::Exception => e - data = JSON.parse e.http_body - say "Failed to assign pull request: #{data['message']}", :red - end - - def process_error(e) - data = JSON.parse e.http_body - say "Failed to create pull request: #{data['message']}", :red + github_api_request "PATCH", data['issue_url'], issue_payload + rescue => e + say "Failed to assign pull request: #{e.message}", :red end # @returns [String] socialcast username to assign the review to @@ -94,6 +79,43 @@ def github_review_buddy(current_user) end end + # @returns [String] github username responsible for the track + # @returns [nil] when user not found + def github_track_reviewer(track) + github_username_for_socialcast_username(socialcast_track_reviewer(track)) + end + + # @returns [String] Socialcast username responsible for the track + # @returns [nil] when user not found + def socialcast_track_reviewer(track) + specialty_reviewers.values.each do |reviewer_hash| + return reviewer_hash['socialcast_username'] if reviewer_hash['label'].to_s.downcase == track.downcase + end + nil + end + + # @returns [String] github username corresponding to the Socialcast username + # @returns [nil] when user not found + def github_username_for_socialcast_username(socialcast_username) + return if socialcast_username.nil? || socialcast_username == "" + + review_buddies.each_pair do |github_username, review_buddy_hash| + return github_username if review_buddy_hash['socialcast_username'] == socialcast_username + end + end + + def github_api_request(method, path, payload = nil) + url = path.include?('http') ? path : "https://api.github.com/#{path}" + JSON.parse RestClient::Request.new(:url => url, :method => method, :payload => payload, :headers => { :accept => :json, :content_type => :json, 'Authorization' => "token #{authorization_token}", :user_agent => 'socialcast-git-extensions' }).execute + rescue RestClient::Exception => e + process_error e + throw e + end + + def process_error(e) + data = JSON.parse e.http_body + say "GitHub request failed: #{data['message']}", :red + end end end end diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index 380f4a4..3e7876c 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -14,10 +14,10 @@ def run_cmd(cmd) end def stub_message(message_body, params = {}) - json_body = { :message => { :body => message_body }.merge!(params) } + json_body = { :message => params.merge!(:body => message_body) } stub_request(:post, "https://testuser:testpassword@testdomain/api/messages.json") - .with(:body => json_body) + .with(:body => json_body.to_json) .to_return(:status => 200, :body => '', :headers => {}) end @@ -430,6 +430,407 @@ def stub_message(message_body, params = {}) end end + describe '#backportpr' do + before do + # https://developer.github.com/v3/search/#search-issues + pr_response = { + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59", + "id" => 13712197, + "html_url" => "https://github.com/socialcast/socialcast-git-extensions/pull/59", + "diff_url" => "https://github.com/socialcast/socialcast-git-extensions/pull/59.diff", + "patch_url" => "https://github.com/socialcast/socialcast-git-extensions/pull/59.patch", + "issue_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/59", + "number" => 59, + "state" => "closed", + "title" => "additional-notifications", + "user" => { + "login" => "MikeSilvis", + "id" => 152323, + "avatar_url" => "https://avatars.githubusercontent.com/u/152323?", + "gravatar_id" => "1bb5f2e12dcbfb8c103689f4ae94f431", + "url" => "https://api.github.com/users/MikeSilvis", + "html_url" => "https://github.com/MikeSilvis", + "followers_url" => "https://api.github.com/users/MikeSilvis/followers", + "following_url" => "https://api.github.com/users/MikeSilvis/following{/other_user}", + "gists_url" => "https://api.github.com/users/MikeSilvis/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/MikeSilvis/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/MikeSilvis/subscriptions", + "organizations_url" => "https://api.github.com/users/MikeSilvis/orgs", + "repos_url" => "https://api.github.com/users/MikeSilvis/repos", + "events_url" => "https://api.github.com/users/MikeSilvis/events{/privacy}", + "received_events_url" => "https://api.github.com/users/MikeSilvis/received_events", + "type" => "User", + "site_admin" => false + }, + "body" => "simply testing this out", + "created_at" => "2014-03-18T22:39:37Z", + "updated_at" => "2014-03-18T22:40:18Z", + "closed_at" => "2014-03-18T22:39:46Z", + "merged_at" => nil, + "merge_commit_sha" => "f73009f4eb245c84da90e8abf9be846c58bc1e3b", + "assignee" => nil, + "milestone" => nil, + "commits_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/commits", + "review_comments_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/comments", + "review_comment_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/comments/{number}", + "comments_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/59/comments", + "statuses_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/statuses/5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "head" => { + "label" => "socialcast:additional-notifications", + "ref" => "additional-notifications", + "sha" => "5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "user" => { + "login" => "socialcast", + "id" => 57931, + "avatar_url" => "https://avatars.githubusercontent.com/u/57931?", + "gravatar_id" => "489ec347da22410e9770ea022e6e2038", + "url" => "https://api.github.com/users/socialcast", + "html_url" => "https://github.com/socialcast", + "followers_url" => "https://api.github.com/users/socialcast/followers", + "following_url" => "https://api.github.com/users/socialcast/following{/other_user}", + "gists_url" => "https://api.github.com/users/socialcast/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/socialcast/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/socialcast/subscriptions", + "organizations_url" => "https://api.github.com/users/socialcast/orgs", + "repos_url" => "https://api.github.com/users/socialcast/repos", + "events_url" => "https://api.github.com/users/socialcast/events{/privacy}", + "received_events_url" => "https://api.github.com/users/socialcast/received_events", + "type" => "Organization", + "site_admin" => false + }, + "repo" => { + "id" => 1000634, + "name" => "socialcast-git-extensions", + "full_name" => "socialcast/socialcast-git-extensions", + "owner" => { + "login" => "socialcast", + "id" => 57931, + "avatar_url" => "https://avatars.githubusercontent.com/u/57931?", + "gravatar_id" => "489ec347da22410e9770ea022e6e2038", + "url" => "https://api.github.com/users/socialcast", + "html_url" => "https://github.com/socialcast", + "followers_url" => "https://api.github.com/users/socialcast/followers", + "following_url" => "https://api.github.com/users/socialcast/following{/other_user}", + "gists_url" => "https://api.github.com/users/socialcast/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/socialcast/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/socialcast/subscriptions", + "organizations_url" => "https://api.github.com/users/socialcast/orgs", + "repos_url" => "https://api.github.com/users/socialcast/repos", + "events_url" => "https://api.github.com/users/socialcast/events{/privacy}", + "received_events_url" => "https://api.github.com/users/socialcast/received_events", + "type" => "Organization", + "site_admin" => false + }, + "private" => false, + "html_url" => "https://github.com/socialcast/socialcast-git-extensions", + "description" => "", + "fork" => false, + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions", + "forks_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/forks", + "keys_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/keys{/key_id}", + "collaborators_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/collaborators{/collaborator}", + "teams_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/teams", + "hooks_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/hooks", + "issue_events_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/events{/number}", + "events_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/events", + "assignees_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/assignees{/user}", + "branches_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/branches{/branch}", + "tags_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/tags", + "blobs_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/blobs{/sha}", + "git_tags_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/tags{/sha}", + "git_refs_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/refs{/sha}", + "trees_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/trees{/sha}", + "statuses_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/statuses/{sha}", + "languages_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/languages", + "stargazers_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/stargazers", + "contributors_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/contributors", + "subscribers_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/subscribers", + "subscription_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/subscription", + "commits_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/commits{/sha}", + "git_commits_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/commits{/sha}", + "comments_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/comments{/number}", + "issue_comment_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/comments/{number}", + "contents_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/contents/{+path}", + "compare_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/compare/{base}...{head}", + "merges_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/merges", + "archive_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/{archive_format}{/ref}", + "downloads_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/downloads", + "issues_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues{/number}", + "pulls_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls{/number}", + "milestones_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/milestones{/number}", + "notifications_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/notifications{?since,all,participating}", + "labels_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/labels{/name}", + "releases_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/releases{/id}", + "created_at" => "2010-10-18T21:23:25Z", + "updated_at" => "2014-05-12T20:03:30Z", + "pushed_at" => "2014-05-12T20:03:31Z", + "git_url" => "git://github.com/socialcast/socialcast-git-extensions.git", + "ssh_url" => "git@github.com:socialcast/socialcast-git-extensions.git", + "clone_url" => "https://github.com/socialcast/socialcast-git-extensions.git", + "svn_url" => "https://github.com/socialcast/socialcast-git-extensions", + "homepage" => "", + "size" => 1719, + "stargazers_count" => 3, + "watchers_count" => 3, + "language" => "Ruby", + "has_issues" => true, + "has_downloads" => true, + "has_wiki" => true, + "forks_count" => 6, + "mirror_url" => nil, + "open_issues_count" => 13, + "forks" => 6, + "open_issues" => 13, + "watchers" => 3, + "default_branch" => "master" + } + }, + "base" => { + "label" => "socialcast:master", + "ref" => "master", + "sha" => "1baae2de301c43d44297647f3f9c1e06697748ad", + "user" => { + "login" => "socialcast", + "id" => 57931, + "avatar_url" => "https://avatars.githubusercontent.com/u/57931?", + "gravatar_id" => "489ec347da22410e9770ea022e6e2038", + "url" => "https://api.github.com/users/socialcast", + "html_url" => "https://github.com/socialcast", + "followers_url" => "https://api.github.com/users/socialcast/followers", + "following_url" => "https://api.github.com/users/socialcast/following{/other_user}", + "gists_url" => "https://api.github.com/users/socialcast/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/socialcast/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/socialcast/subscriptions", + "organizations_url" => "https://api.github.com/users/socialcast/orgs", + "repos_url" => "https://api.github.com/users/socialcast/repos", + "events_url" => "https://api.github.com/users/socialcast/events{/privacy}", + "received_events_url" => "https://api.github.com/users/socialcast/received_events", + "type" => "Organization", + "site_admin" => false + }, + "repo" => { + "id" => 1000634, + "name" => "socialcast-git-extensions", + "full_name" => "socialcast/socialcast-git-extensions", + "owner" => { + "login" => "socialcast", + "id" => 57931, + "avatar_url" => "https://avatars.githubusercontent.com/u/57931?", + "gravatar_id" => "489ec347da22410e9770ea022e6e2038", + "url" => "https://api.github.com/users/socialcast", + "html_url" => "https://github.com/socialcast", + "followers_url" => "https://api.github.com/users/socialcast/followers", + "following_url" => "https://api.github.com/users/socialcast/following{/other_user}", + "gists_url" => "https://api.github.com/users/socialcast/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/socialcast/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/socialcast/subscriptions", + "organizations_url" => "https://api.github.com/users/socialcast/orgs", + "repos_url" => "https://api.github.com/users/socialcast/repos", + "events_url" => "https://api.github.com/users/socialcast/events{/privacy}", + "received_events_url" => "https://api.github.com/users/socialcast/received_events", + "type" => "Organization", + "site_admin" => false + }, + "private" => false, + "html_url" => "https://github.com/socialcast/socialcast-git-extensions", + "description" => "", + "fork" => false, + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions", + "forks_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/forks", + "keys_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/keys{/key_id}", + "collaborators_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/collaborators{/collaborator}", + "teams_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/teams", + "hooks_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/hooks", + "issue_events_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/events{/number}", + "events_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/events", + "assignees_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/assignees{/user}", + "branches_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/branches{/branch}", + "tags_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/tags", + "blobs_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/blobs{/sha}", + "git_tags_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/tags{/sha}", + "git_refs_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/refs{/sha}", + "trees_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/trees{/sha}", + "statuses_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/statuses/{sha}", + "languages_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/languages", + "stargazers_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/stargazers", + "contributors_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/contributors", + "subscribers_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/subscribers", + "subscription_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/subscription", + "commits_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/commits{/sha}", + "git_commits_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/commits{/sha}", + "comments_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/comments{/number}", + "issue_comment_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/comments/{number}", + "contents_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/contents/{+path}", + "compare_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/compare/{base}...{head}", + "merges_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/merges", + "archive_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/{archive_format}{/ref}", + "downloads_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/downloads", + "issues_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues{/number}", + "pulls_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls{/number}", + "milestones_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/milestones{/number}", + "notifications_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/notifications{?since,all,participating}", + "labels_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/labels{/name}", + "releases_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/releases{/id}", + "created_at" => "2010-10-18T21:23:25Z", + "updated_at" => "2014-05-12T20:03:30Z", + "pushed_at" => "2014-05-12T20:03:31Z", + "git_url" => "git://github.com/socialcast/socialcast-git-extensions.git", + "ssh_url" => "git@github.com:socialcast/socialcast-git-extensions.git", + "clone_url" => "https://github.com/socialcast/socialcast-git-extensions.git", + "svn_url" => "https://github.com/socialcast/socialcast-git-extensions", + "homepage" => "", + "size" => 1719, + "stargazers_count" => 3, + "watchers_count" => 3, + "language" => "Ruby", + "has_issues" => true, + "has_downloads" => true, + "has_wiki" => true, + "forks_count" => 6, + "mirror_url" => nil, + "open_issues_count" => 13, + "forks" => 6, + "open_issues" => 13, + "watchers" => 3, + "default_branch" => "master" + } + }, + "_links" => { + "self" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59" + }, + "html" => { + "href" => "https://github.com/socialcast/socialcast-git-extensions/pull/59" + }, + "issue" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/59" + }, + "comments" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/issues/59/comments" + }, + "review_comments" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/comments" + }, + "review_comment" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/comments/{number}" + }, + "commits" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/commits" + }, + "statuses" => { + "href" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/statuses/5e30d5af3f4d1bb3a34cc97568299be028b65f6f" + } + }, + "merged" => false, + "mergeable" => true, + "mergeable_state" => "unstable", + "merged_by" => nil, + "comments" => 0, + "review_comments" => 0, + "commits" => 1, + "additions" => 14, + "deletions" => 2, + "changed_files" => 2 + } + + commits_response = [ + { + "sha" => "5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "commit" => { + "author" => { + "name" => "Mike Silvis", + "email" => "mikesilvis@gmail.com", + "date" => "2014-03-18T22:39:12Z" + }, + "committer" => { + "name" => "Mike Silvis", + "email" => "mikesilvis@gmail.com", + "date" => "2014-03-18T22:39:12Z" + }, + "message" => "adding the ability to specify additional reviewers", + "tree" => { + "sha" => "dcf05deb22223997a5184cd3a1866249f3e73e3b", + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/trees/dcf05deb22223997a5184cd3a1866249f3e73e3b" + }, + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/git/commits/5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "comment_count" => 0 + }, + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/commits/5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "html_url" => "https://github.com/socialcast/socialcast-git-extensions/commit/5e30d5af3f4d1bb3a34cc97568299be028b65f6f", + "comments_url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/commits/5e30d5af3f4d1bb3a34cc97568299be028b65f6f/comments", + "author" => { + "login" => "MikeSilvis", + "id" => 152323, + "avatar_url" => "https://avatars.githubusercontent.com/u/152323?", + "gravatar_id" => "1bb5f2e12dcbfb8c103689f4ae94f431", + "url" => "https://api.github.com/users/MikeSilvis", + "html_url" => "https://github.com/MikeSilvis", + "followers_url" => "https://api.github.com/users/MikeSilvis/followers", + "following_url" => "https://api.github.com/users/MikeSilvis/following{/other_user}", + "gists_url" => "https://api.github.com/users/MikeSilvis/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/MikeSilvis/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/MikeSilvis/subscriptions", + "organizations_url" => "https://api.github.com/users/MikeSilvis/orgs", + "repos_url" => "https://api.github.com/users/MikeSilvis/repos", + "events_url" => "https://api.github.com/users/MikeSilvis/events{/privacy}", + "received_events_url" => "https://api.github.com/users/MikeSilvis/received_events", + "type" => "User", + "site_admin" => false + }, + "committer" => { + "login" => "MikeSilvis", + "id" => 152323, + "avatar_url" => "https://avatars.githubusercontent.com/u/152323?", + "gravatar_id" => "1bb5f2e12dcbfb8c103689f4ae94f431", + "url" => "https://api.github.com/users/MikeSilvis", + "html_url" => "https://github.com/MikeSilvis", + "followers_url" => "https://api.github.com/users/MikeSilvis/followers", + "following_url" => "https://api.github.com/users/MikeSilvis/following{/other_user}", + "gists_url" => "https://api.github.com/users/MikeSilvis/gists{/gist_id}", + "starred_url" => "https://api.github.com/users/MikeSilvis/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/MikeSilvis/subscriptions", + "organizations_url" => "https://api.github.com/users/MikeSilvis/orgs", + "repos_url" => "https://api.github.com/users/MikeSilvis/repos", + "events_url" => "https://api.github.com/users/MikeSilvis/events{/privacy}", + "received_events_url" => "https://api.github.com/users/MikeSilvis/received_events", + "type" => "User", + "site_admin" => false + }, + "parents" => [ + { + "sha" => "1baae2de301c43d44297647f3f9c1e06697748ad", + "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/commits/1baae2de301c43d44297647f3f9c1e06697748ad", + "html_url" => "https://github.com/socialcast/socialcast-git-extensions/commit/1baae2de301c43d44297647f3f9c1e06697748ad" + } + ] + } + ] + + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59"). + with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions' }). + to_return(:status => 200, :body => pr_response.to_json, :headers => {}) + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/commits"). + with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions' }). + to_return(:status => 200, :body => commits_response.to_json, :headers => {}) + stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls"). + with(:body => "{\"title\":\"backport_59_to_v1.x\",\"base\":\"v1.x\",\"head\":\"backport_59_to_v1.x\",\"body\":\"Backport #59 to https://github.com/socialcast/socialcast-git-extensions/tree/v1.x\\n***\\nsimply testing this out\"}", + :headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent'=>'socialcast-git-extensions' }). + to_return(:status => 200, :body => '{"html_url": "https://github.com/socialcast/socialcast-git-extensions/pulls/60"}', :headers => { 'Content-Type' => 'application/json' }) + + stub_message "#reviewrequest backport #59 to v1.x #scgitx\n\n/cc @SocialcastDevelopers", :url => 'https://github.com/socialcast/socialcast-git-extensions/pulls/60', :message_type => 'review_request' + + Socialcast::Gitx::CLI.any_instance.should_receive(:backportpr).and_call_original + Socialcast::Gitx::CLI.any_instance.stub(:say).with do |message| + @said_text = @said_text.to_s + message + end + Socialcast::Gitx::CLI.start ['backportpr', '59', 'v1.x'] + end + it 'creates a branch based on v1.x and cherry-picks in PR 59' do + @said_text.should include "Creating pull request for backport_59_to_v1.x against v1.x in socialcast/socialcast-git-extensions" + @said_text.should include "Message has been posted: http://demo.socialcast.com/messages/123" + end + end + describe '#findpr' do before do # https://developer.github.com/v3/search/#search-issues @@ -485,7 +886,7 @@ def stub_message(message_body, params = {}) } stub_request(:get, "https://api.github.com/search/issues?q=abc123%20type:pr%20repo:socialcast/socialcast-git-extensions"). - with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip, deflate', 'Authorization'=>/token\s\w+/, 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). + with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions'}). to_return(:status => 200, :body => stub_response.to_json, :headers => {}) Socialcast::Gitx::CLI.any_instance.should_receive(:findpr).and_call_original Socialcast::Gitx::CLI.any_instance.stub(:say).with do |message| @@ -505,12 +906,13 @@ def stub_message(message_body, params = {}) before do Socialcast::Gitx::CLI.any_instance.stub(:config_file).and_return(Pathname('')) end - context 'when description != null' do + context 'when description != nil' do before do stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls"). to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1"}), :headers => {}) - stub_message "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\ntesting\n\n", :url => 'http://github.com/repo/project/pulls/1', :message_type => 'review_request' + stub_message "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\ntesting\n\n1 file changed", :url => 'http://github.com/repo/project/pulls/1', :message_type => 'review_request' + Socialcast::Gitx::CLI.any_instance.stub(:changelog_summary).and_return('1 file changed') Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] end it 'should create github pull request' do end # see expectations @@ -532,8 +934,9 @@ def stub_message(message_body, params = {}) stub_request(:patch, "http://github.com/repos/repo/project/issues/1").to_return(:status => 200) end context 'and additional reviewers are specified' do - let(:message_body) { "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\n\nAssigned additionally to @JohnSmith for API review\n\ntesting\n\n" } + let(:message_body) { "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\n\nAssigned additionally to @JohnSmith for API review\n\ntesting\n\n1 file changed" } before do + Socialcast::Gitx::CLI.any_instance.stub(:changelog_summary).and_return('1 file changed') # The Review Buddy should be @mentioned in the message stub_message message_body, :url => 'http://github.com/repo/project/pulls/1', :message_type => 'review_request' Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-a', 'a'] @@ -549,8 +952,9 @@ def stub_message(message_body, params = {}) end end context 'and additional reviewers are not specified' do - let(:message_body) { "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\ntesting\n\n" } + let(:message_body) { "#reviewrequest for FOO #scgitx\n\n/cc @SocialcastDevelopers\n\ntesting\n\n1 file changed" } before do + Socialcast::Gitx::CLI.any_instance.stub(:changelog_summary).and_return('1 file changed') # The Review Buddy should be @mentioned in the message stub_message message_body, :url => 'http://github.com/repo/project/pulls/1', :message_type => 'review_request' Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s']