vclog 1.8.2 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|