vclog 1.8.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.ruby +4 -3
  2. data/.yardopts +7 -0
  3. data/HISTORY.rdoc +207 -0
  4. data/README.rdoc +44 -27
  5. data/bin/vclog +4 -2
  6. data/bin/vclog-autotag +6 -0
  7. data/bin/vclog-bump +6 -0
  8. data/bin/vclog-formats +6 -0
  9. data/bin/vclog-version +6 -0
  10. data/lib/vclog.rb +1 -1
  11. data/lib/vclog.yml +58 -0
  12. data/lib/vclog/adapters.rb +2 -1
  13. data/lib/vclog/adapters/abstract.rb +87 -232
  14. data/lib/vclog/adapters/darcs.rb +72 -67
  15. data/lib/vclog/adapters/git.rb +166 -140
  16. data/lib/vclog/adapters/hg.rb +98 -62
  17. data/lib/vclog/adapters/svn.rb +116 -113
  18. data/lib/vclog/change.rb +110 -81
  19. data/lib/vclog/change_point.rb +77 -0
  20. data/lib/vclog/changelog.rb +58 -296
  21. data/lib/vclog/cli.rb +6 -70
  22. data/lib/vclog/cli/abstract.rb +64 -81
  23. data/lib/vclog/cli/autotag.rb +1 -3
  24. data/lib/vclog/cli/bump.rb +3 -4
  25. data/lib/vclog/cli/formats.rb +4 -4
  26. data/lib/vclog/cli/log.rb +86 -0
  27. data/lib/vclog/cli/version.rb +3 -3
  28. data/lib/vclog/{facets.rb → core_ext.rb} +0 -0
  29. data/lib/vclog/heuristics.rb +112 -38
  30. data/lib/vclog/heuristics/rule.rb +52 -12
  31. data/lib/vclog/heuristics/{label.rb → type.rb} +2 -2
  32. data/lib/vclog/history_file.rb +2 -2
  33. data/lib/vclog/metadata.rb +13 -1
  34. data/lib/vclog/release.rb +26 -12
  35. data/lib/vclog/repo.rb +191 -27
  36. data/lib/vclog/report.rb +187 -0
  37. data/lib/vclog/tag.rb +66 -39
  38. data/lib/vclog/templates/changelog.ansi.rb +9 -26
  39. data/lib/vclog/templates/changelog.atom.erb +3 -3
  40. data/lib/vclog/templates/changelog.gnu.rb +4 -11
  41. data/lib/vclog/templates/changelog.html.erb +11 -2
  42. data/lib/vclog/templates/changelog.markdown.rb +4 -4
  43. data/lib/vclog/templates/changelog.rdoc.rb +4 -4
  44. data/lib/vclog/templates/changelog.rss.erb +2 -6
  45. data/lib/vclog/templates/changelog.xml.erb +14 -2
  46. data/lib/vclog/templates/history.ansi.rb +10 -17
  47. data/lib/vclog/templates/history.atom.erb +4 -4
  48. data/lib/vclog/templates/history.gnu.rb +5 -7
  49. data/lib/vclog/templates/history.html.erb +11 -4
  50. data/lib/vclog/templates/history.json.rb +1 -1
  51. data/lib/vclog/templates/history.markdown.rb +5 -7
  52. data/lib/vclog/templates/history.rdoc.rb +5 -9
  53. data/lib/vclog/templates/history.rss.erb +3 -5
  54. data/lib/vclog/templates/history.xml.erb +15 -3
  55. data/lib/vclog/templates/history.yaml.rb +1 -1
  56. data/man/man1/vclog-autotag.1 +1 -1
  57. data/man/man1/vclog-autotag.1.html +1 -1
  58. data/man/man1/vclog-bump.1 +1 -1
  59. data/man/man1/vclog-bump.1.html +1 -1
  60. data/man/man1/vclog-version.1 +1 -1
  61. data/man/man1/vclog-version.1.html +1 -1
  62. data/man/man1/vclog.1 +25 -13
  63. data/man/man1/vclog.1.html +29 -20
  64. data/man/man1/vclog.1.ronn +31 -18
  65. data/test/unit/case_metadata.rb +1 -1
  66. metadata +48 -34
  67. data/lib/vclog/cli/changelog.rb +0 -33
  68. data/lib/vclog/cli/help.rb +0 -42
  69. data/lib/vclog/cli/history.rb +0 -39
  70. data/lib/vclog/formatter.rb +0 -123
  71. data/lib/vclog/history.rb +0 -131
  72. data/lib/vclog/kernel.rb +0 -12
  73. data/man/man1/vclog-changelog.1 +0 -47
  74. data/man/man1/vclog-changelog.1.html +0 -123
  75. data/man/man1/vclog-changelog.1.ronn +0 -39
  76. data/man/man1/vclog-history.1 +0 -44
  77. data/man/man1/vclog-history.1.html +0 -122
  78. data/man/man1/vclog-history.1.ronn +0 -38
