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
@@ -1,152 +1,155 @@
1
1
  require 'vclog/adapters/abstract'
2
2
 
3
3
  module VCLog
4
- module Adapters
5
-
6
- # = SVN
7
- #
8
- # NOTE: Unfortunately the SVN adapater is very slow. If hits the server
9
- # every time the 'svn log' command is issued. When generating a History
10
- # that's one hit for every tag. If anyone knows a better way please have
11
- # at it --maybe future versions of SVN will improve the situation.
12
- #
13
- class Svn < Abstract
14
-
15
- def initialize(root)
16
- begin
17
- require 'xmlsimple'
18
- rescue LoadError
19
- raise LoadError, "VCLog requires xml-simple for SVN support."
20
- end
21
- super(root)
22
- end
23
4
 
24
- # Extract changes.
25
- def extract_changes
26
- log = []
5
+ module Adapters
6
+
7
+ # SVN Adapter.
8
+ #
9
+ # NOTE: Unfortunately the SVN adapater is very slow. If hits the server
10
+ # every time the 'svn log' command is issued. When generating a History
11
+ # that's two hits for every tag. If anyone knows a better way please have
12
+ # at it --maybe future versions of SVN will improve the situation.
13
+ #
14
+ class Svn < Abstract
27
15
 
28
- xml = `svn log --xml`.strip
16
+ def initialize(root)
17
+ begin
18
+ require 'xmlsimple'
19
+ rescue LoadError
20
+ raise LoadError, "VCLog requires xml-simple for SVN support."
21
+ end
22
+ super(root)
23
+ end
29
24
 
30
- commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
31
- commits = commits['logentry']
25
+ # Extract changes.
26
+ def extract_changes
27
+ log = []
32
28
 
33
- commits.each do |com|
34
- rev = com["revision"]
35
- msg = [com["msg"]].flatten.compact.join('').strip
36
- who = [com["author"]].flatten.compact.join('').strip
37
- date = [com["date"]].flatten.compact.join('').strip
29
+ xml = `svn log -v --xml`.strip
38
30
 
39
- next if msg.empty?
40
- next if msg == "*** empty log message ***"
31
+ commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
32
+ commits = commits['logentry']
41
33
 
42
- date = Time.parse(date)
34
+ commits.each do |com|
35
+ rev = com['revision']
36
+ msg = [com['msg']].flatten.compact.join('').strip
37
+ who = [com['author']].flatten.compact.join('').strip
38
+ date = [com['date']].flatten.compact.join('').strip
43
39
 
44
- log << Change.new(:id=>rev, :date=>date, :who=>who, :msg=>msg)
45
- end
40
+ files = com['paths'].map{ |h| h['path'].map{ |y| y['content'] } }.flatten
46
41
 
47
- log
48
- end
42
+ next if msg.empty?
43
+ next if msg == "*** empty log message ***"
49
44
 
50
- # Extract tags.
51
- def extract_tags
52
- list = []
53
- tagdir = tag_directory
45
+ date = Time.parse(date)
54
46
 
55
- if tagdir
56
- tags = Dir.entries(tagdir).select{ |e| e.index('.') != 0 && e =~ /\d(.*)$/ }
57
- else
58
- tags = []
47
+ log << Change.new(:id=>rev, :date=>date, :who=>who, :msg=>msg, :files=>files)
48
+ end
49
+
50
+ log
59
51
  end
60
52
 
61
- tags.each do |path|
62
- dir = File.join(tagdir, path)
53
+ # Extract tags.
54
+ def extract_tags
55
+ list = []
56
+ tagdir = tag_directory
63
57
 
64
- # using yaml, but maybe use xml instead?
65
- info = `svn info #{dir}`
66
- info = YAML.load(info)
67
- md = /(\d.*)$/.match(info['Path'])
68
- name = md ? md[1] : path
69
- date = info['Last Changed Date']
70
- who = info['Last Changed Author']
71
- rev = info['Revision']
58
+ if tagdir
59
+ tags = Dir.entries(tagdir).select{ |e| e.index('.') != 0 && e =~ /\d(.*)$/ }
60
+ else
61
+ tags = []
62
+ end
72
63
 
73
- # get last commit
74
- xml = `svn log -l1 --xml #{dir}`
75
- commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
76
- commit = commits['logentry'].first
64
+ tags.each do |path|
65
+ dir = File.join(tagdir, path)
77
66
 
