ballantine 0.1.4.pre.beta1 → 0.1.4.pre.beta3
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/lib/ballantine/author.rb +31 -21
- data/lib/ballantine/cli.rb +44 -146
- data/lib/ballantine/commit.rb +46 -0
- data/lib/ballantine/config.rb +16 -5
- data/lib/ballantine/repository.rb +206 -0
- data/lib/ballantine/version.rb +1 -1
- data/lib/ballantine.rb +3 -0
- data/lib/printable.rb +28 -0
- data/lib/string.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faa1e978d804f0d6db7c1c8b2d0d46a1fee27cd0a1f11c811b34f700fa99ac4d
|
4
|
+
data.tar.gz: 17a176cabd94e64d662bde533f605b6d36ebc19f301f6f77df3afc7870fdfedb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 014f30ad7a7764ef9d2d8619a62c541de3e23115e9d9eddebf16da3d1e6f958fc5d2567f57d3072907f8df1d398ca9ff8e8f785675095ad95e8adabfa76e0080
|
7
|
+
data.tar.gz: eed48b827efdbab0ff935444936431ac08a8ef97b15a9291679ec45f64ea9b56a295ad4ae40049749d516d385e7daaad18017449638af38d7c0579cf2be155ff
|
data/lib/ballantine/author.rb
CHANGED
@@ -2,16 +2,22 @@
|
|
2
2
|
|
3
3
|
module Ballantine
|
4
4
|
class Author
|
5
|
-
|
5
|
+
include Printable
|
6
|
+
|
7
|
+
attr_reader :name, :commits_hash
|
6
8
|
|
7
9
|
class << self
|
8
10
|
# @param [String] name
|
9
|
-
# @return [Author]
|
10
|
-
def
|
11
|
+
# @return [Author, NilClass]
|
12
|
+
def find(name:)
|
11
13
|
@_collections = {} unless defined?(@_collections)
|
12
|
-
|
14
|
+
@_collections[name]
|
15
|
+
end
|
13
16
|
|
14
|
-
|
17
|
+
# @param [String] name
|
18
|
+
# @return [Author]
|
19
|
+
def find_or_create_by(name:)
|
20
|
+
find(name:) || @_collections[name] = new(name:)
|
15
21
|
end
|
16
22
|
|
17
23
|
# @return [Array<Author>] authors
|
@@ -23,29 +29,33 @@ module Ballantine
|
|
23
29
|
end
|
24
30
|
|
25
31
|
# @param [String] name
|
26
|
-
def initialize(name)
|
32
|
+
def initialize(name:)
|
27
33
|
@name = name
|
28
|
-
@
|
34
|
+
@commits_hash = {}
|
29
35
|
end
|
30
36
|
|
31
|
-
# @return [
|
37
|
+
# @return [Boolean]
|
32
38
|
def print_commits
|
33
39
|
puts "\n" + "@#{name}".green
|
34
|
-
|
35
|
-
count, word = retrieve_count_and_word(
|
36
|
-
puts " > #{
|
37
|
-
|
40
|
+
commits_hash.each do |repo_name, commits|
|
41
|
+
count, word = retrieve_count_and_word(commits)
|
42
|
+
puts " > #{repo_name.blue}: #{count} new #{word}"
|
43
|
+
commits.each do |commit|
|
44
|
+
puts_r " - #{commit.hash.yellow} #{commit.subject}", commit.url.gray
|
45
|
+
end
|
38
46
|
end
|
39
|
-
|
47
|
+
|
48
|
+
true
|
40
49
|
end
|
41
50
|
|
42
51
|
# returns an array to use slack attachments field
|
43
52
|
# reference: https://api.slack.com/messaging/composing/layouts#building-attachments
|
44
|
-
# @return [Hash]
|
45
|
-
def
|
46
|
-
message =
|
47
|
-
count, word = retrieve_count_and_word(
|
48
|
-
"*#{
|
53
|
+
# @return [Hash]
|
54
|
+
def slack_message
|
55
|
+
message = commits_hash.map do |repo_name, commits|
|
56
|
+
count, word = retrieve_count_and_word(commits)
|
57
|
+
"*#{repo_name}*: #{count} new #{word}\n" \
|
58
|
+
"#{commits.map(&:slack_message).join("\n")}"
|
49
59
|
end.join("\n")
|
50
60
|
|
51
61
|
{
|
@@ -56,10 +66,10 @@ module Ballantine
|
|
56
66
|
|
57
67
|
private
|
58
68
|
|
59
|
-
# @param [Array<
|
69
|
+
# @param [Array<Commit>] commits
|
60
70
|
# @param [Array(Integer, String)] count, word
|
61
|
-
def retrieve_count_and_word(
|
62
|
-
count =
|
71
|
+
def retrieve_count_and_word(commits)
|
72
|
+
count = commits.size
|
63
73
|
word = count == 1 ? "commit" : "commits"
|
64
74
|
[count, word]
|
65
75
|
end
|
data/lib/ballantine/cli.rb
CHANGED
@@ -2,25 +2,9 @@
|
|
2
2
|
|
3
3
|
module Ballantine
|
4
4
|
class CLI < Thor
|
5
|
-
|
6
|
-
GITHUB_REGEXES = [
|
7
|
-
'^https?://(.+)/(.+)/(.+)\.git/?$', # protocol: https -> https://github.com/oohyun15/ballantine.git | https://github.com/oohyun15/ballantine.git/
|
8
|
-
'^https?://(.+)/(.+)/(.+)/?$', # protocol: https -> https://github.com/oohyun15/ballantine | https://github.com/oohyun15/ballantine/
|
9
|
-
'^git@(.+):(.+)/(.+)\.git$', # protocol: ssh -> git@github.com:oohyun15/ballantine.git
|
10
|
-
'^git@(.+):(.+)/(.+)/?$', # protocol: ssh -> git@github.com:oohyun15/ballantine | git@github.com:oohyun15/ballantine/
|
11
|
-
'^git:(.+)/(.+)/(.+)\.git$', # protocol: ssh -> git:github.com/oohyun15/ballantine.git
|
12
|
-
'^git:(.+)/(.+)/(.+)/?$', # protocol: ssh -> git:github.com/oohyun15/ballantine | git:github.com/oohyun15/ballantine/
|
13
|
-
'^ssh://git@(.+)/(.+)/(.+)\.git$', # protocol: ssh -> ssh://git@github.com/oohyun15/ballantine.git
|
14
|
-
].freeze
|
5
|
+
include Printable
|
15
6
|
|
16
|
-
|
17
|
-
|
18
|
-
TYPE_TERMINAL = "terminal"
|
19
|
-
TYPE_SLACK = "slack"
|
20
|
-
|
21
|
-
DEFAULT_LJUST = 80
|
22
|
-
|
23
|
-
attr_reader :app_name, :main_path, :sub_path, :send_type
|
7
|
+
attr_reader :repo
|
24
8
|
|
25
9
|
class << self
|
26
10
|
def exit_on_failure?; exit(1) end
|
@@ -47,14 +31,15 @@ module Ballantine
|
|
47
31
|
raise NotAllowed, "Environment value must be unique."
|
48
32
|
end
|
49
33
|
|
50
|
-
|
51
|
-
raise AssertionFailed, "Environment value must exist: #{
|
34
|
+
env = Config::AVAILABLE_ENVIRONMENTS.find { |key| options[key] }
|
35
|
+
raise AssertionFailed, "Environment value must exist: #{env}" if env.nil?
|
52
36
|
|
37
|
+
conf.env = env
|
53
38
|
value ? conf.set_data(key, value) : conf.print_data(key)
|
54
39
|
end
|
55
40
|
|
56
41
|
desc "diff [TARGET] [SOURCE]", "Diff commits between TARGET and SOURCE"
|
57
|
-
option TYPE_SLACK, type: :boolean, aliases: "-s", default: false, desc: "Send to slack using slack webhook URL."
|
42
|
+
option Config::TYPE_SLACK, type: :boolean, aliases: "-s", default: false, desc: "Send to slack using slack webhook URL."
|
58
43
|
def diff(target, source = %x(git rev-parse --abbrev-ref HEAD).chomp)
|
59
44
|
# validate arguments
|
60
45
|
validate(target, source, **options)
|
@@ -63,30 +48,13 @@ module Ballantine
|
|
63
48
|
system("git pull -f &> /dev/null")
|
64
49
|
|
65
50
|
# init instance variables
|
66
|
-
init_variables(**options)
|
67
|
-
|
68
|
-
# find github url, branch
|
69
|
-
url = github_url(%x(git config --get remote.origin.url).chomp)
|
70
|
-
current_revision = %x(git rev-parse --abbrev-ref HEAD).chomp
|
71
|
-
|
72
|
-
# get commit hash
|
73
|
-
from, sub_from = commit_hash(target)
|
74
|
-
to, sub_to = commit_hash(source)
|
75
|
-
system("git checkout #{current_revision} -f &> /dev/null")
|
51
|
+
init_variables(target, source, **options)
|
76
52
|
|
77
53
|
# check commits
|
78
|
-
check_commits(
|
79
|
-
sub_path.each_with_index do |path, idx|
|
80
|
-
next if sub_from[idx] == sub_to[idx]
|
81
|
-
|
82
|
-
Dir.chdir(path)
|
83
|
-
sub_url = github_url(%x(git config --get remote.origin.url).chomp)
|
84
|
-
check_commits(sub_from[idx], sub_to[idx], sub_url)
|
85
|
-
Dir.chdir(main_path)
|
86
|
-
end
|
54
|
+
check_commits(**options)
|
87
55
|
|
88
|
-
#
|
89
|
-
|
56
|
+
# print commits
|
57
|
+
print_commits(target, source, **options)
|
90
58
|
|
91
59
|
exit(0)
|
92
60
|
end
|
@@ -100,7 +68,7 @@ module Ballantine
|
|
100
68
|
|
101
69
|
private
|
102
70
|
|
103
|
-
def conf;
|
71
|
+
def conf; Config.instance end
|
104
72
|
|
105
73
|
# @param [String] target
|
106
74
|
# @param [String] source
|
@@ -119,130 +87,58 @@ module Ballantine
|
|
119
87
|
raise NotAllowed, "ERROR: target(#{target}) and source(#{source}) can't be equal."
|
120
88
|
end
|
121
89
|
|
122
|
-
if options[TYPE_SLACK] && !conf.get_data(Config::KEY_SLACK_WEBHOOK)
|
90
|
+
if options[Config::TYPE_SLACK] && !conf.get_data(Config::KEY_SLACK_WEBHOOK)
|
123
91
|
raise NotAllowed, "ERROR: Can't find any slack webhook. Set slack webhook using `ballantine config --#{Config::ENV_LOCAL} slack_webhook [YOUR_WEBHOOK]'."
|
124
92
|
end
|
125
93
|
|
126
94
|
nil
|
127
95
|
end
|
128
96
|
|
97
|
+
# @param [String] target
|
98
|
+
# @param [String] source
|
129
99
|
# @param [Hash] options
|
130
100
|
# @return [Boolean]
|
131
|
-
def init_variables(**options)
|
132
|
-
|
133
|
-
@
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
lines.grep(/path =/).map { |line| line[/(?<=path \=).*/, 0].strip }.sort
|
141
|
-
else
|
142
|
-
[]
|
143
|
-
end
|
101
|
+
def init_variables(target, source, **options)
|
102
|
+
conf.print_type = options[Config::TYPE_SLACK] ? Config::TYPE_SLACK : Config::TYPE_TERMINAL
|
103
|
+
@repo = Repository.find_or_create_by(
|
104
|
+
path: Dir.pwd,
|
105
|
+
remote_url: %x(git config --get remote.origin.url).chomp,
|
106
|
+
)
|
107
|
+
|
108
|
+
# init repo
|
109
|
+
repo.init_variables(target, source)
|
144
110
|
true
|
145
111
|
end
|
146
112
|
|
147
|
-
# @param [
|
148
|
-
# @return [
|
149
|
-
def
|
150
|
-
|
151
|
-
return name unless list.grep(name).any?
|
152
|
-
|
153
|
-
system("git fetch origin tag #{name} -f &> /dev/null")
|
154
|
-
%x(git rev-list -n 1 #{name}).chomp[0...7]
|
155
|
-
end
|
156
|
-
|
157
|
-
# @param [String] from
|
158
|
-
# @param [String] to
|
159
|
-
# @param [String] url
|
160
|
-
# @return [NilClass] nil
|
161
|
-
def check_commits(from, to, url)
|
162
|
-
repo = File.basename(%x(git config --get remote.origin.url).chomp, ".git")
|
163
|
-
names = %x(git --no-pager log --pretty=format:"%an" #{from}..#{to}).split("\n").uniq.sort
|
164
|
-
authors = names.map { |name| Author.find_or_create_by(name) }
|
165
|
-
authors.each do |author|
|
166
|
-
format = commit_format(url, ljust: DEFAULT_LJUST - 10)
|
167
|
-
commits =
|
168
|
-
%x(git --no-pager log --reverse --no-merges --author="#{author.name}" --format="#{format}" --abbrev=7 #{from}..#{to})
|
169
|
-
.gsub('"', '\"')
|
170
|
-
.gsub(/[\u0080-\u00ff]/, "")
|
171
|
-
next if commits.empty?
|
172
|
-
|
173
|
-
author.commits[repo] = commits.split("\n")
|
174
|
-
end
|
175
|
-
nil
|
176
|
-
end
|
177
|
-
|
178
|
-
# @param [String] url
|
179
|
-
# @return [String] github_url
|
180
|
-
def github_url(url)
|
181
|
-
owner, repository = GITHUB_REGEXES.each do |regex|
|
182
|
-
if (str = url.match(regex))
|
183
|
-
break [str[2], str[3]]
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
"https://github.com/#{owner}/#{repository}"
|
188
|
-
end
|
189
|
-
|
190
|
-
# @param [String] hash
|
191
|
-
# @return [Array(String, Array<String>)] main, sub's hash
|
192
|
-
def commit_hash(hash)
|
193
|
-
# check argument is tag
|
194
|
-
hash = check_tag(hash)
|
195
|
-
|
196
|
-
system("git checkout #{hash} -f &> /dev/null")
|
197
|
-
system("git pull &> /dev/null")
|
198
|
-
main_hash = %x(git --no-pager log -1 --format='%h').chomp
|
199
|
-
sub_hash =
|
200
|
-
if sub_path.any?
|
201
|
-
%x(git ls-tree HEAD #{@sub_path.join(" ")}).split("\n").map { |line| line.split(" ")[2] }
|
202
|
-
else
|
203
|
-
[]
|
204
|
-
end
|
205
|
-
|
206
|
-
[main_hash, sub_hash]
|
207
|
-
end
|
113
|
+
# @param [Hash] options
|
114
|
+
# @return [Boolean]
|
115
|
+
def check_commits(**options)
|
116
|
+
repo.check_commits
|
208
117
|
|
209
|
-
|
210
|
-
# @param [String] format
|
211
|
-
# @param [Integer] ljust
|
212
|
-
def commit_format(url, ljust: DEFAULT_LJUST)
|
213
|
-
case send_type
|
214
|
-
when TYPE_TERMINAL
|
215
|
-
" - " + "%h".yellow + " %<(#{ljust})%s " + "#{url}/commit/%H".gray
|
216
|
-
when TYPE_SLACK
|
217
|
-
"\\\`<#{url}/commit/%H|%h>\\\` %s - %an"
|
218
|
-
else raise AssertionFailed, "Unknown send type: #{send_type}"
|
219
|
-
end
|
118
|
+
true
|
220
119
|
end
|
221
120
|
|
222
121
|
# @param [String] target
|
223
122
|
# @param [String] source
|
224
|
-
# @param [
|
225
|
-
# @
|
226
|
-
|
227
|
-
# @return [NilClass] nil
|
228
|
-
def send_commits(target, source, from, to, url)
|
123
|
+
# @param [Hash] options
|
124
|
+
# @return [Boolean]
|
125
|
+
def print_commits(target, source, **options)
|
229
126
|
authors = Author.all
|
230
127
|
if authors.empty?
|
231
128
|
raise ArgumentError, "ERROR: There is no commits between \"#{target}\" and \"#{source}\""
|
232
129
|
end
|
233
130
|
|
234
131
|
number = authors.size
|
235
|
-
last_commit = %x(git --no-pager log --reverse --format="#{commit_format(url, ljust: DEFAULT_LJUST - 22)}" --abbrev=7 #{from}..#{to} -1).strip
|
236
132
|
|
237
|
-
case
|
238
|
-
when TYPE_TERMINAL
|
239
|
-
|
133
|
+
case conf.print_type
|
134
|
+
when Config::TYPE_TERMINAL
|
135
|
+
puts_r "Check commits before #{repo.name.red} deployment. (#{target.cyan} <- #{source.cyan})", "#{repo.url}/compare/#{repo.from.hash}...#{repo.to.hash}".gray
|
240
136
|
puts "Author".yellow + ": #{number}"
|
241
|
-
|
137
|
+
puts_r "#{"Last commit".blue}: #{repo.to.hash.yellow} #{repo.to.subject}", repo.to.url.gray
|
242
138
|
authors.map(&:print_commits)
|
243
|
-
when TYPE_SLACK
|
139
|
+
when Config::TYPE_SLACK
|
244
140
|
# set message for each author
|
245
|
-
messages = authors.map(&:
|
141
|
+
messages = authors.map(&:slack_message)
|
246
142
|
actor = %x(git config user.name).chomp
|
247
143
|
|
248
144
|
# send message to slack
|
@@ -252,17 +148,19 @@ module Ballantine
|
|
252
148
|
request = Net::HTTP::Post.new(uri)
|
253
149
|
request.content_type = "application/json"
|
254
150
|
request.body = JSON.dump({
|
255
|
-
"text" => ":white_check_mark: *#{
|
151
|
+
"text" => ":white_check_mark: *#{repo.name}* deployment request by <@#{actor}>" \
|
152
|
+
" (\`<#{repo.url}/tree/#{repo.from.hash}|#{target}>\` <- \`<#{repo.url}/tree/#{repo.to.hash}|#{source}>\` <#{repo.url}/compare/#{repo.from.hash}...#{repo.to.hash}|compare>)" \
|
153
|
+
"\n:technologist: Author: #{number}\nLast commit: #{repo.to.slack_message}",
|
256
154
|
"attachments" => messages,
|
257
155
|
})
|
258
156
|
req_options = { use_ssl: uri.scheme == "https" }
|
259
|
-
response = Net::HTTP.start(uri.hostname, uri.port, req_options)
|
260
|
-
http.request(request)
|
261
|
-
end
|
157
|
+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) { |http| http.request(request) }
|
262
158
|
puts response.message
|
263
159
|
else
|
264
|
-
raise AssertionFailed, "Unknown
|
160
|
+
raise AssertionFailed, "Unknown print type: #{conf.print_type}"
|
265
161
|
end
|
162
|
+
|
163
|
+
true
|
266
164
|
end
|
267
165
|
end
|
268
166
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ballantine
|
4
|
+
class Commit
|
5
|
+
attr_reader :hash, :long_hash, :subject # attributes
|
6
|
+
attr_reader :repo, :author # associations
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# @param [String] hash
|
10
|
+
# @param [Repository] repo
|
11
|
+
# @return [Commit, NilClass]
|
12
|
+
def find(hash:, repo:)
|
13
|
+
@_collections = {} unless defined?(@_collections)
|
14
|
+
@_collections["#{hash[...7]}-#{repo.name}"]
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [String] hash
|
18
|
+
# @param [Repository] repo
|
19
|
+
# @return [Commit]
|
20
|
+
def find_or_create_by(hash:, repo:)
|
21
|
+
find(hash:, repo:) || @_collections["#{hash[...7]}-#{repo.name}"] = new(hash:, repo:)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String] hash
|
26
|
+
# @param [Repository] repo
|
27
|
+
def initialize(hash:, repo:)
|
28
|
+
@hash = hash[...7]
|
29
|
+
@repo = repo
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Commit]
|
33
|
+
def update(**kwargs)
|
34
|
+
# TODO: validate keys and values
|
35
|
+
kwargs.each { |key, value| instance_variable_set("@#{key}", value) }
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def url; @url ||= "#{repo.url}/commit/#{long_hash}" end
|
40
|
+
|
41
|
+
# @return [String]
|
42
|
+
def slack_message
|
43
|
+
"\`<#{url}|#{hash}>\` #{subject} - #{author.name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/ballantine/config.rb
CHANGED
@@ -2,24 +2,35 @@
|
|
2
2
|
|
3
3
|
module Ballantine
|
4
4
|
class Config
|
5
|
+
FILE_BALLANTINE_CONFIG = ".ballantine.json"
|
5
6
|
ENV_LOCAL = "local"
|
6
7
|
ENV_GLOBAL = "global"
|
8
|
+
TYPE_TERMINAL = "terminal"
|
9
|
+
TYPE_SLACK = "slack"
|
7
10
|
AVAILABLE_ENVIRONMENTS = [
|
8
11
|
ENV_LOCAL,
|
9
12
|
ENV_GLOBAL,
|
10
13
|
].freeze
|
11
|
-
|
12
14
|
KEY_SLACK_WEBHOOK = "slack_webhook"
|
13
15
|
AVAILABLE_KEYS = [
|
14
16
|
KEY_SLACK_WEBHOOK,
|
15
17
|
].freeze
|
16
18
|
|
17
|
-
|
19
|
+
attr_reader :data, :loaded
|
20
|
+
attr_accessor :env, :print_type
|
18
21
|
|
19
|
-
|
22
|
+
class << self
|
23
|
+
# @note singleton method
|
24
|
+
# @return [Config]
|
25
|
+
def instance(...)
|
26
|
+
return @_instance if defined?(@_instance)
|
27
|
+
|
28
|
+
@_instance = new(...)
|
29
|
+
end
|
30
|
+
end
|
20
31
|
|
21
|
-
def initialize
|
22
|
-
@env =
|
32
|
+
def initialize
|
33
|
+
@env = ENV_LOCAL
|
23
34
|
@data = {}
|
24
35
|
@loaded = false
|
25
36
|
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ballantine
|
4
|
+
class Repository
|
5
|
+
# reference: https://github.com/desktop/desktop/blob/a7bca44088b105a04714dc4628f4af50f6f179c3/app/src/lib/remote-parsing.ts#L27-L44
|
6
|
+
GITHUB_REGEXES = [
|
7
|
+
'^https?://(.+)/(.+)/(.+)\.git/?$', # protocol: https -> https://github.com/oohyun15/ballantine.git | https://github.com/oohyun15/ballantine.git/
|
8
|
+
'^https?://(.+)/(.+)/(.+)/?$', # protocol: https -> https://github.com/oohyun15/ballantine | https://github.com/oohyun15/ballantine/
|
9
|
+
'^git@(.+):(.+)/(.+)\.git$', # protocol: ssh -> git@github.com:oohyun15/ballantine.git
|
10
|
+
'^git@(.+):(.+)/(.+)/?$', # protocol: ssh -> git@github.com:oohyun15/ballantine | git@github.com:oohyun15/ballantine/
|
11
|
+
'^git:(.+)/(.+)/(.+)\.git$', # protocol: ssh -> git:github.com/oohyun15/ballantine.git
|
12
|
+
'^git:(.+)/(.+)/(.+)/?$', # protocol: ssh -> git:github.com/oohyun15/ballantine | git:github.com/oohyun15/ballantine/
|
13
|
+
'^ssh://git@(.+)/(.+)/(.+)\.git$', # protocol: ssh -> ssh://git@github.com/oohyun15/ballantine.git
|
14
|
+
].freeze
|
15
|
+
FILE_GITMODULES = ".gitmodules"
|
16
|
+
PARSER_TOKEN = "!#!#"
|
17
|
+
|
18
|
+
attr_reader :name, :path, :owner, :from, :to # attributes
|
19
|
+
attr_reader :main_repo, :sub_repos, :commits # associations
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# @param [String] path
|
23
|
+
# @return [Repository, NilClass]
|
24
|
+
def find(path:)
|
25
|
+
@_collections = {} unless defined?(@_collections)
|
26
|
+
@_collections[path]
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [String] path
|
30
|
+
# @param [String] remote_url
|
31
|
+
# @return [Repository]
|
32
|
+
def find_or_create_by(path:, remote_url: nil)
|
33
|
+
find(path:) || @_collections[path] = new(path:, remote_url:)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Array<Repository>]
|
37
|
+
def all
|
38
|
+
return [] unless defined?(@_collections)
|
39
|
+
|
40
|
+
@_collections.values
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String] path
|
45
|
+
# @param [String] remote_url
|
46
|
+
def initialize(path:, remote_url:)
|
47
|
+
@path = path
|
48
|
+
@commits = []
|
49
|
+
@sub_repos = retrieve_sub_repos
|
50
|
+
@owner, @name = GITHUB_REGEXES.each do |regex|
|
51
|
+
str = remote_url.match(regex)
|
52
|
+
break [str[2], str[3]] if str
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def url; @url ||= "https://github.com/#{owner}/#{name}" end
|
57
|
+
|
58
|
+
# @param [String] target
|
59
|
+
# @param [String] source
|
60
|
+
# @return [Boolean]
|
61
|
+
def init_variables(target, source)
|
62
|
+
current_revision = %x(git rev-parse --abbrev-ref HEAD).chomp
|
63
|
+
|
64
|
+
foo = lambda do |hash, context|
|
65
|
+
hash = check_tag(hash)
|
66
|
+
system("git checkout #{hash} -f &> /dev/null")
|
67
|
+
system("git pull &> /dev/null")
|
68
|
+
|
69
|
+
hash = %x(git --no-pager log -1 --format='%h').chomp
|
70
|
+
commit = Commit.find_or_create_by(
|
71
|
+
hash: hash,
|
72
|
+
repo: self,
|
73
|
+
)
|
74
|
+
instance_variable_set("@#{context}", commit)
|
75
|
+
|
76
|
+
if sub_repos.any?
|
77
|
+
%x(git ls-tree HEAD #{sub_repos.map(&:path).join(" ")}).split("\n").map do |line|
|
78
|
+
_, _, sub_hash, sub_path = line.split(" ")
|
79
|
+
sub_repo = Repository.find(
|
80
|
+
path: path + "/" + sub_path,
|
81
|
+
)
|
82
|
+
sub_commit = Commit.find_or_create_by(
|
83
|
+
hash: sub_hash,
|
84
|
+
repo: sub_repo,
|
85
|
+
)
|
86
|
+
sub_repo.instance_variable_set("@#{context}", sub_commit)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
foo.call(target, "from")
|
92
|
+
foo.call(source, "to")
|
93
|
+
|
94
|
+
system("git checkout #{current_revision} -f &> /dev/null")
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Boolean]
|
100
|
+
def check_commits
|
101
|
+
authors = retrieve_authors
|
102
|
+
authors.each do |author|
|
103
|
+
commits = retrieve_commits(author)
|
104
|
+
next if commits.empty?
|
105
|
+
|
106
|
+
author.commits_hash[name] = commits
|
107
|
+
# TODO: append `commits` to `repo.commits`
|
108
|
+
end
|
109
|
+
|
110
|
+
if sub_repos.any?
|
111
|
+
sub_repos.each do |sub_repo|
|
112
|
+
next if sub_repo.from.hash == sub_repo.to.hash
|
113
|
+
|
114
|
+
Dir.chdir(sub_repo.path)
|
115
|
+
sub_repo.check_commits
|
116
|
+
end
|
117
|
+
Dir.chdir(path)
|
118
|
+
end
|
119
|
+
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def conf; Config.instance end
|
126
|
+
|
127
|
+
# @param [String] name
|
128
|
+
# @return [String] hash
|
129
|
+
def check_tag(name)
|
130
|
+
list = %x(git tag -l).split("\n")
|
131
|
+
return name unless list.grep(name).any?
|
132
|
+
|
133
|
+
system("git fetch origin tag #{name} -f &> /dev/null")
|
134
|
+
%x(git rev-list -n 1 #{name}).chomp[0...7]
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [String]
|
138
|
+
def check_format
|
139
|
+
case conf.print_type
|
140
|
+
when Config::TYPE_TERMINAL
|
141
|
+
" - " + "%h".yellow + " %<(#{ljust})%s " + "#{url}/commit/%H".gray
|
142
|
+
when Config::TYPE_SLACK
|
143
|
+
"\\\`<#{url}/commit/%H|%h>\\\` %s - %an"
|
144
|
+
else
|
145
|
+
raise AssertionFailed, "Unknown print type: #{conf.print_type}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [Array<Repository>]
|
150
|
+
def retrieve_sub_repos
|
151
|
+
gitmodule = path + "/" + FILE_GITMODULES
|
152
|
+
return [] unless Dir[gitmodule].any?
|
153
|
+
|
154
|
+
file = File.open(gitmodule)
|
155
|
+
resp = file.read
|
156
|
+
file.close
|
157
|
+
|
158
|
+
resp.split(/\[submodule.*\]/)
|
159
|
+
.select { |line| line.match?(/path = /) }
|
160
|
+
.sort
|
161
|
+
.map do |line|
|
162
|
+
line = line.strip
|
163
|
+
repo = Repository.find_or_create_by(
|
164
|
+
path: path + "/" + line.match(/path = (.*)/)[1],
|
165
|
+
remote_url: line.match(/url = (.*)/)[1],
|
166
|
+
)
|
167
|
+
repo.instance_variable_set("@main_repo", self)
|
168
|
+
repo
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# @return [Array<Author>]
|
173
|
+
def retrieve_authors
|
174
|
+
%x(git --no-pager log --pretty=format:"%an" #{from.hash}..#{to.hash})
|
175
|
+
.split("\n").uniq.sort
|
176
|
+
.map { |name| Author.find_or_create_by(name:) }
|
177
|
+
end
|
178
|
+
|
179
|
+
# @param [Author] author
|
180
|
+
# @return [Array<Commit>]
|
181
|
+
def retrieve_commits(author)
|
182
|
+
command = <<~CMD.tr("\n", " ").strip
|
183
|
+
git --no-pager log --reverse --no-merges --author="#{author.name}"
|
184
|
+
--format="%h#{PARSER_TOKEN}%H#{PARSER_TOKEN}%s"
|
185
|
+
--abbrev=7 #{from.hash}..#{to.hash}
|
186
|
+
CMD
|
187
|
+
results =
|
188
|
+
%x(#{command})
|
189
|
+
.gsub('"', '\"')
|
190
|
+
.gsub(/[\u0080-\u00ff]/, "")
|
191
|
+
.split("\n")
|
192
|
+
|
193
|
+
results.map do |result|
|
194
|
+
hash, long_hash, subject = result.split(PARSER_TOKEN)
|
195
|
+
Commit.find_or_create_by(
|
196
|
+
hash: hash,
|
197
|
+
repo: self,
|
198
|
+
).update(
|
199
|
+
author: author,
|
200
|
+
long_hash: long_hash,
|
201
|
+
subject: subject,
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/lib/ballantine/version.rb
CHANGED
data/lib/ballantine.rb
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
require "thor"
|
4
4
|
require "json"
|
5
5
|
require_relative "string"
|
6
|
+
require_relative "printable"
|
6
7
|
require_relative "ballantine/version"
|
7
8
|
require_relative "ballantine/config"
|
8
9
|
require_relative "ballantine/author"
|
10
|
+
require_relative "ballantine/repository"
|
11
|
+
require_relative "ballantine/commit"
|
9
12
|
require_relative "ballantine/cli"
|
10
13
|
|
11
14
|
module Ballantine
|
data/lib/printable.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Printable
|
4
|
+
# @param [String] msg
|
5
|
+
# @param [String] msg_r
|
6
|
+
# @return [NilClass]
|
7
|
+
def puts_r(msg, msg_r)
|
8
|
+
size = rjust_size(msg, msg_r)
|
9
|
+
puts msg + msg_r.rjust(size)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String] msg
|
13
|
+
# @param [String] msg_r
|
14
|
+
# @return [Integer]
|
15
|
+
def rjust_size(msg, msg_r)
|
16
|
+
sanitized = ->(str) { str.sanitize_colored.size + str.scan(/\p{Hangul}/).size }
|
17
|
+
cols - sanitized.call(msg) - sanitized.call(msg_r) + msg_r.size
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Integer]
|
21
|
+
def cols
|
22
|
+
return @_cols if defined?(@_cols)
|
23
|
+
|
24
|
+
require "io/console"
|
25
|
+
_lines, @_cols = IO.console.winsize
|
26
|
+
@_cols
|
27
|
+
end
|
28
|
+
end
|
data/lib/string.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ballantine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.4.pre.
|
4
|
+
version: 0.1.4.pre.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- oohyun15
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -51,8 +51,11 @@ files:
|
|
51
51
|
- lib/ballantine.rb
|
52
52
|
- lib/ballantine/author.rb
|
53
53
|
- lib/ballantine/cli.rb
|
54
|
+
- lib/ballantine/commit.rb
|
54
55
|
- lib/ballantine/config.rb
|
56
|
+
- lib/ballantine/repository.rb
|
55
57
|
- lib/ballantine/version.rb
|
58
|
+
- lib/printable.rb
|
56
59
|
- lib/string.rb
|
57
60
|
homepage: https://github.com/oohyun15/ballantine
|
58
61
|
licenses:
|