capistrano-committed 0.0.3
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +108 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/capistrano-committed.gemspec +28 -0
- data/lib/capistrano/committed/github_api.rb +70 -0
- data/lib/capistrano/committed/i18n.rb +38 -0
- data/lib/capistrano/committed/version.rb +5 -0
- data/lib/capistrano/committed.rb +22 -0
- data/lib/capistrano/tasks/committed.rake +390 -0
- metadata +132 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 826501e1548e90eaebadb55f869d10323df83b2f
|
|
4
|
+
data.tar.gz: c3cae25051c1dfbb5ac0c5bea849d6824cd8a48a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 924a0422e7448dea1d2a970a116a81d7daba68994c2000c3655af2f3ff471d1e7575d838c921d3237012ae67199fb0080d5410d3ac00dd4b77d98843c4701a8a
|
|
7
|
+
data.tar.gz: 354ece8591d11a4e8238c0d71dafbca8ef5f76d1c16d56db3e8dd2acf7e378c5c2543c6a00160bb1ed5f9fc950b5706d19265e8e7bcc2eeed0c5df97e7a61faf
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Sam Bauers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Capistrano Committed
|
|
2
|
+
|
|
3
|
+
Capistrano Committed is an extension to Capistrano 3 which helps to determine what you are about to deploy.
|
|
4
|
+
|
|
5
|
+
It creates a report, which lets you know which GitHub commits and pull requests are not yet deployed to the target stage (server).
|
|
6
|
+
|
|
7
|
+
It does this by:
|
|
8
|
+
|
|
9
|
+
1. reading the revision log on the server;
|
|
10
|
+
2. getting all the commits on the specified branch from GitHub (via API);
|
|
11
|
+
3. looking through those commits and finding all the pull requests;
|
|
12
|
+
4. getting the info and commits in each pull request;
|
|
13
|
+
5. pumping all that data into a report;
|
|
14
|
+
6. uploading that report to the server.
|
|
15
|
+
|
|
16
|
+
At the moment this only works with GitHub repositories, if you have another Git service you would like it to support then please submit a pull request.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Add this line to your application's Gemfile (usually in the `:development` group):
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem 'capistrano-committed'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
And then execute:
|
|
27
|
+
|
|
28
|
+
$ bundle
|
|
29
|
+
|
|
30
|
+
Or install it yourself as:
|
|
31
|
+
|
|
32
|
+
$ gem install capistrano-committed
|
|
33
|
+
|
|
34
|
+
In your projects Capfile add this line:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require 'capistrano/committed'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
In `config/deploy.rb` (in Rails) you need to set at least these options:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# This is the GitHub user or organisation for the repository
|
|
46
|
+
set :committed_user, nil
|
|
47
|
+
set :committed_repo, nil
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You will usually need to set the `:committed_github_config` option in order to authenticate, this setting is a hash of options which are passed directly to the [GitHub API gem](https://github.com/peter-murach/github). The full list of GitHub API configuration option are in the [GitHub API gem read me file](https://github.com/peter-murach/github#2-configuration).
|
|
51
|
+
|
|
52
|
+
Example of personal access token usage:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
set :committed_github_config, {
|
|
56
|
+
:oauth_token => '65741acbd6473216583421cdef'
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Example of basic auth usage:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
set :committed_github_config, {
|
|
64
|
+
:basic_auth => 'my-username:my-p455w0rd'
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The following settings are optional, the default values are shown here:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# This describes the line that we are looking for and matching against to get
|
|
72
|
+
# revision details from the revision log. Grabbing this from Capistrano locales
|
|
73
|
+
# by default.
|
|
74
|
+
set :committed_revision_line, I18n.t('capistrano.revision_log_message')
|
|
75
|
+
|
|
76
|
+
# The config passed to the GitHub API gem - will usually contain auth details.
|
|
77
|
+
set :committed_github_config, {}
|
|
78
|
+
|
|
79
|
+
# How far back in the revision log we should look
|
|
80
|
+
set :committed_revision_limit, 10
|
|
81
|
+
|
|
82
|
+
# How many days beyond the last revision we fetch should we look for commits
|
|
83
|
+
set :committed_commit_buffer, 1
|
|
84
|
+
|
|
85
|
+
# Where to upload the report - '%s' is replaced with `current_path` if present.
|
|
86
|
+
# `nil` will stop the report from uploading at all, and print to STDOUT instead.
|
|
87
|
+
set :committed_output_path, '%s/public/committed.txt'
|
|
88
|
+
|
|
89
|
+
# This is a regexp pattern that describes issue numbers in commit titles and
|
|
90
|
+
# descriptions. This example matches JIRA numbers enclosed in square braces -
|
|
91
|
+
# e.g. "[ABC-12345]" with the part inside the braces being captured "ABC-12345".
|
|
92
|
+
# Setting this to `nil` will disable issue matching altogether. Note that this
|
|
93
|
+
# setting should specify a string, not a Ruby Regexp object. Specifying a Regexp
|
|
94
|
+
# object might work, but it is not tested.
|
|
95
|
+
set :committed_issue_match, '\[\s?([A-Z0-9]+\-[0-9]+)\s?\]'
|
|
96
|
+
|
|
97
|
+
# This is the URL structure for issues which are found. The default is for a
|
|
98
|
+
# JIRA on Demand instance - e.g. https://example.jira.com/browse/ABC-12345
|
|
99
|
+
# "%s" will be replaced with the issue number. Setting this to `nil` will also
|
|
100
|
+
# disable issue matching altogether.
|
|
101
|
+
set :committed_issue_url, 'https://example.jira.com/browse/%s'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Once your required settings are all in place, you can generate a report by running:
|
|
105
|
+
|
|
106
|
+
```shell
|
|
107
|
+
$ cap <stage> committed:generate
|
|
108
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "capistrano/committed"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'capistrano/committed/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "capistrano-committed"
|
|
8
|
+
spec.version = Capistrano::Committed::VERSION
|
|
9
|
+
spec.authors = ["Sam Bauers"]
|
|
10
|
+
spec.email = ["sam@redant.com.au"]
|
|
11
|
+
spec.license = 'MIT'
|
|
12
|
+
|
|
13
|
+
spec.summary = %q{Tells you what Capistrano 3 is going to deploy based on GitHub commits since the last release.}
|
|
14
|
+
spec.description = %q{Tells you what Capistrano 3 is going to deploy based on GitHub commits since the last release.}
|
|
15
|
+
spec.homepage = "https://github.com/sambauers/capistrano-committed"
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
+
spec.bindir = "exe"
|
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
spec.add_dependency "capistrano", '~> 3.4'
|
|
23
|
+
spec.add_dependency "github_api", "~> 0.12"
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
|
28
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'github_api'
|
|
2
|
+
|
|
3
|
+
module Capistrano
|
|
4
|
+
module Committed
|
|
5
|
+
class GithubApi
|
|
6
|
+
def initialize(config_options = {})
|
|
7
|
+
raise TypeError, '`initialize` requires a hash to be passed as the first and only argument' unless config_options.is_a?(Hash)
|
|
8
|
+
|
|
9
|
+
config_options.merge!({
|
|
10
|
+
:adapter => :net_http,
|
|
11
|
+
:ssl => {:verify => false},
|
|
12
|
+
:per_page => 100,
|
|
13
|
+
:user_agent => 'Committed Ruby Gem (via Github API Ruby Gem)'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
@client = ::Github.new config_options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_commit(user, repo, sha)
|
|
20
|
+
validate_user_and_repo(user, repo)
|
|
21
|
+
raise TypeError, sprintf('`%s` requires a valid commit SHA.', __callee__) unless sha.is_a?(String)
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
@client.repos.commits.get(:user => user, :repo => repo, :sha => sha)
|
|
25
|
+
rescue ::Github::Error::GithubError => e
|
|
26
|
+
rescue_github_errors(e)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_commits_since(user, repo, date, branch = 'master')
|
|
31
|
+
validate_user_and_repo(user, repo)
|
|
32
|
+
date = Time.parse(date) if date.is_a?(String)
|
|
33
|
+
raise TypeError, sprintf('`%s` requires a valid date.', __callee__) unless date.is_a?(Time)
|
|
34
|
+
raise TypeError, sprintf('`%s` requires a valid branch.', __callee__) unless branch.is_a?(String)
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
@client.repos.commits.list(:user => user, :repo => repo, :sha => branch, :since => date.iso8601)
|
|
38
|
+
rescue ::Github::Error::GithubError => e
|
|
39
|
+
rescue_github_errors(e)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_pull_request(user, repo, number)
|
|
44
|
+
validate_user_and_repo(user, repo)
|
|
45
|
+
raise TypeError, sprintf('`%s` requires a valid pull request number.', __callee__) unless number.is_a?(Integer)
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
info = @client.pull_requests.get(:user => user, :repo => repo, :number => number)
|
|
49
|
+
commits = @client.pull_requests.commits(:user => user, :repo => repo, :number => number)
|
|
50
|
+
return {:info => info, :commits => commits}
|
|
51
|
+
rescue ::Github::Error::GithubError => e
|
|
52
|
+
rescue_github_errors(e)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_user_and_repo(user, repo)
|
|
57
|
+
raise TypeError, sprintf('`%s` requires a valid GitHub user.', __caller__) unless user.is_a?(String)
|
|
58
|
+
raise TypeError, sprintf('`%s` requires a valid GitHub repository.', __caller__) unless repo.is_a?(String)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rescue_github_errors(e)
|
|
62
|
+
if e.is_a? ::Github::Error::ServiceError
|
|
63
|
+
raise e, 'There seems to be a problem with the GitHub service.'
|
|
64
|
+
elsif e.is_a? ::Github::Error::ClientError
|
|
65
|
+
raise e, 'There seems to be a problem with the request that was made to GitHub, check that your settings are correct.'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'i18n'
|
|
2
|
+
|
|
3
|
+
en = {
|
|
4
|
+
error: {
|
|
5
|
+
prerequisites: {
|
|
6
|
+
nil: '`:%{variable}` variable is `nil`, it needs to contain the %{name} name.',
|
|
7
|
+
empty: '`:%{variable}` variable is empty, it needs to contain the %{name} name.',
|
|
8
|
+
string: '`:%{variable}` variable is not a string.',
|
|
9
|
+
hash: '`:%{variable}` variable is not a hash.',
|
|
10
|
+
integer: '`:%{variable}` variable is not a integer.',
|
|
11
|
+
string_or_nil: '`:%{variable}` variable is not a string or `nil`.',
|
|
12
|
+
string_or_regexp_or_nil: '`:%{variable}` variable is not a string or `Regexp` object or `nil`.'
|
|
13
|
+
},
|
|
14
|
+
runtime: {
|
|
15
|
+
revisions_empty: 'The %{branch} branch has never been deployed to the %{stage} stage. No log has been generated.',
|
|
16
|
+
revision_commit_missing: 'No commit data has been found for the %{branch} branch on the %{stage} stage. No log has been generated.',
|
|
17
|
+
commits_empty: 'No commit data has been found for the %{branch} branch on the %{stage} stage since %{time}. No log has been generated.'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
output: {
|
|
21
|
+
next_release: 'Next release',
|
|
22
|
+
previous_release: 'Commits before %{time} are omitted from the report ¯\_(ツ)_/¯',
|
|
23
|
+
current_release: 'Release on %{release_time} from commit %{sha} at %{commit_time}',
|
|
24
|
+
pull_request_number: 'Pull Request #%{number}',
|
|
25
|
+
issue_links: 'Issue links:',
|
|
26
|
+
merged_on: 'Merged on: %{time}',
|
|
27
|
+
merged_by: 'Merged by: %{login}',
|
|
28
|
+
commit_sha: 'Commit %{sha}',
|
|
29
|
+
committed_on: 'Committed on: %{time}',
|
|
30
|
+
committed_by: 'Committed by: %{login}'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
I18n.backend.store_translations(:en, { capistrano: { committed: en } })
|
|
35
|
+
|
|
36
|
+
if I18n.respond_to?(:enforce_available_locales=)
|
|
37
|
+
I18n.enforce_available_locales = true
|
|
38
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "capistrano/committed/version"
|
|
2
|
+
require 'capistrano/committed/i18n'
|
|
3
|
+
require "capistrano/committed/github_api"
|
|
4
|
+
|
|
5
|
+
module Capistrano
|
|
6
|
+
module Committed
|
|
7
|
+
class << self
|
|
8
|
+
def scan_for_issues(pattern, string)
|
|
9
|
+
raise TypeError, sprintf('`%s` requires a valid pattern.', __callee__) unless pattern.is_a?(String) || pattern.is_a?(Regexp)
|
|
10
|
+
raise TypeError, sprintf('`%s` requires a valid string.', __callee__) unless pattern.is_a?(String)
|
|
11
|
+
|
|
12
|
+
matches = Regexp.new(pattern).match(string)
|
|
13
|
+
return unless matches && matches[1]
|
|
14
|
+
matches = matches.to_a
|
|
15
|
+
matches.shift
|
|
16
|
+
matches
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
load File.expand_path("../tasks/committed.rake", __FILE__)
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
namespace :committed do
|
|
2
|
+
task :check_prerequisites do
|
|
3
|
+
# Checks all the settings to make sure they are OK - mostly just checks type
|
|
4
|
+
{:committed_user => 'user', :committed_repo => 'repository'}.each do |variable, name|
|
|
5
|
+
raise TypeError,
|
|
6
|
+
I18n.t('capistrano.committed.error.prerequisites.nil', variable: variable, name: name) if fetch(variable).nil?
|
|
7
|
+
raise ArgumentError,
|
|
8
|
+
I18n.t('capistrano.committed.error.prerequisites.empty', variable: variable, name: name) if fetch(variable).empty?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
raise TypeError,
|
|
12
|
+
I18n.t('capistrano.committed.error.prerequisites.string', variable: 'committed_revision_line') unless fetch(:committed_revision_line).is_a?(String)
|
|
13
|
+
raise TypeError,
|
|
14
|
+
I18n.t('capistrano.committed.error.prerequisites.hash', variable: 'committed_github_config') unless fetch(:committed_github_config).is_a?(Hash)
|
|
15
|
+
raise TypeError,
|
|
16
|
+
I18n.t('capistrano.committed.error.prerequisites.integer', variable: 'committed_revision_limit') unless fetch(:committed_revision_limit).is_a?(Integer)
|
|
17
|
+
raise TypeError,
|
|
18
|
+
I18n.t('capistrano.committed.error.prerequisites.integer', variable: 'committed_commit_buffer') unless fetch(:committed_commit_buffer).is_a?(Integer)
|
|
19
|
+
raise TypeError,
|
|
20
|
+
I18n.t('capistrano.committed.error.prerequisites.string_or_nil', variable: 'committed_output_path') unless fetch(:committed_output_path).is_a?(String) || fetch(:committed_output_path).nil?
|
|
21
|
+
raise TypeError,
|
|
22
|
+
I18n.t('capistrano.committed.error.prerequisites.string_or_regexp_or_nil', variable: 'committed_issue_match') unless fetch(:committed_issue_match).is_a?(String) || fetch(:committed_issue_match).is_a?(Regexp) || fetch(:committed_issue_match).nil?
|
|
23
|
+
raise TypeError,
|
|
24
|
+
I18n.t('capistrano.committed.error.prerequisites.string_or_nil', variable: 'committed_issue_url') unless fetch(:committed_issue_url).is_a?(String) || fetch(:committed_issue_url).nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'Generetes a report of commit and pull request status on the current stage'
|
|
28
|
+
task :generate do
|
|
29
|
+
invoke 'committed:check_prerequisites'
|
|
30
|
+
|
|
31
|
+
# Only do this on the primary web server
|
|
32
|
+
on primary :web do
|
|
33
|
+
# Get the Capistrano revision log
|
|
34
|
+
lines = capture(:cat, revision_log).split("\n").reverse
|
|
35
|
+
|
|
36
|
+
# Build the regex to search for revision data in the log, by default this
|
|
37
|
+
# is the localised string from Capistrano
|
|
38
|
+
search = fetch(:committed_revision_line)
|
|
39
|
+
search = Regexp.escape(search)
|
|
40
|
+
search = search.gsub('%\{', '(?<').gsub('\}', '>.+)')
|
|
41
|
+
search = Regexp.new(search)
|
|
42
|
+
|
|
43
|
+
# Build the revisions hash
|
|
44
|
+
revisions = {}
|
|
45
|
+
lines.each do |line|
|
|
46
|
+
matches = search.match(line)
|
|
47
|
+
next unless matches[:branch].to_s == fetch(:branch).to_s
|
|
48
|
+
revisions[matches[:sha]] = {
|
|
49
|
+
:branch => matches[:branch],
|
|
50
|
+
:sha => matches[:sha],
|
|
51
|
+
:release => matches[:release],
|
|
52
|
+
:user => matches[:user],
|
|
53
|
+
:entries => {}
|
|
54
|
+
}
|
|
55
|
+
# Only store a certain number of revisions
|
|
56
|
+
break if revisions.count == fetch(:committed_revision_limit)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# No revisions, no log
|
|
60
|
+
if revisions.empty?
|
|
61
|
+
info I18n.t('capistrano.committed.error.runtime.revisions_empty',
|
|
62
|
+
branch: fetch(:branch).to_s,
|
|
63
|
+
stage: fetch(:stage).to_s)
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Sort revisions by release date
|
|
68
|
+
revisions = revisions.sort_by{|sha, matches| matches[:release]}.to_h
|
|
69
|
+
# Add the "next" revision
|
|
70
|
+
revisions.merge!({
|
|
71
|
+
:next => {
|
|
72
|
+
:entries => {}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
# Reverse the order of revisions in the hash (most recent first)
|
|
76
|
+
revisions = revisions.to_a.reverse.to_h
|
|
77
|
+
revisions.merge!({
|
|
78
|
+
:previous => {
|
|
79
|
+
:entries => {}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
# Initialize the GitHub API client
|
|
84
|
+
github = ::Capistrano::Committed::GithubApi.new(fetch(:committed_github_config))
|
|
85
|
+
|
|
86
|
+
# Get the actual date of the commit referenced to by the revision
|
|
87
|
+
earliest_date = nil
|
|
88
|
+
revisions.each do |sha, revision|
|
|
89
|
+
next if sha == :next || sha == :previous
|
|
90
|
+
commit = github.get_commit(fetch(:committed_user), fetch(:committed_repo), sha)
|
|
91
|
+
unless commit.nil?
|
|
92
|
+
earliest_date = commit[:commit][:committer][:date]
|
|
93
|
+
revisions[sha][:date] = earliest_date
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# No commit data on revisions, no log
|
|
98
|
+
if earliest_date.nil?
|
|
99
|
+
info I18n.t('capistrano.committed.error.runtime.revision_commit_missing',
|
|
100
|
+
branch: fetch(:branch).to_s,
|
|
101
|
+
stage: fetch(:stage).to_s)
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Go back an extra N day
|
|
106
|
+
earliest_date = (Time.parse(earliest_date) - (fetch(:committed_commit_buffer) * 24 * 60 * 60)).iso8601
|
|
107
|
+
revisions[:previous][:date] = earliest_date
|
|
108
|
+
|
|
109
|
+
# Get all the commits on this branch
|
|
110
|
+
commits = github.get_commits_since(fetch(:committed_user),
|
|
111
|
+
fetch(:committed_repo),
|
|
112
|
+
earliest_date,
|
|
113
|
+
fetch(:branch).to_s)
|
|
114
|
+
|
|
115
|
+
# No commits, no log
|
|
116
|
+
if commits.empty?
|
|
117
|
+
info I18n.t('capistrano.committed.error.runtime.commits_empty',
|
|
118
|
+
branch: fetch(:branch).to_s,
|
|
119
|
+
stage: fetch(:stage).to_s,
|
|
120
|
+
time: earliest_date)
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Map commits to a hash keyed by sha
|
|
125
|
+
commits = Hash[commits.map { |commit| [commit[:sha], commit] }]
|
|
126
|
+
|
|
127
|
+
# Get all pull requests listed in the commits
|
|
128
|
+
revision_index = 0
|
|
129
|
+
commits.each do |sha, commit|
|
|
130
|
+
# Match to GitHub generated commit message, or don't
|
|
131
|
+
matches = /^Merge pull request \#([0-9]+)/.match(commit[:commit][:message])
|
|
132
|
+
next unless matches && matches[1]
|
|
133
|
+
|
|
134
|
+
# Get the pull request from GitHub
|
|
135
|
+
pull_request = github.get_pull_request(fetch(:committed_user),
|
|
136
|
+
fetch(:committed_repo),
|
|
137
|
+
matches[1].to_i)
|
|
138
|
+
|
|
139
|
+
# Get the previous revisions commit time and the merge time of the pull request
|
|
140
|
+
previous_revision = revisions[revisions.keys[revision_index + 1]]
|
|
141
|
+
previous_revision_date = Time.parse(previous_revision[:date])
|
|
142
|
+
merged_at = Time.parse(pull_request[:info][:merged_at])
|
|
143
|
+
|
|
144
|
+
# Unless this pull request was merged before the previous release reference was committed
|
|
145
|
+
unless merged_at > previous_revision_date
|
|
146
|
+
# Move to the previous revision
|
|
147
|
+
revision_index += 1
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Push pull request data in to the revision entries hash
|
|
151
|
+
revisions[revisions.keys[revision_index]][:entries][pull_request[:info][:merged_at]] = [{
|
|
152
|
+
:type => :pull_request,
|
|
153
|
+
:info => pull_request[:info],
|
|
154
|
+
:commits => pull_request[:commits]
|
|
155
|
+
}]
|
|
156
|
+
|
|
157
|
+
# Delete commits which are in this pull request from the hash of commits
|
|
158
|
+
commits.delete(sha)
|
|
159
|
+
next if pull_request[:commits].empty?
|
|
160
|
+
pull_request[:commits].each do |c|
|
|
161
|
+
commits.delete(c[:sha])
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Loop through remaining commits and push them into th revision entries hash
|
|
166
|
+
revision_index = 0
|
|
167
|
+
commits.each do |sha, commit|
|
|
168
|
+
previous_revision = revisions[revisions.keys[revision_index + 1]]
|
|
169
|
+
previous_revision_date = Time.parse(previous_revision[:date])
|
|
170
|
+
committed_at = Time.parse(commit[:commit][:committer][:date])
|
|
171
|
+
|
|
172
|
+
unless committed_at > previous_revision_date
|
|
173
|
+
revision_index += 1
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if revisions[revisions.keys[revision_index]][:entries][commit[:commit][:committer][:date]].nil?
|
|
177
|
+
revisions[revisions.keys[revision_index]][:entries][commit[:commit][:committer][:date]] = []
|
|
178
|
+
end
|
|
179
|
+
revisions[revisions.keys[revision_index]][:entries][commit[:commit][:committer][:date]] << {
|
|
180
|
+
:type => :commit,
|
|
181
|
+
:info => commit
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Loop through the revisions to create the output
|
|
186
|
+
output = []
|
|
187
|
+
revisions.each do |sha, revision|
|
|
188
|
+
|
|
189
|
+
# Build the revision header
|
|
190
|
+
output << ''
|
|
191
|
+
output << '==============================================================================================='
|
|
192
|
+
case sha
|
|
193
|
+
when :next
|
|
194
|
+
output << I18n.t('capistrano.committed.output.next_release')
|
|
195
|
+
when :previous
|
|
196
|
+
output << I18n.t('capistrano.committed.output.previous_release',
|
|
197
|
+
time: revision[:date])
|
|
198
|
+
else
|
|
199
|
+
output << I18n.t('capistrano.committed.output.current_release',
|
|
200
|
+
release_time: Time.parse(revision[:release]).iso8601,
|
|
201
|
+
sha: revision[:sha],
|
|
202
|
+
commit_time: revision[:date])
|
|
203
|
+
end
|
|
204
|
+
output << '==============================================================================================='
|
|
205
|
+
output << ''
|
|
206
|
+
|
|
207
|
+
# Loop through the entries in this revision
|
|
208
|
+
revision[:entries].sort_by{|date, entries| date}.reverse.to_h.each do |date, entries|
|
|
209
|
+
entries.each do |entry|
|
|
210
|
+
case entry[:type]
|
|
211
|
+
when :pull_request
|
|
212
|
+
# These are pull requests that are included in this revision
|
|
213
|
+
|
|
214
|
+
# Print out the pull request number and title
|
|
215
|
+
output << sprintf(' * %s',
|
|
216
|
+
I18n.t('capistrano.committed.output.pull_request_number',
|
|
217
|
+
number: entry[:info][:number]))
|
|
218
|
+
output << sprintf(' %s', entry[:info][:title])
|
|
219
|
+
output << ''
|
|
220
|
+
|
|
221
|
+
# Print out each line of the pull request description
|
|
222
|
+
lines = entry[:info][:body].chomp.split("\n")
|
|
223
|
+
unless lines.empty?
|
|
224
|
+
output << sprintf(' %s', lines.join("\n "))
|
|
225
|
+
output << ''
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
unless fetch(:committed_issue_match).nil? || fetch(:committed_issue_url).nil?
|
|
229
|
+
# Get any issue numbers referred to in the commit info and print links to them
|
|
230
|
+
issues = ::Capistrano::Committed.scan_for_issues(fetch(:committed_issue_match),
|
|
231
|
+
entry[:info][:title] + entry[:info][:body])
|
|
232
|
+
unless issues.nil?
|
|
233
|
+
output << sprintf(' %s',
|
|
234
|
+
I18n.t('capistrano.committed.output.issue_links'))
|
|
235
|
+
issues.each do |issue|
|
|
236
|
+
url = sprintf(fetch(:committed_issue_url), issue)
|
|
237
|
+
output << sprintf(' - %s', url)
|
|
238
|
+
end
|
|
239
|
+
output << ''
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Merger details
|
|
244
|
+
output << sprintf(' %s',
|
|
245
|
+
I18n.t('capistrano.committed.output.merged_on',
|
|
246
|
+
time: entry[:info][:merged_at]))
|
|
247
|
+
output << sprintf(' %s',
|
|
248
|
+
I18n.t('capistrano.committed.output.merged_by',
|
|
249
|
+
login: entry[:info][:merged_by][:login]))
|
|
250
|
+
output << ''
|
|
251
|
+
|
|
252
|
+
# Print a link to the pull request on GitHub
|
|
253
|
+
output << sprintf(' %s', entry[:info][:html_url])
|
|
254
|
+
output << ''
|
|
255
|
+
|
|
256
|
+
# Loop through the commits in this pull request
|
|
257
|
+
unless entry[:commits].nil?
|
|
258
|
+
entry[:commits].each do |commit|
|
|
259
|
+
output << ' -------------------------------------------------------------------------------------------'
|
|
260
|
+
output << ' |'
|
|
261
|
+
|
|
262
|
+
# Print the commit ref
|
|
263
|
+
output << sprintf(' | * %s',
|
|
264
|
+
I18n.t('capistrano.committed.output.commit_sha',
|
|
265
|
+
sha: commit[:sha]))
|
|
266
|
+
output << ' |'
|
|
267
|
+
|
|
268
|
+
# Print the commit message
|
|
269
|
+
lines = commit[:commit][:message].chomp.split("\n")
|
|
270
|
+
unless lines.empty?
|
|
271
|
+
output << sprintf(' | > %s', lines.join("\n | > "))
|
|
272
|
+
output << ' |'
|
|
273
|
+
|
|
274
|
+
unless fetch(:committed_issue_match).nil? || fetch(:committed_issue_url).nil?
|
|
275
|
+
# Get any issue numbers referred to in the commit message and print links to them
|
|
276
|
+
issues = ::Capistrano::Committed.scan_for_issues(fetch(:committed_issue_match),
|
|
277
|
+
commit[:commit][:message])
|
|
278
|
+
unless issues.nil?
|
|
279
|
+
output << sprintf(' | %s',
|
|
280
|
+
I18n.t('capistrano.committed.output.issue_links'))
|
|
281
|
+
issues.each do |issue|
|
|
282
|
+
url = sprintf(fetch(:committed_issue_url), issue)
|
|
283
|
+
output << sprintf(' | - %s' , url)
|
|
284
|
+
end
|
|
285
|
+
output << ' |'
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Committer details
|
|
291
|
+
output << sprintf(' | %s',
|
|
292
|
+
I18n.t('capistrano.committed.output.committed_on',
|
|
293
|
+
time: commit[:commit][:committer][:date]))
|
|
294
|
+
output << sprintf(' | %s',
|
|
295
|
+
I18n.t('capistrano.committed.output.committed_by',
|
|
296
|
+
login: commit[:committer][:login]))
|
|
297
|
+
output << ' |'
|
|
298
|
+
|
|
299
|
+
# Print a link to the commit in GitHub
|
|
300
|
+
output << sprintf(' | %s', commit[:html_url])
|
|
301
|
+
output << ' |'
|
|
302
|
+
end
|
|
303
|
+
output << ' -------------------------------------------------------------------------------------------'
|
|
304
|
+
output << ''
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
when :commit
|
|
308
|
+
# These are commits that are included in this revision, but are not in any pull requests
|
|
309
|
+
|
|
310
|
+
# Print the commit ref
|
|
311
|
+
output << sprintf(' * %s',
|
|
312
|
+
I18n.t('capistrano.committed.output.commit_sha',
|
|
313
|
+
sha: entry[:info][:sha]))
|
|
314
|
+
output << ''
|
|
315
|
+
|
|
316
|
+
# Print the commit message
|
|
317
|
+
lines = entry[:info][:commit][:message].chomp.split("\n")
|
|
318
|
+
unless lines.empty?
|
|
319
|
+
output << sprintf(' > %s', lines.join("\n > "))
|
|
320
|
+
output << ''
|
|
321
|
+
|
|
322
|
+
unless fetch(:committed_issue_match).nil? || fetch(:committed_issue_url).nil?
|
|
323
|
+
# Get any issue numbers referred to in the commit message and print links to them
|
|
324
|
+
issues = ::Capistrano::Committed.scan_for_issues(fetch(:committed_issue_match),
|
|
325
|
+
entry[:info][:commit][:message])
|
|
326
|
+
unless issues.nil?
|
|
327
|
+
output << sprintf(' %s',
|
|
328
|
+
I18n.t('capistrano.committed.output.issue_links'))
|
|
329
|
+
issues.each do |issue|
|
|
330
|
+
url = sprintf(fetch(:committed_issue_url), issue)
|
|
331
|
+
output << sprintf(' - %s', url)
|
|
332
|
+
end
|
|
333
|
+
output << ''
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Committer details
|
|
339
|
+
output << sprintf(' %s',
|
|
340
|
+
I18n.t('capistrano.committed.output.committed_on',
|
|
341
|
+
time: entry[:info][:commit][:committer][:date]))
|
|
342
|
+
output << sprintf(' %s',
|
|
343
|
+
I18n.t('capistrano.committed.output.committed_by',
|
|
344
|
+
login: entry[:info][:committer][:login]))
|
|
345
|
+
output << ''
|
|
346
|
+
|
|
347
|
+
# Print a link to the commit in GitHub
|
|
348
|
+
output << sprintf(' %s', entry[:info][:html_url])
|
|
349
|
+
output << ''
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
output << '-----------------------------------------------------------------------------------------------'
|
|
353
|
+
output << ''
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
output << ''
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Send the output to screen, or to a file on the server
|
|
361
|
+
if fetch(:committed_output_path).nil?
|
|
362
|
+
# Just print to STDOUT
|
|
363
|
+
puts output
|
|
364
|
+
else
|
|
365
|
+
# Determine the output path and upload the output there
|
|
366
|
+
output_path = sprintf(fetch(:committed_output_path), current_path)
|
|
367
|
+
upload! StringIO.new(output.join("\n")), output_path
|
|
368
|
+
|
|
369
|
+
# Make sure the report is world readable
|
|
370
|
+
execute(:chmod, 'a+r', output_path)
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Load the default settings
|
|
377
|
+
namespace :load do
|
|
378
|
+
task :defaults do
|
|
379
|
+
# See README for descriptions of each setting
|
|
380
|
+
set :committed_user, ->{ nil }
|
|
381
|
+
set :committed_repo, ->{ nil }
|
|
382
|
+
set :committed_revision_line, ->{ I18n.t('capistrano.revision_log_message') }
|
|
383
|
+
set :committed_github_config, ->{ {} }
|
|
384
|
+
set :committed_revision_limit, ->{ 10 }
|
|
385
|
+
set :committed_commit_buffer, ->{ 1 }
|
|
386
|
+
set :committed_output_path, ->{ '%s/public/committed.txt' }
|
|
387
|
+
set :committed_issue_match, ->{ '\[\s?([A-Z0-9]+\-[0-9]+)\s?\]' }
|
|
388
|
+
set :committed_issue_url, ->{ 'https://example.jira.com/browse/%s' }
|
|
389
|
+
end
|
|
390
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capistrano-committed
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sam Bauers
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-12-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: capistrano
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.4'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: github_api
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.12'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.12'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: bundler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.10'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.10'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '10.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '10.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.4'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.4'
|
|
83
|
+
description: Tells you what Capistrano 3 is going to deploy based on GitHub commits
|
|
84
|
+
since the last release.
|
|
85
|
+
email:
|
|
86
|
+
- sam@redant.com.au
|
|
87
|
+
executables: []
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- ".gitignore"
|
|
92
|
+
- ".rspec"
|
|
93
|
+
- ".travis.yml"
|
|
94
|
+
- Gemfile
|
|
95
|
+
- LICENSE
|
|
96
|
+
- README.md
|
|
97
|
+
- Rakefile
|
|
98
|
+
- bin/console
|
|
99
|
+
- bin/setup
|
|
100
|
+
- capistrano-committed.gemspec
|
|
101
|
+
- lib/capistrano/committed.rb
|
|
102
|
+
- lib/capistrano/committed/github_api.rb
|
|
103
|
+
- lib/capistrano/committed/i18n.rb
|
|
104
|
+
- lib/capistrano/committed/version.rb
|
|
105
|
+
- lib/capistrano/tasks/committed.rake
|
|
106
|
+
homepage: https://github.com/sambauers/capistrano-committed
|
|
107
|
+
licenses:
|
|
108
|
+
- MIT
|
|
109
|
+
metadata: {}
|
|
110
|
+
post_install_message:
|
|
111
|
+
rdoc_options: []
|
|
112
|
+
require_paths:
|
|
113
|
+
- lib
|
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
requirements: []
|
|
125
|
+
rubyforge_project:
|
|
126
|
+
rubygems_version: 2.4.5.1
|
|
127
|
+
signing_key:
|
|
128
|
+
specification_version: 4
|
|
129
|
+
summary: Tells you what Capistrano 3 is going to deploy based on GitHub commits since
|
|
130
|
+
the last release.
|
|
131
|
+
test_files: []
|
|
132
|
+
has_rdoc:
|