78
- msg = [commit["msg"]].flatten.compact.join('').strip
79
- date = [commit["date"]].flatten.compact.join('').strip
67
+ # using yaml, but maybe use xml instead?
68
+ info = `svn info #{dir}`
69
+ info = YAML.load(info)
70
+ md = /(\d.*)$/.match(info['Path'])
71
+ name = md ? md[1] : path
72
+ date = info['Last Changed Date']
73
+ who = info['Last Changed Author']
74
+ rev = info['Revision']
80
75
 
81
- list << Tag.new(:name=>name, :id=>rev, :date=>date, :who=>who, :msg=>msg)
76
+ # get last commit
77
+ xml = `svn log -l1 --xml #{dir}`
78
+ commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
79
+ commit = commits['logentry'].first
80
+
81
+ msg = [commit["msg"]].flatten.compact.join('').strip
82
+ date = [commit["date"]].flatten.compact.join('').strip
83
+
84
+ list << Tag.new(:name=>name, :id=>rev, :date=>date, :who=>who, :msg=>msg)
85
+ end
86
+ list
82
87
  end
83
- list
84
- end
85
88
 
86
- # This isn't perfect, but is there really anyway for it to be?
87
- # It ascends up the current directory tree looking for the
88
- # best candidate for a tags directory.
89
- def tag_directory
90
- fnd = nil
91
- dir = root
92
- while dir != '/' do
93
- entries = Dir.entries(dir)
94
- if entries.include?('.svn')
95
- if entries.include?('tags')
96
- break(fnd = File.join(dir, 'tags'))
89
+ # This isn't perfect, but is there really anyway for it to be?
90
+ # It ascends up the current directory tree looking for the
91
+ # best candidate for a tags directory.
92
+ def tag_directory
93
+ fnd = nil
94
+ dir = root
95
+ while dir != '/' do
96
+ entries = Dir.entries(dir)
97
+ if entries.include?('.svn')
98
+ if entries.include?('tags')
99
+ break(fnd = File.join(dir, 'tags'))
100
+ else
101
+ entries = entries.reject{ |e| e.index('.') == 0 }
102
+ entries = entries.reject{ |e| e !~ /\d$/ }
103
+ break(fnd = dir) unless entries.empty?
104
+ end
97
105
  else
98
- entries = entries.reject{ |e| e.index('.') == 0 }
99
- entries = entries.reject{ |e| e !~ /\d$/ }
100
- break(fnd = dir) unless entries.empty?
106
+ break(fnd=nil)
101
107
  end
102
- else
103
- break(fnd=nil)
108
+ dir = File.dirname(dir)
104
109
  end
105
- dir = File.dirname(dir)
110
+ fnd
106
111
  end
107
- fnd
108
- end
109
112
 
110
- #
111
- def user
112
- @email ||= `svn propget svn:author`.strip
113
- end
113
+ #
114
+ def user
115
+ @email ||= `svn propget svn:author`.strip
116
+ end
114
117
 
115
- # TODO: Best solution to SVN email?
116
- def email
117
- @email ||= ENV['EMAIL']
118
- end
118
+ # TODO: Best solution to SVN email?
119
+ def email
120
+ @email ||= ENV['EMAIL']
121
+ end
119
122
 
120
- #
121
- def repository
122
- info['Repository Root']
123
- end
123
+ #
124
+ def repository
125
+ info['Repository Root']
126
+ end
124
127
 
125
- #
126
- def uuid
127
- info['Repository UUID']
128
- end
128
+ #
129
+ def uuid
130
+ info['Repository UUID']
131
+ end
129
132
 
130
- # TODO: Need to effect svn tag date. How?
131
- def tag(ref, label, date, message)
132
- file = tempfile("message", message)
133
+ # TODO: Need to effect svn tag date. How?
134
+ def tag(ref, label, date, message)
135
+ file = tempfile("message", message)
133
136
 