@@ -2,32 +2,72 @@ module VCLog
2
2
 
3
3
  class Heuristics
4
4
 
5
+ # Defines a categorization rule for commits.
5
6
  #
6
7
  class Rule
8
+
9
+ # Initialize a new log rule.
10
+ #
11
+ # @param [Regexp] pattern
12
+ # An optional regular expression to match agains the commit message.
13
+ # If the pattern does not match the commit message than the rule
14
+ # does not apply.
15
+ #
16
+ # @yieldparam [Change]
17
+ # An encapsulation of the commit.
7
18
  #
8
- def initialize(pattern, &block)
19
+ # @yieldreturn [Boolean]
20
+ # If the return value is +nil+ or +false+, the rule does not apply.
21
+ # If a rule does not apply then be sure not to alter the commit!
22
+ #
23
+ def initialize(pattern=nil, &block)
9
24
  @pattern = pattern
10
25
  @block = block
11
26
  end
12
27
 
28
+ # Message pattern to match for the rule to apply.
29
+ attr :pattern
30
+
13
31
  # Process the rule.
14
- def call(message)
15
- if matchdata = @pattern.match(message)
16
- case @block.arity
17
- when 0
18
- @block.call
19
- when 1
20
- @block.call(matchdata)
21
- else
22
- @block.call(message, matchdata)
23
- end
32
+ #
33
+ # @since 1.9.0
34
+ # If using a message pattern and the block takes two arguments
35
+ # then the first will be the commit object, not the message as
36
+ # was the case in older versions.
37
+ #
38
+ def call(commit)
39
+ if pattern
40
+ call_pattern(commit)
41
+ else
42
+ call_commit(commit)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ #
49
+ def call_pattern(commit)
50
+ if matchdata = @pattern.match(commit.message)
51
+ #case @block.arity
52
+ #when 0
53
+ # @block.call
54
+ #when 1
55
+ # @block.call(matchdata)
56
+ #else
57
+ @block.call(commit, matchdata)
58
+ #end
24
59
  else
25
60
  nil
26
61
  end
27
62
  end
63
+
64
+ #
65
+ def call_commit(commit)
66
+ @block.call(commit)
67
+ end
68
+
28
69
  end
29
70
 
30
71
  end
31
72
 
32
73
  end
33
-
@@ -4,10 +4,10 @@ module VCLog
4
4
 
5
5
  #
6
6
  #
7
- class Label
7
+ class Type
8
8
  #
9
9
  def initialize(type, level, label)
10
- @type = type
10
+ @type = type.to_sym
11
11
  @level = level.to_i
12
12
  @label = label.to_s
13
13
  end
@@ -12,13 +12,13 @@ module VCLog
12
12
  VERS = /(\d+[._])+\d+/
13
13
  DATE = /(\d+[-])+\d+/
14
14
 
15
- #
15
+ # Alias for `File::FNM_CASEFOLD`.
16
16
  CASEFOLD = File::FNM_CASEFOLD
17
17
 
18
18
  # Release tags.
19
19
  attr :tags
20
20
 
21
- #
21
+ # Setup new HistoryFile instance.
22
22
  def initialize(source=nil)
23
23
  if File.file?(source)
24
24
  @file = source
@@ -1,4 +1,16 @@
1
1
  module VCLog
2
- VERSION = "1.8.2"
2
+
3
+ def self.metadata
4
+ @metadata ||= (
5
+ require 'yaml'
6
+ YAML.load_file(File.dirname(__FILE__) + '/../vclog.yml')
7
+ )
8
+ end
9
+
10
+ def self.const_missing(name)
11
+ key = name.to_s.downcase
12
+ metadata.key?(key) ? metadata[key] : super(name)
13
+ end
14
+
3
15
  end
4
16
 
@@ -1,7 +1,8 @@
1
1
  module VCLog
