Skip to content

Commit

Permalink
Merge branch 'master' into staging
Browse files Browse the repository at this point in the history
* master:
  bump version
  tweak status descriptions
  fix dedent intentation with blank lines
  Add git review [--approve|--reject] flags
  add docs
  do not set review status when pull request first created
  set description for peer review commit status
  Create pending status for code review bumps.
  Fix namespace of Thegarage::Gitx::Github module
  • Loading branch information
wireframe committed Dec 11, 2014
2 parents 3756e90 + ee7debc commit a98e6e4
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 132 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ options:
* `--assign` or `-a` = assign pull request to github user
* `--open` or `-o` = open pull request in default web browser.
* `--bump` or `-b` = bump an existing pull request by posting a comment to re-review new changes
* `--approve` = approve/signoff on pull request (with optional feedback)
* `--reject` = reject pull request (with details)

NOTE: the `--bump` option will also update the pull request commit status to mark the branch as 'pending peer review'.
This setting is cleared when a reviewer approves or rejects the pull request.

## git release

Expand Down
2 changes: 1 addition & 1 deletion lib/thegarage/gitx/cli/integrate_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Thegarage
module Gitx
module Cli
class IntegrateCommand < BaseCommand
include Github
include Thegarage::Gitx::Github
desc 'integrate', 'integrate the current branch into one of the aggregate development branches (default = staging)'
method_option :resume, :type => :string, :aliases => '-r', :desc => 'resume merging of feature-branch'
def integrate(integration_branch = 'staging')
Expand Down
2 changes: 1 addition & 1 deletion lib/thegarage/gitx/cli/release_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Thegarage
module Gitx
module Cli
class ReleaseCommand < BaseCommand
include Github
include Thegarage::Gitx::Github

desc 'release', 'release the current branch to production'
method_option :cleanup, :type => :boolean, :desc => 'cleanup merged branches after release'
Expand Down
60 changes: 51 additions & 9 deletions lib/thegarage/gitx/cli/review_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,42 @@ module Thegarage
module Gitx
module Cli
class ReviewCommand < BaseCommand
include Github
include Thegarage::Gitx::Github

BUMP_COMMENT_TEMPLATE = <<-EOS.dedent
[gitx] review bump :tada:
### Changelog Summary
EOS
APPROVAL_COMMENT_TEMPLATE = <<-EOS.dedent
[gitx] review approved :shipit:
### Feedback
### Follow-up Items
EOS
REJECTION_COMMENT_TEMPLATE = <<-EOS.dedent
[gitx] review rejected
### Feedback
EOS

desc "review", "Create or update a pull request on github"
method_option :description, :type => :string, :aliases => '-d', :desc => 'pull request description'
method_option :assignee, :type => :string, :aliases => '-a', :desc => 'pull request assignee'
method_option :open, :type => :boolean, :aliases => '-o', :desc => 'open the pull request in a web browser'
method_option :bump, :type => :boolean, :aliases => '-b', :desc => 'bump an existing pull request by posting a comment to re-review new changes'
method_option :approve, :type => :boolean, :desc => 'approve the pull request an post comment on pull request'
method_option :reject, :type => :boolean, :desc => 'reject the pull request an post comment on pull request'
# @see http://developer.github.com/v3/pulls/
def review
fail 'Github authorization token not found' unless authorization_token

branch = current_branch.name
pull_request = find_or_create_pull_request(branch)
create_bump_comment(pull_request) if options[:bump]
bump_pull_request(pull_request) if options[:bump]
approve_pull_request(pull_request) if options[:approve]
reject_pull_request(pull_request) if options[:reject]
assign_pull_request(pull_request) if options[:assignee]

run_cmd "open #{pull_request.html_url}" if options[:open]
Expand All @@ -42,15 +64,35 @@ def assign_pull_request(pull_request)
github_client.update_issue(github_slug, pull_request.number, title, body, options)
end

def create_bump_comment(pull_request)
comment_template = []
comment_template << '[gitx] review bump :tada:'
comment_template << ''
comment_template << '### Summary of Changes'
def bump_pull_request(pull_request)
comment = get_editor_input(BUMP_COMMENT_TEMPLATE)
github_client.add_comment(github_slug, pull_request.number, comment)

set_review_status('pending', 'Peer review in progress')
end

comment = ask_editor(comment_template.join("\n"), repo.config['core.editor'])
comment = comment.chomp.strip
def reject_pull_request(pull_request)
comment = get_editor_input(REJECTION_COMMENT_TEMPLATE)
github_client.add_comment(github_slug, pull_request.number, comment)

set_review_status('failure', 'Peer review rejected')
end

def approve_pull_request(pull_request)
comment = get_editor_input(APPROVAL_COMMENT_TEMPLATE)
github_client.add_comment(github_slug, pull_request.number, comment)

