git-story-workflow 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ccc8eb83665b1e52179000c20ce0629c6a8ea0c4
4
+ data.tar.gz: 3d8ea8dde58d188a43c01a7c42eb6982524d26c8
5
+ SHA512:
6
+ metadata.gz: 1739520e8a7ef30273b32b3751371673660f23619c2e742fa148d6aecefdb6b0bb78b9068a3dfba655dc74603a8702486d084e25cb8193d612070f287aef6985
7
+ data.tar.gz: a601252d87afa0a9fbffe74806e1578dd140e1442745fc66389461dc0787583a7cf5cb08d375d8ab79495fd56fbbbcd8220b9fac2c811febfafc738a667beb74
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .*.sw[pon]
2
+ .AppleDouble
3
+ .byebug_history
4
+ .rvmrc
5
+ Gemfile.lock
6
+ coverage
7
+ errors.lst
8
+ pkg
9
+ tags
data/COPYING ADDED
@@ -0,0 +1,203 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [2017] [Florian Frank]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
203
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Git::Story
2
+
3
+ ## Description
4
+
5
+ Ruby gem containing an executable that abstracts a standard git workflow.
6
+
7
+ ## Download
8
+
9
+ The homepage of this library is located at
10
+
11
+ * `http://github.com/flori/git-story`
12
+
13
+ ## Author
14
+
15
+ Florian Frank mailto:flori@ping.de
16
+
17
+ ## License
18
+
19
+ Apache License, Version 2.0 – See the COPYING file in the source archive.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ require 'gem_hadar'
4
+
5
+ GemHadar do
6
+ name 'git-story-workflow'
7
+ path_name 'git/story'
8
+ author 'Florian Frank'
9
+ email 'flori@ping.de'
10
+ homepage "http://flori.github.com/#{name}"
11
+ summary 'Gem abstracting a git workflow'
12
+ description "#{summary}…"
13
+ test_dir 'spec'
14
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
15
+ '.AppleDouble', 'tags', '.byebug_history', 'errors.lst'
16
+ readme 'README.md'
17
+ title name.camelize
18
+ executables << 'git-story'
19
+
20
+ dependency 'tins'
21
+ dependency 'term-ansicolor'
22
+ dependency 'complex_config'
23
+
24
+ development_dependency 'rake'
25
+ development_dependency 'simplecov'
26
+ development_dependency 'rspec'
27
+ licenses << 'Apache-2.0'
28
+ end
29
+
30
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bin/git-story ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'git/story'
4
+
5
+ Git::Story::App.new.run
data/config/story.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ pivotal_token: <%= ENV['PIVOTAL_TOKEN'] %>
3
+ pivotal_project: 123456789
4
+ deploy_tag_prefix: production_deploy_
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # stub: git-story-workflow 0.0.0 ruby lib
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "git-story-workflow".freeze
6
+ s.version = "0.0.0"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib".freeze]
10
+ s.authors = ["Florian Frank".freeze]
11
+ s.date = "2017-08-28"
12
+ s.description = "Gem abstracting a git workflow\u2026".freeze
13
+ s.email = "flori@ping.de".freeze
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/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/feature_spec.rb".freeze, "spec/spec_helper.rb".freeze]
17
+ s.homepage = "http://flori.github.com/git-story-workflow".freeze
18
+ s.licenses = ["Apache-2.0".freeze]
19
+ s.rdoc_options = ["--title".freeze, "Git-story-workflow".freeze, "--main".freeze, "README.md".freeze]
20
+ s.rubygems_version = "2.6.13".freeze
21
+ s.summary = "Gem abstracting a git workflow".freeze
22
+ s.test_files = ["spec/git/story/feature_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
+
24
+ if s.respond_to? :specification_version then
25
+ s.specification_version = 4
26
+
27
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
29
+ s.add_development_dependency(%q<rake>.freeze, [">= 0"])
30
+ s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
31
+ s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
32
+ s.add_runtime_dependency(%q<tins>.freeze, [">= 0"])
33
+ s.add_runtime_dependency(%q<term-ansicolor>.freeze, [">= 0"])
34
+ s.add_runtime_dependency(%q<complex_config>.freeze, [">= 0"])
35
+ else
36
+ s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
37
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
38
+ s.add_dependency(%q<simplecov>.freeze, [">= 0"])
39
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
40
+ s.add_dependency(%q<tins>.freeze, [">= 0"])
41
+ s.add_dependency(%q<term-ansicolor>.freeze, [">= 0"])
42
+ s.add_dependency(%q<complex_config>.freeze, [">= 0"])
43
+ end
44
+ else
45
+ s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
46
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
47
+ s.add_dependency(%q<simplecov>.freeze, [">= 0"])
48
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
49
+ s.add_dependency(%q<tins>.freeze, [">= 0"])
50
+ s.add_dependency(%q<term-ansicolor>.freeze, [">= 0"])
51
+ s.add_dependency(%q<complex_config>.freeze, [">= 0"])
52
+ end
53
+ end
data/lib/git/story.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'tins/xt'
2
+ require 'open-uri'
3
+ require 'json'
4
+ require 'complex_config'
5
+
6
+ require 'term/ansicolor'
7
+ Term::ANSIColor.coloring = STDOUT.tty?
8
+ class String
9
+ include Term::ANSIColor
10
+ end
11
+
12
+
13
+ unless defined?(Git)
14
+ module Git
15
+ end
16
+ end
17
+
18
+ module Git::Story
19
+ end
20
+
21
+ require 'git/story/version'
22
+ require 'git/story/utils'
23
+ require 'git/story/setup'
24
+ require 'git/story/app'
@@ -0,0 +1,233 @@
1
+ class Git::Story::App
2
+ class ::String
3
+ include Term::ANSIColor
4
+ end
5
+
6
+ include Tins::GO
7
+ include Git::Story::Utils
8
+ extend Git::Story::Utils
9
+ include ComplexConfig::Provider::Shortcuts
10
+
11
+ annotate :command
12
+
13
+ BRANCH_NAME_REGEX = /\A(?:story|feature)_([a-z0-9-]+)_(\d+)(?:\-[0-9a-f]+)?\z/
14
+
15
+ module StoryAccessors
16
+ attr_accessor :story_name
17
+
18
+ attr_accessor :story_base_name
19
+
20
+ attr_accessor :story_id
21
+ end
22
+
23
+ def initialize(argv = ARGV)
24
+ @argv = argv
25
+ @command = @argv.shift&.to_sym
26
+ end
27
+
28
+ def run
29
+ Git::Story::Setup.perform
30
+ if command_of(@command)
31
+ puts __send__(@command, *@argv)
32
+ else
33
+ fail "Unknown command #{@command.inspect}"
34
+ end
35
+ rescue Errno::EPIPE
36
+ end
37
+
38
+ command doc: 'this help'
39
+ def help
40
+ longest = command_annotations.keys.map(&:size).max
41
+ command_annotations.map { |name, a| "#{name.to_s.ljust(longest)} #{a[:doc]}" }
42
+ end
43
+
44
+ command doc: 'output the current story branch if it is checked out'
45
+ def current(check: true)
46
+ check and check_current
47
+ current_branch
48
+ end
49
+
50
+ def create_name(story_id = nil)
51
+ until story_id.present?
52
+ story_id = ask(prompt: 'Story id? ').strip
53
+ end
54
+ story_id = story_id.gsub(/[^0-9]+/, '')
55
+ stories
56
+ @story_id = Integer(story_id)
57
+ if name = fetch_story_name(@story_id)
58
+ name = name.downcase.gsub(/[^a-z0-9-]+/, '-').gsub(/(\A-*|[\-0-9]*\z)/, '')
59
+ [ 'story', name, @story_id ] * ?_
60
+ end
61
+ end
62
+
63
+ command doc: 'list all stories'
64
+ def list(mark_red: current(check: false))
65
+ stories.map { |b|
66
+ (bn = b.story_base_name) == mark_red ? bn.red : bn.green
67
+ }
68
+ end
69
+
70
+ command doc: 'list all production deploy tags'
71
+ def deploy_tags
72
+ fetch_tags
73
+ `git tag | grep ^#{complex_config.story.deploy_tag_prefix} | sort`.lines.map(&:chomp)
74
+ end
75
+
76
+ command doc: 'output the times of all production deploys'
77
+ def deploys
78
+ deploy_tags.map { |t| format_tag_time(t).green + " #{t.yellow}" }
79
+ end
80
+
81
+ command doc: 'output the last production deploy tag'
82
+ def deploy_tags_last
83
+ deploy_tags.last
84
+ end
85
+
86
+ command doc: 'output the time of the last production deploy'
87
+ def deploys_last
88
+ tag = deploy_tags_last
89
+ format_tag_time(tag).green + " #{tag.yellow}"
90
+ end
91
+
92
+ command doc: 'output log of changes since last production deploy tag'
93
+ def deploy_log
94
+ fetch_tags
95
+ opts = '--pretty=tformat:"%C(yellow)%h%Creset %C(green)%ci%Creset %s (%Cred%an <%ae>%Creset)"'
96
+ `git log #{opts} #{deploy_tags.last}..`
97
+ end
98
+
99
+ command doc: 'output diff since last production deploy tag'
100
+ def deploy_diff
101
+ fetch_tags
102
+ opts = '-u'
103
+ `git diff --color #{opts} #{deploy_tags.last}`
104
+ end
105
+
106
+ command doc: 'outpit migration diff since last production deploy tag'
107
+ def deploy_migrate_diff
108
+ fetch_tags
109
+ opts = '-u'
110
+ `git diff --color #{opts} #{deploy_tags.last} -- db/migrate`
111
+ end
112
+
113
+ command doc: '[STORYID] create a story for story STORYID'
114
+ def create(story_id = nil)
115
+ sh 'git fetch'
116
+ name = create_name(story_id) or
117
+ error "Could not create a story name for story id #{story_id}"
118
+ if old_story = stories.find { |s| s.story_id == @story_id }
119
+ error "story ##{@story_id} already exists in #{old_story}".red
120
+ end
121
+ puts "Now creating story #{name.inspect}".green
122
+ sh "git checkout --track -b #{name}"
123
+ sh "git push -u origin #{name}"
124
+ "Story #{name} created.".green
125
+ end
126
+
127
+ command doc: '[PATTERN] switch to story matching PATTERN'
128
+ def switch(pattern = nil)
129
+ sh 'git fetch'
130
+ ss = stories.map(&:story_base_name)
131
+ if pattern.present?
132
+ b = apply_pattern(pattern, ss)
133
+ if b.size == 1
134
+ b = b.first
135
+ else
136
+ b = nil
137
+ end
138
+ end
139
+ loop do
140
+ unless b
141
+ b = complete prompt: 'Story <TAB>? '.bright_blue do |pattern|
142
+ apply_pattern(pattern, ss)
143
+ end&.strip
144
+ b.empty? and return
145
+ end
146
+ if branch = ss.find { |f| f == b }
147
+ sh "git checkout #{branch}"
148
+ return "Switched to story: #{branch}".green
149
+ else
150
+ b = nil
151
+ end
152
+ end
153
+ rescue Interrupt
154
+ end
155
+
156
+ private
157
+
158
+ def apply_pattern(pattern, stories)
159
+ pattern = pattern.gsub(?#, '')
160
+ stories.grep(/#{Regexp.quote(pattern)}/)
161
+ end
162
+
163
+ def error(msg)
164
+ puts msg.red
165
+ exit 1
166
+ end
167
+
168
+ def pivotal_project
169
+ complex_config.story.pivotal_project
170
+ end
171
+
172
+ def pivotal_token
173
+ complex_config.story.pivotal_token
174
+ end
175
+
176
+ def fetch_story_name(story_id)
177
+ if story = pivotal_get("projects/#{pivotal_project}/stories/#{story_id}")
178
+ story_name = story.name.full? or return
179
+ end
180
+ end
181
+
182
+ def pivotal_get(path)
183
+ path = path.sub(/\A\/*/, '')
184
+ url = "https://www.pivotaltracker.com/services/v5/#{path}"
185
+ $DEBUG and warn "Fetching #{url.inspect}"
186
+ open(url,
187
+ 'X-TrackerToken' => pivotal_token,
188
+ 'Content-Type' => 'application/xml',
189
+ ) do |io|
190
+ JSON.parse(io.read, object_class: JSON::GenericObject)
191
+ end
192
+ rescue OpenURI::HTTPError => e
193
+ if e.message =~ /401/
194
+ raise e.exception, "#{e.message}: API-TOKEN in env var PIVOTAL_TOKEN invalid?"
195
+ end
196
+ end
197
+
198
+ def fetch_tags
199
+ sh 'git fetch --tags'
200
+ end
201
+
202
+ def stories
203
+ sh 'git remote prune origin', error: false
204
+ `git branch -r | grep -e '^ *origin/'`.lines.map do |l|
205
+ b = l.strip
206
+ b_base = File.basename(b)
207
+ if b_base =~ BRANCH_NAME_REGEX
208
+ b.extend StoryAccessors
209
+ b.story_base_name = b_base
210
+ b.story_name = $1
211
+ b.story_id = $2.to_i
212
+ b
213
+ end
214
+ end.compact
215
+ end
216
+
217
+ def current_branch
218
+ `git rev-parse --abbrev-ref HEAD`.strip
219
+ end
220
+
221
+ def check_current
222
+ current_branch =~ BRANCH_NAME_REGEX or
223
+ error 'Switch to a story branch first for this operation!'
224
+ end
225
+
226
+ def format_tag_time(tag)
227
+ if tag =~ /\d{4}_\d{2}_\d{2}-\d{2}_\d{2}/
228
+ time = Time.strptime($&, '%Y_%m_%d-%H_%M')
229
+ day = Time::RFC2822_DAY_NAME[time.wday]
230
+ "#{time.iso8601} #{day}"
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # Installed by the git-story gem
3
+
4
+ require 'tempfile'
5
+
6
+ class CommitMesssageParser
7
+ def initialize
8
+ @line_index = 0
9
+ @story_number_found = false
10
+ @message_data = []
11
+ end
12
+
13
+ attr_reader :line_index
14
+
15
+ def story_number_found?
16
+ @story_number_found
17
+ end
18
+
19
+ def parse(template)
20
+ @message_data = template.readlines
21
+ @message_data.each do |line|
22
+ line =~ /\[.*#\d+.*\]/ and @story_number_found = true
23
+ if line =~ /^\s*#/
24
+ break
25
+ else
26
+ @line_index += 1
27
+ end
28
+ end
29
+ ensure
30
+ @line_index = [ @line_index - 1, 0 ].max
31
+ return self
32
+ end
33
+
34
+ def data
35
+ @message_data[0..@line_index]
36
+ end
37
+
38
+ def footer
39
+ @message_data[(@line_index + 1)..-1]
40
+ end
41
+
42
+ def total
43
+ data + footer
44
+ end
45
+ end
46
+
47
+ story_numbers =
48
+ `git branch --no-color`.sub!(/^\* .*?(?:_(\d+(?:_\d+)*))$/) {
49
+ break $1.split(/_/)
50
+ } || []
51
+ story_numbers.map!(&:chomp)
52
+ unless story_numbers.empty?
53
+ Tempfile.open('commit') do |output|
54
+ File.open(ARGV.first) do |template|
55
+ message_parsed = CommitMesssageParser.new.parse(template)
56
+ if message_parsed.story_number_found?
57
+ output.puts message_parsed.total
58
+ else
59
+ output.puts message_parsed.data, "",
60
+ "[#{story_numbers.map { |story_number| "##{story_number}" } * ' '}]",
61
+ "", message_parsed.footer
62
+ end
63
+ output.rewind
64
+ File.open(ARGV.first, 'w') do |message|
65
+ message.write output.read
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,40 @@
1
+ module Git::Story::Setup
2
+ include Git::Story::Utils
3
+ extend Git::Story::Utils
4
+
5
+ MARKER = 'Installed by the git-story gem'
6
+
7
+ PREPARE_COMMIT_MESSAGE_SRC =
8
+ File.join(File.dirname(__FILE__), 'prepare-commit-msg')
9
+ PREPARE_COMMIT_MESSAGE_DST =
10
+ '.git/hooks/prepare-commit-msg'
11
+
12
+ module_function
13
+
14
+ def perform(force: false)
15
+ pcm = '.git/hooks/prepare-commit-msg'
16
+ if File.exist?(pcm)
17
+ if force
18
+ install_prepare_commit_msg
19
+ elsif File.read(pcm).match?(MARKER)
20
+ ;
21
+ else
22
+ ask(
23
+ prompt: "File #{pcm.inspect} not created by git-story."\
24
+ " Overwrite? (y/n, default is %s)",
25
+ default: ?n,
26
+ ) do |response|
27
+ if response == ?y
28
+ install_prepare_commit_msg
29
+ end
30
+ end
31
+ end
32
+ else
33
+ install_prepare_commit_msg
34
+ end
35
+ end
36
+
37
+ def install_prepare_commit_msg
38
+ cp PREPARE_COMMIT_MESSAGE_SRC, PREPARE_COMMIT_MESSAGE_DST
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ module Git::Story::Utils
4
+ include FileUtils::Verbose
5
+
6
+ def sh(*a, error: true)
7
+ system(*a)
8
+ if error && !$?.success?
9
+ STDERR.puts ("Failed with rc #{$?.exitstatus}: " + a.join(' ')).red
10
+ exit $?.exitstatus
11
+ end
12
+ end
13
+
14
+ def ask(prompt: '? ', **options, &block)
15
+ response = options[:preset]
16
+ unless response
17
+ if options[:default]
18
+ $stdout.print prompt % options[:default]
19
+ response = $stdin.gets.chomp
20
+ response.empty? and response = options[:default]
21
+ else
22
+ $stdout.print prompt
23
+ response = $stdin.gets
24
+ end
25
+ end
26
+ response = response.to_s.chomp
27
+ if block
28
+ block.(response)
29
+ else
30
+ response
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ module Git::Story
2
+ # Git::Story version
3
+ VERSION = '0.0.0'
4
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Git::Story::Feature do
4
+ it 'foos'
5
+ end
@@ -0,0 +1,16 @@
1
+ if ENV['START_SIMPLECOV'].to_i == 1
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_filter "#{File.basename(File.dirname(__FILE__))}/"
5
+ end
6
+ end
7
+ if ENV['CODECLIMATE_REPO_TOKEN']
8
+ require "codeclimate-test-reporter"
9
+ CodeClimate::TestReporter.start
10
+ end
11
+ require 'rspec'
12
+ begin
13
+ require 'byebug'
14
+ rescue LoadError
15
+ end
16
+ require 'git/story'
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-story-workflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Florian Frank
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gem_hadar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tins
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: term-ansicolor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: complex_config
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Gem abstracting a git workflow…
112
+ email: flori@ping.de
113
+ executables:
114
+ - git-story
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - README.md
118
+ - lib/git/story.rb
119
+ - lib/git/story/app.rb
120
+ - lib/git/story/setup.rb
121
+ - lib/git/story/utils.rb
122
+ - lib/git/story/version.rb
123
+ files:
124
+ - ".gitignore"
125
+ - COPYING
126
+ - Gemfile
127
+ - README.md
128
+ - Rakefile
129
+ - VERSION
130
+ - bin/git-story
131
+ - config/story.yml
132
+ - git-story-workflow.gemspec
133
+ - lib/git/story.rb
134
+ - lib/git/story/app.rb
135
+ - lib/git/story/prepare-commit-msg
136
+ - lib/git/story/setup.rb
137
+ - lib/git/story/utils.rb
138
+ - lib/git/story/version.rb
139
+ - spec/git/story/feature_spec.rb
140
+ - spec/spec_helper.rb
141
+ homepage: http://flori.github.com/git-story-workflow
142
+ licenses:
143
+ - Apache-2.0
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options:
147
+ - "--title"
148
+ - Git-story-workflow
149
+ - "--main"
150
+ - README.md
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 2.6.13
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Gem abstracting a git workflow
169
+ test_files:
170
+ - spec/git/story/feature_spec.rb
171
+ - spec/spec_helper.rb