yugui-chkbuild 0.1.2

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.
Files changed (66) hide show
  1. data/README.ja.rd +191 -0
  2. data/Rakefile +56 -0
  3. data/VERSION +1 -0
  4. data/bin/last-build +28 -0
  5. data/bin/start-build +37 -0
  6. data/chkbuild.gemspec +107 -0
  7. data/core_ext/io.rb +17 -0
  8. data/core_ext/string.rb +10 -0
  9. data/lib/chkbuild.rb +45 -0
  10. data/lib/chkbuild/build.rb +718 -0
  11. data/lib/chkbuild/lock.rb +57 -0
  12. data/lib/chkbuild/logfile.rb +230 -0
  13. data/lib/chkbuild/main.rb +138 -0
  14. data/lib/chkbuild/options.rb +62 -0
  15. data/lib/chkbuild/scm/cvs.rb +132 -0
  16. data/lib/chkbuild/scm/git.rb +223 -0
  17. data/lib/chkbuild/scm/svn.rb +215 -0
  18. data/lib/chkbuild/scm/xforge.rb +33 -0
  19. data/lib/chkbuild/target.rb +180 -0
  20. data/lib/chkbuild/targets/gcc.rb +94 -0
  21. data/lib/chkbuild/targets/ruby.rb +456 -0
  22. data/lib/chkbuild/title.rb +107 -0
  23. data/lib/chkbuild/upload.rb +66 -0
  24. data/lib/misc/escape.rb +535 -0
  25. data/lib/misc/gdb.rb +74 -0
  26. data/lib/misc/timeoutcom.rb +174 -0
  27. data/lib/misc/udiff.rb +244 -0
  28. data/lib/misc/util.rb +232 -0
  29. data/sample/build-autoconf-ruby +69 -0
  30. data/sample/build-gcc-ruby +43 -0
  31. data/sample/build-ruby +37 -0
  32. data/sample/build-ruby2 +36 -0
  33. data/sample/build-svn +55 -0
  34. data/sample/build-yarv +35 -0
  35. data/sample/test-apr +12 -0
  36. data/sample/test-catcherr +23 -0
  37. data/sample/test-combfail +21 -0
  38. data/sample/test-core +14 -0
  39. data/sample/test-core2 +19 -0
  40. data/sample/test-date +9 -0
  41. data/sample/test-dep +17 -0
  42. data/sample/test-depver +14 -0
  43. data/sample/test-echo +9 -0
  44. data/sample/test-env +9 -0
  45. data/sample/test-error +9 -0
  46. data/sample/test-fail +18 -0
  47. data/sample/test-fmesg +16 -0
  48. data/sample/test-gcc-v +15 -0
  49. data/sample/test-git +11 -0
  50. data/sample/test-leave-proc +9 -0
  51. data/sample/test-limit +9 -0
  52. data/sample/test-make +9 -0
  53. data/sample/test-neterr +16 -0
  54. data/sample/test-savannah +14 -0
  55. data/sample/test-sleep +9 -0
  56. data/sample/test-timeout +9 -0
  57. data/sample/test-timeout2 +10 -0
  58. data/sample/test-timeout3 +9 -0
  59. data/sample/test-upload +13 -0
  60. data/sample/test-warn +13 -0
  61. data/setup/upload-rsync-ssh +572 -0
  62. data/test/misc/test-escape.rb +17 -0
  63. data/test/misc/test-logfile.rb +108 -0
  64. data/test/misc/test-timeoutcom.rb +23 -0
  65. data/test/test_helper.rb +9 -0
  66. metadata +123 -0