set_review_status('success', 'Peer review approved')
end

def get_editor_input(template)
text = ask_editor(template, repo.config['core.editor'])
text = text.chomp.strip
end

def set_review_status(state, description)
latest_commit = repo.head.target_id
update_review_status(latest_commit, state, description)
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/thegarage/gitx/extensions/string.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class String
# @see http://api.rubyonrails.org/classes/String.html#method-i-strip_heredoc
def undent
a = $1 if match(/\A(\s+)(.*\n)(?:\1.*\n)*\z/)
gsub(/^#{a}/,'')
indent = scan(/^[ \t]*(?=\S)/).min.size || 0
gsub(/^[ \t]{#{indent}}/, '')
end
alias :dedent :undent

Expand Down
238 changes: 121 additions & 117 deletions lib/thegarage/gitx/github.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,138 +2,142 @@

module Thegarage
module Gitx
module Cli
module Github
CLIENT_URL = 'https://github.com/thegarage/thegarage-gitx'
PULL_REQUEST_FOOTER = <<-EOS.dedent
# Pull Request Protips(tm):
# * Include description of how this change accomplishes the task at hand.
# * Use GitHub flavored Markdown http://github.github.com/github-flavored-markdown/
# * Review CONTRIBUTING.md for recommendations of artifacts, links, images, screencasts, etc.
#
# This footer will automatically be stripped from the pull request description
EOS

def find_or_create_pull_request(branch)
pull_request = find_pull_request(branch)
pull_request ||= begin
execute_command(UpdateCommand, :update)
pull_request = create_pull_request(branch)
say 'Created pull request: '
say pull_request.html_url, :green

pull_request
end
module Github
REVIEW_CONTEXT = 'peer_review'
CLIENT_URL = 'https://github.com/thegarage/thegarage-gitx'
PULL_REQUEST_FOOTER = <<-EOS.dedent
# Pull Request Protips(tm):
# * Include description of how this change accomplishes the task at hand.
# * Use GitHub flavored Markdown http://github.github.com/github-flavored-markdown/
# * Review CONTRIBUTING.md for recommendations of artifacts, links, images, screencasts, etc.
#
# This footer will automatically be stripped from the pull request description
EOS

def find_or_create_pull_request(branch)
pull_request = find_pull_request(branch)
pull_request ||= begin
execute_command(Thegarage::Gitx::Cli::UpdateCommand, :update)
pull_request = create_pull_request(branch)
say 'Created pull request: '
say pull_request.html_url, :green

pull_request
end
pull_request
end

# @return [Sawyer::Resource] data structure of pull request info if found
# @return nil if no pull request found
def find_pull_request(branch)
head_reference = "#{github_organization}:#{branch}"
params = {
head: head_reference,
state: 'open'
}
pull_requests = github_client.pull_requests(github_slug, params)
pull_requests.first
end
# @return [Sawyer::Resource] data structure of pull request info if found
# @return nil if no pull request found
def find_pull_request(branch)
head_reference = "#{github_organization}:#{branch}"
params = {
head: head_reference,
state: 'open'
}
pull_requests = github_client.pull_requests(github_slug, params)
pull_requests.first
end

# Get the current commit status of a branch
# @see https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
def branch_status(branch)
response = github_client.status(github_slug, branch)
response.state
end
# Get the current commit status of a branch
# @see https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
def branch_status(branch)
response = github_client.status(github_slug, branch)
response.state
end

# @see http://developer.github.com/v3/pulls/
def create_pull_request(branch)
say "Creating pull request for "
say "#{branch} ", :green
say "against "
say "#{Thegarage::Gitx::BASE_BRANCH} ", :green
say "in "
say github_slug, :green

title = branch
body = pull_request_body(branch)
github_client.create_pull_request(github_slug, Thegarage::Gitx::BASE_BRANCH, branch, title, body)
end
# Update build status with peer review status
def update_review_status(commit_sha, state, description)
github_client.create_status(github_slug, commit_sha, state, context: REVIEW_CONTEXT, description: description)
end

def pull_request_body(branch)
changelog = run_cmd("git log #{Thegarage::Gitx::BASE_BRANCH}...#{branch} --reverse --no-merges --pretty=format:'* %s%n%b'")
description = options[:description]
# @see http://developer.github.com/v3/pulls/
def create_pull_request(branch)
say "Creating pull request for "
say "#{branch} ", :green
say "against "
say "#{Thegarage::Gitx::BASE_BRANCH} ", :green
say "in "
say github_slug, :green

title = branch
body = pull_request_body(branch)
github_client.create_pull_request(github_slug, Thegarage::Gitx::BASE_BRANCH, branch, title, body)
end

description_template = []
description_template << "#{description}\n" if description
description_template << '### Changelog'
description_template << changelog
description_template << PULL_REQUEST_FOOTER
def pull_request_body(branch)
changelog = run_cmd("git log #{Thegarage::Gitx::BASE_BRANCH}...#{branch} --reverse --no-merges --pretty=format:'* %s%n%b'")
description = options[:description]

body = ask_editor(description_template.join("\n"), repo.config['core.editor'])
body.gsub(PULL_REQUEST_FOOTER, '').chomp.strip
end
description_template = []
description_template << "#{description}\n" if description
description_template << '### Changelog'
description_template << changelog
description_template << PULL_REQUEST_FOOTER

# token is cached in local git config for future use
# @return [String] auth token stored in git (current repo, user config or installed global settings)
# @see http://developer.github.com/v3/oauth/#scopes
# @see http://developer.github.com/v3/#user-agent-required
def authorization_token
auth_token = repo.config['thegarage.gitx.githubauthtoken']
return auth_token unless auth_token.to_s.blank?

auth_token = create_authorization
repo.config['thegarage.gitx.githubauthtoken'] = auth_token
auth_token
end
body = ask_editor(description_template.join("\n"), repo.config['core.editor'])
body.gsub(PULL_REQUEST_FOOTER, '').chomp.strip
end

def create_authorization
password = ask("Github password for #{username}: ", :echo => false)
say ''
client = Octokit::Client.new(login: username, password: password)
response = client.create_authorization(authorization_request_options)
response.token
end
# token is cached in local git config for future use
# @return [String] auth token stored in git (current repo, user config or installed global settings)
# @see http://developer.github.com/v3/oauth/#scopes
# @see http://developer.github.com/v3/#user-agent-required
def authorization_token
auth_token = repo.config['thegarage.gitx.githubauthtoken']
return auth_token unless auth_token.to_s.blank?

auth_token = create_authorization
repo.config['thegarage.gitx.githubauthtoken'] = auth_token
auth_token
end

def authorization_request_options
timestamp = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S%z')
client_name = "The Garage Git eXtensions - #{github_slug} #{timestamp}"
options = {
:scopes => ['repo'],
:note => client_name,
:note_url => CLIENT_URL
}
two_factor_auth_token = ask("Github two factor authorization token (if enabled): ", :echo => false)
say ''
options[:headers] = {'X-GitHub-OTP' => two_factor_auth_token} if two_factor_auth_token
options
end
def create_authorization
password = ask("Github password for #{username}: ", :echo => false)
say ''
client = Octokit::Client.new(login: username, password: password)
response = client.create_authorization(authorization_request_options)
response.token
end

def github_client
@client ||= Octokit::Client.new(:access_token => authorization_token)
end
def authorization_request_options
timestamp = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S%z')
client_name = "The Garage Git eXtensions - #{github_slug} #{timestamp}"
options = {
:scopes => ['repo'],
:note => client_name,
:note_url => CLIENT_URL
}
two_factor_auth_token = ask("Github two factor authorization token (if enabled): ", :echo => false)
say ''
options[:headers] = {'X-GitHub-OTP' => two_factor_auth_token} if two_factor_auth_token
options
end

# @return [String] github username (ex: 'wireframe') of the current github.user
# @raise error if github.user is not configured
def username
username = repo.config['github.user']
fail "Github user not configured. Run: `git config --global github.user 'me@email.com'`" unless username
username
end
def github_client
@client ||= Octokit::Client.new(:access_token => authorization_token)
end

# @return the github slug for the current repository's remote origin url.
# @example
# git@github.com:socialcast/thegarage/gitx.git #=> thegarage/gitx
# @example
# https://github.com/socialcast/thegarage/gitx.git #=> thegarage/gitx
def github_slug
remote = repo.config['remote.origin.url']
remote.to_s.gsub(/\.git$/,'').split(/[:\/]/).last(2).join('/')
end
# @return [String] github username (ex: 'wireframe') of the current github.user
# @raise error if github.user is not configured
def username
username = repo.config['github.user']
fail "Github user not configured. Run: `git config --global github.user 'me@email.com'`" unless username
username
end

def github_organization
github_slug.split('/').first
end
# @return the github slug for the current repository's remote origin url.
# @example
# git@github.com:socialcast/thegarage/gitx.git #=> thegarage/gitx
# @example
# https://github.com/socialcast/thegarage/gitx.git #=> thegarage/gitx
def github_slug
remote = repo.config['remote.origin.url']
remote.to_s.gsub(/\.git$/,'').split(/[:\/]/).last(2).join('/')
end

def github_organization
github_slug.split('/').first
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/thegarage/gitx/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Thegarage
module Gitx
VERSION = '2.9.0'
VERSION = '2.10.0'
end
end
Loading

0 comments on commit a98e6e4

Please sign in to comment.