rubycut-vclog 1.9.4
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/.yardopts +7 -0
- data/History.md +249 -0
- data/License.txt +23 -0
- data/README.md +133 -0
- data/bin/vclog +6 -0
- data/bin/vclog-autotag +6 -0
- data/bin/vclog-bump +6 -0
- data/bin/vclog-formats +6 -0
- data/bin/vclog-news +6 -0
- data/bin/vclog-version +6 -0
- data/lib/vclog.rb +6 -0
- data/lib/vclog.yml +68 -0
- data/lib/vclog/adapters.rb +12 -0
- data/lib/vclog/adapters/abstract.rb +131 -0
- data/lib/vclog/adapters/darcs.rb +93 -0
- data/lib/vclog/adapters/git.rb +190 -0
- data/lib/vclog/adapters/hg.rb +129 -0
- data/lib/vclog/adapters/svn.rb +155 -0
- data/lib/vclog/change.rb +207 -0
- data/lib/vclog/change_point.rb +77 -0
- data/lib/vclog/changelog.rb +233 -0
- data/lib/vclog/cli.rb +8 -0
- data/lib/vclog/cli/abstract.rb +92 -0
- data/lib/vclog/cli/autotag.rb +36 -0
- data/lib/vclog/cli/bump.rb +29 -0
- data/lib/vclog/cli/formats.rb +28 -0
- data/lib/vclog/cli/log.rb +86 -0
- data/lib/vclog/cli/news.rb +29 -0
- data/lib/vclog/cli/version.rb +30 -0
- data/lib/vclog/config.rb +143 -0
- data/lib/vclog/core_ext.rb +11 -0
- data/lib/vclog/heuristics.rb +192 -0
- data/lib/vclog/heuristics/rule.rb +73 -0
- data/lib/vclog/heuristics/type.rb +29 -0
- data/lib/vclog/history_file.rb +69 -0
- data/lib/vclog/metadata.rb +16 -0
- data/lib/vclog/rc.rb +9 -0
- data/lib/vclog/release.rb +67 -0
- data/lib/vclog/repo.rb +298 -0
- data/lib/vclog/report.rb +200 -0
- data/lib/vclog/tag.rb +151 -0
- data/lib/vclog/templates/changelog.ansi.rb +35 -0
- data/lib/vclog/templates/changelog.atom.erb +52 -0
- data/lib/vclog/templates/changelog.gnu.rb +24 -0
- data/lib/vclog/templates/changelog.html.erb +49 -0
- data/lib/vclog/templates/changelog.json.rb +1 -0
- data/lib/vclog/templates/changelog.markdown.rb +30 -0
- data/lib/vclog/templates/changelog.rdoc.rb +30 -0
- data/lib/vclog/templates/changelog.rss.erb +54 -0
- data/lib/vclog/templates/changelog.xml.erb +28 -0
- data/lib/vclog/templates/changelog.xsl +34 -0
- data/lib/vclog/templates/changelog.yaml.rb +1 -0
- data/lib/vclog/templates/history.ansi.rb +57 -0
- data/lib/vclog/templates/history.atom.erb +84 -0
- data/lib/vclog/templates/history.gnu.rb +39 -0
- data/lib/vclog/templates/history.html.erb +60 -0
- data/lib/vclog/templates/history.json.rb +1 -0
- data/lib/vclog/templates/history.markdown.rb +38 -0
- data/lib/vclog/templates/history.rdoc.rb +36 -0
- data/lib/vclog/templates/history.rss.erb +84 -0
- data/lib/vclog/templates/history.xml.erb +43 -0
- data/lib/vclog/templates/history.yaml.rb +1 -0
- data/man/man1/index.txt +9 -0
- data/man/man1/vclog-autotag.1.ronn +29 -0
- data/man/man1/vclog-bump.1.ronn +21 -0
- data/man/man1/vclog-news.1.ronn +25 -0
- data/man/man1/vclog-version.1.ronn +14 -0
- data/man/man1/vclog.1.ronn +49 -0
- data/spec/feature_git_changes.rb +58 -0
- data/spec/feature_git_history.rb +58 -0
- data/spec/feature_hg_changes.rb +58 -0
- data/spec/feature_hg_history.rb +58 -0
- data/spec/featurettes/repo_creation.rb +64 -0
- data/spec/featurettes/shellout.rb +16 -0
- data/test/case_metadata.rb +10 -0
- metadata +265 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'vclog/adapters/abstract'
|
2
|
+
|
3
|
+
module VCLog
|
4
|
+
|
5
|
+
module Adapters
|
6
|
+
|
7
|
+
# Mercurial Adapter
|
8
|
+
#
|
9
|
+
class Hg < Abstract
|
10
|
+
|
11
|
+
#
|
12
|
+
# Collect changes.
|
13
|
+
#
|
14
|
+
def extract_changes
|
15
|
+
list = []
|
16
|
+
changelog = `hg log -v`.strip
|
17
|
+
changes = changelog.split("\n\n\n")
|
18
|
+
changes.each do |entry|
|
19
|
+
settings = parse_entry(entry)
|
20
|
+
list << Change.new(settings)
|
21
|
+
end
|
22
|
+
list
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Collect tags.
|
27
|
+
#
|
28
|
+
# @todo Extract first commit prior to tag and provide it with Tag object.
|
29
|
+
#
|
30
|
+
def extract_tags
|
31
|
+
list = []
|
32
|
+
if File.exist?('.hgtags')
|
33
|
+
File.readlines('.hgtags').each do |line|
|
34
|
+
rev, tag = line.strip.split(' ')
|
35
|
+
entry = `hg log -v -r #{rev}`.strip
|
36
|
+
settings = parse_entry(entry)
|
37
|
+
settings[:name] = tag
|
38
|
+
list << Tag.new(settings)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
list
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Username.
|
46
|
+
#
|
47
|
+
# @todo check .hgrc for user.
|
48
|
+
#
|
49
|
+
def user
|
50
|
+
ENV['HGUSER'] || ENV['USER']
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# User's email address.
|
55
|
+
#
|
56
|
+
# @todo check .hgrc for email.
|
57
|
+
#
|
58
|
+
def email
|
59
|
+
ENV['HGEMAIL'] || ENV['EMAIL']
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# URI of repository.
|
64
|
+
#
|
65
|
+
def uri
|
66
|
+
@uri ||= `hg showconfig paths.default`.strip
|
67
|
+
end
|
68
|
+
|
69
|
+
# @deprecated
|
70
|
+
alias_method :repository, :uri
|
71
|
+
|
72
|
+
#
|
73
|
+
#
|
74
|
+
#
|
75
|
+
def uuid
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# TODO: Will multi-line messages work okay this way?
|
81
|
+
#
|
82
|
+
def tag(ref, label, date, msg)
|
83
|
+
file = tempfile("message", msg)
|
84
|
+
date = date.strftime('%Y-%m-%d') unless String===date
|
85
|
+
|
86
|
+
cmd = %[hg tag -r #{ref} -d #{date} -m "$(cat #{file})" #{label}]
|
87
|
+
|
88
|
+
puts cmd if $DEBUG
|
89
|
+
`#{cmd}` unless $DRYRUN
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
#
|
95
|
+
# Parse log entry.
|
96
|
+
#
|
97
|
+
def parse_entry(entry)
|
98
|
+
settings = {}
|
99
|
+
|
100
|
+
entry.strip!
|
101
|
+
|
102
|
+
if md = /^changeset:(.*?)$/.match(entry)
|
103
|
+
settings[:id] = md[1].strip
|
104
|
+
end
|
105
|
+
|
106
|
+
if md = /^date:(.*?)$/.match(entry)
|
107
|
+
settings[:date] = Time.parse(md[1].strip)
|
108
|
+
end
|
109
|
+
|
110
|
+
if md = /^user:(.*?)$/.match(entry)
|
111
|
+
settings[:who] = md[1].strip
|
112
|
+
end
|
113
|
+
|
114
|
+
if md = /^files:(.*?)$/.match(entry)
|
115
|
+
settings[:files] = md[1].strip.split(' ')
|
116
|
+
end
|
117
|
+
|
118
|
+
if md = /^description:(.*?)\Z/m.match(entry)
|
119
|
+
settings[:msg] = md[1].strip
|
120
|
+
end
|
121
|
+
|
122
|
+
return settings
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'vclog/adapters/abstract'
|
2
|
+
|
3
|
+
module VCLog
|
4
|
+
|
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
|
15
|
+
|
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
|
24
|
+
|
25
|
+
# Extract changes.
|
26
|
+
def extract_changes
|
27
|
+
log = []
|
28
|
+
|
29
|
+
xml = `svn log -v --xml`.strip
|
30
|
+
|
31
|
+
commits = XmlSimple.xml_in(xml, {'KeyAttr' => 'rev'})
|
32
|
+
commits = commits['logentry']
|
33
|
+
|
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
|
39
|
+
|
40
|
+
files = com['paths'].map{ |h| h['path'].map{ |y| y['content'] } }.flatten
|
41
|
+
|
42
|
+
next if msg.empty?
|
43
|
+
next if msg == "*** empty log message ***"
|
44
|
+
|
45
|
+
date = Time.parse(date)
|
46
|
+
|
47
|
+
log << Change.new(:id=>rev, :date=>date, :who=>who, :msg=>msg, :files=>files)
|
48
|
+
end
|
49
|
+
|
50
|
+
log
|
51
|
+
end
|
52
|
+
|
53
|
+
# Extract tags.
|
54
|
+
def extract_tags
|
55
|
+
list = []
|
56
|
+
tagdir = tag_directory
|
57
|
+
|
58
|
+
if tagdir
|
59
|
+
tags = Dir.entries(tagdir).select{ |e| e.index('.') != 0 && e =~ /\d(.*)$/ }
|
60
|
+
else
|
61
|
+
tags = []
|
62
|
+
end
|
63
|
+
|
64
|
+
tags.each do |path|
|
65
|
+
dir = File.join(tagdir, path)
|
66
|
+
|
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']
|
75
|
+
|
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
|
87
|
+
end
|
88
|
+
|
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
|
105
|
+
else
|
106
|
+
break(fnd=nil)
|
107
|
+
end
|
108
|
+
dir = File.dirname(dir)
|
109
|
+
end
|
110
|
+
fnd
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
def user
|
115
|
+
@email ||= `svn propget svn:author`.strip
|
116
|
+
end
|
117
|
+
|
118
|
+
# TODO: Best solution to SVN email?
|
119
|
+
def email
|
120
|
+
@email ||= ENV['EMAIL']
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
def repository
|
125
|
+
info['Repository Root']
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
def uuid
|
130
|
+
info['Repository UUID']
|
131
|
+
end
|
132
|
+
|
133
|
+
# TODO: Need to effect svn tag date. How?
|
134
|
+
def tag(ref, label, date, message)
|
135
|
+
file = tempfile("message", message)
|
136
|
+
|
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
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
#
|
147
|
+
def info
|
148
|
+
@info ||= YAML.load(`svn info`.strip)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/vclog/change.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'vclog/change_point'
|
2
|
+
|
3
|
+
module VCLog
|
4
|
+
|
5
|
+
# The Change class models an entry in a change log.
|
6
|
+
#
|
7
|
+
class Change
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
# Commit revision/reference id.
|
12
|
+
attr_accessor :id
|
13
|
+
|
14
|
+
# Date/time of commit.
|
15
|
+
attr_accessor :date
|
16
|
+
|
17
|
+
# Committer.
|
18
|
+
attr_accessor :author
|
19
|
+
|
20
|
+
# Commit message.
|
21
|
+
attr_accessor :message
|
22
|
+
|
23
|
+
# List of files changed in the commit.
|
24
|
+
attr_accessor :files
|
25
|
+
|
26
|
+
# Type of change, as assigned by hueristics.
|
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.
|
32
|
+
attr_accessor :level
|
33
|
+
|
34
|
+
# The descriptive label of this change, as assigned by hueristics.
|
35
|
+
attr_accessor :label
|
36
|
+
|
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.
|
43
|
+
def initialize(data={})
|
44
|
+
@type = :default
|
45
|
+
@level = nil
|
46
|
+
@label = nil
|
47
|
+
@color = []
|
48
|
+
|
49
|
+
data.each do |k,v|
|
50
|
+
__send__("#{k}=", v) if respond_to?("#{k}=")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Set authors attributes, ensuring the value is stripped of white space.
|
56
|
+
#
|
57
|
+
def author=(author)
|
58
|
+
@author = author.strip
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Set date attribute, converting vale given to an instance of Time.
|
63
|
+
#
|
64
|
+
def date=(date)
|
65
|
+
@date = parse_date(date)
|
66
|
+
end
|
67
|
+
|
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
|
90
|
+
|
91
|
+
#
|
92
|
+
def type=(type)
|
93
|
+
@type = type.to_sym
|
94
|
+
end
|
95
|
+
|
96
|
+
# Alternate name for id.
|
97
|
+
alias_method :rev, :id
|
98
|
+
alias_method :rev=, :id=
|
99
|
+
|
100
|
+
# Alternate name for id.
|
101
|
+
alias_method :revision, :id
|
102
|
+
alias_method :revision=, :id=
|
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
|
+
|
112
|
+
# Alternate name for message.
|
113
|
+
alias_method :msg, :message
|
114
|
+
alias_method :msg=, :message=
|
115
|
+
|
116
|
+
# Alias for author.
|
117
|
+
alias_method :who, :author
|
118
|
+
alias_method :who=, :author=
|
119
|
+
|
120
|
+
attr_reader :summary
|
121
|
+
|
122
|
+
attr_reader :details
|
123
|
+
|
124
|
+
#
|
125
|
+
# Compare changes by date.
|
126
|
+
#
|
127
|
+
def <=>(other)
|
128
|
+
other.date <=> date
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Inspection string of change object.
|
133
|
+
#
|
134
|
+
def inspect
|
135
|
+
"#<Change:#{object_id} #{date}>"
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Convert to Hash.
|
140
|
+
#
|
141
|
+
def to_h
|
142
|
+
{ 'author' => self.author,
|
143
|
+
'date' => self.date,
|
144
|
+
'id' => self.id,
|
145
|
+
'message' => self.message,
|
146
|
+
'type' => self.type
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Apply heuristic rules to change.
|
152
|
+
#
|
153
|
+
def apply_heuristics(heuristics)
|
154
|
+
heuristics.apply(self)
|
155
|
+
end
|
156
|
+
|
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
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Output message with optional adjustments.
|
167
|
+
#
|
168
|
+
def to_s(opts={})
|
169
|
+
if opts[:summary]
|
170
|
+
summary
|
171
|
+
else
|
172
|
+
message.strip
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
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
|
+
#
|
184
|
+
def parse_date(date)
|
185
|
+
case date
|
186
|
+
when Time
|
187
|
+
date
|
188
|
+
else
|
189
|
+
Time.parse(date.to_s)
|
190
|
+
end
|
191
|
+
end
|
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
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|