2
2
 
3
- # A Release encapsulate a collection of
4
- # Change objects associated to a Tag.
3
+ # A Release encapsulate a collection of {Change} objects
4
+ # associated with a {Tag}.
5
+ #
5
6
  class Release
6
7
 
7
8
  # Tag object this release represents.
@@ -10,44 +11,57 @@ module VCLog
10
11
  # Array of Change objects.
11
12
  attr :changes
12
13
 
14
+ #
13
15
  # New Release object.
14
16
  #
15
- # tag - Tag object
16
- # change - Array of Change objects
17
+ # @param [Tag] tag
18
+ # A Tag object.
19
+ #
20
+ # @param [Array<Change>] changes
21
+ # An array of Change objects.
17
22
  #
18
23
  def initialize(tag, changes)
19
- @tag = tag
24
+ @tag = tag
20
25
  @changes = changes
21
26
  end
22
27
 
28
+ #
23
29
  # Group +changes+ by type and sort by level.
24
- # Returns an associative array of [type, changes].
30
+ #
31
+ # @return [Array<Array>]
32
+ # Returns an associative array of [type, changes].
33
+ #
25
34
  def groups
26
35
  @groups ||= (
27
- changes.group_by{ |e| e.type }.sort{ |a,b| b[1][0].level <=> a[1][0].level }
36
+ changes.group_by{ |e| e.label }.sort{ |a,b| b[1][0].level <=> a[1][0].level }
28
37
  )
29
38
  end
30
39
 
40
+ #
31
41
  # Compare release by tag.
42
+ #
43
+ # @param [Release] other
44
+ # Another release instance.
45
+ #
32
46
  def <=>(other)
33
47
  @tag <=> other.tag
34
48
  end
35
49
 
50
+ #
36
51
  # Convert Release to Hash.
52
+ #
53
+ # @todo Should +version+ be +name+?
54
+ #
37
55
  def to_h
38
56
  { 'version' => tag.name,
39
57
  'date' => tag.date,
40
58
  'message' => tag.message,
41
59
  'author' => tag.author,
42
- 'revision' => tag.revision,
60
+ 'id' => tag.id,
43
61
  'changes' => changes.map{|change| change.to_h}
44
62
  }
45
63
  end
46
64
 
47
- #def to_json
48
- # to_h.to_json
49
- #end
50
65
  end
51
66
 
52
67
  end
53
-
@@ -1,64 +1,66 @@
1
1
  require 'confection'
2
2
 
3
+ require 'vclog/core_ext'
3
4
  require 'vclog/adapters'
4
5
  require 'vclog/heuristics'
5
6
  require 'vclog/history_file'
7
+ require 'vclog/changelog'
8
+ require 'vclog/tag'
9
+ require 'vclog/release'
10
+ require 'vclog/report'
6
11
 
7
12
  module VCLog
8
13
 
9
14
  #
10
15
  class Repo
11
- # Remove some undeeded methods to make way for delegation to scm adapter.
12
- %w{display}.each{ |name| undef_method(name) }
13
16
 
17
+ #
14
18
  # File glob used to find project root directory.
19
+ #
15
20
  ROOT_GLOB = '{.git/,.hg/,_darcs/,.svn/}'
16
21
 
17
- # File glob used to find the vclog configuration directory.
18
- #CONFIG_GLOB = '{.,.config/,config/,task/,tasks/}vclog{,.rb}'
19
-
22
+ #
20
23
  # Project's root directory.
24
+ #
21
25
  attr :root
22
26
 
27
+ #
28
+ # Options hash.
23
29
  #
24
30
  attr :options
25
31
 
26
- # Default change level.
27
- # TODO: get from config file?
28
- attr :level
29
-
32
+ #
33
+ # Setup new Repo instance.
30
34
  #
31
35
  def initialize(root, options={})
32
36
  @root = root || lookup_root
33
37
  @options = options
34
38
 
35
- #@config_directory = Dir[File.join(@root, CONFIG_GLOB)]
36
-
37
- @level = (options[:level] || 0).to_i
39
+ vcs_type = read_vcs_type
38
40
 
39
- type = read_type
40
- raise ArgumentError, "Not a recognized version control system." unless type
41
+ raise ArgumentError, "Not a recognized version control system." unless vcs_type
41
42
 
