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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57fd6d4ff94ab8ef89840a6c7fd9af4ff8865a6aa15ab9c562cbe536c98675af
4
- data.tar.gz: 4831ce63881313940343c5e0bf447cba8de302c122ffedb8682cd0ce6125a7f3
3
+ metadata.gz: b81ff3a4b94ba6bed90472b28eccdad8d3df7e4af30dc3ffe772cbef4ab78cc0
4
+ data.tar.gz: b2b7347b35bd6d1f1ac63389091b8c0fe80bfb744604ba9b379e5a896fd3b682
5
5
  SHA512:
6
- metadata.gz: a873c02d9271925c6855017e64034d4a06dba3706c27c6e64d588e5a7aaec12097a3ea0dca44caa6a6202e4867e8ea1b4232cb4e6be9fea54633e1fdd06a4cbe
7
- data.tar.gz: 0e9afef53d03a5f306ee92796d2066cf5ce62edc78c42442d8cdb84667fafe4d331a0f6a850b16baf6cb6d8cad8ceddcd76413baab81210fbe57520df1eec202
6
+ metadata.gz: '05349a0f364e7531b7a22602f4dea569498130b09aa90ab42a749d20b37a29b19a4da542b08fc88b418867d55e352d84a4c2ca7e4435585e8fe679b28807f604'
7
+ data.tar.gz: b4fe39f78898e26603998ee218223a6d8b76ea895c50c2dcfe6329947e7e13923ef03921bea9bc28e8cd5488c06475a76618a1a3931404777208df81c5a298c1
data/Rakefile CHANGED
@@ -27,6 +27,7 @@ GemHadar do
27
27
  development_dependency 'rake'
28
28
  development_dependency 'simplecov'
29
29
  development_dependency 'rspec'
30
+ development_dependency 'byebug'
30
31
  licenses << 'Apache-2.0'
31
32
  end
32
33
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.2
1
+ 1.6.2
data/config/story.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  ---
2
- pivotal_token: <%= ENV['PIVOTAL_TOKEN'] %>
3
- pivotal_project: 123456789
2
+ pivotal_token: <%= ENV['PIVOTAL_TOKEN'] %>
3
+ pivotal_project: 1254912
4
+ pivotal_reference_prefix: pivotal
4
5
  deploy_tag_prefix: production_deploy_
5
- semaphore_project_url: https://xxx.semaphoreci.com/projects/yyy
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 %>
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: git-story-workflow 1.4.2 ruby lib
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.4.2"
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 = "2021-09-27"
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.15".freeze
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
- Git::Story::Setup.perform
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 << "Github: #{url.color(33)}\n"
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
- def fetch_statuses(pivotal_ids)
223
- tg = ThreadGroup.new
224
- pivotal_ids.each do |pid|
225
- tg.add Thread.new { Thread.current[:status] = status(pid) }
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
- tg.list.with_infobar(label: 'Story').map do |t|
228
- +infobar
229
- t.join
230
- t[:status]
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: '[PATTERN] switch to story matching PATTERN'
265
- def switch(pattern = nil)
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: '[PATTERN] delete story branch matching PATTERN'
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Git::Story
2
2
  # Git::Story version
3
- VERSION = '1.4.2'
3
+ VERSION = '1.6.2'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
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.2
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: 2021-09-27 00:00:00.000000000 Z
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.15
221
+ rubygems_version: 3.2.22
208
222
  signing_key:
209
223
  specification_version: 4
210
224
  summary: Gem abstracting a git workflow