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
@@ -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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
data/lib/vclog/history_file.rb
CHANGED
@@ -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
|
data/lib/vclog/metadata.rb
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
module VCLog
|
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
|
|
data/lib/vclog/release.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module VCLog
|
2
2
|
|
3
|
-
# A Release encapsulate a collection of
|
4
|
-
#
|
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
|
-
#
|
16
|
-
#
|
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
|
24
|
+
@tag = tag
|
20
25
|
@changes = changes
|
21
26
|
end
|
22
27
|
|
28
|
+
#
|
23
29
|
# Group +changes+ by type and sort by level.
|
24
|
-
#
|
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.
|
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
|
-
'
|
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
|
-
|
data/lib/vclog/repo.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
27
|
-
#
|
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
|
-
|
36
|
-
|
37
|
-
@level = (options[:level] || 0).to_i
|
39
|
+
vcs_type = read_vcs_type
|
38
40
|
|
39
|
-
|
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(
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|