portvcs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|