42
- @adapter = Adapters.const_get(type.capitalize).new(self)
43
+ @adapter = Adapters.const_get(vcs_type.capitalize).new(self)
43
44
  end
44
45
 
46
+ #
45
47
  # Returns instance of an Adapter subclass.
48
+ #
46
49
  def adapter
47
50
  @adapter
48
51
  end
49
52
 
50
53
  #
51
- #def config_directory
52
- # @config_directory
53
- #end
54
-
54
+ # Check force option.
55
55
  #
56
56
  def force?
57
57
  options[:force]
58
58
  end
59
59
 
60
60
  #
61
- def read_type
61
+ #
62
+ #
63
+ def read_vcs_type
62
64
  dir = nil
63
65
  Dir.chdir(root) do
64
66
  dir = Dir.glob("{.git,.hg,.svn,_darcs}").first
@@ -66,6 +68,7 @@ module VCLog
66
68
  dir[1..-1] if dir
67
69
  end
68
70
 
71
+ #
69
72
  # Find project root. This searches up from the current working
70
73
  # directory for a Confection configuration file or source control
71
74
  # manager directory.
@@ -77,6 +80,7 @@ module VCLog
77
80
  # _darcs/
78
81
  #
79
82
  # If all else fails the current directory is returned.
83
+ #
80
84
  def lookup_root
81
85
  root = nil
82
86
  Dir.ascend(Dir.pwd) do |path|
@@ -89,24 +93,25 @@ module VCLog
89
93
  root || Dir.pwd
90
94
  end
91
95
 
96
+ #
92
97
  # Load heuristics script.
98
+ #
93
99
  def heuristics
94
100
  @heuristics ||= Heuristics.new(&Confection[:vclog])
95
101
  end
96
102
 
97
- # Heurtistics script.
98
- #def heuristics_file
99
- # @heuristics_file ||= Dir[File.join(root, CONFIG_GLOB)].first
100
- #end
101
-
103
+ #
102
104
  # Access to Repo's HISTORY file.
105
+ #
103
106
  def history_file
104
107
  @history_file ||= HistoryFile.new(options[:history_file] || root)
105
108
  end
106
109
 
110
+ #
107
111
  # Read history file and make a commit tag for any release not already
108
112
  # tagged. Unless the force option is set the user will be prompted for
109
113
  # each new tag.
114
+ #
110
115
  def autotag(prefix=nil)
111
116
  history_file.tags.each do |tag|
112
117
  label = "#{prefix}#{tag.name}"
@@ -123,7 +128,162 @@ module VCLog
123
128
  end
124
129
  end
125
130
 
