git-story-workflow 1.4.2 → 1.6.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 +4 -4
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/config/story.yml +6 -3
- data/git-story-workflow.gemspec +6 -4
- data/lib/git/story/app.rb +119 -48
- data/lib/git/story/setup.rb +46 -4
- data/lib/git/story/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b81ff3a4b94ba6bed90472b28eccdad8d3df7e4af30dc3ffe772cbef4ab78cc0
|
4
|
+
data.tar.gz: b2b7347b35bd6d1f1ac63389091b8c0fe80bfb744604ba9b379e5a896fd3b682
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '05349a0f364e7531b7a22602f4dea569498130b09aa90ab42a749d20b37a29b19a4da542b08fc88b418867d55e352d84a4c2ca7e4435585e8fe679b28807f604'
|
7
|
+
data.tar.gz: b4fe39f78898e26603998ee218223a6d8b76ea895c50c2dcfe6329947e7e13923ef03921bea9bc28e8cd5488c06475a76618a1a3931404777208df81c5a298c1
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.6.2
|
data/config/story.yml
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
---
|
2
|
-
pivotal_token:
|
3
|
-
pivotal_project:
|
2
|
+
pivotal_token: <%= ENV['PIVOTAL_TOKEN'] %>
|
3
|
+
pivotal_project: 1254912
|
4
|
+
pivotal_reference_prefix: pivotal
|
4
5
|
deploy_tag_prefix: production_deploy_
|
5
|
-
|
6
|
+
semaphore_auth_token: <%= ENV['SEMAPHORE_AUTH_TOKEN'] %>
|
7
|
+
semaphore_project_url: https://betterplace.semaphoreci.com/projects/betterplace
|
8
|
+
todo_nudging: <%= ENV['TODO_NUDGING'].to_i == 1 %>
|
data/git-story-workflow.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: git-story-workflow 1.
|
2
|
+
# stub: git-story-workflow 1.6.2 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "git-story-workflow".freeze
|
6
|
-
s.version = "1.
|
6
|
+
s.version = "1.6.2"
|
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 = "
|
11
|
+
s.date = "2022-01-12"
|
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]
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
|
|
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.2.
|
20
|
+
s.rubygems_version = "3.2.22".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
|
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
|
31
31
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
32
32
|
s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<byebug>.freeze, [">= 0"])
|
33
34
|
s.add_runtime_dependency(%q<infobar>.freeze, [">= 0"])
|
34
35
|
s.add_runtime_dependency(%q<tins>.freeze, [">= 0"])
|
35
36
|
s.add_runtime_dependency(%q<mize>.freeze, [">= 0"])
|
@@ -41,6 +42,7 @@ Gem::Specification.new do |s|
|
|
41
42
|
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
42
43
|
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
43
44
|
s.add_dependency(%q<rspec>.freeze, [">= 0"])
|
45
|
+
s.add_dependency(%q<byebug>.freeze, [">= 0"])
|
44
46
|
s.add_dependency(%q<infobar>.freeze, [">= 0"])
|
45
47
|
s.add_dependency(%q<tins>.freeze, [">= 0"])
|
46
48
|
s.add_dependency(%q<mize>.freeze, [">= 0"])
|
data/lib/git/story/app.rb
CHANGED
@@ -39,7 +39,13 @@ class Git::Story::App
|
|
39
39
|
@opts = go 'n:', @argv
|
40
40
|
@debug = debug
|
41
41
|
determine_command
|
42
|
-
|
42
|
+
if !cc.story? && @command != :setup
|
43
|
+
warn "git story configuration file " +
|
44
|
+
"config/story.yml".bold + " is missing.\n\nCall " +
|
45
|
+
"git story setup".bold + " to create an initial one, inspect and " +
|
46
|
+
"edit it yourself, and also set the appropriate env vars.\n\n"
|
47
|
+
@command, @argv = :help, []
|
48
|
+
end
|
43
49
|
end
|
44
50
|
|
45
51
|
def run
|
@@ -72,6 +78,15 @@ class Git::Story::App
|
|
72
78
|
)
|
73
79
|
end
|
74
80
|
|
81
|
+
command doc: 'initialize git story config file if missing'
|
82
|
+
def setup
|
83
|
+
if cc.config?
|
84
|
+
red("config/story.yml configration already exists!")
|
85
|
+
else
|
86
|
+
Git::Story::Setup.perform
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
75
90
|
command doc: 'output the current story branch if it is checked out'
|
76
91
|
def current(check: true)
|
77
92
|
if check
|
@@ -85,31 +100,9 @@ class Git::Story::App
|
|
85
100
|
end
|
86
101
|
end
|
87
102
|
|
88
|
-
def provide_name(story_id = nil)
|
89
|
-
until story_id.present?
|
90
|
-
story_id = ask(prompt: 'Story id? ').strip
|
91
|
-
end
|
92
|
-
story_id = story_id.gsub(/[^0-9]+/, '')
|
93
|
-
@story_id = Integer(story_id)
|
94
|
-
if stories.any? { |s| s.story_id == @story_id }
|
95
|
-
@reason = "story for ##@story_id already created"
|
96
|
-
return
|
97
|
-
end
|
98
|
-
if name = fetch_story_name(@story_id)
|
99
|
-
name = normalize_name(
|
100
|
-
name,
|
101
|
-
max_size: 128 - 'story'.size - @story_id.to_s.size - 2 * ?_.size
|
102
|
-
).full? || name
|
103
|
-
[ 'story', name, @story_id ] * ?_
|
104
|
-
else
|
105
|
-
@reason = "name for ##@story_id could not be fetched from tracker"
|
106
|
-
return
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
103
|
command doc: '[STORY_ID] fetch status of current story, -n SECONDS refreshes'
|
111
104
|
def status(story_id = current(check: true)&.[](/_(\d+)\z/, 1)&.to_i)
|
112
|
-
if story = fetch_story(story_id)
|
105
|
+
if story = fetch_story(story_id, with_owner: true)
|
113
106
|
color_state =
|
114
107
|
case cs = story.current_state
|
115
108
|
when 'unscheduled', 'planned', 'unstarted'
|
@@ -132,20 +125,21 @@ class Git::Story::App
|
|
132
125
|
else
|
133
126
|
t
|
134
127
|
end
|
135
|
-
owners = Array(fetch_story_owners(story_id)).map { |o| "#{o.name} <#{o.email}>" }
|
136
128
|
result = <<~end
|
137
129
|
Id: #{(?# + story.id.to_s).green}
|
138
130
|
Name: #{story.name.inspect.bold}
|
139
131
|
Type: #{color_type}
|
140
132
|
Estimate: #{story.estimate.to_s.full? { |e| e.yellow.bold } || 'n/a'}
|
141
133
|
State: #{color_state}
|
142
|
-
Branch: #{current_branch_checked?&.color('#ff5f00')}
|
143
134
|
Labels: #{story.labels.map { |l| l.name.on_color(91) }.join(' ')}
|
144
|
-
Owners: #{owners.join(', ').yellow}
|
135
|
+
Owners: #{story.owners.map { |o| "%s <%s>" % [ o.name, o.email ] }.join(', ').yellow}
|
145
136
|
Pivotal: #{story.url.color(33)}
|
146
137
|
end
|
147
|
-
if url = github_url(current_branch_checked?)
|
148
|
-
result <<
|
138
|
+
if branch = current_branch_checked? and url = github_url(current_branch_checked?)
|
139
|
+
result << <<~end
|
140
|
+
Github: #{url.color(33)}
|
141
|
+
Branch: #{branch.color('#ff5f00')}
|
142
|
+
end
|
149
143
|
end
|
150
144
|
result
|
151
145
|
end
|
@@ -211,7 +205,8 @@ class Git::Story::App
|
|
211
205
|
fetch_tags
|
212
206
|
opts = ([
|
213
207
|
'--color=never',
|
214
|
-
'--pretty=%B'
|
208
|
+
'--pretty=%B',
|
209
|
+
'--reverse',
|
215
210
|
] | rest) * ' '
|
216
211
|
output = capture("git log #{opts} #{ref}")
|
217
212
|
pivotal_ids = SortedSet[]
|
@@ -219,18 +214,48 @@ class Git::Story::App
|
|
219
214
|
fetch_statuses(pivotal_ids) * (?┄ * Tins::Terminal.cols << ?\n)
|
220
215
|
end
|
221
216
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
217
|
+
command doc: '[REF] Create some parts of deploy document'
|
218
|
+
def deploy_document(ref = default_ref, rest: [])
|
219
|
+
ref = build_ref_range(ref)
|
220
|
+
fetch_commits
|
221
|
+
fetch_tags
|
222
|
+
opts = ([
|
223
|
+
'--color=never',
|
224
|
+
'--pretty=%B'
|
225
|
+
] | rest) * ' '
|
226
|
+
output = capture("git log #{opts} #{ref}")
|
227
|
+
pivotal_ids = SortedSet[]
|
228
|
+
output.scan(/\[\s*#\s*(\d+)\s*\]/) { pivotal_ids << $1.to_i }
|
229
|
+
stories = ''
|
230
|
+
attendees = Set[]
|
231
|
+
fetch_stories(pivotal_ids) do |pid|
|
232
|
+
story = fetch_story(pid, with_owner: true)
|
233
|
+
if !story
|
234
|
+
stories << "• ** Story with id #{pid} could not be found **"
|
235
|
+
next
|
236
|
+
end
|
237
|
+
attendees.merge story.owners
|
238
|
+
stories << <<~end
|
239
|
+
• [##{story.id}] #{story.name}
|
240
|
+
○ Pivotal: https://www.pivotaltracker.com/story/show/#{pid}
|
241
|
+
○ Type: #{story.story_type}
|
242
|
+
○ Status: #{story.current_state}
|
243
|
+
○ Owners: #{story.owners.map { |o| "%s <%s>" % [ o.name, o.email ] }.join(', ')}
|
244
|
+
○ Tasks to be done before deployment:
|
245
|
+
☐ …
|
246
|
+
○ Tasks to be done after deployment:
|
247
|
+
☐ …
|
248
|
+
end
|
226
249
|
end
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
250
|
+
attendees.map! { |a| "• @#{a.email}" }
|
251
|
+
<<~end
|
252
|
+
#{attendees.join(?\n)}
|
253
|
+
|
254
|
+
#{stories}
|
231
255
|
end
|
232
256
|
end
|
233
257
|
|
258
|
+
|
234
259
|
command doc: '[REF] output diff since last production deploy tag'
|
235
260
|
def deploy_diff(ref = default_ref, rest: [])
|
236
261
|
ref = build_ref_range(ref)
|
@@ -261,8 +286,8 @@ class Git::Story::App
|
|
261
286
|
"Story #{name} created.".green
|
262
287
|
end
|
263
288
|
|
264
|
-
command doc: '
|
265
|
-
def switch
|
289
|
+
command doc: 'switch to selected story branch'
|
290
|
+
def switch
|
266
291
|
fetch_commits
|
267
292
|
if branch = pick_branch(prompt: 'Switch to story? %s')
|
268
293
|
sh "git checkout #{branch}"
|
@@ -270,7 +295,7 @@ class Git::Story::App
|
|
270
295
|
end
|
271
296
|
end
|
272
297
|
|
273
|
-
command doc: '
|
298
|
+
command doc: 'delete selected story branch'
|
274
299
|
def delete(pattern = nil)
|
275
300
|
fetch_commits
|
276
301
|
if branch = pick_branch(prompt: 'Delete story branch? %s', symbol: ?⌦)
|
@@ -322,6 +347,28 @@ class Git::Story::App
|
|
322
347
|
|
323
348
|
private
|
324
349
|
|
350
|
+
def provide_name(story_id = nil)
|
351
|
+
until story_id.present?
|
352
|
+
story_id = ask(prompt: 'Story id? ').strip
|
353
|
+
end
|
354
|
+
story_id = story_id.gsub(/[^0-9]+/, '')
|
355
|
+
@story_id = Integer(story_id)
|
356
|
+
if stories.any? { |s| s.story_id == @story_id }
|
357
|
+
@reason = "story for ##@story_id already created"
|
358
|
+
return
|
359
|
+
end
|
360
|
+
if name = fetch_story_name(@story_id)
|
361
|
+
name = normalize_name(
|
362
|
+
name,
|
363
|
+
max_size: 128 - 'story'.size - @story_id.to_s.size - 2 * ?_.size
|
364
|
+
).full? || name
|
365
|
+
[ 'story', name, @story_id ] * ?_
|
366
|
+
else
|
367
|
+
@reason = "name for ##@story_id could not be fetched from tracker"
|
368
|
+
return
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
325
372
|
def determine_command
|
326
373
|
c, command = [], nil
|
327
374
|
possible_commands = []
|
@@ -433,11 +480,6 @@ class Git::Story::App
|
|
433
480
|
name
|
434
481
|
end
|
435
482
|
|
436
|
-
def apply_pattern(pattern, stories)
|
437
|
-
pattern = pattern.gsub(?#, '')
|
438
|
-
stories.grep(/#{Regexp.quote(pattern)}/)
|
439
|
-
end
|
440
|
-
|
441
483
|
def error(msg)
|
442
484
|
puts msg.red
|
443
485
|
exit 1
|
@@ -455,8 +497,11 @@ class Git::Story::App
|
|
455
497
|
fetch_story(story_id)&.name
|
456
498
|
end
|
457
499
|
|
458
|
-
def fetch_story(story_id)
|
459
|
-
pivotal_get("projects/#{pivotal_project}/stories/#{story_id}").full?
|
500
|
+
def fetch_story(story_id, with_owner: false)
|
501
|
+
if story = pivotal_get("projects/#{pivotal_project}/stories/#{story_id}").full?
|
502
|
+
story.owners = Array((fetch_story_owners(story_id) if with_owner))
|
503
|
+
end
|
504
|
+
story
|
460
505
|
end
|
461
506
|
|
462
507
|
def fetch_story_owners(story_id)
|
@@ -571,4 +616,30 @@ class Git::Story::App
|
|
571
616
|
url = url.sub('git@github.com:', 'https://github.com/')
|
572
617
|
url = url.sub(/(\.git)\z/, "/tree/#{branch}")
|
573
618
|
end
|
619
|
+
|
620
|
+
def fetch_stories(pivotal_ids, &block)
|
621
|
+
block or raise ArgumentError, '&block parameter is required'
|
622
|
+
tg = ThreadGroup.new
|
623
|
+
pivotal_ids.each do |pid|
|
624
|
+
order = 0
|
625
|
+
tg.add(
|
626
|
+
Thread.new do
|
627
|
+
Thread.current[:order] = order
|
628
|
+
Thread.current[:result] = block.(pid)
|
629
|
+
rescue
|
630
|
+
end
|
631
|
+
)
|
632
|
+
end
|
633
|
+
tg.list.with_infobar(label: 'Story').map do |t|
|
634
|
+
t.join
|
635
|
+
+infobar
|
636
|
+
[ t[:order], t[:result] ]
|
637
|
+
end.sort_by(&:first).transpose[1]
|
638
|
+
end
|
639
|
+
|
640
|
+
def fetch_statuses(pivotal_ids)
|
641
|
+
fetch_stories(pivotal_ids) do |pid|
|
642
|
+
status(pid)
|
643
|
+
end
|
644
|
+
end
|
574
645
|
end
|
data/lib/git/story/setup.rb
CHANGED
@@ -8,15 +8,35 @@ module Git::Story::Setup
|
|
8
8
|
PREPARE_COMMIT_MESSAGE_SRC = File.join(__dir__, 'prepare-commit-msg')
|
9
9
|
PREPARE_COMMIT_MESSAGE_DST = File.join(HOOKS_DIR, 'prepare-commit-msg')
|
10
10
|
|
11
|
+
CONFIG_TEMPLATE = <<~end
|
12
|
+
---
|
13
|
+
pivotal_token: <%= ENV['PIVOTAL_TOKEN'] %>
|
14
|
+
pivotal_project: 123456789
|
15
|
+
pivotal_reference_prefix: pivotal
|
16
|
+
deploy_tag_prefix: production_deploy_
|
17
|
+
semaphore_auth_token: <%= ENV['SEMAPHORE_AUTH_TOKEN'] %>
|
18
|
+
semaphore_project_url: https://betterplace.semaphoreci.com/projects/betterplace
|
19
|
+
todo_nudging: <%= ENV['TODO_NUDGING'].to_i == 1 %>
|
20
|
+
end
|
21
|
+
|
22
|
+
|
11
23
|
module_function
|
12
24
|
|
13
25
|
def perform(force: false)
|
26
|
+
unless File.directory?('.git')
|
27
|
+
puts "No directory .git found, you need an initialized git repo for this to work"
|
28
|
+
return
|
29
|
+
end
|
30
|
+
install_config('config/story.yml', force: force)
|
31
|
+
install_hooks(force: force)
|
32
|
+
"Setup was performed."
|
33
|
+
end
|
34
|
+
|
35
|
+
def install_hooks(force: false)
|
14
36
|
for filename in %w[ prepare-commit-msg pre-push ]
|
15
37
|
if path = file_installed?(filename)
|
16
|
-
if force
|
38
|
+
if force || File.read(path).match?(MARKER)
|
17
39
|
install_file filename
|
18
|
-
elsif File.read(path).match?(MARKER)
|
19
|
-
;
|
20
40
|
else
|
21
41
|
ask(
|
22
42
|
prompt: "File #{path.inspect} not created by git-story."\
|
@@ -43,6 +63,28 @@ module Git::Story::Setup
|
|
43
63
|
|
44
64
|
def install_file(filename)
|
45
65
|
File.exist?(HOOKS_DIR) or mkdir_p(HOOKS_DIR)
|
46
|
-
cp File.join(__dir__, filename), File.join(HOOKS_DIR, filename)
|
66
|
+
cp File.join(__dir__, filename), dest = File.join(HOOKS_DIR, filename)
|
67
|
+
puts "#{filename.to_s.inspect} was installed to #{dest.to_s.inspect}."
|
68
|
+
end
|
69
|
+
|
70
|
+
def install_config(filename, force: false)
|
71
|
+
filename = File.expand_path(filename)
|
72
|
+
if !force && File.exist?(filename)
|
73
|
+
ask(
|
74
|
+
prompt: "File #{filename.to_s.inspect} exists."\
|
75
|
+
" Overwrite? (y/n, default is %s) ",
|
76
|
+
default: ?n,
|
77
|
+
) do |response|
|
78
|
+
if response != ?y
|
79
|
+
puts "Skipping creation of #{filename.to_s.inspect}."
|
80
|
+
return
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
mkdir_p File.dirname(filename)
|
85
|
+
File.secure_write(filename) do |io|
|
86
|
+
io.puts CONFIG_TEMPLATE
|
87
|
+
end
|
88
|
+
puts "#{filename.to_s.inspect} was created."
|
47
89
|
end
|
48
90
|
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: 1.
|
4
|
+
version: 1.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Frank
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem_hadar
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: infobar
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -204,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
218
|
- !ruby/object:Gem::Version
|
205
219
|
version: '0'
|
206
220
|
requirements: []
|
207
|
-
rubygems_version: 3.2.
|
221
|
+
rubygems_version: 3.2.22
|
208
222
|
signing_key:
|
209
223
|
specification_version: 4
|
210
224
|
summary: Gem abstracting a git workflow
|