git-story-workflow 0.10.0 → 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/VERSION +1 -1
- data/git-story-workflow.gemspec +6 -6
- data/lib/git/story.rb +0 -1
- data/lib/git/story/app.rb +65 -85
- data/lib/git/story/version.rb +1 -1
- metadata +6 -8
- data/lib/git/story/semaphore.rb +0 -141
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7436139595b89b702416d8a9d0f792d2c4cf078a89d56ae462093e4a36c8b1f4
|
4
|
+
data.tar.gz: 3a2542c050b36b046b49130b356284825fa3fd6d2cfef9bffbdea7735298555f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb7071e5a13b018ed232b9ff0e15357037b417c24720ea420b0d1ed370e83ecd8b54b65f60f4f426c8a6c6c30db236894a6839504d389bf0343e6f2efac2771f
|
7
|
+
data.tar.gz: c1755fe341e1f171169627dfe4a9b40b20a9fe34fc565f5ae2ee2d2925aebbdb38949fd5b8a1e7285730bd25fffba1c7d7b750113a44b9af4acb4204de2725a9
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/git-story-workflow.gemspec
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: git-story-workflow 0.
|
2
|
+
# stub: git-story-workflow 1.0.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "git-story-workflow".freeze
|
6
|
-
s.version = "0.
|
6
|
+
s.version = "1.0.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
10
10
|
s.authors = ["Florian Frank".freeze]
|
11
|
-
s.date = "2020-
|
11
|
+
s.date = "2020-09-10"
|
12
12
|
s.description = "Gem abstracting a git workflow\u2026".freeze
|
13
13
|
s.email = "flori@ping.de".freeze
|
14
14
|
s.executables = ["git-story".freeze]
|
15
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/git/story.rb".freeze, "lib/git/story/app.rb".freeze, "lib/git/story/
|
16
|
-
s.files = [".gitignore".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/git-story".freeze, "config/story.yml".freeze, "git-story-workflow.gemspec".freeze, "lib/git/story.rb".freeze, "lib/git/story/app.rb".freeze, "lib/git/story/prepare-commit-msg".freeze, "lib/git/story/
|
15
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/git/story.rb".freeze, "lib/git/story/app.rb".freeze, "lib/git/story/setup.rb".freeze, "lib/git/story/utils.rb".freeze, "lib/git/story/version.rb".freeze]
|
16
|
+
s.files = [".gitignore".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/git-story".freeze, "config/story.yml".freeze, "git-story-workflow.gemspec".freeze, "lib/git/story.rb".freeze, "lib/git/story/app.rb".freeze, "lib/git/story/prepare-commit-msg".freeze, "lib/git/story/setup.rb".freeze, "lib/git/story/utils.rb".freeze, "lib/git/story/version.rb".freeze, "spec/git/story/app_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
17
17
|
s.homepage = "http://flori.github.com/git-story-workflow".freeze
|
18
18
|
s.licenses = ["Apache-2.0".freeze]
|
19
19
|
s.rdoc_options = ["--title".freeze, "Git-story-workflow".freeze, "--main".freeze, "README.md".freeze]
|
20
|
-
s.rubygems_version = "3.1.
|
20
|
+
s.rubygems_version = "3.1.4".freeze
|
21
21
|
s.summary = "Gem abstracting a git workflow".freeze
|
22
22
|
s.test_files = ["spec/git/story/app_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
23
23
|
|
data/lib/git/story.rb
CHANGED
data/lib/git/story/app.rb
CHANGED
@@ -36,9 +36,9 @@ class Git::Story::App
|
|
36
36
|
def initialize(argv = ARGV.dup, debug: ENV['DEBUG'].to_i == 1)
|
37
37
|
@rest_argv = (sep = argv.index('--')) ? argv.slice!(sep..-1).tap(&:shift) : []
|
38
38
|
@argv = argv
|
39
|
-
@opts
|
40
|
-
@
|
41
|
-
|
39
|
+
@opts = go 'n:', @argv
|
40
|
+
@debug = debug
|
41
|
+
determine_command
|
42
42
|
end
|
43
43
|
|
44
44
|
def run
|
@@ -50,8 +50,11 @@ class Git::Story::App
|
|
50
50
|
puts __send__(@command, *@argv)
|
51
51
|
end
|
52
52
|
else
|
53
|
-
|
54
|
-
|
53
|
+
if @command
|
54
|
+
@command = @command.inspect
|
55
|
+
else
|
56
|
+
@command = 'n/a'
|
57
|
+
end
|
55
58
|
STDERR.puts "Unknown command #{@command}\n\n#{help.join(?\n)}"
|
56
59
|
exit 1
|
57
60
|
end
|
@@ -64,7 +67,7 @@ class Git::Story::App
|
|
64
67
|
longest = command_annotations.keys.map(&:size).max
|
65
68
|
result.concat(
|
66
69
|
command_annotations.map { |name, a|
|
67
|
-
"#{name.to_s.ljust(longest)} #{a[:doc]}"
|
70
|
+
"#{name.to_s.gsub(?_, ' ').ljust(longest)} #{a[:doc]}"
|
68
71
|
}
|
69
72
|
)
|
70
73
|
end
|
@@ -104,62 +107,6 @@ class Git::Story::App
|
|
104
107
|
end
|
105
108
|
end
|
106
109
|
|
107
|
-
command doc: '[BRANCH] display test status of branch, -n SECONDS refreshes'
|
108
|
-
def test_status(branch = current(check: false))
|
109
|
-
url = nil
|
110
|
-
watch do
|
111
|
-
auth_token = complex_config.story.semaphore_auth_token
|
112
|
-
project = complex_config.story.semaphore_test_project
|
113
|
-
url = "https://semaphoreci.com/api/v1/projects/#{project}/#{branch}/status?auth_token=#{auth_token}"
|
114
|
-
Git::Story::SemaphoreResponse.get(url, debug: @debug)
|
115
|
-
end
|
116
|
-
rescue => e
|
117
|
-
"Getting #{url.inspect} => #{e.class}: #{e}".red
|
118
|
-
end
|
119
|
-
|
120
|
-
command doc: '[SERVER] display deploy status of branch, -n SECONDS refreshes'
|
121
|
-
def deploy_status(server = complex_config.story.semaphore_default_server)
|
122
|
-
url = nil
|
123
|
-
watch do
|
124
|
-
auth_token = complex_config.story.semaphore_auth_token
|
125
|
-
project = complex_config.story.semaphore_test_project
|
126
|
-
url = "https://semaphoreci.com/api/v1/projects/#{project}/servers/#{server}?auth_token=#{auth_token}"
|
127
|
-
server = Git::Story::SemaphoreResponse.get(url, debug: @debug)
|
128
|
-
deploys = server.deploys
|
129
|
-
upcoming = deploys.select(&:pending?)&.last
|
130
|
-
passed = deploys.select(&:passed?)
|
131
|
-
current = passed.first
|
132
|
-
if !passed.empty? && upcoming
|
133
|
-
upcoming.estimated_duration = passed.sum { |d| d.duration.to_f } / passed.size
|
134
|
-
end
|
135
|
-
<<~end
|
136
|
-
Server: #{server.server_name&.green}
|
137
|
-
Branch: #{server.branch_name&.color('#ff5f00')}
|
138
|
-
Semaphore: #{server.server_url}
|
139
|
-
Strategy: #{server.strategy}
|
140
|
-
Upcoming:
|
141
|
-
#{upcoming}
|
142
|
-
Current:
|
143
|
-
#{current}
|
144
|
-
end
|
145
|
-
end
|
146
|
-
rescue => e
|
147
|
-
"Getting #{url.inspect} => #{e.class}: #{e}".red
|
148
|
-
end
|
149
|
-
|
150
|
-
command doc: '[BRANCH] display build status for branch, -n SECONDS refreshes'
|
151
|
-
def build_status(branch = current(check: false))
|
152
|
-
watch do
|
153
|
-
[
|
154
|
-
"Test Status".bold,
|
155
|
-
test_status(branch) || 'n/a',
|
156
|
-
"Deploy Status".bold,
|
157
|
-
deploy_status || 'n/a',
|
158
|
-
] * "\n\n"
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
|
163
110
|
command doc: '[STORY_ID] fetch status of current story, -n SECONDS refreshes'
|
164
111
|
def status(story_id = current(check: true)&.[](/_(\d+)\z/, 1)&.to_i)
|
165
112
|
if story = fetch_story(story_id)
|
@@ -317,34 +264,25 @@ class Git::Story::App
|
|
317
264
|
command doc: '[PATTERN] switch to story matching PATTERN'
|
318
265
|
def switch(pattern = nil)
|
319
266
|
fetch_commits
|
320
|
-
|
321
|
-
branch = Search.new(
|
322
|
-
match: -> answer {
|
323
|
-
answer = answer.strip.delete(?#).downcase
|
324
|
-
|
325
|
-
matcher = Amatch::PairDistance.new(answer)
|
326
|
-
matches = ss.map { |n| [ n, -matcher.similar(n.downcase) ] }.
|
327
|
-
select { |_, s| s < 0 }.sort_by(&:last).map(&:first)
|
328
|
-
|
329
|
-
matches.empty? and matches = ss
|
330
|
-
matches.first(Tins::Terminal.lines - 1)
|
331
|
-
},
|
332
|
-
query: -> _answer, matches, selector {
|
333
|
-
matches.each_with_index.
|
334
|
-
map { |m, i| i == selector ? '⏻ ' + Search.on_blue(m) : '┊ ' + m } * ?\n
|
335
|
-
},
|
336
|
-
found: -> _answer, matches, selector {
|
337
|
-
matches[selector]
|
338
|
-
},
|
339
|
-
prompt: 'Story? %s'.bold,
|
340
|
-
output: STDOUT
|
341
|
-
).start
|
342
|
-
if branch
|
267
|
+
if branch = pick_branch(prompt: 'Switch to story? %s')
|
343
268
|
sh "git checkout #{branch}"
|
344
269
|
return "Switched to story: #{branch}".green
|
345
270
|
end
|
346
271
|
end
|
347
272
|
|
273
|
+
command doc: '[PATTERN] delete story branch matching PATTERN'
|
274
|
+
def delete(pattern = nil)
|
275
|
+
fetch_commits
|
276
|
+
if branch = pick_branch(prompt: 'Delete story branch? %s', symbol: ?⌦)
|
277
|
+
sh "git pull origin #{branch}"
|
278
|
+
sh "git branch -d #{branch}"
|
279
|
+
if ask(prompt: 'Delete remote branch? (y/N) ') =~ /\Ay\z/i
|
280
|
+
sh "git push origin #{branch} --delete"
|
281
|
+
end
|
282
|
+
return "Deleted story branch: #{branch}".green
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
348
286
|
command doc: '[BRANCH] open branch on github'
|
349
287
|
def github(branch = current(check: false))
|
350
288
|
if url = github_url(branch)
|
@@ -364,6 +302,48 @@ class Git::Story::App
|
|
364
302
|
|
365
303
|
private
|
366
304
|
|
305
|
+
def determine_command
|
306
|
+
c, command = [], nil
|
307
|
+
possible_commands = []
|
308
|
+
until @argv.empty?
|
309
|
+
c << @argv.shift
|
310
|
+
command = c.join(?_).to_sym
|
311
|
+
if command_of(command)
|
312
|
+
possible_commands << [ command, @argv.dup ]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
unless possible_commands.empty?
|
316
|
+
@command, argv = possible_commands.last
|
317
|
+
@argv.replace(argv)
|
318
|
+
end
|
319
|
+
self
|
320
|
+
end
|
321
|
+
|
322
|
+
def pick_branch(prompt:, symbol: ?⏻)
|
323
|
+
ss = stories.map(&:story_base_name)
|
324
|
+
branch = Search.new(
|
325
|
+
match: -> answer {
|
326
|
+
answer = answer.strip.delete(?#).downcase
|
327
|
+
|
328
|
+
matcher = Amatch::PairDistance.new(answer)
|
329
|
+
matches = ss.map { |n| [ n, -matcher.similar(n.downcase) ] }.
|
330
|
+
select { |_, s| s < 0 }.sort_by(&:last).map(&:first)
|
331
|
+
|
332
|
+
matches.empty? and matches = ss
|
333
|
+
matches.first(Tins::Terminal.lines - 1)
|
334
|
+
},
|
335
|
+
query: -> _answer, matches, selector {
|
336
|
+
matches.each_with_index.
|
337
|
+
map { |m, i| i == selector ? "#{symbol} " + Search.on_blue(m) : '┊ ' + m } * ?\n
|
338
|
+
},
|
339
|
+
found: -> _answer, matches, selector {
|
340
|
+
matches[selector]
|
341
|
+
},
|
342
|
+
prompt: prompt.bold,
|
343
|
+
output: STDOUT
|
344
|
+
).start
|
345
|
+
end
|
346
|
+
|
367
347
|
def default_ref
|
368
348
|
tags.last
|
369
349
|
end
|
data/lib/git/story/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-story-workflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Frank
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem_hadar
|
@@ -159,7 +159,6 @@ extra_rdoc_files:
|
|
159
159
|
- README.md
|
160
160
|
- lib/git/story.rb
|
161
161
|
- lib/git/story/app.rb
|
162
|
-
- lib/git/story/semaphore.rb
|
163
162
|
- lib/git/story/setup.rb
|
164
163
|
- lib/git/story/utils.rb
|
165
164
|
- lib/git/story/version.rb
|
@@ -176,7 +175,6 @@ files:
|
|
176
175
|
- lib/git/story.rb
|
177
176
|
- lib/git/story/app.rb
|
178
177
|
- lib/git/story/prepare-commit-msg
|
179
|
-
- lib/git/story/semaphore.rb
|
180
178
|
- lib/git/story/setup.rb
|
181
179
|
- lib/git/story/utils.rb
|
182
180
|
- lib/git/story/version.rb
|
@@ -186,7 +184,7 @@ homepage: http://flori.github.com/git-story-workflow
|
|
186
184
|
licenses:
|
187
185
|
- Apache-2.0
|
188
186
|
metadata: {}
|
189
|
-
post_install_message:
|
187
|
+
post_install_message:
|
190
188
|
rdoc_options:
|
191
189
|
- "--title"
|
192
190
|
- Git-story-workflow
|
@@ -205,8 +203,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
203
|
- !ruby/object:Gem::Version
|
206
204
|
version: '0'
|
207
205
|
requirements: []
|
208
|
-
rubygems_version: 3.1.
|
209
|
-
signing_key:
|
206
|
+
rubygems_version: 3.1.4
|
207
|
+
signing_key:
|
210
208
|
specification_version: 4
|
211
209
|
summary: Gem abstracting a git workflow
|
212
210
|
test_files:
|
data/lib/git/story/semaphore.rb
DELETED
@@ -1,141 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'time'
|
3
|
-
require 'open-uri'
|
4
|
-
require 'infobar'
|
5
|
-
|
6
|
-
class Git::Story::SemaphoreResponse < JSON::GenericObject
|
7
|
-
def self.get(url, debug: false)
|
8
|
-
data = URI.open(url).read
|
9
|
-
debug and STDERR.puts JSON.pretty_generate(JSON(data))
|
10
|
-
result = JSON(data, object_class: self)
|
11
|
-
result.debug = debug
|
12
|
-
result
|
13
|
-
end
|
14
|
-
|
15
|
-
def duration(time = nil)
|
16
|
-
unless time
|
17
|
-
if finished_at.nil?
|
18
|
-
time = Time.now
|
19
|
-
else
|
20
|
-
time = Time.parse(finished_at)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
if started_at
|
24
|
-
Tins::Duration.new(time - Time.parse(started_at))
|
25
|
-
else
|
26
|
-
Tins::Duration.new(0)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def pending?
|
31
|
-
result == 'pending'
|
32
|
-
end
|
33
|
-
|
34
|
-
def building?
|
35
|
-
!started_at.nil?
|
36
|
-
end
|
37
|
-
|
38
|
-
def passed?
|
39
|
-
result == 'passed'
|
40
|
-
end
|
41
|
-
|
42
|
-
def failed?
|
43
|
-
result == 'failed'
|
44
|
-
end
|
45
|
-
|
46
|
-
def canceled?
|
47
|
-
result == 'canceled'
|
48
|
-
end
|
49
|
-
|
50
|
-
def finished?
|
51
|
-
finished_at.blank?
|
52
|
-
end
|
53
|
-
|
54
|
-
def sha1
|
55
|
-
commit.id[0,10]
|
56
|
-
end
|
57
|
-
|
58
|
-
def entity_url
|
59
|
-
server_html_url || build_url
|
60
|
-
end
|
61
|
-
|
62
|
-
def entity_name
|
63
|
-
branch_name || server_name
|
64
|
-
end
|
65
|
-
|
66
|
-
def branch_history
|
67
|
-
if branch_history_url
|
68
|
-
self.class.get(branch_history_url, debug: debug)&.builds
|
69
|
-
else
|
70
|
-
[]
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def estimated_duration
|
75
|
-
if ed = super
|
76
|
-
ed
|
77
|
-
else
|
78
|
-
times = branch_history.select(&:passed?).map { |b|
|
79
|
-
Time.parse(b.finished_at) - Time.parse(b.started_at)
|
80
|
-
}
|
81
|
-
if times.empty?
|
82
|
-
duration
|
83
|
-
else
|
84
|
-
times.sum / times.size
|
85
|
-
end.to_f
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def infobar_style
|
90
|
-
case
|
91
|
-
when passed?, pending?
|
92
|
-
{
|
93
|
-
done_fg_color: '#005f00',
|
94
|
-
done_bg_color: '#00d700',
|
95
|
-
todo_fg_color: '#00d700',
|
96
|
-
todo_bg_color: '#005f00',
|
97
|
-
}
|
98
|
-
else
|
99
|
-
{
|
100
|
-
done_fg_color: '#5f0000',
|
101
|
-
done_bg_color: '#d70000',
|
102
|
-
todo_fg_color: '#d70000',
|
103
|
-
todo_bg_color: '#5f0000',
|
104
|
-
}
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def to_s
|
109
|
-
r = case
|
110
|
-
when pending? && building?
|
111
|
-
"#{entity_name} ##{sha1} building for #{duration(Time.now)}".yellow.bold
|
112
|
-
when pending?
|
113
|
-
"#{entity_name} ##{sha1} pending at the moment".yellow
|
114
|
-
when passed?
|
115
|
-
"#{entity_name} ##{sha1} passed after #{duration}".green
|
116
|
-
when failed?
|
117
|
-
"#{entity_name} ##{sha1} failed after #{duration}".red
|
118
|
-
else
|
119
|
-
"#{entity_name} ##{sha1} in state #{result}".blue
|
120
|
-
end
|
121
|
-
r = StringIO.new(r)
|
122
|
-
duration_seconds = duration.to_f.to_i
|
123
|
-
if passed? || failed?
|
124
|
-
total_seconds = duration_seconds
|
125
|
-
else
|
126
|
-
total_seconds = estimated_duration.to_i
|
127
|
-
end
|
128
|
-
Infobar(
|
129
|
-
current: duration_seconds,
|
130
|
-
total: total_seconds,
|
131
|
-
message: ' %l %c/%t seconds ',
|
132
|
-
style: infobar_style,
|
133
|
-
output: r
|
134
|
-
).update
|
135
|
-
r <<
|
136
|
-
"\n Semaphore: #{entity_url}" <<
|
137
|
-
"\n Commit: #{commit.url}\n#{commit.message&.gsub(/^/, " " * 10)&.color(33)}" <<
|
138
|
-
"\n Authored: #{(commit.author_name + ' <' + commit.author_email + ?>).bold} @#{commit.timestamp}"
|
139
|
-
r.tap(&:rewind).read
|
140
|
-
end
|
141
|
-
end
|