From e958ed81f2a3d965cc4e34ea430fbc1d8cabe82a Mon Sep 17 00:00:00 2001 From: Michiel Sikkes Date: Sun, 20 Jan 2013 19:43:28 +0100 Subject: [PATCH] add `ci-status` command for the GitHub Status API --- README.md | 6 +++++ features/ci_status.feature | 47 ++++++++++++++++++++++++++++++++++++++ features/steps.rb | 12 ++++++++++ lib/hub/commands.rb | 43 +++++++++++++++++++++++++++++++++- lib/hub/context.rb | 4 ++++ lib/hub/github_api.rb | 8 +++++++ test/hub_test.rb | 24 +++++++++++++++++++ 7 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 features/ci_status.feature diff --git a/README.md b/README.md index 809b829d..e3f232d8 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,12 @@ superpowers: $ hub submodule add -b ryppl --name pip ryppl/pip vendor/pip > git submodule add -b ryppl --name pip git://github.com/ryppl/pip.git vendor/pip +### git ci-status + + $ hub ci-status + > (prints commit ci state and exits with exitcode) + > One of: success (0), error (1), failure (1), pending (2), no status (3) + ### git help diff --git a/features/ci_status.feature b/features/ci_status.feature new file mode 100644 index 00000000..2685e155 --- /dev/null +++ b/features/ci_status.feature @@ -0,0 +1,47 @@ +Feature: hub ci-status + + Background: + Given I am in "pencilbox" git repo + And I am "michiels" on github.com with OAuth token "OTOKEN" + + Scenario: Fetch commit SHA + Given the "origin" remote has url "git://github.com/michiels/pencilbox.git" + Given the GitHub API server: + """ + get('/repos/michiels/pencilbox/statuses/the_sha') { + json [ { :state => "success" } ] + } + """ + When I run `hub ci-status the_sha` + Then the output should contain exactly "success\n" + And the exit status should be 0 + + Scenario: Exit status 1 for 'error' and 'failure' + Given the "origin" remote has url "git://github.com/michiels/pencilbox.git" + Given a HEAD commit with GitHub status "error" + When I run `hub ci-status` + Then the exit status should be 1 + + Scenario: Use HEAD when no sha given + Given the "origin" remote has url "git://github.com/michiels/pencilbox.git" + Given a HEAD commit with GitHub status "pending" + When I run `hub ci-status` + Then the exit status should be 2 + + Scenario: Exit status 3 for no statuses available + Given the "origin" remote has url "git://github.com/michiels/pencilbox.git" + Given the GitHub API server: + """ + get('/repos/michiels/pencilbox/statuses/the_sha') { + json [ ] + } + """ + When I run `hub ci-status the_sha` + Then the output should contain exactly "no status\n" + And the exit status should be 3 + + Scenario: Non-GitHub repo + Given the "origin" remote has url "mygh:Manganeez/repo.git" + When I run `hub ci-status` + Then the stderr should contain "Aborted: the origin remote doesn't point to a GitHub repository.\n" + And the exit status should be 1 \ No newline at end of file diff --git a/features/steps.rb b/features/steps.rb index ecfd17cb..645c34fa 100644 --- a/features/steps.rb +++ b/features/steps.rb @@ -83,6 +83,18 @@ Given /^the GitHub API server:$/ do |endpoints_str| set_env 'HUB_TEST_HOST', "127.0.0.1:#{@server.port}" end +Given /^a HEAD commit with GitHub status "([^"]*)"$/ do |status| + empty_commit + commit_sha = run_silent %(git rev-parse HEAD) + status_endpoint = <<-EOS + get('/repos/michiels/pencilbox/statuses/#{commit_sha}') { + json [ { :state => "#{status}" } ] + } + EOS + + step %{the GitHub API server:}, status_endpoint +end + Then /^shell$/ do in_current_dir do system '/bin/bash -i' diff --git a/lib/hub/commands.rb b/lib/hub/commands.rb index 169908b9..5e2e31ba 100644 --- a/lib/hub/commands.rb +++ b/lib/hub/commands.rb @@ -38,7 +38,7 @@ module Hub OWNER_RE = /[a-zA-Z0-9-]+/ NAME_WITH_OWNER_RE = /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/ - CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request] + CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request ci-status] def run(args) slurp_global_flags(args) @@ -70,6 +70,46 @@ module Hub abort "fatal: #{err.message}" end + + # $ hub ci-status + # $ hub ci-status 6f6d9797f9d6e56c3da623a97cfc3f45daf9ae5f + def ci_status(args) + args.shift + head_project = local_repo.current_project + + unless head_project + abort "Aborted: the origin remote doesn't point to a GitHub repository." + end + + sha = args.shift + + if sha.nil? + sha = local_repo.head_sha + end + + statuses = api_client.statuses(head_project, sha) + + if statuses.any? + commit_state = statuses.first['state'] + else + commit_state = 'no status' + end + + case commit_state + when 'success' + exitcode = 0 + when 'failure', 'error' + exitcode = 1 + when 'pending' + exitcode = 2 + else + exitcode = 3 + end + + puts commit_state + exit(exitcode) + end + # $ hub pull-request # $ hub pull-request "My humble contribution" # $ hub pull-request -i 92 @@ -809,6 +849,7 @@ GitHub Commands: create Create this repository on GitHub and add GitHub as origin browse Open a GitHub page in the default browser compare Open a compare page on GitHub + ci-status Show the CI status of a commit See 'git help ' for more information on a specific command. help diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 551798e3..2cb479ed 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -133,6 +133,10 @@ module Hub end end + def head_sha + git_command('rev-parse -q HEAD') + end + def repo_host project = main_project and project.host end diff --git a/lib/hub/github_api.rb b/lib/hub/github_api.rb index 9cfd7179..651109a1 100644 --- a/lib/hub/github_api.rb +++ b/lib/hub/github_api.rb @@ -110,6 +110,14 @@ module Hub res.data end + def statuses project, sha + res = get "https://%s/repos/%s/%s/statuses/%s" % + [api_host(project.host), project.owner, project.name, sha] + + res.error! unless res.success? + res.data + end + # Methods for performing HTTP requests # # Requires access to a `config` object that implements: diff --git a/test/hub_test.rb b/test/hub_test.rb index 0d1fa21e..4b7ba46a 100644 --- a/test/hub_test.rb +++ b/test/hub_test.rb @@ -257,6 +257,30 @@ class HubTest < Test::Unit::TestCase "push origin,staging master new-feature" end + def test_ci_status_use_last_sha + stub_command_output "rev-parse -q HEAD", "head_sha" + stub_request(:get, "https://api.github.com/repos/defunkt/hub/statuses/head_sha").to_return(:body => Hub::JSON.generate([ { :state => "success" } ])) + + expected = "success\n" + assert_output expected, "ci-status" + end + + def test_ci_status_with_sha + stub_request(:get, "https://api.github.com/repos/defunkt/hub/statuses/sha").to_return(:body => Hub::JSON.generate([ { :state => "failure" } ])) + + expected = "failure\n" + assert_output expected, "ci-status sha" + end + + def test_ci_status_without_github_project + stub_repo_url('gh:singingwolfboy/sekrit.git') + stub_branch('refs/heads/feature') + stub_tracking('feature', 'origin', 'feature') + + expected = "Aborted: the origin remote doesn't point to a GitHub repository.\n" + assert_output expected, "ci-status" + end + def test_pullrequest expected = "Aborted: head branch is the same as base (\"master\")\n" << "(use `-h ` to specify an explicit pull request head)\n"