jacaetevha-growthspurt 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-07-10
2
+
3
+ * 1 major enhancement
4
+
5
+ * Released it to GitHub
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ VERSION
6
+ bin/growthspurt
7
+ example.rb
8
+ growth-spurt.gemspec
9
+ lib/growthspurt.rb
10
+ lib/update_version_when_branching.rb
11
+ test/test_growthspurt.rb
data/README.txt ADDED
@@ -0,0 +1,60 @@
1
+ = growthspurt
2
+
3
+ * http://github.com/jacaetevha/growth-spurt
4
+
5
+ == DESCRIPTION:
6
+
7
+ === What is GrowthSpurt?
8
+
9
+ It's a utility for creating branches in Git and automatically creating/updating change files based on commit messages.
10
+
11
+ === Why GrowthSpurt?
12
+
13
+ I originally wrote this in order help automate builds of our Ruby code into a Java/Maven-centric company build process. Most of the company dealt with Subversion, but I wanted to use Git. Most of the company coded in Java and managed builds/deployments with Maven, but I wanted to use Ruby and Capistrano. I lost on the Capistrano-vs-Maven front, but I was able to continue using Ruby and Git. So, this script was built to automate branching in Git in a standard Major.Minor.Micro format (eg. 1.0.1, 1.0.2, 1.0.3) and pushing those branches to a remote repository, and then using Git to create a CHANGES file and a changes.xml file. The latter is used by Maven's Changes plugin (http://maven.apache.org/plugins/maven-changes-plugin/).
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ * create CHANGES and changes.xml files based on commit messages from Git for any digit.digit.digit branch, as well as the master branch
18
+ * can create a new remote and local tracking branch for the new "growth" (branch)
19
+ * doesn't handle Nano format branches (eg. 1.2.3.45)
20
+ * once on the branch the check-in of the change files isn't happening properly because they aren't being added
21
+ * when creating a new version of this project the gemspec file needs to be updated
22
+
23
+ == SYNOPSIS:
24
+
25
+ To get a synopsis, run growthspurt -h
26
+
27
+ == REQUIREMENTS:
28
+
29
+ * grit 1.1.1
30
+ * facets 2.5.2 (in particular, dictionary & enumerable
31
+
32
+ == INSTALL:
33
+
34
+ * sudo gem install grit facets
35
+ * sudo gem install jacaetevha-growth-spurt
36
+
37
+ == LICENSE:
38
+
39
+ (The MIT License)
40
+
41
+ Copyright (c) 2009 Jason Rogers
42
+
43
+ Permission is hereby granted, free of charge, to any person obtaining
44
+ a copy of this software and associated documentation files (the
45
+ 'Software'), to deal in the Software without restriction, including
46
+ without limitation the rights to use, copy, modify, merge, publish,
47
+ distribute, sublicense, and/or sell copies of the Software, and to
48
+ permit persons to whom the Software is furnished to do so, subject to
49
+ the following conditions:
50
+
51
+ The above copyright notice and this permission notice shall be
52
+ included in all copies or substantial portions of the Software.
53
+
54
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
55
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
56
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
57
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
58
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
59
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
60
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.6
data/bin/growthspurt ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby -wKU
2
+ require 'rubygems'
3
+ require 'lib/growthspurt.rb'
4
+
5
+ GrowthSpurt.new(ARGV, STDIN).run
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ # == Synopsis
4
+ # This script will create a new branch of the CRM application and check that into Git.
5
+ #
6
+ # == Examples
7
+ # This command does will create a branch called 1.0.5 and will update the CHANGES and changes.xml
8
+ # files with differences between 1.0.5 and 1.0.2
9
+ # growthspurt -p 1.0.2 -c 1.0.5
10
+ #
11
+ # Other examples:
12
+ # growthspurt -q -p 1.0.4 -c 1.0.5
13
+ # growthspurt --verbose -p testing_branch -c qa_branch_1
14
+ # growthspurt -n
15
+ #
16
+ # == Usage
17
+ # growthspurt [options]
18
+ #
19
+ # For help use: growthspurt -h
20
+ #
21
+ # == Options
22
+ # -h, --help Displays help message
23
+ #
24
+ # -v, --version Display the version, then exit
25
+ #
26
+ # -q, --quiet Output as little as possible, overrides verbose
27
+ #
28
+ # -V, --verbose Verbose output
29
+ #
30
+ # -t, --title The value of the title attribute in the changes.xml file
31
+ #
32
+ # -P, --changes-path The path to changes.xml file (defaults to src/changes)
33
+ #
34
+ # -N, --no-branch Don't create a new branch, just update the CHANGES and changes.xml files
35
+ #
36
+ # -n, --next Create a new branch with this tag as its name (required, unless -n is specified)
37
+ #
38
+ # -m, --message Message when creating new branch (defaults to "created version <value of -n argument>")
39
+ #
40
+ # -p, --prev Previous version (optional), used in conjunction with -c. Specifying this option
41
+ # will create the CHANGES and changes.xml files in the "next" branch with the changes
42
+ # between the current branch and the "previous" branch.
43
+ #
44
+ # Eg. While running this script within the master branch the following command will create
45
+ # a 1.0.5 branch based off of master with a CHANGES file that contains the differences
46
+ # between the master branch and the 1.0.4 branch:
47
+ #
48
+ # growthspurt -p 1.0.4 -n 1.0.5
49
+ #
50
+ # == Author
51
+ # Jason Rogers
52
+ #
53
+ # == Copyright
54
+ # Copyright (c) 2009 Jason Rogers
55
+
56
+ require 'optparse'
57
+ require 'rdoc/usage'
58
+ require 'ostruct'
59
+ require 'rexml/document'
60
+ require 'time'
61
+ require 'fileutils'
62
+ require 'grit'
63
+ require 'facets/dictionary'
64
+ require 'facets/enumerable'
65
+
66
+ class GrowthSpurt
67
+ include REXML
68
+ include Grit
69
+
70
+ VERSION = File.readlines(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION'))).first.chomp
71
+
72
+ def self.pre_commit_hooks
73
+ @@pre_commit_hooks ||= []
74
+ end
75
+
76
+ attr_reader :options, :diffs, :repo
77
+
78
+ def initialize(arguments, stdin)
79
+ @arguments = arguments
80
+ @stdin = stdin
81
+
82
+ # Set defaults
83
+ @options = OpenStruct.new
84
+ @options.verbose = false
85
+ @options.quiet = false
86
+ @options.create_branch = true
87
+ @options.changes_dot_xml_directory = File.join('src', 'changes')
88
+ @repo = Repo.new('.')
89
+ @diffs = []
90
+ end
91
+
92
+ # Parse options, check arguments, then process the command
93
+ def run
94
+
95
+ if parsed_options? && arguments_valid?
96
+
97
+ puts "Start at #{DateTime.now}\n\n" if @options.verbose
98
+
99
+ output_options if @options.verbose # [Optional]
100
+
101
+ process_arguments
102
+ process_command
103
+
104
+ puts "\nFinished at #{DateTime.now}" if @options.verbose
105
+
106
+ else
107
+ output_usage
108
+ end
109
+
110
+ end
111
+
112
+ protected
113
+ def parsed_options?
114
+ # Specify options
115
+ opts = OptionParser.new
116
+ opts.on('-v', '--version') { output_version ; exit 0 }
117
+ opts.on('-h', '--help') { output_help }
118
+ opts.on('-V', '--verbose') { @options.verbose = true }
119
+ opts.on('-q', '--quiet') { @options.quiet = true }
120
+ opts.on('-p', '--prev=name') { |v| @options.previous_version = v }
121
+ opts.on('-n', '--next=name') { |v| @options.new_version = v }
122
+ opts.on('-N', '--no-branch') { @options.create_branch = false }
123
+ opts.on('-m', '--message=msg') { |v| @options.message = v }
124
+ opts.on('-t', '--title=title') { |v| @options.title = v }
125
+ opts.on('-c', '--changes-path=path') { |v| @options.changes_dot_xml_directory = v }
126
+ opts.on('-P', '--pre-commit-hooks=file') { |v| load File.expand_path(v) }
127
+
128
+ opts.parse!(@arguments) rescue return false
129
+
130
+ process_options
131
+ true
132
+ end
133
+
134
+ # Performs post-parse processing on options
135
+ def process_options
136
+ @options.verbose = false if @options.quiet
137
+ end
138
+
139
+ def output_options
140
+ puts "Options:\n"
141
+
142
+ @options.marshal_dump.each do |name, val|
143
+ puts " #{name} = #{val}"
144
+ end
145
+ end
146
+
147
+ # True if required arguments were provided
148
+ def arguments_valid?
149
+ #
150
+ # Not sure if the following makes sense. Could be that we want
151
+ # to specify a (non-existent) branch to see what the changes.xml
152
+ # file would look like.
153
+ #
154
+ if @options.new_version
155
+ unless @options.create_branch
156
+ puts "\n\nWARNING: you specified a new branch name, but you did not specify to create it.\nThe changes.xml file will reference this new branch, but it won't be created.\n\n"
157
+ end
158
+ end
159
+
160
+ if @options.create_branch
161
+ return false if @options.new_version.nil? || @options.new_version.empty?
162
+ unless git_remote_branch_installed?
163
+ puts "cannot create new branch, either install git-remote-branch (sudo gem install git-remote-branch) or make sure it's in your PATH"
164
+ return false
165
+ end
166
+ end
167
+
168
+ true
169
+ end
170
+
171
+ # Setup the arguments
172
+ def process_arguments
173
+ end
174
+
175
+ def output_help
176
+ output_version
177
+ RDoc::usage() #exits app
178
+ end
179
+
180
+ def output_usage
181
+ RDoc::usage('usage') # gets usage from comments above
182
+ end
183
+
184
+ def output_version
185
+ puts "#{File.basename(__FILE__)} version #{VERSION}"
186
+ end
187
+
188
+ def process_command
189
+ @options.message = "created version #{@options.new_version}"
190
+
191
+ if !@options.new_version && @options.create_branch
192
+ raise 'must specify a version in order to create a branch (eg. 1.0.1)'
193
+ exit 1
194
+ end
195
+
196
+ @diffs = load_diffs
197
+
198
+ update_changes_files
199
+
200
+ if @options.create_branch
201
+ puts "creating initial branch #{@options.new_version}"
202
+ `#{grb_location} create #{@options.new_version}`
203
+
204
+ update_pom_dot_xml
205
+ update_dot_gitignore
206
+
207
+ run_pre_commit_hooks
208
+
209
+ puts "committing to #{@options.new_version}"
210
+ `git commit -v -a -m"#{@options.message}"`
211
+
212
+ puts "pushing to main repository"
213
+ `git push`
214
+ end
215
+ end
216
+
217
+ private
218
+ def run_pre_commit_hooks
219
+ self.class.pre_commit_hooks.each do | hook |
220
+ hook.run(self)
221
+ end
222
+ end
223
+
224
+ def load_diffs(opts={})
225
+ options = {:last => @options.previous_version, :current => current_branch, :diff_collection => self.diffs}.merge(opts)
226
+ commits = options[:last] ?
227
+ repo.commits_between(options[:last], options[:current]) :
228
+ repo.commits_since(options[:current]) #2nd arg could be a date string
229
+ commits.reverse.each do |commit|
230
+ options[:diff_collection] << CommitSnapshot.new(commit)
231
+ end
232
+ options[:diff_collection]
233
+ end
234
+
235
+ def git_remote_branch_installed?
236
+ grb_location.nil? == false && grb_location.empty? == false
237
+ end
238
+
239
+ def grb_location
240
+ @grb ||= `which grb`.chomp
241
+ end
242
+
243
+ def current_branch
244
+ Head.current(repo).name
245
+ end
246
+
247
+ def release_branches
248
+ branches = repo.branches.select {|e| e.name =~ /[\d]+\.[\d]+\.[\d]+/ || e.name == 'master'}.collect{|e| e.name}.sensible_sort
249
+ commits_per_branch = Dictionary.alpha
250
+ branches.each_with_index do | branch, index |
251
+ if branch == "master"
252
+ commits_per_branch[branch] = repo.commits_between(branches[index - 1], branch)
253
+ else
254
+ commits_per_branch[branch] = repo.commits(branch).sort{|a, b| b.date <=> a.date}
255
+ end
256
+ end
257
+ commits_per_branch
258
+ end
259
+
260
+ def update_changes_files
261
+ rest_of_message = "between #{@options.previous_version} and #{@options.new_version}" if @options.new_version && @options.previous_version
262
+ rest_of_message = "between #{current_branch} and #{@options.new_version}" if @options.new_version && !@options.previous_version
263
+ rest_of_message = "" unless @options.new_version && @options.create_branch
264
+ unless self.diffs.empty?
265
+ File.open('CHANGES', 'w') do | f |
266
+ self.diffs.each do | diff |
267
+ f << diff.formatted_for_changes_file
268
+ end
269
+ end
270
+ puts "created/updated CHANGES for differences #{rest_of_message}"
271
+ repo.add('CHANGES') if @options.create_branch
272
+ end
273
+
274
+ FileUtils.mkdir_p(@options.changes_dot_xml_directory) unless File.exists?(@options.changes_dot_xml_directory)
275
+ changes_dot_xml_file = File.join(@options.changes_dot_xml_directory, 'changes.xml')
276
+ File.open(changes_dot_xml_file, 'w') do | f |
277
+ write_to_file = lambda {|s| f << s}
278
+ changes_dot_xml_header( &write_to_file )
279
+ release_branches.each_pair do | branch_name, commits |
280
+ next if commits.nil? || commits.empty?
281
+ f << " <release version='#{branch_name == 'master' ? @options.new_version : branch_name}' date='#{commits.last.date}' description='#{commits.last.message.chomp[0..10]}'>\n"
282
+ commits.each do | commit |
283
+ CommitSnapshot.new(commit).formatted_for_changes_dot_xml_file( &write_to_file )
284
+ end
285
+ f << " </release>\n"
286
+ end
287
+
288
+ changes_dot_xml_footer( &write_to_file )
289
+ puts "created/updated changes.xml for differences #{rest_of_message}"
290
+ repo.add(changes_dot_xml_file) if @options.create_branch
291
+ end
292
+ end
293
+
294
+ def update_dot_gitignore
295
+ lines = (File.exists?('.gitignore') ? File.readlines('.gitignore') : []).collect{|line| line.chomp}
296
+ lines.delete_if {|line| line == 'CHANGES' || line.include?('changes.xml')}
297
+ File.open('.gitignore', 'w') do | f |
298
+ lines.each do | line |
299
+ f << "#{line}\n"
300
+ end
301
+ end
302
+ end
303
+
304
+ def update_pom_dot_xml
305
+ return unless File.exists?('pom.xml')
306
+ xml = File.read('pom.xml')
307
+ doc = Document.new(xml)
308
+ doc.root.elements["version"].text = @options.new_version
309
+ File.open('pom.xml', 'w') do | f |
310
+ f << doc.to_s
311
+ end
312
+ end
313
+
314
+ def changes_dot_xml_header
315
+ v = %Q{<document>\n <properties>\n <title>#{@options.title}</title>\n </properties>\n <body>\n}
316
+ yield v if block_given?
317
+ v
318
+ end
319
+
320
+ def changes_dot_xml_footer
321
+ v = %Q{\n </body>\n</document>}
322
+ yield v if block_given?
323
+ v
324
+ end
325
+ end
326
+
327
+ class CommitSnapshot
328
+ ATTRIBUTES = [:commit_ref, :author, :email, :date, :messages]
329
+ attr_accessor(*ATTRIBUTES)
330
+
331
+ def initialize(*args)
332
+ if args[0].kind_of? Hash
333
+ ATTRIBUTES.each do | sym |
334
+ self.instance_variable_set "@#{sym}", args[0][sym]
335
+ end
336
+ if self.messages
337
+ self.messages = (self.messages.kind_of?(String) ? self.messages.split(/[;\n]/) : self.messages).delete_if{|e| e.empty?}
338
+ end
339
+ self.date = Time.parse( self.date ) if self.date && !self.date.kind_of?(Time)
340
+ else
341
+ commit = args[0]
342
+ initialize(:commit_ref => commit.id_abbrev, :author => commit.author.name, :email => commit.author.email, :date => commit.date, :messages => commit.message)
343
+ end
344
+ end
345
+
346
+ def formatted_date
347
+ self.date.strftime('%Y-%m-%d')
348
+ end
349
+
350
+ def formatted_for_changes_file
351
+ separator = ('-' * self.author.length) + '---------------------------'
352
+ each_message = self.messages.join("\n")
353
+ each_message.gsub!(/(.{1,80}|\S{81,})(?: +|$\n?)/, " \\1\n")
354
+ v = "#{separator}\n %s on %s, %s\n#{separator}\n%s\n\n" % [self.author, formatted_date, self.commit_ref[0...7], each_message]
355
+ yield v if block_given?
356
+ v
357
+ end
358
+
359
+ def formatted_for_changes_dot_xml_file
360
+ # <action dev=.. due-to=.. due-to-email=.. issue=.. type=.. system=.. date=.. >
361
+ template = " <action dev='%s' type='%s' date='%s'><![CDATA[%s: %s]]></action>\n"
362
+ reference = self.commit_ref[0...7]
363
+ v = self.messages.collect { | message_within_commit |
364
+ msg = message_within_commit.downcase
365
+ action_type = if !!(msg =~ /bug|fix/)
366
+ 'fix'
367
+ elsif !!(msg =~ /update/)
368
+ 'update'
369
+ elsif !!(msg =~ /remove/)
370
+ 'remove'
371
+ else
372
+ 'add'
373
+ end
374
+
375
+ template % [self.author, action_type, formatted_date, reference, message_within_commit]
376
+ }.join('')
377
+ yield v if block_given?
378
+ v
379
+ end
380
+ end
381
+
382
+ module Enumerable
383
+ # kudos to Piers Cawley: http://www.bofh.org.uk/2007/12/16/comprehensible-sorting-in-ruby
384
+ def sensible_sort
385
+ sort_by {|k| k.to_s.split(/((?:(?:^|\s)[-+])?(?:\.\d+|\d+(?:\.\d+?(?:[eE]\d+)?(?:$|(?![eE\.])))?))/ms).map {|v| Float(v) rescue v.downcase}}
386
+ end
387
+ end
388
+
389
+ if __FILE__ == $0
390
+ # Create and run the application
391
+ app = GrowthSpurt.new(ARGV, STDIN)
392
+ app.run
393
+ end
@@ -0,0 +1,2 @@
1
+ puts "tests??? later..."
2
+ true
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jacaetevha-growthspurt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Jason Rogers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-13 00:00:00 -07:00
13
+ default_executable: growthspurt
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: facets
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.5.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: grit
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.1
34
+ version:
35
+ description: ""
36
+ email: jacaetevha@gmail.com
37
+ executables:
38
+ - growthspurt
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.txt
43
+ files:
44
+ - History.txt
45
+ - Manifest.txt
46
+ - README.txt
47
+ - VERSION
48
+ - bin/growthspurt
49
+ - lib/growthspurt.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/jacaetevha/growth-spurt
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: ""
76
+ test_files:
77
+ - test/test_growthspurt.rb