vclog 1.8.2 → 1.9.0

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.
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