git-semaphore 0.0.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +9 -19
- data/.pryrc +6 -0
- data/.ruby-version +1 -1
- data/CODE_OF_CONDUCT.md +49 -0
- data/LICENSE.txt +17 -18
- data/README.md +166 -40
- data/Rakefile +8 -9
- data/bin/console +14 -0
- data/bin/setup +10 -0
- data/exe/git-semaphore +79 -0
- data/git-semaphore.gemspec +15 -19
- data/lib/git/semaphore/api.rb +133 -0
- data/lib/git/semaphore/api_cache.rb +59 -0
- data/lib/git/semaphore/api_enrich.rb +40 -0
- data/lib/git/semaphore/app.rb +121 -0
- data/lib/{git-semaphore → git/semaphore}/banner.rb +0 -0
- data/lib/{git-semaphore → git/semaphore}/copyright.rb +1 -1
- data/lib/{git-semaphore → git/semaphore}/version.rb +1 -1
- data/lib/git/semaphore.rb +77 -0
- metadata +55 -181
- data/.bundle/config +0 -3
- data/.irbrc +0 -13
- data/bin/git-semaphore +0 -115
- data/features/cassettes/cucumber_tags/api_branch_status.json +0 -20
- data/features/cassettes/cucumber_tags/api_project_branches.json +0 -14
- data/features/cassettes/cucumber_tags/api_projects.json +0 -46
- data/features/cassettes/cucumber_tags/vcr_api_branches.yml +0 -70
- data/features/cassettes/cucumber_tags/vcr_api_projects.yml +0 -102
- data/features/cassettes/cucumber_tags/vcr_api_rebuild_last_revision.yml +0 -55
- data/features/cassettes/cucumber_tags/vcr_api_status.yml +0 -76
- data/features/coming_soon.feature +0 -13
- data/features/env_config.feature +0 -11
- data/features/git_config.feature +0 -11
- data/features/help_and_version.feature +0 -33
- data/features/semaphore_app_api.feature +0 -29
- data/features/semaphore_app_config.feature +0 -37
- data/features/semaphore_auth_token.feature +0 -32
- data/features/semaphore_project_token.feature +0 -32
- data/features/step_definitions/git-semaphore_steps.rb +0 -121
- data/features/step_definitions/patch_methadone_steps.rb +0 -5
- data/features/step_definitions/vcr_semaphore_steps.rb +0 -25
- data/features/support/env.rb +0 -7
- data/features/support/semaphoreapp.rb +0 -2
- data/features/support/vcr.rb +0 -14
- data/features/working_directory.feature +0 -22
- data/lib/git-semaphore/api.rb +0 -60
- data/lib/git-semaphore/app.rb +0 -123
- data/lib/git-semaphore.rb +0 -15
- data/spec_helper.rb +0 -6
- data/vendor/bundle/.gitignore +0 -2
@@ -0,0 +1,133 @@
|
|
1
|
+
module Git
|
2
|
+
module Semaphore
|
3
|
+
class API
|
4
|
+
|
5
|
+
def self.projects auth_token
|
6
|
+
get_json projects_uri(auth_token)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.branches project_hash_id, auth_token
|
10
|
+
get_json branches_uri(project_hash_id, auth_token)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.status project_hash_id, branch_id, auth_token
|
14
|
+
get_json status_uri(project_hash_id, branch_id, auth_token)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.history project_hash_id, branch_id, auth_token
|
18
|
+
get_paginated_response(history_uri(project_hash_id, branch_id, auth_token)) do |previous_page, next_page|
|
19
|
+
previous_page['builds'] += next_page['builds']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.rebuild project_hash_id, branch_id, auth_token
|
24
|
+
get_json last_revision_uri(project_hash_id, branch_id, auth_token), :post
|
25
|
+
end
|
26
|
+
|
27
|
+
# private helper functions
|
28
|
+
|
29
|
+
def self.projects_uri auth_token
|
30
|
+
# https://semaphoreci.com/docs/projects-api.html
|
31
|
+
# GET /api/v1/projects
|
32
|
+
request_uri(auth_token, :path => File.join('projects'))
|
33
|
+
end
|
34
|
+
|
35
|
+
private_class_method :projects_uri
|
36
|
+
|
37
|
+
def self.branches_uri project_hash_id, auth_token
|
38
|
+
# https://semaphoreci.com/docs/branches-and-builds-api.html#project_branches
|
39
|
+
# GET /api/v1/projects/:hash_id/branches
|
40
|
+
request_uri(auth_token, :path => File.join('projects', project_hash_id, 'branches'))
|
41
|
+
end
|
42
|
+
|
43
|
+
private_class_method :branches_uri
|
44
|
+
|
45
|
+
def self.status_uri project_hash_id, branch_id, auth_token
|
46
|
+
# https://semaphoreci.com/docs/branches-and-builds-api.html#branch_status
|
47
|
+
# GET /api/v1/projects/:hash_id/:id/status
|
48
|
+
request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id, 'status'))
|
49
|
+
end
|
50
|
+
|
51
|
+
private_class_method :status_uri
|
52
|
+
|
53
|
+
def self.history_uri project_hash_id, branch_id, auth_token, page = 1
|
54
|
+
# https://semaphoreci.com/docs/branches-and-builds-api.html#branch_history
|
55
|
+
# GET /api/v1/projects/:hash_id/:id
|
56
|
+
request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id), :page => page)
|
57
|
+
end
|
58
|
+
|
59
|
+
private_class_method :history_uri
|
60
|
+
|
61
|
+
def self.last_revision_uri project_hash_id, branch_id, auth_token
|
62
|
+
# https://semaphoreci.com/docs/branches-and-builds-api.html#rebuild
|
63
|
+
# POST /api/v1/projects/:project_hash_id/:branch_id/build
|
64
|
+
request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id, 'build'))
|
65
|
+
end
|
66
|
+
|
67
|
+
private_class_method :last_revision_uri
|
68
|
+
|
69
|
+
# more private helper functions
|
70
|
+
|
71
|
+
def self.get_response uri, action = :get
|
72
|
+
response = ::Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == 'https'), :verify_mode => ::OpenSSL::SSL::VERIFY_NONE) do |net_http|
|
73
|
+
case action
|
74
|
+
when :get
|
75
|
+
net_http.get uri.request_uri
|
76
|
+
when :post
|
77
|
+
net_http.post uri.request_uri, uri.query
|
78
|
+
else
|
79
|
+
raise 'Unsupported action'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def response.json_body
|
84
|
+
raise "JSON response expected" unless self['content-type'] =~ %r{application/json}
|
85
|
+
JSON.parse(body)
|
86
|
+
end
|
87
|
+
|
88
|
+
response
|
89
|
+
end
|
90
|
+
|
91
|
+
private_class_method :get_response
|
92
|
+
|
93
|
+
def self.get_paginated_response uri, action = :get
|
94
|
+
response = get_response(uri, action)
|
95
|
+
body = response.json_body
|
96
|
+
loop do
|
97
|
+
pagination_header = response['Pagination']
|
98
|
+
break unless pagination_header
|
99
|
+
pagination = JSON.parse(pagination_header)
|
100
|
+
break if pagination['last_page']
|
101
|
+
uri.query = uri.query.sub(/page=(\d+)/, "page=#{pagination['next_page']}")
|
102
|
+
response = get_response(uri, action)
|
103
|
+
yield(body, response.json_body)
|
104
|
+
end
|
105
|
+
body
|
106
|
+
end
|
107
|
+
|
108
|
+
private_class_method :get_paginated_response
|
109
|
+
|
110
|
+
def self.get_json uri, action = :get
|
111
|
+
get_response(uri, action).json_body
|
112
|
+
end
|
113
|
+
|
114
|
+
private_class_method :get_json
|
115
|
+
|
116
|
+
# https://semaphoreci.com/docs/
|
117
|
+
SEMAPHORE_API_HOST = 'semaphoreci.com'.freeze
|
118
|
+
SEMAPHORE_API_URI = '/api/v1/'.freeze
|
119
|
+
|
120
|
+
def self.request_uri auth_token, options = {}
|
121
|
+
page = options.delete(:page) # API pagination
|
122
|
+
options[:host] ||= SEMAPHORE_API_HOST
|
123
|
+
options[:path].prepend SEMAPHORE_API_URI
|
124
|
+
options[:query] = "auth_token=#{auth_token}"
|
125
|
+
options[:query] << "&page=#{page}" if page
|
126
|
+
URI::HTTPS.build(options)
|
127
|
+
end
|
128
|
+
|
129
|
+
private_class_method :request_uri
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Git
|
2
|
+
module Semaphore
|
3
|
+
class API
|
4
|
+
class Cache
|
5
|
+
|
6
|
+
def self.projects auth_token
|
7
|
+
@projects ||= Git::Semaphore.from_json_cache(projects_cache) do
|
8
|
+
API::Enrich.projects auth_token
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.branches project_hash_id, auth_token
|
13
|
+
@branches ||= Git::Semaphore.from_json_cache(branches_cache(project_hash_id)) do
|
14
|
+
API.branches project_hash_id, auth_token
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.status project_hash_id, branch_id, auth_token
|
19
|
+
@status ||= Git::Semaphore.from_json_cache(status_cache(project_hash_id, branch_id)) do
|
20
|
+
API.status project_hash_id, branch_id, auth_token
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.history project_hash_id, branch_id, auth_token
|
25
|
+
@history ||= Git::Semaphore.from_json_cache(history_cache(project_hash_id, branch_id)) do
|
26
|
+
API::Enrich.history project_hash_id, branch_id, auth_token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# private helper functions
|
31
|
+
|
32
|
+
def self.projects_cache
|
33
|
+
File.join(Git::Semaphore.cache_dir, 'projects.json')
|
34
|
+
end
|
35
|
+
|
36
|
+
private_class_method :projects_cache
|
37
|
+
|
38
|
+
def self.branches_cache project_hash_id
|
39
|
+
File.join(Git::Semaphore.cache_dir_for(project_hash_id), 'branches.json')
|
40
|
+
end
|
41
|
+
|
42
|
+
private_class_method :branches_cache
|
43
|
+
|
44
|
+
def self.status_cache project_hash_id, branch_id
|
45
|
+
File.join(Git::Semaphore.cache_dir_for(project_hash_id), "#{branch_id}_status.json")
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method :status_cache
|
49
|
+
|
50
|
+
def self.history_cache project_hash_id, branch_id
|
51
|
+
File.join(Git::Semaphore.cache_dir_for(project_hash_id), "#{branch_id}_history.json")
|
52
|
+
end
|
53
|
+
|
54
|
+
private_class_method :history_cache
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Git
|
2
|
+
module Semaphore
|
3
|
+
class API
|
4
|
+
class Enrich
|
5
|
+
|
6
|
+
def self.projects auth_token
|
7
|
+
API.projects(auth_token).tap do |results|
|
8
|
+
results.each do |project|
|
9
|
+
# full repository name on github.com: 'pvdb/git-semaphore'
|
10
|
+
project['full_name'] = [project['owner'], project['name']].join('/')
|
11
|
+
# https://semaphoreci.com/pvdb/git-semaphore -> https://github.com/pvdb/git-semaphore
|
12
|
+
project['github_url'] = project['html_url'].sub(/semaphoreci\.com/, 'github.com')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.history project_hash_id, branch_id, auth_token
|
18
|
+
API.history(project_hash_id, branch_id, auth_token).tap do |results|
|
19
|
+
results['builds'].each do |build|
|
20
|
+
# build['result'] = "passed", "failed", "stopped" or "pending"
|
21
|
+
next unless started_at = build['started_at']
|
22
|
+
next unless finished_at = build['finished_at']
|
23
|
+
started_at = DateTime.parse(started_at).to_time
|
24
|
+
finished_at = DateTime.parse(finished_at).to_time
|
25
|
+
build['date'] = {
|
26
|
+
:started_at => started_at.to_date,
|
27
|
+
:finished_at => finished_at.to_date,
|
28
|
+
}
|
29
|
+
build['duration'] = {
|
30
|
+
:seconds => (finished_at - started_at).to_i,
|
31
|
+
:minutes => "%0.2f" % ((finished_at - started_at) / 60),
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class Git::Semaphore::App
|
2
|
+
|
3
|
+
attr_writer :project_name
|
4
|
+
attr_writer :branch_name
|
5
|
+
attr_writer :commit_sha
|
6
|
+
|
7
|
+
attr_reader :branch_url
|
8
|
+
|
9
|
+
def initialize auth_token, git_repo, config = ENV
|
10
|
+
@auth_token = auth_token
|
11
|
+
@git_repo = git_repo
|
12
|
+
|
13
|
+
self.project_name = config['SEMAPHORE_PROJECT_NAME']
|
14
|
+
self.branch_name = config['SEMAPHORE_BRANCH_NAME']
|
15
|
+
self.commit_sha = config['SEMAPHORE_COMMIT_SHA']
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_json
|
19
|
+
{
|
20
|
+
semaphore_auth_token: @auth_token,
|
21
|
+
semaphore_project_name: self.project_name,
|
22
|
+
semaphore_branch_name: self.branch_name,
|
23
|
+
semaphore_commit_sha: self.commit_sha,
|
24
|
+
}.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def project_name
|
28
|
+
return @project_name unless @project_name.nil?
|
29
|
+
File.basename @git_repo.git.work_tree if @git_repo
|
30
|
+
end
|
31
|
+
|
32
|
+
def branch_name
|
33
|
+
return @branch_name unless @branch_name.nil?
|
34
|
+
@git_repo.head.name if @git_repo
|
35
|
+
end
|
36
|
+
|
37
|
+
def commit_sha
|
38
|
+
return @commit_sha unless @commit_sha.nil?
|
39
|
+
@git_repo.head.commit.id if @git_repo
|
40
|
+
end
|
41
|
+
|
42
|
+
def projects
|
43
|
+
Git::Semaphore::API::Cache.projects(@auth_token)
|
44
|
+
end
|
45
|
+
|
46
|
+
def branches
|
47
|
+
Git::Semaphore::API::Cache.branches(project_hash_id, @auth_token)
|
48
|
+
end
|
49
|
+
|
50
|
+
def status
|
51
|
+
Git::Semaphore::API::Cache.status(project_hash_id, branch_id, @auth_token)
|
52
|
+
end
|
53
|
+
|
54
|
+
def history
|
55
|
+
Git::Semaphore::API::Cache.history(project_hash_id, branch_id, @auth_token)
|
56
|
+
end
|
57
|
+
|
58
|
+
def rebuild
|
59
|
+
Git::Semaphore::API.rebuild(project_hash_id, branch_id, @auth_token)
|
60
|
+
end
|
61
|
+
|
62
|
+
def branch_url
|
63
|
+
branch_hash = project_hash['branches'].find { |hash|
|
64
|
+
hash['branch_name'] == branch_name
|
65
|
+
}
|
66
|
+
branch_hash['branch_url']
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def project_hash_for project_name
|
72
|
+
projects.find { |project_hash|
|
73
|
+
project_hash['name'] == project_name
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def project_hash
|
78
|
+
project_hash_for(project_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def project_hash_id_for project_name
|
82
|
+
project_hash_for(project_name)['hash_id']
|
83
|
+
end
|
84
|
+
|
85
|
+
def project_hash_id
|
86
|
+
project_hash_id_for(project_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def branch_hash_for branch_name
|
90
|
+
branches.find { |branch_hash|
|
91
|
+
branch_hash['name'] == branch_name
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def branch_hash
|
96
|
+
branch_hash_for(branch_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def branch_id_for branch_name
|
100
|
+
branch_hash_for(branch_name)['id'].to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
def branch_id
|
104
|
+
branch_id_for(branch_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_statuses
|
108
|
+
history['builds']
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_status_for commit_sha
|
112
|
+
build_statuses.find { |build_status|
|
113
|
+
build_status['commit']['id'] == commit_sha
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_status
|
118
|
+
build_status_for(commit_sha)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Git
|
2
2
|
module Semaphore
|
3
3
|
# TODO replace hard-coded details with data retrieved from the gemspec
|
4
|
-
COPYRIGHT = "git-semaphore v#{Git::Semaphore::VERSION} Copyright (c) 2012-
|
4
|
+
COPYRIGHT = "git-semaphore v#{Git::Semaphore::VERSION} Copyright (c) 2012-2016 Peter Vandenberk"
|
5
5
|
end
|
6
6
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'openssl'
|
4
|
+
require 'net/http'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
require 'grit'
|
8
|
+
|
9
|
+
module Git
|
10
|
+
module Semaphore
|
11
|
+
|
12
|
+
def self.home_dir
|
13
|
+
@home_dir ||= begin
|
14
|
+
ENV['HOME'] || File.expand_path("~#{Etc.getlogin}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.cache_dir
|
19
|
+
@cache_dir ||= begin
|
20
|
+
File.join(self.home_dir, '.git', 'semaphore').tap do |cache_dir|
|
21
|
+
FileUtils.mkdir_p(cache_dir)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.cache_dir_for identifier
|
27
|
+
File.join(self.cache_dir, identifier).tap do |cache_dir|
|
28
|
+
FileUtils.mkdir_p(cache_dir)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.empty_cache_dir
|
33
|
+
FileUtils.rm_r Dir.glob(File.join(self.cache_dir, '*'))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_json_cache path
|
37
|
+
if File.exists? path
|
38
|
+
JSON.parse(File.read(path))
|
39
|
+
else
|
40
|
+
yield.tap do |content|
|
41
|
+
File.open(path, 'w') { |file| file.write content.to_json }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.git_repo
|
47
|
+
@git_repo ||= begin
|
48
|
+
Grit::Repo.new(Dir.pwd)
|
49
|
+
rescue Grit::InvalidGitRepositoryError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.env_auth_token
|
55
|
+
@env_auth_token ||= ENV['SEMAPHORE_AUTH_TOKEN']
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.git_auth_token
|
59
|
+
git_repo && git_repo.config['semaphore.authtoken']
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.auth_token
|
63
|
+
git_auth_token || env_auth_token
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
require 'git/semaphore/version'
|
70
|
+
require 'git/semaphore/banner'
|
71
|
+
require 'git/semaphore/copyright'
|
72
|
+
|
73
|
+
require 'git/semaphore/api'
|
74
|
+
require 'git/semaphore/api_cache'
|
75
|
+
require 'git/semaphore/api_enrich'
|
76
|
+
|
77
|
+
require 'git/semaphore/app'
|