pvn 0.0.1
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/README.markdown +38 -0
- data/bin/pvn +12 -0
- data/bin/pvndiff +10 -0
- data/lib/pvn/app.rb +144 -0
- data/lib/pvn/base/textlines.rb +35 -0
- data/lib/pvn/base/util.rb +14 -0
- data/lib/pvn/cmddoc.rb +77 -0
- data/lib/pvn/config.rb +65 -0
- data/lib/pvn/describe.rb +40 -0
- data/lib/pvn/diff/diffcmd.rb +49 -0
- data/lib/pvn/diff/differ.rb +67 -0
- data/lib/pvn/diff/diffopts.rb +58 -0
- data/lib/pvn/doc.rb +34 -0
- data/lib/pvn/io/element.rb +113 -0
- data/lib/pvn/io/fselement.rb +22 -0
- data/lib/pvn/log/formatter/color_formatter.rb +29 -0
- data/lib/pvn/log/formatter/entries_formatter.rb +33 -0
- data/lib/pvn/log/formatter/entry_formatter.rb +57 -0
- data/lib/pvn/log/formatter/log_formatter.rb +59 -0
- data/lib/pvn/log/formatter/message_formatter.rb +19 -0
- data/lib/pvn/log/formatter/path_formatter.rb +41 -0
- data/lib/pvn/log/formatter/summary_formatter.rb +49 -0
- data/lib/pvn/log/logcmd.rb +136 -0
- data/lib/pvn/log/logentry.rb +116 -0
- data/lib/pvn/log/logfactory.rb +101 -0
- data/lib/pvn/log/logoptions.rb +43 -0
- data/lib/pvn/pct/linecount.rb +33 -0
- data/lib/pvn/pct/pctcmd.rb +204 -0
- data/lib/pvn/pct/statcmd.rb +13 -0
- data/lib/pvn/revision/entry.rb +94 -0
- data/lib/pvn/revision.rb +119 -0
- data/lib/pvn/subcommands/base/clargs.rb +53 -0
- data/lib/pvn/subcommands/base/command.rb +60 -0
- data/lib/pvn/subcommands/base/doc.rb +94 -0
- data/lib/pvn/subcommands/base/options.rb +22 -0
- data/lib/pvn/subcommands/log/command.rb +70 -0
- data/lib/pvn/subcommands/log/options.rb +43 -0
- data/lib/pvn/subcommands/pct/clargs.rb +33 -0
- data/lib/pvn/subcommands/pct/command.rb +57 -0
- data/lib/pvn/subcommands/revision/multiple_revisions_option.rb +43 -0
- data/lib/pvn/subcommands/revision/revision_option.rb +74 -0
- data/lib/pvn/subcommands/revision/revision_regexp_option.rb +39 -0
- data/lib/pvn/svn/command/svncmd.rb +39 -0
- data/lib/pvn/svn/environment.rb +23 -0
- data/lib/pvn/svn/svnelement.rb +89 -0
- data/lib/pvn/svn/svninfo.rb +42 -0
- data/lib/pvn/svn/svnroot.rb +29 -0
- data/lib/pvn/upp/uppcmd.rb +112 -0
- data/lib/pvn/wherecmd.rb +55 -0
- data/lib/pvn.rb +8 -0
- data/lib/svnx/command.rb +60 -0
- data/lib/svnx/entry.rb +34 -0
- data/lib/svnx/info/command.rb +21 -0
- data/lib/svnx/info/entries.rb +27 -0
- data/lib/svnx/info/entry.rb +34 -0
- data/lib/svnx/log/command.rb +93 -0
- data/lib/svnx/log/entries.rb +57 -0
- data/lib/svnx/log/entry.rb +54 -0
- data/lib/svnx/status/command.rb +21 -0
- data/lib/svnx/status/entries.rb +22 -0
- data/lib/svnx/status/entry.rb +33 -0
- data/lib/synoption/base_option.rb +149 -0
- data/lib/synoption/boolean_option.rb +21 -0
- data/lib/synoption/doc.rb +81 -0
- data/lib/synoption/fixnum_option.rb +13 -0
- data/lib/synoption/match.rb +47 -0
- data/lib/synoption/option.rb +10 -0
- data/lib/synoption/optionable.rb +66 -0
- data/lib/synoption/set.rb +114 -0
- data/lib/system/cachecmd.rb +65 -0
- data/lib/system/cmdexec.rb +13 -0
- data/lib/system/cmdline.rb +70 -0
- data/lib/system/command/arg.rb +12 -0
- data/lib/system/command/cachefile.rb +39 -0
- data/lib/system/command/caching.rb +36 -0
- data/lib/system/command/line.rb +47 -0
- data/lib/system/command.rb +72 -0
- data/test/integration/svnx/log/test.rb +43 -0
- data/test/unit/pvn/app_test.rb +22 -0
- data/test/unit/pvn/io/element/log/log_test.rb +35 -0
- data/test/unit/pvn/log/formatter/entry_formatter_test.rb +90 -0
- data/test/unit/pvn/revision/entry_test.rb +123 -0
- data/test/unit/pvn/subcommands/log/command_test.rb +22 -0
- data/test/unit/pvn/subcommands/log/options_test.rb +85 -0
- data/test/unit/pvn/subcommands/revision/multiple_revisions_option_test.rb +67 -0
- data/test/unit/pvn/subcommands/revision/revision_option_test.rb +57 -0
- data/test/unit/pvn/subcommands/revision/revision_regexp_option_test.rb +69 -0
- data/test/unit/svnx/info/entry_test.rb +52 -0
- data/test/unit/svnx/log/cmargs_test.rb +22 -0
- data/test/unit/svnx/log/entries_test.rb +73 -0
- data/test/unit/svnx/log/entry_test.rb +21 -0
- data/test/unit/svnx/status/entry_test.rb +39 -0
- data/test/unit/synoption/base_option_test.rb +182 -0
- data/test/unit/synoption/match_test.rb +61 -0
- data/test/unit/synoption/option_test.rb +15 -0
- data/test/unit/synoption/set_test.rb +37 -0
- data/test/unit/system/command/caching_test.rb +97 -0
- data/test/unit/system/command/line_test.rb +33 -0
- metadata +199 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'riel'
|
6
|
+
require 'pvn/log/logentry'
|
7
|
+
require 'pvn/base/textlines'
|
8
|
+
|
9
|
+
module PVN; module Log; end; end
|
10
|
+
|
11
|
+
module PVN;;Log
|
12
|
+
SVN_LOG_SUMMARY_LINE_RE = Regexp.new('^r(\d+) \| (\S+) \| (\S+) (\S+) (\S+) \((.*)\) \| (\d+) lines?$')
|
13
|
+
SVN_LOG_SEPARATOR_LINE_RE = Regexp.new('^-{72}$')
|
14
|
+
SVN_LOG_VERBOSE_START_RE = Regexp.new('^Changed paths:$')
|
15
|
+
SVN_LOG_FILE_NAME_RE = Regexp.new('^ (\w) (.*)$')
|
16
|
+
|
17
|
+
class TextFactory
|
18
|
+
include Loggable
|
19
|
+
|
20
|
+
def initialize lines
|
21
|
+
@textlines = TextLines.new lines
|
22
|
+
@entries = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def entries
|
26
|
+
@entries ||= read_entries
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_entries
|
30
|
+
entries = Array.new
|
31
|
+
while entry = create_next_entry
|
32
|
+
entries << entry
|
33
|
+
end
|
34
|
+
entries
|
35
|
+
end
|
36
|
+
|
37
|
+
def match_line re, offset = 0
|
38
|
+
@textlines.match_line re, offset
|
39
|
+
end
|
40
|
+
|
41
|
+
def advance_line nlines = 1
|
42
|
+
@textlines.advance_line nlines
|
43
|
+
end
|
44
|
+
|
45
|
+
def match_log_start_line
|
46
|
+
match_line(SVN_LOG_SEPARATOR_LINE_RE) && match_line(SVN_LOG_SUMMARY_LINE_RE, 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_line?
|
50
|
+
@textlines.has_line?
|
51
|
+
end
|
52
|
+
|
53
|
+
def current_line
|
54
|
+
@textlines.line
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_comment
|
58
|
+
comment = Array.new
|
59
|
+
while has_line? && !match_line(SVN_LOG_SEPARATOR_LINE_RE)
|
60
|
+
comment << current_line.chomp
|
61
|
+
advance_line
|
62
|
+
end
|
63
|
+
comment
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_files
|
67
|
+
advance_line
|
68
|
+
files = Array.new
|
69
|
+
while (ln = current_line) && !ln.strip.empty?
|
70
|
+
md = match_line(SVN_LOG_FILE_NAME_RE)
|
71
|
+
changetype = md[1]
|
72
|
+
fname = md[2]
|
73
|
+
files << fname
|
74
|
+
advance_line
|
75
|
+
end
|
76
|
+
files
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_entry fielddata
|
80
|
+
advance_line 2
|
81
|
+
fields = Hash[Entry::FIELDS.zip fielddata[1 .. -1]]
|
82
|
+
if match_line SVN_LOG_VERBOSE_START_RE
|
83
|
+
fields[:files] = read_files
|
84
|
+
end
|
85
|
+
advance_line
|
86
|
+
fields[:comment] = read_comment
|
87
|
+
return Entry.new fields
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_next_entry
|
91
|
+
while has_line?
|
92
|
+
if fielddata = match_log_start_line
|
93
|
+
return create_entry fielddata
|
94
|
+
else
|
95
|
+
advance_line
|
96
|
+
end
|
97
|
+
end
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'synoption/set'
|
5
|
+
require 'synoption/option'
|
6
|
+
require 'synoption/boolean_option'
|
7
|
+
require 'pvn/subcommands/revision/revision_regexp_option'
|
8
|
+
|
9
|
+
module PVN
|
10
|
+
DEFAULT_LIMIT = 5
|
11
|
+
|
12
|
+
class LimitOption < Option
|
13
|
+
def initialize lmtargs = Hash.new
|
14
|
+
super :limit, '-l', "the number of log entries", DEFAULT_LIMIT, :negate => [ %r{^--no-?limit} ]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class VerboseOption < BooleanOption
|
19
|
+
def initialize optargs = Hash.new
|
20
|
+
super :verbose, '-v', [ "include the files in the change" ], false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class FormatOption < BooleanOption
|
25
|
+
def initialize optargs = Hash.new
|
26
|
+
super :format, '-f', "use the custom (colorized) format", true, :negate => [ '-F', %r{^--no-?format} ], :as_cmdline_option => nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class LogOptionSet < OptionSet
|
31
|
+
attr_accessor :revision
|
32
|
+
attr_reader :format
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
super
|
36
|
+
|
37
|
+
add LimitOption.new
|
38
|
+
@revision = add RevisionRegexpOption.new(:unsets => :limit)
|
39
|
+
@verbose = add VerboseOption.new
|
40
|
+
@format = add FormatOption.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
module PVN
|
5
|
+
class LineCount
|
6
|
+
attr_accessor :to
|
7
|
+
attr_accessor :from
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize args = Hash.new
|
11
|
+
@name = args[:name] || ""
|
12
|
+
@to = args[:to] || 0
|
13
|
+
@from = args[:from] || 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def +(other)
|
17
|
+
# the new wc has the name of the lhs of the add:
|
18
|
+
self.class.new :to => @to + other.to, :from => @from + other.from, :name => @name
|
19
|
+
end
|
20
|
+
|
21
|
+
def diff
|
22
|
+
@to - @from
|
23
|
+
end
|
24
|
+
|
25
|
+
def diffpct
|
26
|
+
@from.zero? ? "0" : 100.0 * diff / @from
|
27
|
+
end
|
28
|
+
|
29
|
+
def write
|
30
|
+
printf "%8d %8d %8d %8.1f%% %s\n", @from, @to, diff, diffpct, @name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'system/command'
|
5
|
+
require 'pvn/pct/linecount'
|
6
|
+
require 'pvn/io/fselement'
|
7
|
+
require 'pvn/svn/svnelement'
|
8
|
+
require 'pvn/svn/svnroot'
|
9
|
+
require 'pvn/io/element'
|
10
|
+
|
11
|
+
module PVN
|
12
|
+
class PctOptionSet < OptionSet
|
13
|
+
attr_accessor :revision
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
@revision = add RevisionRegexpOption.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class PctCommand < Command
|
22
|
+
DEFAULT_LIMIT = 5
|
23
|
+
COMMAND = "pct"
|
24
|
+
|
25
|
+
self.doc do |doc|
|
26
|
+
doc.subcommands = [ COMMAND ]
|
27
|
+
doc.description = "Print the comparison of size of locally-changed files."
|
28
|
+
doc.usage = "[OPTIONS] FILE..."
|
29
|
+
doc.summary = [ "Prints the changes in the size (length) of files that have ",
|
30
|
+
"been changed locally, to show the extent to which they have",
|
31
|
+
"increased or decreased. The columns are:",
|
32
|
+
" - length in svn repository",
|
33
|
+
" - length in local version",
|
34
|
+
" - difference",
|
35
|
+
" - percentage change",
|
36
|
+
" - file name",
|
37
|
+
"When more than one file is specified, the total is displayed",
|
38
|
+
"as the last line." ]
|
39
|
+
doc.examples << [ "pvn pct main.c", "Prints the number of lines and percentage by which main.c has changed." ]
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize args
|
43
|
+
# not calling super, since all of our stuff goes on in this ctor.
|
44
|
+
|
45
|
+
info "args: #{args}"
|
46
|
+
|
47
|
+
@options = PctOptionSet.new
|
48
|
+
|
49
|
+
@options.process self, args, args[:command_args]
|
50
|
+
info "args: #{args}"
|
51
|
+
|
52
|
+
files = get_files @options.arguments
|
53
|
+
info "files: #{files.inspect}"
|
54
|
+
|
55
|
+
rev = @options.revision.value
|
56
|
+
info "rev: #{rev}"
|
57
|
+
|
58
|
+
@output = Array.new
|
59
|
+
|
60
|
+
totallc = LineCount.new :name => "total"
|
61
|
+
|
62
|
+
files.sort.each do |file|
|
63
|
+
info "file: #{file}"
|
64
|
+
|
65
|
+
fromlc, tolc = if rev
|
66
|
+
[ file.svn.line_count(rev - 1), file.svn.line_count(rev) ]
|
67
|
+
else
|
68
|
+
[ file.svn.line_count(rev), file.local.line_count ]
|
69
|
+
end
|
70
|
+
|
71
|
+
info "fromlc: #{fromlc}"
|
72
|
+
info "tolc: #{tolc}"
|
73
|
+
|
74
|
+
filelc = LineCount.new :from => fromlc, :to => tolc, :name => file.local.name
|
75
|
+
filelc.write
|
76
|
+
|
77
|
+
totallc += filelc
|
78
|
+
info "totallc: #{totallc.inspect}"
|
79
|
+
end
|
80
|
+
|
81
|
+
if files.size > 1
|
82
|
+
totallc.write
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_command subcmd, *args
|
87
|
+
cmd = "svn #{subcmd}"
|
88
|
+
info "cmd: #{cmd}"
|
89
|
+
info "args: #{args}"
|
90
|
+
args = args.flatten
|
91
|
+
|
92
|
+
revcl = @options.revision.to_command_line
|
93
|
+
if revcl
|
94
|
+
cmd << " " << revcl.join(" ")
|
95
|
+
end
|
96
|
+
cmd << " " << Util::quote_list(args)
|
97
|
+
info "cmd: #{cmd}".on_blue
|
98
|
+
cmd
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_changed_files filespec
|
102
|
+
rev = @options.revision.value
|
103
|
+
info "rev: #{rev}"
|
104
|
+
|
105
|
+
if rev
|
106
|
+
get_changed_files_revision filespec
|
107
|
+
else
|
108
|
+
get_changed_files_wc filespec
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def svn_fullname_to_local_file svnname
|
113
|
+
svnelement = SVNElement.new :name => '.'
|
114
|
+
info "svnelement.info: #{svnelement.info}"
|
115
|
+
|
116
|
+
fullurl = svnelement.info[:repository_root] + svnname
|
117
|
+
info "fullurl : #{fullurl}".yellow
|
118
|
+
|
119
|
+
svnroot = SVNRootElement.new
|
120
|
+
info "svnroot : #{svnroot}".yellow
|
121
|
+
|
122
|
+
info "svnroot.info : #{svnroot.info}".yellow
|
123
|
+
|
124
|
+
here = SVNElement.new :name => '.'
|
125
|
+
|
126
|
+
hereinfo = here.info
|
127
|
+
info "hereinfo[:url] : #{hereinfo[:url]}".yellow
|
128
|
+
reporoot = hereinfo[:repository_root]
|
129
|
+
|
130
|
+
info "reporoot : #{reporoot}".yellow
|
131
|
+
|
132
|
+
info "svnroot[:url] : #{svnroot.info[:url]}".yellow
|
133
|
+
|
134
|
+
rootpath = svnroot.name
|
135
|
+
rooturl = svnroot.info[:url]
|
136
|
+
|
137
|
+
info "rootpath : #{rootpath}".yellow
|
138
|
+
info "rooturl : #{rooturl}".yellow
|
139
|
+
|
140
|
+
if fullurl.index(rooturl) == 0
|
141
|
+
localname = fullurl[rooturl.length .. -1]
|
142
|
+
info "localname : #{localname}".yellow
|
143
|
+
localname
|
144
|
+
else
|
145
|
+
info "fullurl : #{fullurl}".on_red
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_changed_files_revision filespec
|
152
|
+
lc = LogCommand.new :revision => @options.revision.value, :verbose => true
|
153
|
+
info "lc: #{lc}"
|
154
|
+
entry = lc.entries[0]
|
155
|
+
return Array.new if entry.nil?
|
156
|
+
|
157
|
+
# entry.write true
|
158
|
+
|
159
|
+
files = entry.files.collect do |svnfile|
|
160
|
+
localfile = svn_fullname_to_local_file svnfile
|
161
|
+
Element.new :file => localfile
|
162
|
+
end
|
163
|
+
info "files: #{files}".yellow
|
164
|
+
files
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_changed_files_wc filespec
|
168
|
+
files = Array.new
|
169
|
+
cmd = to_command "st", filespec
|
170
|
+
IO.popen cmd do |io|
|
171
|
+
io.each do |line|
|
172
|
+
info "line: #{line}".yellow
|
173
|
+
next if line.index %r{^\?}
|
174
|
+
pn = Element.new :file => line.chomp[8 .. -1]
|
175
|
+
files << pn if pn.local.file?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
info "files: #{files}".red
|
179
|
+
files
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_files fileargs
|
183
|
+
info "fileargs: #{fileargs}".yellow
|
184
|
+
|
185
|
+
files = Array.new
|
186
|
+
if fileargs.empty?
|
187
|
+
files = get_changed_files fileargs
|
188
|
+
else
|
189
|
+
fileargs.collect do |fn|
|
190
|
+
elmt = Element.new :file => fn
|
191
|
+
if elmt.local.directory?
|
192
|
+
files.concat get_changed_files(fn)
|
193
|
+
else
|
194
|
+
files << elmt
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
info "files: #{files}".yellow
|
200
|
+
|
201
|
+
files
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'svnx/log/entries'
|
5
|
+
|
6
|
+
module PVN::Revision
|
7
|
+
DATE_REGEXP = Regexp.new '^\{(.*?)\}'
|
8
|
+
SVN_REVISION_WORDS = %w{ HEAD BASE COMMITTED PREV }
|
9
|
+
RELATIVE_REVISION_RE = Regexp.new '^([\+\-])(\d+)$'
|
10
|
+
|
11
|
+
# Returns the Nth revision from the given logging output.
|
12
|
+
|
13
|
+
# -n means to count from the end of the list.
|
14
|
+
# +n means to count from the beginning of the list.
|
15
|
+
# n means the literal revision number.
|
16
|
+
class Entry
|
17
|
+
include Loggable
|
18
|
+
|
19
|
+
attr_reader :value
|
20
|
+
attr_reader :log_entry
|
21
|
+
|
22
|
+
class << self
|
23
|
+
alias_method :orig_new, :new
|
24
|
+
|
25
|
+
def new args = Hash.new
|
26
|
+
value = args[:value]
|
27
|
+
|
28
|
+
# these are lines from "svn log -v <file>"
|
29
|
+
xmllines = args[:xmllines]
|
30
|
+
if xmllines.kind_of? Array
|
31
|
+
xmllines = xmllines.join ''
|
32
|
+
end
|
33
|
+
|
34
|
+
case value
|
35
|
+
when Fixnum
|
36
|
+
if value < 0
|
37
|
+
RelativeEntry.orig_new value, xmllines
|
38
|
+
else
|
39
|
+
FixnumEntry.orig_new value
|
40
|
+
end
|
41
|
+
when String
|
42
|
+
if SVN_REVISION_WORDS.include? value
|
43
|
+
StringEntry.orig_new value
|
44
|
+
elsif md = RELATIVE_REVISION_RE.match(value)
|
45
|
+
RelativeEntry.orig_new md[0].to_i, xmllines
|
46
|
+
elsif DATE_REGEXP.match value
|
47
|
+
StringEntry.orig_new value
|
48
|
+
else
|
49
|
+
FixnumEntry.orig_new value.to_i
|
50
|
+
end
|
51
|
+
when Date
|
52
|
+
# $$$ this (and Time) will probably have to be converted to svn's format
|
53
|
+
raise "date not yet handled"
|
54
|
+
when Time
|
55
|
+
raise "time not yet handled"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def matches_relative? str
|
60
|
+
RELATIVE_REVISION_RE.match str
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize value
|
65
|
+
@value = value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FixnumEntry < Entry
|
70
|
+
end
|
71
|
+
|
72
|
+
class StringEntry < Entry
|
73
|
+
end
|
74
|
+
|
75
|
+
class RelativeEntry < FixnumEntry
|
76
|
+
def initialize value, xmllines
|
77
|
+
raise "cannot determine relative revision without xmllines" unless xmllines
|
78
|
+
|
79
|
+
logentries = SVNx::Log::Entries.new :xmllines => xmllines
|
80
|
+
|
81
|
+
nentries = logentries.size
|
82
|
+
|
83
|
+
# logentries are in descending order, so the most recent one is index 0
|
84
|
+
|
85
|
+
if value.abs > nentries
|
86
|
+
raise "ERROR: no entry for revision: #{value.abs}; number of entries: #{nentries}"
|
87
|
+
else
|
88
|
+
idx = value < 0 ? -1 + value.abs : nentries - value
|
89
|
+
@log_entry = logentries[idx]
|
90
|
+
super @log_entry.revision.to_i
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/pvn/revision.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'riel'
|
6
|
+
require 'pvn/log/logcmd'
|
7
|
+
|
8
|
+
module PVN
|
9
|
+
# Returns the Nth revision from the given logging output.
|
10
|
+
|
11
|
+
# -n means to count from the end of the list.
|
12
|
+
# +n means to count from the beginning of the list.
|
13
|
+
# n means the literal revision number.
|
14
|
+
class Revision
|
15
|
+
include Loggable
|
16
|
+
|
17
|
+
DATE_REGEXP = Regexp.new('^\{(.*?)\}')
|
18
|
+
SVN_REVISION_WORDS = %w{ HEAD BASE COMMITTED PREV }
|
19
|
+
|
20
|
+
def self.revision_from_args optset, optargs
|
21
|
+
revarg = optargs.shift
|
22
|
+
RIEL::Log.debug "revarg: #{revarg}"
|
23
|
+
RIEL::Log.debug "optargs: #{optargs}"
|
24
|
+
|
25
|
+
rev = Revision.new(:fname => optset.arguments[0], :value => revarg, :use_cache => false).revision
|
26
|
+
RIEL::Log.debug "rev: #{rev}"
|
27
|
+
|
28
|
+
if rev.nil?
|
29
|
+
raise ArgumentError.new "invalid revision: #{revarg} on #{optargs[-1]}"
|
30
|
+
end
|
31
|
+
rev
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :revision
|
35
|
+
|
36
|
+
def initialize args = Hash.new
|
37
|
+
debug "args : #{args}".yellow
|
38
|
+
value = args[:value]
|
39
|
+
num = value.kind_of?(String) ? value.strip : value
|
40
|
+
debug "num : #{num}".yellow
|
41
|
+
@fname = args[:fname]
|
42
|
+
debug "fname : #{@fname}".yellow
|
43
|
+
|
44
|
+
@revision = nil
|
45
|
+
|
46
|
+
from, to = num.kind_of?(String) ? num.split(':') : num
|
47
|
+
info "from: #{from}; to: #{to}".on_blue
|
48
|
+
info "from: #{from.class}; to: #{to.class}".on_blue
|
49
|
+
|
50
|
+
@revision = convert_to_revision from
|
51
|
+
if to
|
52
|
+
@revision += ':' + convert_to_revision(to)
|
53
|
+
end
|
54
|
+
|
55
|
+
info "@revision: #{@revision}; #{@revision.class}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
@revision
|
60
|
+
end
|
61
|
+
|
62
|
+
def convert_to_revision arg
|
63
|
+
if SVN_REVISION_WORDS.include? arg
|
64
|
+
info "word: #{arg}"
|
65
|
+
arg
|
66
|
+
elsif neg = Integer.negative?(arg)
|
67
|
+
info "neg: #{arg}"
|
68
|
+
get_negative_revision neg
|
69
|
+
elsif arg.kind_of? Fixnum
|
70
|
+
info "fixnum: #{arg}"
|
71
|
+
arg.to_s
|
72
|
+
elsif md = DATE_REGEXP.match(arg)
|
73
|
+
info "md: #{md.inspect}".on_red
|
74
|
+
md[0]
|
75
|
+
elsif md = %r{^\+(\d+)}.match(arg)
|
76
|
+
arg = md[1].to_i
|
77
|
+
debug "arg: #{arg}"
|
78
|
+
get_revision @fname, nil, arg
|
79
|
+
else
|
80
|
+
arg.to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_negative_revision neg
|
85
|
+
debug "neg : #{neg}"
|
86
|
+
|
87
|
+
# if these two are the same number (revision(-3) == revision(-2)) then
|
88
|
+
# we're at the end/beginning of the list.
|
89
|
+
currrev = get_negative_revision_unchecked neg
|
90
|
+
if neg == -1
|
91
|
+
debug "currrev: #{currrev}"
|
92
|
+
@revision = currrev
|
93
|
+
else
|
94
|
+
debug "neg : #{neg}"
|
95
|
+
prevrev = get_negative_revision_unchecked neg + 1
|
96
|
+
|
97
|
+
debug "prevrev: #{prevrev}"
|
98
|
+
debug "currrev: #{currrev}"
|
99
|
+
|
100
|
+
# $$$ does -r 123 -l 2 == -r 123 -l 3?
|
101
|
+
@revision = prevrev == currrev ? nil : currrev
|
102
|
+
# @revision = prevrev == currrev ? currrev : currrev
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_negative_revision_unchecked neg
|
107
|
+
# count the limit backward, and get the "first" (last) match
|
108
|
+
limit = -1 * neg
|
109
|
+
get_revision @fname, limit, 1
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_revision fname, limit, num
|
113
|
+
logcmd = LogCommand.new :limit => limit, :command_args => [ fname ]
|
114
|
+
entries = logcmd.entries
|
115
|
+
entry = entries[-1 * num]
|
116
|
+
entry && entry.revision.to_i
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
module PVN; module App; end; end
|
5
|
+
|
6
|
+
module PVN::App::Base
|
7
|
+
class CmdLineArgs
|
8
|
+
include Loggable
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def has_option name
|
12
|
+
define_method name do
|
13
|
+
info "name: #{name}"
|
14
|
+
self.instance_eval do
|
15
|
+
meth = name
|
16
|
+
info "meth: #{meth}"
|
17
|
+
val = @optset.send name
|
18
|
+
info "val: #{val}"
|
19
|
+
val
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :unprocessed
|
26
|
+
|
27
|
+
def initialize optset, args
|
28
|
+
@optset = optset
|
29
|
+
process args
|
30
|
+
@unprocessed = args
|
31
|
+
end
|
32
|
+
|
33
|
+
def process args
|
34
|
+
options_processed = Array.new
|
35
|
+
|
36
|
+
while !args.empty?
|
37
|
+
processed = false
|
38
|
+
@optset.options.each do |opt|
|
39
|
+
if opt.process args
|
40
|
+
processed = true
|
41
|
+
options_processed << opt
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
break unless processed
|
46
|
+
end
|
47
|
+
|
48
|
+
options_processed.each do |opt|
|
49
|
+
opt.post_process @optset, args
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|