126
- # Delegate to SCM adapter.
131
+ #
132
+ # List of all changes.
133
+ #
134
+ def changes
135
+ @changes ||= apply_heuristics(adapter.changes)
136
+ end
137
+
138
+ #
139
+ # List of all change points.
140
+ #
141
+ def change_points
142
+ @change_points ||= apply_heuristics(adapter.change_points)
143
+ end
144
+
145
+ #
146
+ # Apply heuristics to changes.
147
+ #
148
+ def apply_heuristics(changes)
149
+ changes.each do |change|
150
+ change.apply_heuristics(heuristics)
151
+ end
152
+ end
153
+
154
+ #
155
+ # Collect releases for the given set of +changes+.
156
+ #
157
+ # Releases are groups of changes segregated by tags. The release version,
158
+ # release date and release note are defined by hard tag commits.
159
+ #
160
+ # @param [Array<Change>] changes
161
+ # List of Change objects.
162
+ #
163
+ # @return [Array<Release>]
164
+ # List of Release objects.
165
+ #
166
+ def releases(changes)
167
+ rel = []
168
+ tags = self.tags.dup
169
+
170
+ #ver = repo.bump(version)
171
+
172
+ name = options[:version] || 'HEAD'
173
+ user = adapter.user
174
+ date = ::Time.now + (3600 * 24) # one day ahead
175
+
176
+ change = Change.new(:id=>'HEAD', :date=>date, :who=>user)
177
+
178
+ tags << Tag.new(:name=>name, :date=>date, :who=>user, :msg=>"Current Development", :commit=>change)
179
+
180
+ # TODO: Do we need to add a Time.now tag?
181
+ # add current verion to release list (if given)
182
+ #previous_version = tags[0].name
183
+ #if current_version < previous_version # TODO: need to use natural comparision
184
+ # raise ArgumentError, "Release version is less than previous version (#{previous_version})."
185
+ #end
186
+
187
+ # sort by release date
188
+ tags = tags.sort{ |a,b| a.date <=> b.date }
189
+
190
+ # organize into deltas
191
+ delta = []
192
+ last = nil
193
+ tags.each do |tag|
194
+ delta << [tag, [last, tag.commit.date]]
195
+ last = tag.commit.date
196
+ end
197
+
198
+ # gather changes for each delta
199
+ delta.each do |tag, (started, ended)|
200
+ if started
201
+ set = changes.select{ |c| c.date >= started && c.date < ended }
202
+ #gt_vers, gt_date = gt.name, gt.date
203
+ #lt_vers, lt_date = lt.name, lt.date
204
+ #gt_date = Time.parse(gt_date) unless Time===gt_date
205
+ #lt_date = Time.parse(lt_date) unless Time===lt_date
206
+ #log = changelog.after(gt).before(lt)
207
+ else
208
+ #lt_vers, lt_date = lt.name, lt.date
209
+ #lt_date = Time.parse(lt_date) unless Time===lt_date
210
+ #log = changelog.before(lt_date)
211
+ set = changes.select{ |c| c.date < ended }
212
+ end
213
+ rel << Release.new(tag, set)
214
+ end
215
+ rel.sort
216
+ end
217
+
218
+ #
219
+ # Print a report with given options.
220
+ #
221
+ def report(options)
222
+ report = Report.new(self, options)
223
+ report.print
224
+ end
225
+
226
+ #
227
+ # Make an educated guess as to the next version number based on
228
+ # changes made since previous release.
229
+ #
230
+ # @return [String] version number
231
+ #
232
+ # @todo Allow configuration of version bump thresholds
233
+ #
234
+ def bump
235
+ last_release = releases(changes).first
236
+ max = last_release.changes.map{ |c| c.level }.max
237
+ if max > 1
238
+ bump_part('major')
239
+ elsif max >= 0
240
+ bump_part('minor')
241
+ else
242
+ bump_part('patch')
243
+ end
244
+ end
245
+
246
+ #
247
+ # Provides a bumped version number.
248
+ #
249
+ def bump_part(part=nil)
250
+ raise "bad version part - #{part}" unless ['major', 'minor', 'patch', 'build', ''].include?(part.to_s)
251
+
252
+ if tags.last
253
+ v = tags[-1].name # TODO: ensure the latest version
254
+ v = tags[-2].name if v == 'HEAD'
255
+ else
256
+ v = '0.0.0'
257
+ end
258
+
259
+ v = v.split(/\W/) # TODO: preserve split chars
260
+
261
+ case part.to_s
262
+ when 'major'
263
+ v[0] = v[0].succ
264
+ (1..(v.size-1)).each{ |i| v[i] = '0' }
265
+ v.join('.')
266
+ when 'minor'
267
+ v[1] = '0' unless v[1]
268
+ v[1] = v[1].succ
269
+ (2..(v.size-1)).each{ |i| v[i] = '0' }
270
+ v.join('.')
271
+ when 'patch'
272
+ v[1] = '0' unless v[1]
273
+ v[2] = '0' unless v[2]
274
+ v[2] = v[2].succ
275
+ (3..(v.size-1)).each{ |i| v[i] = '0' }
276
+ v.join('.')
277
+ else
278
+ v[-1] = '0' unless v[-1]
279
+ v[-1] = v[-1].succ
280
+ v.join('.')
281
+ end
282
+ end
283
+
284
+ #
285
+ # Delegate missing methods to SCM adapter.
286
+ #
127
287
  def method_missing(s, *a, &b)
128
288
  if adapter.respond_to?(s)
129
289
  adapter.send(s, *a, &b)
@@ -132,9 +292,11 @@ module VCLog
132
292
  end
133
293
  end
134
294
 
135
- private
295
+ private
136
296
 
297
+ #
137
298
  # Ask yes/no question.
299
+ #
138
300
  def ask_yn(message)
139
301
  case ask(message)
140
302
  when 'y', 'Y', 'yes'
@@ -144,7 +306,9 @@ module VCLog
144
306
  end
145
307
  end
146
308
 
309
+ #
147
310
  # Returns a String.
311
+ #
148
312
  def new_tag_message(label, tag)
149
313
  "#{label} / #{tag.date.strftime('%Y-%m-%d')}\n#{tag.message}"
150
314
  end