ballantine 0.1.2
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/bin/ballantine +5 -0
- data/lib/ballantine/author.rb +63 -0
- data/lib/ballantine/cli.rb +256 -0
- data/lib/ballantine/string.rb +19 -0
- data/lib/ballantine/version.rb +5 -0
- data/lib/ballantine.rb +7 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a2188ad3272546c25226bdbb1209075baadee05fb7f011cafe13874b1af45777
|
4
|
+
data.tar.gz: 0fe5889c616c93c7f62ce8353671543750604fc4c6def9958abdfe3a0f6e5c42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab52789a4ae2fb510d48f34cd7cf8c2920610e5f341fdbbd94f687fee7ace7ff1f1ff7cbce32fe2e4ce78a783762aa29cf1c9053be5f042ef5b12f84f9101b28
|
7
|
+
data.tar.gz: bda07819f4146ac05b3ab1c39f4debe647f9c72a3a1dc50cfac433bf3381ecc6b96e96c92559775c08ecbf4f4e14b39a235a5a23d8636d2e956b6eddee33ad60
|
data/bin/ballantine
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class Author
|
4
|
+
attr_accessor :name, :commits
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# @param [String] name
|
8
|
+
# @return [Author] author
|
9
|
+
def find_or_create_by(name)
|
10
|
+
@@_collections = {} unless defined?(@@_collections)
|
11
|
+
return @@_collections[name] unless @@_collections[name].nil?
|
12
|
+
@@_collections[name] = new(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<Author>] authors
|
16
|
+
def all
|
17
|
+
return [] unless defined?(@@_collections)
|
18
|
+
@@_collections.sort.map(&:last) # sort and take values
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String] name
|
23
|
+
def initialize(name)
|
24
|
+
@name = name
|
25
|
+
@commits = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [NilClass] nil
|
29
|
+
def print_commits
|
30
|
+
puts "\n" + "@#{name}".green
|
31
|
+
@commits.each do |repo, lists|
|
32
|
+
count, word = retrieve_count_and_word(lists)
|
33
|
+
puts " > #{repo.blue}: #{count} new #{word}\n"
|
34
|
+
puts lists
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns an array to use slack attachments field
|
40
|
+
# reference: https://api.slack.com/messaging/composing/layouts#building-attachments
|
41
|
+
# @return [Hash] result
|
42
|
+
def serialize_commits
|
43
|
+
message = @commits.map do |repo, lists|
|
44
|
+
count, word = retrieve_count_and_word(lists)
|
45
|
+
"*#{repo}*: #{count} new #{word}\n#{lists.join("\n")}"
|
46
|
+
end.join("\n")
|
47
|
+
|
48
|
+
results = {
|
49
|
+
'text' => "- <@#{name}>\n#{message}",
|
50
|
+
'color' => '#00B86A', # green
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @param [Array<String>] lists
|
57
|
+
# @param [Array(Integer, String)] count, word
|
58
|
+
def retrieve_count_and_word(lists)
|
59
|
+
count = lists.size
|
60
|
+
word = count == 1 ? 'commit' : 'commits'
|
61
|
+
[count, word]
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Ballantine
|
4
|
+
require 'thor'
|
5
|
+
require 'json'
|
6
|
+
require_relative './author'
|
7
|
+
require_relative './string'
|
8
|
+
|
9
|
+
class CLI < Thor
|
10
|
+
# reference: https://github.com/desktop/desktop/blob/a7bca44088b105a04714dc4628f4af50f6f179c3/app/src/lib/remote-parsing.ts#L27-L44
|
11
|
+
GITHUB_REGEXES = [
|
12
|
+
'^https?://(.+)/(.+)/(.+)\.git/?$', # protocol: https -> https://github.com/oohyun15/ballantine.git | https://github.com/oohyun15/ballantine.git/
|
13
|
+
'^https?://(.+)/(.+)/(.+)/?$', # protocol: https -> https://github.com/oohyun15/ballantine | https://github.com/oohyun15/ballantine/
|
14
|
+
'^git@(.+):(.+)/(.+)\.git$', # protocol: ssh -> git@github.com:oohyun15/ballantine.git
|
15
|
+
'^git@(.+):(.+)/(.+)/?$', # protocol: ssh -> git@github.com:oohyun15/ballantine | git@github.com:oohyun15/ballantine/
|
16
|
+
'^git:(.+)/(.+)/(.+)\.git$', # protocol: ssh -> git:github.com/oohyun15/ballantine.git
|
17
|
+
'^git:(.+)/(.+)/(.+)/?$', # protocol: ssh -> git:github.com/oohyun15/ballantine | git:github.com/oohyun15/ballantine/
|
18
|
+
'^ssh://git@(.+)/(.+)/(.+)\.git$', # protocol: ssh -> ssh://git@github.com/oohyun15/ballantine.git
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
FILE_GITMODULES = '.gitmodules'
|
22
|
+
FILE_BALLANTINE_CONFIG = '.ballantine.json'
|
23
|
+
|
24
|
+
TYPE_TERMINAL = 'terminal'
|
25
|
+
TYPE_SLACK = 'slack'
|
26
|
+
|
27
|
+
DEFAULT_LJUST = 80
|
28
|
+
|
29
|
+
AVAILABLE_CONFIG = ['slack_webhook'].freeze
|
30
|
+
|
31
|
+
attr_reader :app_name, :main_path, :sub_path, :slack_webhook, :send_type
|
32
|
+
|
33
|
+
package_name 'Ballantine'
|
34
|
+
|
35
|
+
desc 'init', 'Initialize ballantine configuration'
|
36
|
+
def init
|
37
|
+
puts "🥃 Init ballantine configuration"
|
38
|
+
slack_webhook = ask("Q. Set slack webhook\n> ")
|
39
|
+
|
40
|
+
config = {
|
41
|
+
slack_webhook: slack_webhook
|
42
|
+
}
|
43
|
+
File.write('./' + FILE_BALLANTINE_CONFIG, JSON.dump(config))
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'config', 'Describe ballantine configuration'
|
47
|
+
def config
|
48
|
+
load_config
|
49
|
+
AVAILABLE_CONFIG.each do |key|
|
50
|
+
puts "#{key}: #{instance_variable_get('@' + key)}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'diff [TARGET] [SOURCE]', 'Diff commits between TARGET and SOURCE'
|
55
|
+
option TYPE_SLACK, type: :boolean, aliases: '-s', default: false, desc: 'send to slack using slack webhook URL.'
|
56
|
+
def diff(from, to = `git rev-parse --abbrev-ref HEAD`.chomp)
|
57
|
+
load_config
|
58
|
+
preprocess(from, to, **options)
|
59
|
+
|
60
|
+
# check argument is tag
|
61
|
+
from = check_tag(from)
|
62
|
+
to = check_tag(to)
|
63
|
+
|
64
|
+
# check commits are newest
|
65
|
+
system 'git pull -f &> /dev/null'
|
66
|
+
|
67
|
+
# set instance variables
|
68
|
+
@send_type = options[TYPE_SLACK] ? TYPE_SLACK : TYPE_TERMINAL
|
69
|
+
@app_name = File.basename(`git config --get remote.origin.url`.chomp, '.git')
|
70
|
+
@main_path = Dir.pwd
|
71
|
+
@sub_path = if Dir[FILE_GITMODULES].any?
|
72
|
+
file = File.open(FILE_GITMODULES)
|
73
|
+
lines = file.readlines.map(&:chomp)
|
74
|
+
file.close
|
75
|
+
lines.grep(/path =/).map{ |line| line[/(?<=path \=).*/, 0].strip }.sort
|
76
|
+
else
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
|
80
|
+
# find github url, branch
|
81
|
+
main_url = github_url(`git config --get remote.origin.url`.chomp)
|
82
|
+
current_branch = `git rev-parse --abbrev-ref HEAD`.chomp
|
83
|
+
|
84
|
+
# get commit hash
|
85
|
+
main_from, sub_from = commit_hash(from)
|
86
|
+
main_to, sub_to = commit_hash(to)
|
87
|
+
system "git checkout #{current_branch} -f &> /dev/null"
|
88
|
+
|
89
|
+
# check commits
|
90
|
+
check_commits(main_from, main_to, main_url)
|
91
|
+
@sub_path.each_with_index do |path, idx|
|
92
|
+
next if sub_from[idx] == sub_to[idx]
|
93
|
+
Dir.chdir(path)
|
94
|
+
sub_url = github_url(`git config --get remote.origin.url`.chomp)
|
95
|
+
check_commits(sub_from[idx], sub_to[idx], sub_url)
|
96
|
+
Dir.chdir(@main_path)
|
97
|
+
end
|
98
|
+
|
99
|
+
send_commits(from, to, main_url)
|
100
|
+
|
101
|
+
exit 0
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'version', 'Display version information about ballntine'
|
105
|
+
def version
|
106
|
+
puts "ballantine version #{Ballantine::VERSION}"
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def self.exit_on_failure?
|
112
|
+
exit 1
|
113
|
+
end
|
114
|
+
|
115
|
+
def load_config
|
116
|
+
return if Dir[FILE_BALLANTINE_CONFIG].empty?
|
117
|
+
|
118
|
+
JSON.parse(File.read('./' + FILE_BALLANTINE_CONFIG)).each do |key, value|
|
119
|
+
next unless AVAILABLE_CONFIG.include?(key)
|
120
|
+
instance_variable_set('@' + key, value)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param [String] from
|
125
|
+
# @param [String] to
|
126
|
+
# @param [Hash] options
|
127
|
+
# @return [NilClass] nil
|
128
|
+
def preprocess(from, to, **options)
|
129
|
+
if Dir['.git'].empty?
|
130
|
+
raise SystemCallError, "ERROR: There is no \".git\" in #{Dir.pwd}."
|
131
|
+
end
|
132
|
+
|
133
|
+
if (uncommitted = `git diff HEAD --name-only`.split("\n")).any?
|
134
|
+
raise SystemCallError, "ERROR: Uncommitted file exists. stash or commit uncommitted files.\n#{uncommitted.join("\n")}"
|
135
|
+
end
|
136
|
+
|
137
|
+
if from == to
|
138
|
+
raise ArgumentError, "ERROR: target(#{from}) and source(#{to}) can't be equal."
|
139
|
+
end
|
140
|
+
|
141
|
+
if options[TYPE_SLACK] && !@slack_webhook
|
142
|
+
raise ArgumentError, "ERROR: Can't find any slack webhook. Set slack webhook using `ballantine init`."
|
143
|
+
end
|
144
|
+
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [String] name
|
149
|
+
# @return [String] hash
|
150
|
+
def check_tag(name)
|
151
|
+
list = `git tag -l`.split("\n")
|
152
|
+
return name unless list.grep(name).any?
|
153
|
+
|
154
|
+
system "git fetch origin tag #{name} -f &> /dev/null"
|
155
|
+
`git rev-list -n 1 #{name}`.chomp[0...7]
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param [String] from
|
159
|
+
# @param [String] to
|
160
|
+
# @param [String] url
|
161
|
+
# @return [NilClass] nil
|
162
|
+
def check_commits(from, to, url)
|
163
|
+
repo = File.basename(`git config --get remote.origin.url`.chomp, '.git')
|
164
|
+
names = `git --no-pager log --pretty=format:"%an" #{from}..#{to}`.split("\n").uniq.sort
|
165
|
+
authors = names.map{ |name| Author.find_or_create_by(name) }
|
166
|
+
authors.each do |author|
|
167
|
+
format = commit_format(url, ljust: DEFAULT_LJUST - 10)
|
168
|
+
commits = `git --no-pager log --reverse --no-merges --author="#{author.name}" --format="#{format}" --abbrev=7 #{from}..#{to}`.gsub('"', '\"').gsub(/[\u0080-\u00ff]/, '')
|
169
|
+
next if commits.empty?
|
170
|
+
author.commits[repo] = commits.split("\n")
|
171
|
+
end
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
|
175
|
+
# @param [String] url
|
176
|
+
# @return [String] github_url
|
177
|
+
def github_url(url)
|
178
|
+
owner, repository = GITHUB_REGEXES.each do |regex|
|
179
|
+
if (str = url.match(regex))
|
180
|
+
break [str[2], str[3]]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
"https://github.com/#{owner}/#{repository}"
|
185
|
+
end
|
186
|
+
|
187
|
+
# @param [String] hash
|
188
|
+
# @param [Array<String>] sub_path
|
189
|
+
# @return [Array(String, Array<String>)] main, sub's hash
|
190
|
+
def commit_hash(hash)
|
191
|
+
system "git checkout #{hash} -f &> /dev/null"
|
192
|
+
system 'git pull &> /dev/null'
|
193
|
+
main_hash = `git --no-pager log -1 --format='%h'`.chomp
|
194
|
+
sub_hash = if @sub_path.any?
|
195
|
+
`git ls-tree HEAD #{@sub_path.join(' ')}`.split("\n").map{ |line| line.split(' ')[2] }
|
196
|
+
else
|
197
|
+
[]
|
198
|
+
end
|
199
|
+
|
200
|
+
[main_hash, sub_hash]
|
201
|
+
end
|
202
|
+
|
203
|
+
# @param [String] url
|
204
|
+
# @param [String] format
|
205
|
+
# @param [Integer] ljust
|
206
|
+
def commit_format(url, ljust: DEFAULT_LJUST)
|
207
|
+
case @send_type
|
208
|
+
when TYPE_TERMINAL
|
209
|
+
" - "+ "%h".yellow + " %<(#{ljust})%s " + "#{url}/commit/%H".gray
|
210
|
+
when TYPE_SLACK
|
211
|
+
"\\\`<#{url}/commit/%H|%h>\\\` %s - %an"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# @param [String] from
|
216
|
+
# @param [String] to
|
217
|
+
# @param [String] url
|
218
|
+
# @return [NilClass] nil
|
219
|
+
def send_commits(from, to, url)
|
220
|
+
authors = Author.all
|
221
|
+
if authors.empty?
|
222
|
+
raise ArgumentError, "ERROR: There is no commits between \"#{from}\" and \"#{to}\""
|
223
|
+
end
|
224
|
+
number = authors.size
|
225
|
+
last_commit = `git --no-pager log --reverse --format="#{commit_format(url, ljust: DEFAULT_LJUST - 22)}" --abbrev=7 #{from}..#{to} -1`.strip
|
226
|
+
|
227
|
+
case @send_type
|
228
|
+
when TYPE_TERMINAL
|
229
|
+
puts "Check commits before #{@app_name.red} deployment. (#{from.cyan} <- #{to.cyan})".ljust(DEFAULT_LJUST + 34) + " #{url}/compare/#{from}...#{to}".gray
|
230
|
+
puts "Author".yellow + ": #{number}"
|
231
|
+
puts "Last commit".blue + ": #{last_commit}"
|
232
|
+
authors.map(&:print_commits)
|
233
|
+
when TYPE_SLACK
|
234
|
+
# set message for each author
|
235
|
+
messages = authors.map(&:serialize_commits)
|
236
|
+
actor = `git config user.name`
|
237
|
+
|
238
|
+
# send message to slack
|
239
|
+
require 'net/http'
|
240
|
+
require 'uri'
|
241
|
+
uri = URI.parse(@slack_webhook)
|
242
|
+
request = Net::HTTP::Post.new(uri)
|
243
|
+
request.content_type = 'application/json'
|
244
|
+
request.body = JSON.dump({
|
245
|
+
'text' => ":check: *#{@app_name}* deployment request by <@#{actor}> (\`<#{url}/tree/#{from}|#{from}>\` <- \`<#{url}/tree/#{to}|#{to}>\` <#{url}/compare/#{from}...#{to}|compare>)\n:technologist: Author: #{number}\nLast commit: #{last_commit}",
|
246
|
+
'attachments' => messages
|
247
|
+
})
|
248
|
+
req_options = { use_ssl: uri.scheme == 'https' }
|
249
|
+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
250
|
+
http.request(request)
|
251
|
+
end
|
252
|
+
puts response.message
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# @override
|
4
|
+
class String
|
5
|
+
NC = "\e[0m"
|
6
|
+
GRAY = "\e[1;30m"
|
7
|
+
RED = "\e[1;31m"
|
8
|
+
GREEN = "\e[1;32m"
|
9
|
+
YELLOW = "\e[1;33m"
|
10
|
+
BLUE = "\e[1;34m"
|
11
|
+
CYAN = "\e[1;36m"
|
12
|
+
|
13
|
+
def gray; "#{GRAY}#{self}#{NC}" end
|
14
|
+
def red; "#{RED}#{self}#{NC}" end
|
15
|
+
def green; "#{GREEN}#{self}#{NC}" end
|
16
|
+
def yellow; "#{YELLOW}#{self}#{NC}" end
|
17
|
+
def blue; "#{BLUE}#{self}#{NC}" end
|
18
|
+
def cyan; "#{CYAN}#{self}#{NC}" end
|
19
|
+
end
|
data/lib/ballantine.rb
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ballantine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- oohyun15
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-08-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.9.28
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.9.28
|
41
|
+
description: Ballantine helps you describe your commits easier and prettier from cli
|
42
|
+
& slack.
|
43
|
+
email:
|
44
|
+
- sakiss4774@gmail.com
|
45
|
+
executables:
|
46
|
+
- ballantine
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- bin/ballantine
|
51
|
+
- lib/ballantine.rb
|
52
|
+
- lib/ballantine/author.rb
|
53
|
+
- lib/ballantine/cli.rb
|
54
|
+
- lib/ballantine/string.rb
|
55
|
+
- lib/ballantine/version.rb
|
56
|
+
homepage: https://github.com/oohyun15/ballantine
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.7.6
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Describe your commits.
|
79
|
+
test_files: []
|