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.
- data/.ruby +4 -3
- data/.yardopts +7 -0
- data/HISTORY.rdoc +207 -0
- data/README.rdoc +44 -27
- data/bin/vclog +4 -2
- data/bin/vclog-autotag +6 -0
- data/bin/vclog-bump +6 -0
- data/bin/vclog-formats +6 -0
- data/bin/vclog-version +6 -0
- data/lib/vclog.rb +1 -1
- data/lib/vclog.yml +58 -0
- data/lib/vclog/adapters.rb +2 -1
- data/lib/vclog/adapters/abstract.rb +87 -232
- data/lib/vclog/adapters/darcs.rb +72 -67
- data/lib/vclog/adapters/git.rb +166 -140
- data/lib/vclog/adapters/hg.rb +98 -62
- data/lib/vclog/adapters/svn.rb +116 -113
- data/lib/vclog/change.rb +110 -81
- data/lib/vclog/change_point.rb +77 -0
- data/lib/vclog/changelog.rb +58 -296
- data/lib/vclog/cli.rb +6 -70
- data/lib/vclog/cli/abstract.rb +64 -81
- data/lib/vclog/cli/autotag.rb +1 -3
- data/lib/vclog/cli/bump.rb +3 -4
- data/lib/vclog/cli/formats.rb +4 -4
- data/lib/vclog/cli/log.rb +86 -0
- data/lib/vclog/cli/version.rb +3 -3
- data/lib/vclog/{facets.rb → core_ext.rb} +0 -0
- data/lib/vclog/heuristics.rb +112 -38
- data/lib/vclog/heuristics/rule.rb +52 -12
- data/lib/vclog/heuristics/{label.rb → type.rb} +2 -2
- data/lib/vclog/history_file.rb +2 -2
- data/lib/vclog/metadata.rb +13 -1
- data/lib/vclog/release.rb +26 -12
- data/lib/vclog/repo.rb +191 -27
- data/lib/vclog/report.rb +187 -0
- data/lib/vclog/tag.rb +66 -39
- data/lib/vclog/templates/changelog.ansi.rb +9 -26
- data/lib/vclog/templates/changelog.atom.erb +3 -3
- data/lib/vclog/templates/changelog.gnu.rb +4 -11
- data/lib/vclog/templates/changelog.html.erb +11 -2
- data/lib/vclog/templates/changelog.markdown.rb +4 -4
- data/lib/vclog/templates/changelog.rdoc.rb +4 -4
- data/lib/vclog/templates/changelog.rss.erb +2 -6
- data/lib/vclog/templates/changelog.xml.erb +14 -2
- data/lib/vclog/templates/history.ansi.rb +10 -17
- data/lib/vclog/templates/history.atom.erb +4 -4
- data/lib/vclog/templates/history.gnu.rb +5 -7
- data/lib/vclog/templates/history.html.erb +11 -4
- data/lib/vclog/templates/history.json.rb +1 -1
- data/lib/vclog/templates/history.markdown.rb +5 -7
- data/lib/vclog/templates/history.rdoc.rb +5 -9
- data/lib/vclog/templates/history.rss.erb +3 -5
- data/lib/vclog/templates/history.xml.erb +15 -3
- data/lib/vclog/templates/history.yaml.rb +1 -1
- data/man/man1/vclog-autotag.1 +1 -1
- data/man/man1/vclog-autotag.1.html +1 -1
- data/man/man1/vclog-bump.1 +1 -1
- data/man/man1/vclog-bump.1.html +1 -1
- data/man/man1/vclog-version.1 +1 -1
- data/man/man1/vclog-version.1.html +1 -1
- data/man/man1/vclog.1 +25 -13
- data/man/man1/vclog.1.html +29 -20
- data/man/man1/vclog.1.ronn +31 -18
- data/test/unit/case_metadata.rb +1 -1
- metadata +48 -34
- data/lib/vclog/cli/changelog.rb +0 -33
- data/lib/vclog/cli/help.rb +0 -42
- data/lib/vclog/cli/history.rb +0 -39
- data/lib/vclog/formatter.rb +0 -123
- data/lib/vclog/history.rb +0 -131
- data/lib/vclog/kernel.rb +0 -12
- data/man/man1/vclog-changelog.1 +0 -47
- data/man/man1/vclog-changelog.1.html +0 -123
- data/man/man1/vclog-changelog.1.ronn +0 -39
- data/man/man1/vclog-history.1 +0 -44
- data/man/man1/vclog-history.1.html +0 -122
- data/man/man1/vclog-history.1.ronn +0 -38
data/lib/vclog/adapters/svn.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
25
|
+
# Extract changes.
|
26
|
+
def extract_changes
|
27
|
+
log = []
|
32
28
|
|
33
|
-
|
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
|
-
|
40
|
-
|
31
|
+
commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
|
32
|
+
commits = commits['logentry']
|
41
33
|
|
42
|
-
|
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
|
-
|
45
|
-
end
|
40
|
+
files = com['paths'].map{ |h| h['path'].map{ |y| y['content'] } }.flatten
|
46
41
|
|
47
|
-
|
48
|
-
|
42
|
+
next if msg.empty?
|
43
|
+
next if msg == "*** empty log message ***"
|
49
44
|
|
50
|
-
|
51
|
-
def extract_tags
|
52
|
-
list = []
|
53
|
-
tagdir = tag_directory
|
45
|
+
date = Time.parse(date)
|
54
46
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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.
|
62
|
-
|
53
|
+
# Extract tags.
|
54
|
+
def extract_tags
|
55
|
+
list = []
|
56
|
+
tagdir = tag_directory
|
63
57
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
99
|
-
entries = entries.reject{ |e| e !~ /\d$/ }
|
100
|
-
break(fnd = dir) unless entries.empty?
|
106
|
+
break(fnd=nil)
|
101
107
|
end
|
102
|
-
|
103
|
-
break(fnd=nil)
|
108
|
+
dir = File.dirname(dir)
|
104
109
|
end
|
105
|
-
|
110
|
+
fnd
|
106
111
|
end
|
107
|
-
fnd
|
108
|
-
end
|
109
112
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
113
|
+
#
|
114
|
+
def user
|
115
|
+
@email ||= `svn propget svn:author`.strip
|
116
|
+
end
|
114
117
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
118
|
+
# TODO: Best solution to SVN email?
|
119
|
+
def email
|
120
|
+
@email ||= ENV['EMAIL']
|
121
|
+
end
|
119
122
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
123
|
+
#
|
124
|
+
def repository
|
125
|
+
info['Repository Root']
|
126
|
+
end
|
124
127
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
128
|
+
#
|
129
|
+
def uuid
|
130
|
+
info['Repository UUID']
|
131
|
+
end
|
129
132
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
+
# TODO: Need to effect svn tag date. How?
|
134
|
+
def tag(ref, label, date, message)
|
135
|
+
file = tempfile("message", message)
|
133
136
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
-
|
data/lib/vclog/change.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'vclog/
|
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
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
95
|
-
|
138
|
+
#
|
139
|
+
# Convert to Hash.
|
96
140
|
#
|
97
141
|
def to_h
|
98
|
-
{ 'author' =>
|
99
|
-
'date' =>
|
100
|
-
'
|
101
|
-
'message' =>
|
102
|
-
'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
|
-
#
|
107
|
-
#
|
108
|
-
#
|
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
|
-
|
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
|
-
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
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
|
-
|
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
|