134
- Dir.chdir(root) do
135
- cmd = %[svn copy -r #{ref} -F "#{mfile.path}" . #{tag_directory}/#{label}]
136
- puts cmd if $DEBUG
137
- `#{cmd}` unless $DRYRUN
137
+ Dir.chdir(root) do
138
+ cmd = %[svn copy -r #{ref} -F "#{mfile.path}" . #{tag_directory}/#{label}]
139
+ puts cmd if $DEBUG
140
+ `#{cmd}` unless $DRYRUN
141
+ end
138
142
  end
139
- end
140
143
 
141
- private
144
+ private
145
+
146
+ #
147
+ def info
148
+ @info ||= YAML.load(`svn info`.strip)
149
+ end
142
150
 
143
- #
144
- def info
145
- @info ||= YAML.load(`svn info`.strip)
146
151
  end
147
152
 
148
153
  end
149
154
 
150
155
  end
151
- end
152
-
@@ -1,4 +1,4 @@
1
- require 'vclog/kernel'
1
+ require 'vclog/change_point'
2
2
 
3
3
  module VCLog
4
4
 
@@ -8,7 +8,7 @@ module VCLog
8
8
 
9
9
  include Comparable
10
10
 
11
- # Commit reference id.
11
+ # Commit revision/reference id.
12
12
  attr_accessor :id
13
13
 
14
14
  # Date/time of commit.
@@ -23,45 +23,92 @@ module VCLog
23
23
  # List of files changed in the commit.
24
24
  attr_accessor :files
25
25
 
26
+ # Type of change, as assigned by hueristics.
26
27
  attr_accessor :type
28
+
29
+ # The priority level of this change, as assigned by hueristics.
30
+ # This can be `nil`, as Heuristics will always make sure a
31
+ # commit has an inteer level before going out to template.
27
32
  attr_accessor :level
33
+
34
+ # The descriptive label of this change, as assigned by hueristics.
28
35
  attr_accessor :label
29
36
 
30
- #
37
+ # ANSI color to apply. Actually this can be a list
38
+ # of any support ansi gem terms, but usually it's
39
+ # just the color term, such as `:red`.
40
+ attr_accessor :color
41
+
42
+ # Setup new Change instance.
31
43
  def initialize(data={})
44
+ @type = :default
45
+ @level = nil
46
+ @label = nil
47
+ @color = []
48
+
32
49
  data.each do |k,v|
33
- __send__("#{k}=", v)
50
+ __send__("#{k}=", v) if respond_to?("#{k}=")
34
51
  end
35
52
  end
36
53
 
54
+ #
55
+ # Set authors attributes, ensuring the value is stripped of white space.
37
56
  #
38
57
  def author=(author)
39
58
  @author = author.strip
40
59
  end
41
60
 
61
+ #
62
+ # Set date attribute, converting vale given to an instance of Time.
42
63
  #
43
64
  def date=(date)
44
65
  @date = parse_date(date)
45
66
  end
46
67
 
47
- # Alternate name for id.
48
- alias_method :ref, :id
49
- alias_method :ref=, :id=
50
- alias_method :reference, :id
51
- alias_method :reference=, :id=
68
+ #
69
+ # Set the commit message.
70
+ #
71
+ def message=(msg)
72
+ @message = msg
73
+
74
+ lines = msg.lines.to_a
75
+ @summary = lines.first.strip
76
+ @details = lines[1..-1].join('').strip
77
+
78
+ msg
79
+ end
80
+
81
+ #
82
+ # Set the ANSI color terms.
83
+ #
84
+ # @param [Symbol,Array<Symbol>] code
85
+ # An ANSI gem recognized term, or array of such.
86
+ #
87
+ def color=(code)
88
+ @color = [code].flatten
89
+ end
52
90
 
53
- # Alternate name for id.
54
91
  #
55
- # @deprecated
92
+ def type=(type)
93
+ @type = type.to_sym
94
+ end
95
+
96
+ # Alternate name for id.
56
97
  alias_method :rev, :id
57
98
  alias_method :rev=, :id=
58
99
 
59
100
  # Alternate name for id.
60
- #
61
- # @deprecated
62
101
  alias_method :revision, :id
63
102
  alias_method :revision=, :id=
64
103
 
104
+ # Alternate name for id.
105
+ alias_method :ref, :id
106
+ alias_method :ref=, :id=
107
+
108
+ # Alternate name for id.
109
+ alias_method :reference, :id
110
+ alias_method :reference=, :id=
111
+
65
112
  # Alternate name for message.
66
113
  alias_method :msg, :message
67
114
  alias_method :msg=, :message=
@@ -70,100 +117,70 @@ module VCLog
70
117
  alias_method :who, :author
71
118
  alias_method :who=, :author=
72
119
 
120
+ attr_reader :summary
73
121
 
74
- #def clean_type(type)
75
- # case type.to_s
76
- # when 'maj', 'major' then :major
77
- # when 'min', 'minor' then :minor
78
- # when 'bug' then :bug
79
- # when '' then :other
80
- # else
81
- # type.to_sym
82
- # end
83
- #end
122
+ attr_reader :details
84
123
 
124
+ #
125
+ # Compare changes by date.
85
126
  #
86
127
  def <=>(other)
87
128
  other.date <=> date
88
129
  end
89
130
 
131
+ #
132
+ # Inspection string of change object.
133
+ #
90
134
  def inspect
91
135
  "#<Change:#{object_id} #{date}>"
92
136
  end
93
137
 
94
- # TODO: Rename revision to `referece` or `ref`.
95
-
138
+ #
139
+ # Convert to Hash.
96
140
  #
97
141
  def to_h
98
- { 'author' => @author,
99
- 'date' => @date,
100
- 'revision' => @revision,
101
- 'message' => @message,
102
- 'type' => @type
142
+ { 'author' => self.author,
143
+ 'date' => self.date,
144
+ 'id' => self.id,
145
+ 'message' => self.message,
146
+ 'type' => self.type
103
147
  }
104
148
  end
105
149
 
106
- #def to_json
107
- # to_h.to_json
108
- #end
109
-
110
- #def to_yaml(*args)
111
- # to_h.to_yaml(*args)
112
- #end
113
-
150
+ #
151
+ # Apply heuristic rules to change.
152
+ #
114
153
  def apply_heuristics(heuristics)
115
- type, level, label, msg = *heuristics.lookup(message)
116
-
117
- self.type = type
118
- self.level = level
119
- self.label = label
120
- self.message = msg || @message
154
+ heuristics.apply(self)
121
155
  end
122
156
 
123
- =begin
124
- #
125
- def type_phrase
126
- case type.to_s
127
- when 'maj', 'major'
128
- 'Major Enhancements'
129
- when 'min', 'minor'
130
- 'Minor Enhancements'
131
- when 'bug'
132
- 'Bug Fixes'
133
- when ''
134
- 'General Enhancements'
135
- when '-'
136
- 'Administrative Changes'
137
- else
138
- "#{type.to_s.capitalize} Enhancements"
139
- end
157
+ #
158
+ # Parse point entries from commit message. Point entries
159
+ # are outlined changes via line that start with an asterisk.
160
+ #
161
+ def points
162
+ @points ||= parse_points
140
163
  end
141
164
 
142
- # Looks for a "[type]" indicator at the end of the commit message.
143
- # If that is not found, it looks at front of message for
144
- # "[type]" or "[type]:". Failing that it tries just "type:".
145
- #
146
- def split_type(note)
147
- note = note.to_s.strip
148
- if md = /\[(.*?)\]\Z/.match(note)
149
- t = md[1].strip.downcase
150
- n = note[0...(md.begin(0))]
151
- elsif md = /\A\[(.*?)\]\:?/.match(note)
152
- t = md[1].strip.downcase
153
- n = note[md.end(0)..-1]
154
- elsif md = /\A(\w+?)\:/.match(note)
155
- t = md[1].strip.downcase
156
- n = note[md.end(0)..-1]
165
+ #
166
+ # Output message with optional adjustments.
167
+ #
168
+ def to_s(opts={})
169
+ if opts[:summary]
170
+ summary
157
171
  else
158
- n, t = note, nil
172
+ message.strip
159
173
  end
160
- n.gsub!(/^\s*?\n/m,'') # remove blank lines
161
- return n, t
162
174
  end
163
- =end
164
175
 
165
- private
176
+ private
166
177
 
178
+ #
179
+ # Convert given +date+ into Time instance.
180
+ #
181
+ # @param [String,Data,Time] date
182
+ # A valid data/time string or object.
183
+ #
167
184
  def parse_date(date)
168
185
  case date
169
186
  when Time
@@ -173,6 +190,18 @@ module VCLog
173
190
  end
174
191
  end
175
192
 
193
+ #
194
+ # Split message into individual points.
195
+ #
196
+ # @todo Improve the parsing of point messages.
197
+ #
198
+ def parse_points
199
+ point_messages = message.split(/^\*/)
200
+ point_messages.map do |msg|
201
+ ChangePoint.new(self, msg)
202
+ end
203
+ end
204
+
176
205
  end
177
206
 
178
207
  end