portvcs 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.rdoc +104 -0
- data/Rakefile +43 -0
- data/bin/portvcs +164 -0
- data/doc/LICENSE +22 -0
- data/doc/README.rdoc +104 -0
- data/doc/TODO +2 -0
- data/etc/fr.FreeBSD.org.yaml +7 -0
- data/etc/portvcs.yaml +7 -0
- data/lib/portvcs/meta.rb +6 -0
- data/lib/portvcs/portvcs.rb +180 -0
- data/lib/portvcs/utils.rb +88 -0
- data/test/helper.rb +45 -0
- data/test/ports/category/program/Makefile +0 -0
- data/test/ports/category/program/files/patch-aa +0 -0
- data/test/ports/category/program/pkg-descr +0 -0
- data/test/semis/cvstest/portvcs/Makefile +2 -0
- data/test/semis/localhost_cvs.yaml +7 -0
- data/test/semis/localhost_cvs_incomplete.yaml +7 -0
- data/test/test_cvsdumb.rb +39 -0
- data/test/test_utils.rb +68 -0
- metadata +103 -0
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
=Name
|
2
|
+
|
3
|
+
portvcs--FreeBSD ports commits history viewer that doesn't require
|
4
|
+
neither local ports tree nor CVS checkouts.
|
5
|
+
|
6
|
+
|
7
|
+
==Synopsis
|
8
|
+
|
9
|
+
portvcs [options] category/port
|
10
|
+
|
11
|
+
|
12
|
+
==Description
|
13
|
+
|
14
|
+
The portvcs utility connects to a remote VCS (Version Control System)
|
15
|
+
server and grabs the history of commits.
|
16
|
+
|
17
|
+
At the time of writing FreeBSD uses CVS (jeez) for its ports collection,
|
18
|
+
so portvcs (with default configuration) connects to well-known French
|
19
|
+
CVS server via TCP port 2401 and reads the logs of a particular port.
|
20
|
+
By default the utility constrains the history to 1 year but that's easy
|
21
|
+
changeable via +-d+ CL (command line) option.
|
22
|
+
|
23
|
+
portvcs isn't restricted to FreeBSD ports--it can read logs from
|
24
|
+
(probably) any CVS server but in that case its usefulness is very
|
25
|
+
doubtful.
|
26
|
+
|
27
|
+
The options are as follows:
|
28
|
+
|
29
|
+
--ports-tree-local DIR:: Set this if you have the ports tree
|
30
|
+
directory in an unusual place.
|
31
|
+
|
32
|
+
--config-dirs:: List all possible locations for the
|
33
|
+
configuration file. The first found wins.
|
34
|
+
|
35
|
+
--config NAME:: The name of the configuration file. If
|
36
|
+
it contains +/+ in it, the list from
|
37
|
+
<tt>\--config-dirs</tt> is ignored.
|
38
|
+
|
39
|
+
-d EXP:: Passes the EXP to VCS server to set limits
|
40
|
+
for the logs. Usually it's something like
|
41
|
+
'2 months ago<'. For all possible values,
|
42
|
+
read the help for the option +-d+ in cvs
|
43
|
+
help for its log command, e.g, type:
|
44
|
+
|
45
|
+
% info -n '(cvs)log options'
|
46
|
+
|
47
|
+
--host STR:: A hostname of a remote server.
|
48
|
+
|
49
|
+
--port N:: A port number (default is 2401) of the
|
50
|
+
remote server.
|
51
|
+
|
52
|
+
--user STR:: A user name on the remote server.
|
53
|
+
|
54
|
+
--pass STR:: A password for the user on the remote
|
55
|
+
server.
|
56
|
+
|
57
|
+
--cvsroot DIR:: CVSROOT on a remote server.
|
58
|
+
|
59
|
+
--ports-tree DIR:: A directory that follows CVSROOT and serves
|
60
|
+
as a ports tree. For example, CVSROOT can
|
61
|
+
be +/home/ncvs+ and the ports directory can
|
62
|
+
be +ports+ or may be empty at all.
|
63
|
+
|
64
|
+
--vcs-version:: Just get version of a remote VCS server and
|
65
|
+
exit.
|
66
|
+
|
67
|
+
--cvs-pass-scramble STR:: A handy option for encrypting...
|
68
|
+
--cvs-pass-descramble STR:: ... and decrypting CVS passwords.
|
69
|
+
|
70
|
+
-V:: Show portvcs version and exit.
|
71
|
+
|
72
|
+
-v:: Be more verbose. You can supply it several
|
73
|
+
times, viz. +-vv+ dumps even more debug info.
|
74
|
+
|
75
|
+
==Configuration
|
76
|
+
|
77
|
+
portvcs looks for its configuration at 3 places at start up.
|
78
|
+
|
79
|
+
1. At <tt>PORTVCS_CONF</tt> env variable. (Its format is exactly similar
|
80
|
+
to CL options.)
|
81
|
+
|
82
|
+
2. At configuration file. Its default name is <tt>portvcs.yaml</tt> and
|
83
|
+
it can be stored in several system directories which are observable
|
84
|
+
by <tt>--config--dirs</tt> CL option.
|
85
|
+
|
86
|
+
3. At command line.
|
87
|
+
|
88
|
+
Higher number levels overrides the values from lower number levels.
|
89
|
+
|
90
|
+
The configuration file must be in YAML format. Look into <tt>`gem env
|
91
|
+
gemdir`/gems/portvcs-x.y.z/etc/</tt> directory for samples.
|
92
|
+
|
93
|
+
|
94
|
+
==Examples
|
95
|
+
|
96
|
+
% portvcs www/firefox
|
97
|
+
% portvcs /usr/ports/www/firefox
|
98
|
+
% portvcs www/firefox/files/patch-configure.in
|
99
|
+
|
100
|
+
% pwd
|
101
|
+
/usr/ports/x11-toolkits/gtk20
|
102
|
+
% portvcs Makefile
|
103
|
+
% portvcs files/patch-gtk_gtksignal.h
|
104
|
+
% portvcs -d '3 month ago<' .
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*-ruby-*-
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
spec = Gem::Specification.new() {|i|
|
10
|
+
i.name = "portvcs"
|
11
|
+
i.version = `bin/#{i.name} -V`
|
12
|
+
i.summary = "FreeBSD ports commits history viewer that doesn't require neither local ports tree nor CVS checkouts."
|
13
|
+
i.author = 'Alexander Gromnitsky'
|
14
|
+
i.email = 'alexander.gromnitsky@gmail.com'
|
15
|
+
i.homepage = "http://github.com/gromnitsky/#{i.name}"
|
16
|
+
i.platform = Gem::Platform::RUBY
|
17
|
+
i.required_ruby_version = '>= 1.9.2'
|
18
|
+
i.files = FileList['lib/**/*', 'bin/*', 'doc/*', 'etc/*', '[A-Z]*', 'test/**/*']
|
19
|
+
|
20
|
+
i.executables = FileList['bin/*'].gsub(/^bin\//, '')
|
21
|
+
i.default_executable = i.name
|
22
|
+
|
23
|
+
i.test_files = FileList['test/test_*.rb']
|
24
|
+
|
25
|
+
i.rdoc_options << '-m' << 'doc/README.rdoc'
|
26
|
+
i.extra_rdoc_files = FileList['doc/*']
|
27
|
+
|
28
|
+
i.add_development_dependency('open4', '>= 1.0.1')
|
29
|
+
}
|
30
|
+
|
31
|
+
Rake::GemPackageTask.new(spec).define()
|
32
|
+
|
33
|
+
task(default: %(repackage))
|
34
|
+
|
35
|
+
Rake::RDocTask.new('doc') {|i|
|
36
|
+
i.main = 'doc/README.rdoc'
|
37
|
+
i.rdoc_files = FileList['doc/*', 'lib/**/*.rb']
|
38
|
+
i.rdoc_files.exclude("lib/**/plugins")
|
39
|
+
}
|
40
|
+
|
41
|
+
Rake::TestTask.new() {|i|
|
42
|
+
i.test_files = FileList['test/test_*.rb']
|
43
|
+
}
|
data/bin/portvcs
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*-ruby-*-
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'optparse'
|
6
|
+
require 'yaml'
|
7
|
+
require 'shellwords.rb'
|
8
|
+
|
9
|
+
require_relative '../lib/portvcs/portvcs.rb'
|
10
|
+
require_relative '../lib/portvcs/utils.rb'
|
11
|
+
|
12
|
+
include PortVCS
|
13
|
+
|
14
|
+
$conf = {
|
15
|
+
verbose: 0,
|
16
|
+
banner: "Usage: #{File.basename($0)} [options] category/port",
|
17
|
+
|
18
|
+
vcs: 'cvs',
|
19
|
+
vcs_legal: ['cvs'], # TODO: get this array automatically
|
20
|
+
host: '',
|
21
|
+
port: 2401,
|
22
|
+
user: '',
|
23
|
+
cvsroot: '',
|
24
|
+
pass: '',
|
25
|
+
ports_tree: '',
|
26
|
+
|
27
|
+
date_cond: "#{(DateTime.now - 366).strftime('%d %b %Y')}<",
|
28
|
+
|
29
|
+
config: 'portvcs.yaml',
|
30
|
+
config_dirs: [File.absolute_path("#{File.dirname(File.expand_path($0))}/../etc"),
|
31
|
+
'/etc', '/usr/etc', '/usr/local/etc',
|
32
|
+
"#{Gem.dir}/gems/#{Meta::NAME}-#{Meta::VERSION}/etc"],
|
33
|
+
|
34
|
+
mode: 'log',
|
35
|
+
ports_tree_local: '/usr/ports'
|
36
|
+
}
|
37
|
+
|
38
|
+
def veputs(level, t)
|
39
|
+
puts(t) if $conf[:verbose] >= level
|
40
|
+
end
|
41
|
+
|
42
|
+
def errx(ec, t)
|
43
|
+
STDERR.puts File.basename($0) + ' error: ' + t.to_s
|
44
|
+
exit ec if ec > 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def cl_parse(myargs, times = 1)
|
48
|
+
o = OptionParser.new()
|
49
|
+
o.banner = $conf[:banner]
|
50
|
+
o.on('-V', 'Show the program version') {
|
51
|
+
puts Meta::VERSION
|
52
|
+
exit 0
|
53
|
+
}
|
54
|
+
o.on('--cvs-pass-scramble STR', 'Handy encoding of CVS passwords') { |v|
|
55
|
+
puts CVSDumb.pass_scramble(v)
|
56
|
+
exit 0
|
57
|
+
}
|
58
|
+
o.on('--cvs-pass-descramble STR') { |v|
|
59
|
+
puts CVSDumb.pass_descramble(v)
|
60
|
+
exit 0
|
61
|
+
}
|
62
|
+
o.on('--config NAME', "Set a config name (default is #{$conf[:config]})") {|v|
|
63
|
+
$conf[:config] = v
|
64
|
+
}
|
65
|
+
o.on('--config-dirs', 'Show possible config locations') {
|
66
|
+
$conf[:config_dirs].each { |i|
|
67
|
+
f = i + '/' + $conf[:config]
|
68
|
+
puts (File.readable?(f) ? '* ' : ' ') + f
|
69
|
+
}
|
70
|
+
exit 0
|
71
|
+
}
|
72
|
+
o.on('--ports-tree-local DIR', "(Default is #{$conf[:ports_tree_local]})") { |v| $conf[:ports_tree_local] = v }
|
73
|
+
o.on('-v', 'Be more verbose') {
|
74
|
+
# protect variable from incrementing in a second parse round
|
75
|
+
$conf[:verbose] += 1 if times <= 1
|
76
|
+
}
|
77
|
+
o.on('--host STR') { |v| $conf[:host] = v }
|
78
|
+
o.on('--port N', Integer, "(default is #{$conf[:port]})") { |v| $conf[:port] = v }
|
79
|
+
o.on('--user STR') { |v| $conf[:user] = v }
|
80
|
+
o.on('--pass STR') { |v| $conf[:pass] = v }
|
81
|
+
o.on('--ports-tree DIR') { |v| $conf[:ports_tree] = v }
|
82
|
+
o.on('--cvsroot DIR') { |v| $conf[:cvsroot] = v }
|
83
|
+
o.on('-d EXP', "Date condition, for example '1 Jan 2010<'") { |v| $conf[:date_cond] = v }
|
84
|
+
o.on('--vcs TYPE', $conf[:vcs_legal], "Select version control system (#{$conf[:vcs_legal].join(', ')})") { |v| $conf[:vcs] = v }
|
85
|
+
o.on('--vcs-version', 'Get version of a remote VCS server') { $conf[:mode] = 'version' }
|
86
|
+
|
87
|
+
begin
|
88
|
+
o.parse!(myargs)
|
89
|
+
rescue
|
90
|
+
errx(1, $!)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def pager_setup
|
95
|
+
[ENV['PAGER'], 'less', 'more'].compact.uniq.each {|i|
|
96
|
+
next unless Utils.in_path?(i.split.first)
|
97
|
+
io = IO.popen(i, "w") rescue next
|
98
|
+
next if $? and $?.exited? # pager didn't work
|
99
|
+
veputs(2, "Pager: #{i}")
|
100
|
+
return io
|
101
|
+
}
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# --[ main ]------------------------------------------------------------
|
107
|
+
|
108
|
+
# 1. parse env var
|
109
|
+
if ENV.key?(Meta::NAME.upcase + '_CONF')
|
110
|
+
cl_parse ENV[Meta::NAME.upcase + '_CONF'].shellsplit
|
111
|
+
end
|
112
|
+
|
113
|
+
# 2. parse CL in case of --config option
|
114
|
+
argv1 = ARGV.dup
|
115
|
+
cl_parse argv1
|
116
|
+
|
117
|
+
# 3. load config & final CL parse
|
118
|
+
begin
|
119
|
+
r = Utils.config_load($conf, $conf[:config], $conf[:config_dirs])
|
120
|
+
rescue
|
121
|
+
errx(1, "cannot load config: #{$!}")
|
122
|
+
end
|
123
|
+
veputs(1, "Loaded config: #{r}")
|
124
|
+
cl_parse ARGV, 2
|
125
|
+
|
126
|
+
abort($conf[:banner]) if (ARGV.size == 0 || ARGV[0] =~ /^\s*$/) && $conf[:mode] == 'log'
|
127
|
+
|
128
|
+
# print our env
|
129
|
+
if $conf[:verbose] >= 1
|
130
|
+
puts 'Libs dir: '+Utils.gem_libdir
|
131
|
+
pp $conf
|
132
|
+
end
|
133
|
+
|
134
|
+
# 4. connect to remote VCS
|
135
|
+
v = VCS.new($conf[:vcs])
|
136
|
+
begin
|
137
|
+
v.open(debug: ($conf[:verbose] >= 2 ? true : false), host: $conf[:host], cvsroot: $conf[:cvsroot],
|
138
|
+
port: $conf[:port], user: $conf[:user], pass: $conf[:pass],
|
139
|
+
ports_tree: $conf[:ports_tree]) { |vp|
|
140
|
+
|
141
|
+
case $conf[:mode]
|
142
|
+
when 'version'
|
143
|
+
puts vp.version
|
144
|
+
else
|
145
|
+
if !(p = Utils.port_extract_name(ARGV[0].dup.strip, $conf[:ports_tree_local]))
|
146
|
+
errx(1, "invalid port name")
|
147
|
+
end
|
148
|
+
p[1] = 'Makefile' if !p[1]
|
149
|
+
|
150
|
+
out = pager_setup() || STDOUT # get output stream
|
151
|
+
vp.log(p[0], p[1], $conf[:date_cond]) {|i|
|
152
|
+
begin
|
153
|
+
out.puts i
|
154
|
+
rescue Errno::EPIPE
|
155
|
+
out = STDOUT
|
156
|
+
retry
|
157
|
+
end
|
158
|
+
}
|
159
|
+
out.close unless out == STDOUT
|
160
|
+
end
|
161
|
+
}
|
162
|
+
rescue
|
163
|
+
errx(1, $!)
|
164
|
+
end
|
data/doc/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2010 Alexander Gromnitsky.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/doc/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
=Name
|
2
|
+
|
3
|
+
portvcs--FreeBSD ports commits history viewer that doesn't require
|
4
|
+
neither local ports tree nor CVS checkouts.
|
5
|
+
|
6
|
+
|
7
|
+
==Synopsis
|
8
|
+
|
9
|
+
portvcs [options] category/port
|
10
|
+
|
11
|
+
|
12
|
+
==Description
|
13
|
+
|
14
|
+
The portvcs utility connects to a remote VCS (Version Control System)
|
15
|
+
server and grabs the history of commits.
|
16
|
+
|
17
|
+
At the time of writing FreeBSD uses CVS (jeez) for its ports collection,
|
18
|
+
so portvcs (with default configuration) connects to well-known French
|
19
|
+
CVS server via TCP port 2401 and reads the logs of a particular port.
|
20
|
+
By default the utility constrains the history to 1 year but that's easy
|
21
|
+
changeable via +-d+ CL (command line) option.
|
22
|
+
|
23
|
+
portvcs isn't restricted to FreeBSD ports--it can read logs from
|
24
|
+
(probably) any CVS server but in that case its usefulness is very
|
25
|
+
doubtful.
|
26
|
+
|
27
|
+
The options are as follows:
|
28
|
+
|
29
|
+
--ports-tree-local DIR:: Set this if you have the ports tree
|
30
|
+
directory in an unusual place.
|
31
|
+
|
32
|
+
--config-dirs:: List all possible locations for the
|
33
|
+
configuration file. The first found wins.
|
34
|
+
|
35
|
+
--config NAME:: The name of the configuration file. If
|
36
|
+
it contains +/+ in it, the list from
|
37
|
+
<tt>\--config-dirs</tt> is ignored.
|
38
|
+
|
39
|
+
-d EXP:: Passes the EXP to VCS server to set limits
|
40
|
+
for the logs. Usually it's something like
|
41
|
+
'2 months ago<'. For all possible values,
|
42
|
+
read the help for the option +-d+ in cvs
|
43
|
+
help for its log command, e.g, type:
|
44
|
+
|
45
|
+
% info -n '(cvs)log options'
|
46
|
+
|
47
|
+
--host STR:: A hostname of a remote server.
|
48
|
+
|
49
|
+
--port N:: A port number (default is 2401) of the
|
50
|
+
remote server.
|
51
|
+
|
52
|
+
--user STR:: A user name on the remote server.
|
53
|
+
|
54
|
+
--pass STR:: A password for the user on the remote
|
55
|
+
server.
|
56
|
+
|
57
|
+
--cvsroot DIR:: CVSROOT on a remote server.
|
58
|
+
|
59
|
+
--ports-tree DIR:: A directory that follows CVSROOT and serves
|
60
|
+
as a ports tree. For example, CVSROOT can
|
61
|
+
be +/home/ncvs+ and the ports directory can
|
62
|
+
be +ports+ or may be empty at all.
|
63
|
+
|
64
|
+
--vcs-version:: Just get version of a remote VCS server and
|
65
|
+
exit.
|
66
|
+
|
67
|
+
--cvs-pass-scramble STR:: A handy option for encrypting...
|
68
|
+
--cvs-pass-descramble STR:: ... and decrypting CVS passwords.
|
69
|
+
|
70
|
+
-V:: Show portvcs version and exit.
|
71
|
+
|
72
|
+
-v:: Be more verbose. You can supply it several
|
73
|
+
times, viz. +-vv+ dumps even more debug info.
|
74
|
+
|
75
|
+
==Configuration
|
76
|
+
|
77
|
+
portvcs looks for its configuration at 3 places at start up.
|
78
|
+
|
79
|
+
1. At <tt>PORTVCS_CONF</tt> env variable. (Its format is exactly similar
|
80
|
+
to CL options.)
|
81
|
+
|
82
|
+
2. At configuration file. Its default name is <tt>portvcs.yaml</tt> and
|
83
|
+
it can be stored in several system directories which are observable
|
84
|
+
by <tt>--config--dirs</tt> CL option.
|
85
|
+
|
86
|
+
3. At command line.
|
87
|
+
|
88
|
+
Higher number levels overrides the values from lower number levels.
|
89
|
+
|
90
|
+
The configuration file must be in YAML format. Look into <tt>`gem env
|
91
|
+
gemdir`/gems/portvcs-x.y.z/etc/</tt> directory for samples.
|
92
|
+
|
93
|
+
|
94
|
+
==Examples
|
95
|
+
|
96
|
+
% portvcs www/firefox
|
97
|
+
% portvcs /usr/ports/www/firefox
|
98
|
+
% portvcs www/firefox/files/patch-configure.in
|
99
|
+
|
100
|
+
% pwd
|
101
|
+
/usr/ports/x11-toolkits/gtk20
|
102
|
+
% portvcs Makefile
|
103
|
+
% portvcs files/patch-gtk_gtksignal.h
|
104
|
+
% portvcs -d '3 month ago<' .
|
data/doc/TODO
ADDED
data/etc/portvcs.yaml
ADDED
data/lib/portvcs/meta.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# :include: ../../README.rdoc
|
4
|
+
module PortVCS
|
5
|
+
|
6
|
+
# A wrapper for *Dumb classes
|
7
|
+
class VCS
|
8
|
+
|
9
|
+
def self.select(type)
|
10
|
+
case type
|
11
|
+
when 'cvs'
|
12
|
+
return CVSDumb
|
13
|
+
else
|
14
|
+
fail "unknown VCS '#{type}'"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# sys -- a class like CVSDumb
|
19
|
+
def initialize(sys)
|
20
|
+
@sys = VCS.select(sys)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :sys
|
24
|
+
|
25
|
+
def open(p)
|
26
|
+
s1 = s2 = @sys.new(p)
|
27
|
+
if block_given?
|
28
|
+
begin
|
29
|
+
s1 = yield(s2)
|
30
|
+
rescue
|
31
|
+
fail $!
|
32
|
+
ensure
|
33
|
+
s2.close
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
return s1
|
38
|
+
end
|
39
|
+
|
40
|
+
end # VCS
|
41
|
+
|
42
|
+
class CVSDumb
|
43
|
+
# debug print prefixes
|
44
|
+
CLIENT = 'C: '
|
45
|
+
SERVER = 'S: '
|
46
|
+
|
47
|
+
PASSPAIR = {
|
48
|
+
?! => 120, ?" => 53, ?% => 109, ?& => 72, ?' => 108, ?( => 70, ?) => 64,
|
49
|
+
?* => 76, ?+ => 67, ?, => 116, ?- => 74, ?. => 68, ?/ => 87, ?0 => 111,
|
50
|
+
?1 => 52, ?2 => 75, ?3 => 119, ?4 => 49, ?5 => 34, ?6 => 82, ?7 => 81,
|
51
|
+
?8 => 95, ?9 => 65, ?: => 112, ?; => 86, ?< => 118, ?= => 110, ?> => 122,
|
52
|
+
?? => 105, ?A => 57, ?B => 83, ?C => 43, ?D => 46, ?E => 102, ?F => 40,
|
53
|
+
?G => 89, ?H => 38, ?I => 103, ?J => 45, ?K => 50, ?L => 42, ?M => 123,
|
54
|
+
?N => 91, ?O => 35, ?P => 125, ?Q => 55, ?R => 54, ?S => 66, ?T => 124,
|
55
|
+
?U => 126, ?V => 59, ?W => 47, ?X => 92, ?Y => 71, ?Z => 115, ?_ => 56,
|
56
|
+
?a => 121, ?b => 117, ?c => 104, ?d => 101, ?e => 100, ?f => 69, ?g => 73,
|
57
|
+
?f => 69, ?h => 99, ?i => 63, ?j => 94, ?k => 93, ?l => 39, ?m => 37,
|
58
|
+
?n => 61, ?o => 48, ?p => 58, ?q => 113, ?r => 32, ?s => 90, ?t => 44,
|
59
|
+
?u => 98, ?v => 60, ?w => 51, ?x => 33, ?y => 97, ?z => 62
|
60
|
+
}
|
61
|
+
|
62
|
+
def self.pass_scramble(t)
|
63
|
+
r = ?A
|
64
|
+
t.each_char {|i|
|
65
|
+
fail "invalid char in pass: #{i}" if ! PASSPAIR.key? i
|
66
|
+
r << PASSPAIR[i].chr
|
67
|
+
}
|
68
|
+
return r
|
69
|
+
end
|
70
|
+
|
71
|
+
# the algorithm is symmetric
|
72
|
+
def self.pass_descramble(t)
|
73
|
+
pass_scramble(t)[2..-1]
|
74
|
+
end
|
75
|
+
|
76
|
+
# p is a hash
|
77
|
+
def initialize(p)
|
78
|
+
@host = p[:host]
|
79
|
+
@port = p[:port]
|
80
|
+
@user = p[:user]
|
81
|
+
@pass = p[:pass]
|
82
|
+
@cvsroot = p[:cvsroot].strip
|
83
|
+
@debug = p[:debug]
|
84
|
+
@ports_tree = p[:ports_tree].strip
|
85
|
+
|
86
|
+
@client = TCPSocket.open(@host, @port)
|
87
|
+
request("BEGIN AUTH REQUEST")
|
88
|
+
request(@cvsroot)
|
89
|
+
request(@user)
|
90
|
+
request(CVSDumb.pass_scramble(@pass))
|
91
|
+
request("END AUTH REQUEST")
|
92
|
+
fail "cannot auth to #{@user}@#{@host}" if getline != 'I LOVE YOU'
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_accessor :debug
|
96
|
+
|
97
|
+
def version
|
98
|
+
request('version')
|
99
|
+
request('noop')
|
100
|
+
r = getline_text
|
101
|
+
getline # for 'noop'
|
102
|
+
return r
|
103
|
+
end
|
104
|
+
|
105
|
+
def log(dir, file = nil, date_cond = nil)
|
106
|
+
request("Root #{@cvsroot}")
|
107
|
+
request("Directory .")
|
108
|
+
request("#{@cvsroot}/#{@ports_tree.to_s == '' ? '' : @ports_tree + '/'}#{dir}")
|
109
|
+
request("Argument -N")
|
110
|
+
if date_cond
|
111
|
+
request("Argument -d")
|
112
|
+
request("Argument #{date_cond}")
|
113
|
+
end
|
114
|
+
request("Argument #{file}") if file != nil
|
115
|
+
request('log')
|
116
|
+
|
117
|
+
getmultilines_text {|i| yield i }
|
118
|
+
end
|
119
|
+
|
120
|
+
def close
|
121
|
+
@client.close
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
# debug print
|
128
|
+
def dputs(type, t)
|
129
|
+
puts "#{type}#{t}" if debug
|
130
|
+
end
|
131
|
+
|
132
|
+
def getline
|
133
|
+
r = @client.gets.strip
|
134
|
+
dputs(SERVER, r)
|
135
|
+
return r
|
136
|
+
end
|
137
|
+
|
138
|
+
def getline_text
|
139
|
+
@client
|
140
|
+
r = getline
|
141
|
+
if r.match(/^M (.+)/)
|
142
|
+
getline # read final 'ok\n'
|
143
|
+
return $1
|
144
|
+
else
|
145
|
+
fail "protocol error (unexpected string): #{r}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def getmultilines_text
|
150
|
+
is_error = false
|
151
|
+
e = []
|
152
|
+
while line = @client.gets.strip
|
153
|
+
break if line == 'ok'
|
154
|
+
if line == 'error'
|
155
|
+
is_error = true
|
156
|
+
break
|
157
|
+
end
|
158
|
+
|
159
|
+
if (line =~ /^(M|E) ?(.*)/)
|
160
|
+
if $1 == 'E'
|
161
|
+
e << $2
|
162
|
+
else
|
163
|
+
yield $2
|
164
|
+
end
|
165
|
+
else
|
166
|
+
fail "protocol error (unexpected string): #{line}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
fail "protocol error: " + e.join(', ') if is_error
|
171
|
+
end
|
172
|
+
|
173
|
+
def request(t)
|
174
|
+
@client.send("#{t}\n", 0)
|
175
|
+
dputs(CLIENT, t)
|
176
|
+
end
|
177
|
+
|
178
|
+
end # CVSDumb
|
179
|
+
|
180
|
+
end # module
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require_relative 'meta'
|
3
|
+
|
4
|
+
module PortVCS
|
5
|
+
|
6
|
+
class Utils
|
7
|
+
|
8
|
+
# load config file immediately if it contains '/' in its name,
|
9
|
+
# otherwise search through several dirs for it.
|
10
|
+
#
|
11
|
+
# conf -- a hash to merge result with
|
12
|
+
#
|
13
|
+
# return a loaded filename or nil on error
|
14
|
+
def self.config_load(conf, file, dirs)
|
15
|
+
p = ->(f) {
|
16
|
+
if File.readable?(f)
|
17
|
+
begin
|
18
|
+
myconf = YAML.load_file(f)
|
19
|
+
rescue
|
20
|
+
abort("cannot parse #{f}: #{$!}")
|
21
|
+
end
|
22
|
+
%w(host port user pass cvsroot ports_tree).each { |i|
|
23
|
+
fail "missing or nil '#{i}' in #{f}" if ! myconf.key?(i.to_sym) || ! myconf[i.to_sym]
|
24
|
+
}
|
25
|
+
conf.merge!(myconf)
|
26
|
+
return file
|
27
|
+
end
|
28
|
+
return nil
|
29
|
+
}
|
30
|
+
|
31
|
+
if file.index('/')
|
32
|
+
return p.call(file)
|
33
|
+
else
|
34
|
+
dirs.each {|dir| return dir+'/'+file if p.call(dir + '/' + file) }
|
35
|
+
end
|
36
|
+
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# p -- a string to examine
|
41
|
+
# ptree -- a directory name with a local port tree, for example "/usr/ports"
|
42
|
+
def self.port_extract_name(t, ptree)
|
43
|
+
return nil if t.to_s == '' || (! t.index('/') && Dir.pwd !~ /^#{ptree}/)
|
44
|
+
|
45
|
+
t.sub!(/#{ptree}(\/*)?/, '') if t =~ /^#{ptree}/
|
46
|
+
return nil if t[0] == '/'
|
47
|
+
|
48
|
+
# idempotent lambda
|
49
|
+
inptree = ->() {
|
50
|
+
if Dir.pwd =~ /^#{ptree}/
|
51
|
+
l = (d = Dir.pwd.sub(/#{ptree}(\/*)?/, '')).split('/').length
|
52
|
+
return File.split(d + '/' + t) if l >= 2 && l <= 3
|
53
|
+
end
|
54
|
+
}
|
55
|
+
|
56
|
+
case t.split('/').length
|
57
|
+
when 1
|
58
|
+
return inptree.call
|
59
|
+
when 2
|
60
|
+
# assuming files/patch-aa in /usr/ports/www/firefox/files/
|
61
|
+
return inptree.call if inptree.call
|
62
|
+
# assuming www/firefox
|
63
|
+
return [t]
|
64
|
+
when 3..4
|
65
|
+
# assuming www/firefox/Makefile
|
66
|
+
return File.split(t)
|
67
|
+
end
|
68
|
+
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.gem_libdir
|
73
|
+
t = ["#{File.dirname(File.expand_path($0))}/../lib/#{PortVCS::Meta::NAME}",
|
74
|
+
"#{Gem.dir}/gems/#{PortVCS::Meta::NAME}-#{PortVCS::Meta::VERSION}/lib/#{PortVCS::Meta::NAME}"]
|
75
|
+
t.each {|i| return i if File.readable?(i) }
|
76
|
+
fail "both paths are invalid: #{t}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.in_path?(file)
|
80
|
+
return true if file =~ %r%\A/% and File.exist? file
|
81
|
+
|
82
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path|
|
83
|
+
File.exist? File.join(path, file)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end # Utils
|
88
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# various staff for minitest
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open4'
|
5
|
+
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
require_relative '../lib/portvcs/utils'
|
9
|
+
include PortVCS
|
10
|
+
|
11
|
+
# don't run tests automatically if they were invoked as 'gem check -t ...'
|
12
|
+
if $0 =~ /gem/
|
13
|
+
require 'minitest/unit'
|
14
|
+
else
|
15
|
+
require 'minitest/autorun'
|
16
|
+
end
|
17
|
+
|
18
|
+
def cmd_run(cmd)
|
19
|
+
so = sr = ''
|
20
|
+
status = Open4::popen4(cmd) { |pid, stdin, stdout, stderr|
|
21
|
+
so = stdout.read
|
22
|
+
sr = stderr.read
|
23
|
+
}
|
24
|
+
[status.exitstatus, sr, so]
|
25
|
+
end
|
26
|
+
|
27
|
+
# return the right directory for (probably executable) _c_
|
28
|
+
def cmd(c)
|
29
|
+
case File.basename(Dir.pwd)
|
30
|
+
when Meta::NAME.downcase
|
31
|
+
# test probably is executed from the Rakefile
|
32
|
+
Dir.chdir('test')
|
33
|
+
when 'test'
|
34
|
+
# we are in the test directory, there is nothing special to do
|
35
|
+
else
|
36
|
+
# tests were invoked by 'gem check -t bwkfanboy'
|
37
|
+
begin
|
38
|
+
Dir.chdir(Utils.gem_libdir + '/../../test')
|
39
|
+
rescue
|
40
|
+
raise "running tests from '#{Dir.pwd}' isn't supported: #{$!}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
'../bin/' + c
|
45
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestCVSDumb < MiniTest::Unit::TestCase
|
4
|
+
CMD = 'portvcs'
|
5
|
+
CONFIG = 'semis/localhost_cvs.yaml'
|
6
|
+
MSG = 'This test requires a running CVS server on localhost with auth credentials
|
7
|
+
listed in semis/localhost_cvs.yaml file'
|
8
|
+
|
9
|
+
def setup
|
10
|
+
cmd 'foobar' # cd to tests directory
|
11
|
+
|
12
|
+
@skip_log_of_the_something = true
|
13
|
+
# this probably won't work on your machine
|
14
|
+
if ENV.key?('CVSROOT').to_s !~ /^\s*$/
|
15
|
+
cd 'semis/cvstest'
|
16
|
+
cmd_run("rm -rf '" + ENV['CVSROOT']+'/test/portvcs' + "'")
|
17
|
+
r = cmd_run("cvs import -m 'Huh?' test foo bar")
|
18
|
+
if r[0] != 0
|
19
|
+
warn("cannot import a test file into #{ENV['CVSROOT']}: "+r[1..-1].join('; '))
|
20
|
+
else
|
21
|
+
@skip_log_of_the_something = false
|
22
|
+
end
|
23
|
+
cd '../..'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_version
|
28
|
+
r = cmd_run("#{cmd CMD} --config #{CONFIG} --vcs-version")
|
29
|
+
assert_equal(0, r[0], MSG)
|
30
|
+
assert_match(/Concurrent Versions System \(CVS\) 1.11/, r[2])
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_log_of_the_something
|
34
|
+
skip('cvsroot on localhost is not ready') if @skip_log_of_the_something
|
35
|
+
r = cmd_run("#{cmd CMD} --config #{CONFIG} test/portvcs")
|
36
|
+
assert_equal(0, r[0], MSG)
|
37
|
+
refute_equal(0, r[2].size)
|
38
|
+
end
|
39
|
+
end
|
data/test/test_utils.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestUtils < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
cmd 'foobar' # cd to tests directory
|
6
|
+
@ptree = Dir.pwd + '/' + 'ports'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_name_resolution
|
10
|
+
save = Dir.pwd
|
11
|
+
|
12
|
+
assert_equal(nil, Utils.port_extract_name(nil, @ptree))
|
13
|
+
assert_equal(nil, Utils.port_extract_name('', nil))
|
14
|
+
assert_equal(nil, Utils.port_extract_name(' ', nil))
|
15
|
+
assert_equal(nil, Utils.port_extract_name('foo ', nil))
|
16
|
+
assert_equal(nil, Utils.port_extract_name('/', nil))
|
17
|
+
assert_equal(nil, Utils.port_extract_name('////////// //////', nil))
|
18
|
+
assert_equal(nil, Utils.port_extract_name('//////////', nil))
|
19
|
+
assert_equal(nil, Utils.port_extract_name('/foo', nil))
|
20
|
+
assert_equal(nil, Utils.port_extract_name('foo/', nil))
|
21
|
+
|
22
|
+
assert_equal(['foo/bar'], Utils.port_extract_name('/foo/bar', nil))
|
23
|
+
assert_equal(['q/w', 'e'], Utils.port_extract_name('/q/w/e', nil))
|
24
|
+
assert_equal(['q/w/e', 'r'], Utils.port_extract_name('/q/w/e/r', nil))
|
25
|
+
assert_equal(nil, Utils.port_extract_name('/q/w/e/r/t', nil))
|
26
|
+
assert_equal(nil, Utils.port_extract_name('/q/w/e/r/t/y/u/i/o', nil))
|
27
|
+
|
28
|
+
Dir.chdir @ptree
|
29
|
+
assert_equal(nil, Utils.port_extract_name('program', @ptree))
|
30
|
+
assert_equal(['category/program'],
|
31
|
+
Utils.port_extract_name('category/program', @ptree))
|
32
|
+
assert_equal(['category/program', 'Makefile'],
|
33
|
+
Utils.port_extract_name('category/program/Makefile', @ptree))
|
34
|
+
|
35
|
+
Dir.chdir 'category'
|
36
|
+
assert_equal(nil, Utils.port_extract_name('program', @ptree))
|
37
|
+
|
38
|
+
Dir.chdir 'program'
|
39
|
+
assert_equal(["category/program", "pkg-descr"],
|
40
|
+
Utils.port_extract_name('pkg-descr', @ptree))
|
41
|
+
|
42
|
+
assert_equal(["category/program/files", "patch-aa"],
|
43
|
+
Utils.port_extract_name('files/patch-aa', @ptree))
|
44
|
+
|
45
|
+
Dir.chdir 'files'
|
46
|
+
assert_equal(["category/program/files", "patch-aa"],
|
47
|
+
Utils.port_extract_name('patch-aa', @ptree))
|
48
|
+
|
49
|
+
Dir.chdir save
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_config
|
53
|
+
t = { foo: 'bar' }
|
54
|
+
t_exp = {
|
55
|
+
foo: 'bar', host: '127.0.0.1', port: 2401, user: 'anonymous',
|
56
|
+
pass: 'anoncvs', cvsroot: '/usr/local/cvsroot', ports_tree: ''
|
57
|
+
}
|
58
|
+
|
59
|
+
assert_equal('semis/localhost_cvs.yaml',
|
60
|
+
Utils.config_load(t, 'semis/localhost_cvs.yaml', nil))
|
61
|
+
assert_equal(t_exp, t)
|
62
|
+
|
63
|
+
assert_raises(RuntimeError) {
|
64
|
+
Utils.config_load(t, 'semis/localhost_cvs_incomplete.yaml', nil)
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: portvcs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Alexander Gromnitsky
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-13 00:00:00 +02:00
|
18
|
+
default_executable: portvcs
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: open4
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 1
|
32
|
+
version: 1.0.1
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description:
|
36
|
+
email: alexander.gromnitsky@gmail.com
|
37
|
+
executables:
|
38
|
+
- portvcs
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- doc/TODO
|
43
|
+
- doc/README.rdoc
|
44
|
+
- doc/LICENSE
|
45
|
+
files:
|
46
|
+
- lib/portvcs/portvcs.rb
|
47
|
+
- lib/portvcs/utils.rb
|
48
|
+
- lib/portvcs/meta.rb
|
49
|
+
- bin/portvcs
|
50
|
+
- doc/TODO
|
51
|
+
- doc/README.rdoc
|
52
|
+
- doc/LICENSE
|
53
|
+
- etc/portvcs.yaml
|
54
|
+
- etc/fr.FreeBSD.org.yaml
|
55
|
+
- README.rdoc
|
56
|
+
- Rakefile
|
57
|
+
- test/ports/category/program/files/patch-aa
|
58
|
+
- test/ports/category/program/Makefile
|
59
|
+
- test/ports/category/program/pkg-descr
|
60
|
+
- test/helper.rb
|
61
|
+
- test/semis/localhost_cvs.yaml
|
62
|
+
- test/semis/localhost_cvs_incomplete.yaml
|
63
|
+
- test/semis/cvstest/portvcs/Makefile
|
64
|
+
- test/test_utils.rb
|
65
|
+
- test/test_cvsdumb.rb
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://github.com/gromnitsky/portvcs
|
68
|
+
licenses: []
|
69
|
+
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options:
|
72
|
+
- -m
|
73
|
+
- doc/README.rdoc
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 1
|
83
|
+
- 9
|
84
|
+
- 2
|
85
|
+
version: 1.9.2
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.7
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: FreeBSD ports commits history viewer that doesn't require neither local ports tree nor CVS checkouts.
|
101
|
+
test_files:
|
102
|
+
- test/test_utils.rb
|
103
|
+
- test/test_cvsdumb.rb
|