@@ -0,0 +1,132 @@
1
+ # Copyright (C) 2006,2007,2009 Tanaka Akira <akr@fsij.org>
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ require "uri"
26
+
27
+ class ChkBuild::Build
28
+ def cvs(cvsroot, mod, branch, opts={})
29
+ network_access {
30
+ cvs_internal(cvsroot, mod, branch, opts)
31
+ }
32
+ end
33
+
34
+ def cvs_internal(cvsroot, mod, branch, opts={})
35
+ opts = opts.dup
36
+ opts[:section] ||= 'cvs'
37
+ working_dir = opts.fetch(:working_dir, mod)
38
+ if !File.exist? "#{ENV['HOME']}/.cvspass"
39
+ opts['ENV:CVS_PASSFILE'] = '/dev/null' # avoid warning
40
+ end
41
+ if File.directory?(working_dir)
42
+ Dir.chdir(working_dir) {
43
+ h1 = cvs_revisions
44
+ self.run("cvs", "-f", "-z3", "-Q", "update", "-kb", "-dP", opts)
45
+ h2 = cvs_revisions
46
+ cvs_print_revisions(h1, h2, opts[:viewvc]||opts[:viewcvs]||opts[:cvsweb])
47
+ }
48
+ else
49
+ h1 = nil
50
+ if File.identical?(@build_dir, '.') &&
51
+ !(ts = build_time_sequence - [@start_time]).empty? &&
52
+ File.directory?(old_working_dir = "#{@target_dir}/#{ts.last}/#{working_dir}")
53
+ Dir.chdir(old_working_dir) {
54
+ h1 = cvs_revisions
55
+ }
56
+ end
57
+ if branch
58
+ self.run("cvs", "-f", "-z3", "-Qd", cvsroot, "co", "-kb", "-d", working_dir, "-Pr", branch, mod, opts)
59
+ else
60
+ self.run("cvs", "-f", "-z3", "-Qd", cvsroot, "co", "-kb", "-d", working_dir, "-P", mod, opts)
61
+ end
62
+ Dir.chdir(working_dir) {
63
+ h2 = cvs_revisions
64
+ cvs_print_revisions(h1, h2, opts[:viewvc]||opts[:viewcvs]||opts[:cvsweb])
65
+ }
66
+ end
67
+ end
68
+
69
+ def cvs_revisions
70
+ h = {}
71
+ Dir.glob("**/CVS").each {|cvs_dir|
72
+ cvsroot = IO.read("#{cvs_dir}/Root").chomp
73
+ repository = IO.read("#{cvs_dir}/Repository").chomp
74
+ ds = cvs_dir.split(%r{/})[0...-1]
75
+ IO.foreach("#{cvs_dir}/Entries") {|line|
76
+ h[[ds, $1]] = [cvsroot, repository, $2] if %r{^/([^/]+)/([^/]*)/} =~ line
77
+ }
78
+ }
79
+ h
80
+ end
81
+
82
+ def cvs_uri(viewcvs, repository, filename, r1, r2)
83
+ uri = URI.parse(viewcvs)
84
+ path = uri.path.dup
85
+ path << "/" << Escape.uri_path(repository).to_s if repository != '.'
86
+ path << "/" << Escape.uri_path(filename).to_s
87
+ uri.path = path
88
+ query = (uri.query || '').split(/[;&]/)
89
+ if r1 == 'none'
90
+ query << "rev=#{r2}"
91
+ elsif r2 == 'none'
92
+ query << "rev=#{r1}"
93
+ else
94
+ query << "r1=#{r1}" << "r2=#{r2}"
95
+ end
96
+ uri.query = query.join(';')
97
+ uri.to_s
98
+ end
99
+
100
+ def cvs_print_changes(h1, h2, viewcvs=nil)
101
+ (h1.keys | h2.keys).sort.each {|k|
102
+ f = k.flatten.join('/')
103
+ cvsroot1, repository1, r1 = h1[k] || [nil, nil, 'none']
104
+ cvsroot2, repository2, r2 = h2[k] || [nil, nil, 'none']
105
+ if r1 != r2
106
+ if r1 == 'none'
107
+ line = "ADD"
108
+ elsif r2 == 'none'
109
+ line = "DEL"
110
+ else
111
+ line = "CHG"
112
+ end
113
+ line << " #{f}\t#{r1}->#{r2}"
114
+ if viewcvs
115
+ line << "\t" << cvs_uri(viewcvs, repository1 || repository2, k[1], r1, r2)
116
+ end
117
+ puts line
118
+ end
119
+ }
120
+ end
121
+
122
+ def cvs_print_revisions(h1, h2, viewcvs=nil)
123
+ cvs_print_changes(h1, h2, viewcvs) if h1
124
+ puts 'revisions:'
125
+ h2.keys.sort.each {|k|
126
+ f = k.flatten.join('/')
127
+ cvsroot2, repository2, r2 = h2[k] || [nil, nil, 'none']
128
+ digest = sha256_digest_file(f)
129
+ puts "#{f}\t#{r2}\t#{digest}"
130
+ }
131
+ end
132
+ end
@@ -0,0 +1,223 @@
1
+ # Copyright (C) 2008,2009 Tanaka Akira <akr@fsij.org>
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ require 'fileutils'
26
+ require "uri"
27
+
28
+ require "pp"
29
+
30
+ module ChkBuild; end # for testing
31
+
32
+ class ChkBuild::Build
33
+ def git_with_file(basename)
34
+ n = 1
35
+ until !File.exist?(name = "#{self.build_dir}/#{basename}#{n}")
36
+ n += 1
37
+ end
38
+ yield name
39
+ end
40
+
41
+ def git_logfile(opts)
42
+ git_with_file("git.log.") {|errfile|
43
+ opts2 = opts.dup
44
+ opts2[:stderr] = errfile
45
+ begin
46
+ yield opts2
47
+ ensure
48
+ if File.exist?(errfile)
49
+ errcontent = File.read(errfile)
50
+ errcontent.gsub!(/^.*[\r\e].*\n/, "")
51
+ puts errcontent if !errcontent.empty?
52
+ end
53
+ end
54
+ }
55
+ end
56
+
57
+ def git(cloneurl, working_dir, opts={})
58
+ network_access {
59
+ git_internal(cloneurl, working_dir, opts)
60
+ }
61
+ end
62
+
63
+ def git_internal(cloneurl, working_dir, opts={})
64
+ urigen = nil
65
+ opts = opts.dup
66
+ opts[:section] ||= 'git'
67
+ if opts[:github]
68
+ urigen = GitHub.new(*opts[:github])
69
+ end
70
+ if shared_dir = opts[:shared_gitdir]
71
+ opts_shared = opts.dup
72
+ opts_shared[:section] += "(shared)"
73
+ Dir.chdir(shared_dir) {
74
+ if File.directory?(working_dir) && File.exist?("#{working_dir}/.git")
75
+ Dir.chdir(working_dir) {
76
+ git_logfile(opts_shared) {|opts2|
77
+ self.run("git", "pull", opts2)
78
+ }
79
+ }
80
+ else
81
+ FileUtils.rm_rf(working_dir) if File.exist?(working_dir)
82
+ pdir = File.dirname(working_dir)
83
+ FileUtils.mkdir_p(pdir) if !File.directory?(pdir)
84
+ git_logfile(opts_shared) {|opts2|
85
+ self.run "git", "clone", "-q", cloneurl, working_dir, opts2
86
+ }
87
+ end
88
+ cloneurl = "#{shared_dir}/#{working_dir}"
89
+ }
90
+ end
91
+ if File.exist?(working_dir) && File.exist?("#{working_dir}/.git")
92
+ Dir.chdir(working_dir) {
93
+ old_head = git_head_commit
94
+ git_logfile(opts) {|opts2|
95
+ self.run "git", "pull", opts2
96
+ }
97
+ logs = git_oneline_logs(old_head)
98
+ git_print_logs(old_head, logs, urigen)
99
+ }
100
+ else
101
+ FileUtils.rm_rf(working_dir) if File.exist?(working_dir)
102
+ pdir = File.dirname(working_dir)
103
+ FileUtils.mkdir_p(pdir) if !File.directory?(pdir)
104
+ old_head = nil
105
+ if File.identical?(self.build_dir, '.') &&
106
+ !(ts = self.build_time_sequence - [self.start_time]).empty? &&
107
+ File.directory?(old_working_dir = self.target_dir + ts.last + working_dir)
108
+ Dir.chdir(old_working_dir) {
109
+ old_head = git_head_commit
110
+ }
111
+ end
112
+ git_logfile(opts) {|opts2|
113
+ self.run "git", "clone", "-q", cloneurl, working_dir, opts2
114
+ }
115
+ Dir.chdir(working_dir) {
116
+ logs = git_oneline_logs(old_head)
117
+ git_print_logs(old_head, logs, urigen)
118
+ }
119
+ end
120
+ end
121
+
122
+ def github(user, project, working_dir, opts={})
123
+ opts = opts.dup
124
+ opts[:github] = [user, project]
125
+ git("git://github.com/#{user}/#{project}.git", working_dir, opts)
126
+ end
127
+
128
+ def git_oneline_logs(old_head=nil)
129
+ result = []
130
+ if old_head
131
+ command = "git log --pretty=oneline #{old_head}..HEAD"
132
+ else
133
+ command = "git log --pretty=oneline --max-count=1"
134
+ end
135
+ IO.popen(command) {|f|
136
+ f.each_line {|line|
137
+ # <sha1><sp><title line>
138
+ if /\A([0-9a-fA-F]+)\s+(.*)/ =~ line
139
+ result << [$1, $2]
140
+ end
141
+ }
142
+ }
143
+ result
144
+ end
145
+
146
+ def git_head_commit
147
+ IO.popen("git rev-list --max-count=1 HEAD") {|f|
148
+ # <sha1><LF>
149
+ # 4db0223676a371da8c4247d9a853529ef50a3b01
150
+ f.read.chomp
151
+ }
152
+ end
153
+
154
+ def git_revisions
155
+ h = IO.popen("git ls-tree -z -r HEAD") {|f|
156
+ git_parse_status(f)
157
+ }
158
+ IO.popen("git rev-list --max-count=1 HEAD") {|f|
159
+ # <sha1><LF>
160
+ # 4db0223676a371da8c4247d9a853529ef50a3b01
161
+ commit_hash = f.read.chomp
162
+ h[nil] = commit_hash
163
+ }
164
+ h
165
+ end
166
+
167
+ def git_parse_status(f)
168
+ h = {}
169
+ f.each_line("\0") {|line|
170
+ # <mode> SP <type> SP <object> TAB <file>\0
171
+ # 100644 blob 9518934185ea26856cf1bcdf75f7cc51fcd82534 core/array/allocate_spec.rb
172
+ if /\A\d+ [^ ]+ ([0-9a-fA-F]+)\t([^\0]+)\0\z/ =~ line
173
+ rev = $1
174
+ path = $2
175
+ h[path] = rev
176
+ end
177
+ }
178
+ h
179
+ end
180
+
181
+ def git_path_sort(ary)
182
+ ary.sort_by {|path|
183
+ path.gsub(%r{([^/]+)(/|\z)}) {
184
+ if $2 == ""
185
+ if $1 == '.'
186
+ "A"
187
+ else
188
+ "B#{$1}"
189
+ end
190
+ else
191
+ "C#{$1}\0"
192
+ end
193
+ }
194
+ }
195
+ end
196
+
197
+ class GitHub
198
+ def initialize(user, project)
199
+ @user = user
200
+ @project = project
201
+ end
202
+
203
+ def commit_uri(commit_hash)
204
+ # http://github.com/brixen/rubyspec/commit/b8f8eb6765afe915f2ecfdbbe59a53e6393d6865
205
+ "http://github.com/#{@user}/#{@project}/commit/#{commit_hash}"
206
+ end
207
+ end
208
+
209
+ def git_print_logs(old_head, logs, urigen=nil)
210
+ if !old_head
211
+ puts "last commit:"
212
+ end
213
+ logs.each {|commit_hash, title_line|
214
+ if urigen
215
+ commit = urigen.commit_uri(commit_hash)
216
+ else
217
+ commit = commit_hash
218
+ end
219
+ line = "COMMIT #{title_line}\t#{commit}"
220
+ puts line
221
+ }
222
+ end
223
+ end
@@ -0,0 +1,215 @@
1
+ # Copyright (C) 2006,2007,2009 Tanaka Akira <akr@fsij.org>
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ require 'fileutils'
26
+ require "uri"
27
+
28
+ module ChkBuild; end # for testing
29
+
30
+ class ChkBuild::ViewVC
31
+ def initialize(uri, old=false)
32
+ @uri = uri
33
+ @old = old
34
+ end
35
+
36
+ def rev_uri(r)
37
+ revision = @old ? 'rev' : 'revision'
38
+ extend_uri("", [['view', 'rev'], [revision, r.to_s]]).to_s
39
+ end
40
+
41
+ def markup_uri(d, f, r)
42
+ pathrev = @old ? 'rev' : 'pathrev'
43
+ extend_uri("/#{d}/#{f}", [['view', 'markup'], [pathrev, r.to_s]]).to_s
44
+ end
45
+
46
+ def dir_uri(d, f, r)
47
+ pathrev = @old ? 'rev' : 'pathrev'
48
+ extend_uri("/#{d}/#{f}", [[pathrev, r.to_s]]).to_s
49
+ end
50
+
51
+ def diff_uri(d, f, r1, r2)
52
+ pathrev = @old ? 'rev' : 'pathrev'
53
+ extend_uri("/#{d}/#{f}", [
54
+ ['p1', "#{d}/#{f}"],
55
+ ['r1', r1.to_s],
56
+ ['r2', r2.to_s],
57
+ [pathrev, r2.to_s]]).to_s
58
+ end
59
+
60
+ def extend_uri(path, params)
61
+ uri = URI.parse(@uri)
62
+ uri.path = uri.path + Escape.uri_path(path).to_s
63
+ query = Escape.html_form(params).to_s
64
+ (uri.query || '').split(/[;&]/).each {|param| query << ';' << param }
65
+ uri.query = query
66
+ uri
67
+ end
68
+ end
69
+
70
+ class ChkBuild::Build
71
+ def svn(svnroot, rep_dir, working_dir, opts={})
72
+ network_access {
73
+ svn_internal(svnroot, rep_dir, working_dir, opts)
74
+ }
75
+ end
76
+
77
+ def svn_internal(svnroot, rep_dir, working_dir, opts={})
78
+ url = svnroot + '/' + rep_dir
79
+ opts = opts.dup
80
+ opts[:section] ||= 'svn'
81
+ if opts[:viewvc]||opts[:viewcvs]
82
+ viewvc = ChkBuild::ViewVC.new(opts[:viewvc]||opts[:viewcvs], opts[:viewcvs]!=nil)
83
+ else
84
+ viewvc = nil
85
+ end
86
+ if File.exist?(working_dir) && File.exist?("#{working_dir}/.svn")
87
+ Dir.chdir(working_dir) {
88
+ self.run "svn", "cleanup", opts
89
+ opts[:section] = nil
90
+ h1 = svn_revisions
91
+ self.run "svn", "update", "-q", opts
92
+ h2 = svn_revisions
93
+ svn_print_changes(h1, h2, viewvc, rep_dir)
94
+ }
95
+ else
96
+ if File.exist?(working_dir)
97
+ FileUtils.rm_rf(working_dir)
98
+ end
99
+ h1 = h2 = nil
100
+ if File.identical?(self.build_dir, '.') &&
101
+ !(ts = self.build_time_sequence - [self.start_time]).empty? &&
102
+ File.directory?(old_working_dir = self.target_dir + ts.last + working_dir)
103
+ Dir.chdir(old_working_dir) {
104
+ h1 = svn_revisions
105
+ }
106
+ end
107
+ self.run "svn", "checkout", "-q", url, working_dir, opts
108
+ Dir.chdir(working_dir) {
109
+ h2 = svn_revisions
110
+ svn_print_changes(h1, h2, viewvc, rep_dir) if h1
111
+ }
112
+ end
113
+ end
114
+
115
+ def svn_revisions
116
+ IO.popen("svn status -v") {|f|
117
+ svn_parse_status(f)
118
+ }
119
+ end
120
+
121
+ def svn_parse_status(f)
122
+ h = {}
123
+ f.each {|line|
124
+ if /\d+\s+(\d+)\s+\S+\s+(.+)/ =~ line
125
+ rev = $1.to_i
126
+ path = $2
127
+ dir = File.directory?(path)
128
+ path << '/' if dir && path != '.'
129
+ h[path] = [rev, dir]
130
+ end
131
+ }
132
+ h
133
+ end
134
+
135
+ def svn_path_sort(ary)
136
+ ary.sort_by {|path|
137
+ path.gsub(%r{([^/]+)(/|\z)}) {
138
+ if $2 == ""
139
+ if $1 == '.'
140
+ "A"
141
+ else
142
+ "B#{$1}"
143
+ end
144
+ else
145
+ "C#{$1}\0"
146
+ end
147
+ }
148
+ }
149
+ end
150
+
151
+ def svn_rev_uri(viewvc, r)
152
+ return nil if !viewvc
153
+ viewvc.rev_uri(r)
154
+ end
155
+
156
+ def svn_markup_uri(viewvc, d, f, r)
157
+ return nil if !viewvc
158
+ viewvc.markup_uri(d, f, r)
159
+ end
160
+
161
+ def svn_dir_uri(viewvc, d, f, r)
162
+ return nil if !viewvc
163
+ viewvc.dir_uri(d, f, r)
164
+ end
165
+
166
+ def svn_diff_uri(viewvc, d, f, r1, r2)
167
+ return nil if !viewvc
168
+ viewvc.diff_uri(d, f, r1, r2)
169
+ end
170
+
171
+ def svn_print_changes(h1, h2, viewvc=nil, rep_dir=nil)
172
+ top_r1, _ = h1['.']
173
+ top_r2, _ = h2['.']
174
+ h1.delete '.'
175
+ h2.delete '.'
176
+ return if top_r1 == top_r2
177
+ svn_print_chg_line('.', top_r1, top_r2, svn_rev_uri(viewvc, top_r2))
178
+ svn_path_sort(h1.keys|h2.keys).each {|f|
179
+ r1, d1 = h1[f] || ['none', nil]
180
+ r2, d2 = h2[f] || ['none', nil]
181
+ next if r1 == r2 # no changes
182
+ next if d1 && d2 # skip directory changes
183
+ if !d1 && !d2 && r1 != 'none' && r2 != 'none'
184
+ svn_print_chg_line(f, r1, r2,
185
+ svn_diff_uri(viewvc, rep_dir, f, top_r1, top_r2))
186
+ else
187
+ svn_print_del_line(f, r1,
188
+ d1 ? svn_dir_uri(viewvc, rep_dir, f, top_r1) :
189
+ svn_markup_uri(viewvc, rep_dir, f, top_r1)) if r1 != 'none'
190
+ svn_print_add_line(f, r2,
191
+ d2 ? svn_dir_uri(viewvc, rep_dir, f, top_r2) :
192
+ svn_markup_uri(viewvc, rep_dir, f, top_r2)) if r2 != 'none'
193
+ end
194
+ }
195
+ end
196
+
197
+ def svn_print_chg_line(f, r1, r2, uri)
198
+ line = "CHG #{f}\t#{r1}->#{r2}"
199
+ line << "\t" << uri.to_s if uri
200
+ puts line
201
+ end
202
+
203
+ def svn_print_del_line(f, r, uri)
204
+ line = "DEL #{f}\t#{r}->none"
205
+ line << "\t" << uri.to_s if uri
206
+ puts line
207
+ end
208
+
209
+ def svn_print_add_line(f, r, uri)
210
+ line = "ADD #{f}\tnone->#{r}"
211
+ line << "\t" << uri.to_s if uri
212
+ puts line
213
+ end
214
+
215
+ end