git-story-workflow 1.4.2 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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