yggdrasil 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ class Yggdrasil
2
+
3
+ # @param [Array] args
4
+ def update(args)
5
+ target_paths, options = parse_options(args,
6
+ {'--username'=>:username, '--password'=>:password,
7
+ '-r'=>:revision, '--revision'=>:revision,
8
+ '--non-interactive'=>:non_interactive?})
9
+ options = input_user_pass(options)
10
+ sync_mirror options
11
+
12
+ updates = Array.new
13
+ FileUtils.cd @mirror_dir do
14
+ out = system3("#@svn status -qu --depth infinity --no-auth-cache --non-interactive" +
15
+ " --username '#{options[:username]}' --password '#{options[:password]}'")
16
+ out.split(/\n/).each do |line|
17
+ updates << $1 if /^.*\*.*\s(\S+)\s*$/ =~ line
18
+ end
19
+ end
20
+
21
+ matched_updates = select_updates(updates, target_paths)
22
+
23
+ confirmed_updates = confirm_updates(matched_updates,options) do |relative_path|
24
+ FileUtils.cd @mirror_dir do
25
+ cmd = "#@svn diff"
26
+ cmd += " --no-auth-cache --non-interactive"
27
+ cmd += " --username #{options[:username]} --password #{options[:password]}"
28
+ if options.has_key?(:revision)
29
+ cmd += " --old=#{relative_path} --new=#{relative_path}@#{options[:revision]}"
30
+ else
31
+ cmd += " --old=#{relative_path} --new=#{relative_path}@HEAD"
32
+ end
33
+ puts system3(cmd)
34
+ end
35
+ end
36
+ # res == 'Y' or --non-interactive
37
+
38
+ return unless confirmed_updates
39
+ return if confirmed_updates == 0 # no files to update
40
+
41
+ cmd_arg = "#@svn update --no-auth-cache --non-interactive"
42
+ cmd_arg += " --username #{options[:username]} --password #{options[:password]}"
43
+ if options.has_key?(:revision)
44
+ cmd_arg += " -r #{options[:revision]}"
45
+ else
46
+ cmd_arg += " -r HEAD"
47
+ end
48
+ cmd_arg += ' ' + confirmed_updates.join(' ')
49
+ FileUtils.cd @mirror_dir do
50
+ puts system3(cmd_arg)
51
+
52
+ # reflect mirror to real file
53
+ confirmed_updates.each do |update_file|
54
+ if File.exist?(update_file)
55
+ FileUtils.copy_file @mirror_dir+'/'+update_file, '/'+update_file
56
+ else
57
+ system3 "rm -rf /#{update_file}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,14 @@
1
- module Yggdrasil
2
- VERSION = "0.0.0"
1
+ class Yggdrasil
2
+ VERSION = "0.0.1"
3
+ CMD = File::basename($0)
4
+
5
+ def Yggdrasil.version
6
+ puts <<"EOS"
7
+ #{CMD}, version #{VERSION}
8
+
9
+ Copyright (C) 2012-2013 Tomohisa Kusukawa.
10
+ Yggdrasil is open source software, see https://github.com/tkusukawa/yggdrasil/
11
+
12
+ EOS
13
+ end
3
14
  end
data/lib/yggdrasil.rb CHANGED
@@ -1,5 +1,245 @@
1
+ require 'fileutils'
2
+ require "open3"
1
3
  require "yggdrasil/version"
4
+ require "yggdrasil/help"
5
+ require "yggdrasil/init"
6
+ require "yggdrasil/add"
7
+ require "yggdrasil/commit"
8
+ require "yggdrasil/cleanup"
9
+ require "yggdrasil/diff"
10
+ require "yggdrasil/list"
11
+ require "yggdrasil/log"
12
+ require "yggdrasil/status"
13
+ require "yggdrasil/update"
14
+ require "yggdrasil/revert"
2
15
 
3
- module Yggdrasil
4
- # Your code goes here...
16
+ class Yggdrasil
17
+
18
+ def Yggdrasil.command(args, input = nil)
19
+ $stdin = StringIO.new(input) if input != nil
20
+ ENV['LANG'] = 'en_US.UTF-8'
21
+
22
+ if args.size == 0
23
+ Yggdrasil::help([])
24
+ return
25
+ end
26
+ case args[0]
27
+ when 'add'
28
+ new.add(args[1..-1])
29
+ when 'cleanup'
30
+ new.cleanup(args[1..-1])
31
+ when 'commit', 'ci'
32
+ new.commit(args[1..-1])
33
+ when 'diff', 'di'
34
+ new.diff(args[1..-1])
35
+ when 'help', 'h', '?'
36
+ help(args[1..-1])
37
+ when 'init'
38
+ init(args[1..-1])
39
+ when 'list', 'ls'
40
+ new.list(args[1..-1])
41
+ when 'log'
42
+ new.log(args[1..-1])
43
+ when 'status', 'stat', 'st'
44
+ new.status(args[1..-1])
45
+ when 'revert'
46
+ new.revert(args[1..-1])
47
+ when 'update'
48
+ new.update(args[1..-1])
49
+ when 'version', '--version'
50
+ version
51
+ else
52
+ error "Unknown subcommand: '#{args[0]}'"
53
+ end
54
+ end
55
+
56
+ # @param [String] cmd
57
+ def Yggdrasil.system3(cmd, err_exit=true, stdin=nil)
58
+ if stdin.nil?
59
+ out,stat = Open3.capture2e cmd
60
+ else
61
+ out,stat = Open3.capture2e cmd, :stdin_data=>stdin
62
+ end
63
+ unless stat.success?
64
+ return nil unless err_exit
65
+ $stderr.puts "#{CMD} error: command failure: #{cmd}"
66
+ $stderr.puts "command output:"
67
+ $stderr.puts out
68
+ exit stat.exitstatus
69
+ end
70
+ return out
71
+ end
72
+
73
+ protected
74
+ # @param [String] msg
75
+ def Yggdrasil.error(msg)
76
+ puts "#{CMD} error: #{msg}"
77
+ puts
78
+ exit 1
79
+ end
80
+
81
+ def Yggdrasil.parse_options(args, valid_params)
82
+ options = Hash.new
83
+ pos = 0
84
+ while args.size > pos
85
+ if valid_params.has_key?(args[pos])
86
+ option_note = args[pos]
87
+ option_key = valid_params[option_note]
88
+ args = args[0...pos]+args[pos+1..-1]
89
+ if option_key.to_s[-1] == '?'
90
+ options[option_key] = true
91
+ else
92
+ error "Not enough arguments provided: #{option_note}" unless args.size > pos
93
+ option_value = args[pos].dup
94
+ args = args[0...pos]+args[pos+1..-1]
95
+ options[option_key] = option_value
96
+ end
97
+ next
98
+ end
99
+ pos += 1
100
+ end
101
+ return args, options
102
+ end
103
+
104
+ def Yggdrasil.input_user_pass(options)
105
+ until options.has_key?(:username) do
106
+ print "Input svn username: "
107
+ input = $stdin.gets
108
+ options[:username] = input.chomp
109
+ end
110
+ until options.has_key?(:password) do
111
+ print "Input svn password: "
112
+ #input = `sh -c 'read -s hoge;echo $hoge'`
113
+ system3 'stty -echo', false
114
+ input = $stdin.gets
115
+ system3 'stty echo', false
116
+ options[:password] = input.chomp
117
+ end
118
+ return options
119
+ end
120
+
121
+ def initialize
122
+
123
+ @config = read_config
124
+ ENV["PATH"] = @config[:path]
125
+ @svn = @config[:svn]
126
+ @repo = @config[:repo]
127
+ @current_dir = `readlink -f .`.chomp
128
+ @mirror_dir = ENV["HOME"]+"/.yggdrasil/mirror"
129
+ end
130
+
131
+ # load config value from config file
132
+ def read_config
133
+ @config=Hash.new
134
+ begin
135
+ config_file = open("#{ENV['HOME']}/.yggdrasil/config")
136
+ rescue
137
+ puts "#{CMD} error: can not open config file: #{ENV['HOME']}/.yggdrasil/config"
138
+ exit 1
139
+ end
140
+ l = 0
141
+ while (line = config_file.gets)
142
+ l += 1
143
+ next if /^\s*#.*$/ =~ line # comment line
144
+ if /^\s*(\S+)\s*=\s*(\S+).*$/ =~ line
145
+ @config[$1.to_sym] = $2
146
+ else
147
+ puts "#{CMD} error: syntax error. :#{ENV['HOME']}/.yggdrasil/config(#{l})"
148
+ exit 1
149
+ end
150
+ end
151
+ config_file.close
152
+ @config
153
+ end
154
+
155
+ def sync_mirror(options)
156
+ updates = Array.new
157
+ FileUtils.cd @mirror_dir do
158
+ out = system3("#@svn ls #@repo --depth infinity --no-auth-cache --non-interactive" +
159
+ " --username '#{options[:username]}' --password '#{options[:password]}'")
160
+ files = out.split(/\n/)
161
+ out = system3("#@svn status -q --depth infinity --no-auth-cache --non-interactive" +
162
+ " --username '#{options[:username]}' --password '#{options[:password]}'")
163
+ out.split(/\n/).each do |line|
164
+ files << $1 if /^.*\s(\S+)\s*$/ =~ line
165
+ end
166
+ files.sort!
167
+ files.uniq!
168
+ files.each do |file|
169
+ if !File.exist?('/'+file)
170
+ system3 "#@svn delete #{file} --force" +
171
+ " --no-auth-cache --non-interactive"
172
+ elsif File.file?('/'+file)
173
+ if !File.exist?(@mirror_dir+'/'+file)
174
+ system3 "#@svn revert --no-auth-cache --non-interactive #{file}"
175
+ end
176
+ FileUtils.copy_file '/'+file, @mirror_dir+'/'+file
177
+ end
178
+ end
179
+ out = system3("#@svn status -q --depth infinity --no-auth-cache --non-interactive" +
180
+ " --username '#{options[:username]}' --password '#{options[:password]}'")
181
+ out.split(/\n/).each do |line|
182
+ updates << $1 if /^.*\s(\S+)\s*$/ =~ line
183
+ end
184
+ end
185
+ updates
186
+ end
187
+
188
+ def select_updates(updates, target_paths)
189
+
190
+ target_relatives = Array.new
191
+ if target_paths.size == 0
192
+ target_relatives << @current_dir.sub(%r{^/},'')
193
+ else
194
+ target_paths.each do |path|
195
+ if %r{^/} =~ path
196
+ target_relatives << path.sub(%r{^/},'') # cut first '/'
197
+ else
198
+ target_relatives << @current_dir.sub(%r{^/},'') + '/' + path
199
+ end
200
+ end
201
+ end
202
+
203
+ # search updated files in the specified dir
204
+ cond = '^'+target_relatives.join('|^') # make reg exp
205
+ matched_updates = Array.new
206
+ updates.each do |update|
207
+ matched_updates << update if update.match(cond)
208
+ end
209
+
210
+ # search parent updates of matched updates
211
+ parents = Array.new
212
+ updates.each do |update|
213
+ matched_updates.each do |matched_update|
214
+ parents << update if matched_update.match("^#{update}/")
215
+ end
216
+ end
217
+ matched_updates += parents
218
+ matched_updates.sort.uniq
219
+ end
220
+
221
+ def confirm_updates(updates, options)
222
+ until options.has_key?(:non_interactive?)
223
+ puts
224
+ (0...updates.size).each do |i|
225
+ puts "#{i}:#{updates[i]}"
226
+ end
227
+ print "OK? [Y|n|<num to diff>]:"
228
+ res = $stdin.gets
229
+ return nil unless res
230
+ res.chomp!
231
+ return nil if res == 'n'
232
+ break if res == 'Y'
233
+ next unless updates[res.to_i]
234
+ if /^\d+$/ =~ res
235
+ yield updates[res.to_i]
236
+ end
237
+ end
238
+ # res == 'Y'
239
+ updates
240
+ end
241
+
242
+ def method_missing(action, *args)
243
+ Yggdrasil.__send__ action, *args
244
+ end
5
245
  end
data/spec/add_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Yggdrasil, "add" do
4
+ it '-------- add' do
5
+ puts '-------- add'
6
+ prepare_environment
7
+ init_yggdrasil
8
+ end
9
+
10
+ it 'should warn: add non-exist files' do
11
+ puts '---- should warn: add non-exist files'
12
+ out = catch_stdout{Yggdrasil.command %w{add hoge}}
13
+ out.should == "no such file: #{`readlink -f hoge`}"
14
+
15
+ out = catch_stdout{Yggdrasil.command %w{add /etc/hoge}}
16
+ out.should == "no such file: /etc/hoge\n"
17
+ end
18
+
19
+ it 'should success: add exist files' do
20
+ puts '---- should success: add exist files'
21
+ Yggdrasil.command %w{add Gemfile /etc/fstab /etc/fstab}
22
+ File.exist?("/tmp/yggdrasil-test/.yggdrasil/mirror#{`readlink -f Gemfile`.chomp}").should be_true
23
+ File.exist?("/tmp/yggdrasil-test/.yggdrasil/mirror#{`readlink -f /etc/fstab`.chomp}").should be_true
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Yggdrasil, "cleanup" do
4
+ it '-------- cleanup' do
5
+ puts '-------- cleanup'
6
+ prepare_environment
7
+ init_yggdrasil
8
+ end
9
+
10
+ it 'should success cleanup' do
11
+ puts "---- should success cleanup"
12
+ puts "-- rm .svn"
13
+ `rm -rf /tmp/yggdrasil-test/.yggdrasil/mirror/.svn`
14
+
15
+ puts "-- cleanup"
16
+ Yggdrasil.command %w{cleanup --username hoge --password foo}
17
+
18
+ puts "-- check .svn"
19
+ res = File.exist?("/tmp/yggdrasil-test/.yggdrasil/mirror/.svn")
20
+ p res
21
+ res.should == true
22
+ end
23
+ end
@@ -0,0 +1,129 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Yggdrasil, "commit" do
4
+ it '-------- commit' do
5
+ puts '-------- commit'
6
+ prepare_environment
7
+
8
+ puts '-- init'
9
+ Yggdrasil.command %w{init} +
10
+ %w{--repo svn://localhost/tmp/yggdrasil-test/svn-repo/mng-repo/host-name/} +
11
+ %w{--username hoge --password foo}
12
+ end
13
+
14
+ it 'should commit added files' do
15
+ puts '---- should commit added files'
16
+ `echo hoge > /tmp/yggdrasil-test/A`
17
+ `echo foo > /tmp/yggdrasil-test/B`
18
+ FileUtils.cd "/tmp/yggdrasil-test" do
19
+ puts '-- add'
20
+ Yggdrasil.command %w{add A /tmp/yggdrasil-test/B}
21
+ end
22
+
23
+ puts "-- commit"
24
+ FileUtils.cd '/tmp/yggdrasil-test' do
25
+ Yggdrasil.command %w{commit --username hoge --password foo},
26
+ "0\nY\nadd A and B\n"
27
+ end
28
+
29
+ puts "\n-- check committed file 'tmp/yggdrasil-test/A'"
30
+ res = `svn cat file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test/A`
31
+ puts res
32
+ res.should == "hoge\n"
33
+
34
+ puts "\n-- check committed file 'tmp/yggdrasil-test/B'"
35
+ res = `svn cat file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test/B`
36
+ puts res
37
+ res.should == "foo\n"
38
+ end
39
+
40
+ it 'should commit modified file' do
41
+ puts "---- should commit modified file"
42
+ puts "-- modify"
43
+ `echo hoge >> /tmp/yggdrasil-test/A`
44
+
45
+ puts "-- commit"
46
+ Yggdrasil.command %w{commit / --username hoge --password foo},
47
+ "0\nY\nmodify A\n"
48
+
49
+ puts "\n-- check committed file 'tmp/yggdrasil-test/A'"
50
+ res = `svn cat file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test/A`
51
+ puts res
52
+ res.should == "hoge\nhoge\n"
53
+ end
54
+
55
+ it 'should accept password interactive' do
56
+ puts "---- should accept password interactive"
57
+ `echo A >> /tmp/yggdrasil-test/A`
58
+
59
+ Yggdrasil.command %w{commit /tmp --username hoge},
60
+ "foo\nY\nmodify A\n" # interactive input: password,Y/n, commit message
61
+
62
+ puts "\n-- check committed file 'tmp/yggdrasil-test/A'"
63
+ res = `svn cat file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test/A`
64
+ puts res
65
+ res.should == "hoge\nhoge\nA\n"
66
+ end
67
+
68
+ it 'should commit specified file only' do
69
+ puts "---- should commit specified file only"
70
+ `echo A >> /tmp/yggdrasil-test/A`
71
+ `echo B >> /tmp/yggdrasil-test/B`
72
+
73
+ Yggdrasil.command %w{commit
74
+ --username hoge --password foo -m modify /tmp/yggdrasil-test/B},
75
+ "0\nY\n"
76
+
77
+ puts "\n-- check committed file 'tmp/yggdrasil-test/B'"
78
+ res = `svn cat file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test/B`
79
+ puts res
80
+ res.should == "foo\nB\n"
81
+ end
82
+
83
+ it 'should not commit deleted file' do
84
+ puts "---- should not commit deleted file"
85
+ `rm -f /tmp/yggdrasil-test/A`
86
+
87
+ Yggdrasil.command %w{commit --username hoge --password foo -m delete},
88
+ "0\nn\n"
89
+ puts "\n-- check file exists on repo"
90
+ res = `svn ls file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test`
91
+ puts res
92
+ res.should == "A\nB\n"
93
+ end
94
+
95
+ it 'should commit deleted file' do
96
+ puts "---- should commit deleted file"
97
+ `echo hoge > /tmp/yggdrasil-test/A`
98
+ `rm -f /tmp/yggdrasil-test/B`
99
+
100
+ Yggdrasil.command %w{commit -m delete /tmp/yggdrasil-test} +
101
+ %w{--username hoge --password foo},
102
+ "0\n1\nY\n"
103
+
104
+ puts "\n-- check committed delete file"
105
+ res = `svn ls file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test`
106
+ puts res
107
+ res.should == "A\n"
108
+ end
109
+
110
+ it 'should commit all files at once' do
111
+ puts "---- should revert all files at once"
112
+
113
+ `echo HOGE >> /tmp/yggdrasil-test/A`
114
+ `rm -f /tmp/yggdrasil-test/B`
115
+ `mkdir /tmp/yggdrasil-test/c`
116
+ `echo bar > /tmp/yggdrasil-test/c/C`
117
+ Yggdrasil.command %w{add /tmp/yggdrasil-test/c/C}
118
+
119
+ Yggdrasil.command %w{commit -m delete /tmp/yggdrasil-test/c/C} +
120
+ %w{--username hoge --password foo},
121
+ "0\n1\nY\n"
122
+
123
+ puts "\n-- check committed delete file"
124
+ res = `svn ls file:///tmp/yggdrasil-test/svn-repo/mng-repo/host-name/tmp/yggdrasil-test`
125
+ puts res
126
+ res.should == "A\nc/\n"
127
+ end
128
+
129
+ end
data/spec/diff_spec.rb ADDED
@@ -0,0 +1,104 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Yggdrasil, "diff" do
4
+ it '-------- diff' do
5
+ puts '-------- diff'
6
+ prepare_environment
7
+ init_yggdrasil
8
+
9
+ # modify and not commit yet
10
+ `echo HOGE >> /tmp/yggdrasil-test/A`
11
+ `echo FOO >> /tmp/yggdrasil-test/B`
12
+ end
13
+
14
+ it 'should success diff (absolute/relative path)' do
15
+ puts "---- should success diff (absolute/relative path)"
16
+ out = catch_stdout do
17
+ FileUtils.cd "/tmp/yggdrasil-test" do
18
+ Yggdrasil.command(%w{diff /tmp/yggdrasil-test/A B --username hoge --password foo})
19
+ end
20
+ end
21
+ out.should == <<"EOS"
22
+ Index: tmp/yggdrasil-test/A
23
+ ===================================================================
24
+ --- tmp/yggdrasil-test/A (revision 3)
25
+ +++ tmp/yggdrasil-test/A (working copy)
26
+ @@ -1,2 +1,3 @@
27
+ hoge
28
+ hoge
29
+ +HOGE
30
+ Index: tmp/yggdrasil-test/B
31
+ ===================================================================
32
+ --- tmp/yggdrasil-test/B (revision 3)
33
+ +++ tmp/yggdrasil-test/B (working copy)
34
+ @@ -1,2 +1,3 @@
35
+ foo
36
+ foo
37
+ +FOO
38
+ EOS
39
+ end
40
+
41
+ it 'should success (no path)' do
42
+ puts "---- should success (no path)"
43
+ out = catch_stdout do
44
+ FileUtils.cd "/tmp/yggdrasil-test" do
45
+ Yggdrasil.command %w{diff --username hoge --password foo}
46
+ end
47
+ end
48
+ out.should == <<"EOS"
49
+ Index: tmp/yggdrasil-test/A
50
+ ===================================================================
51
+ --- tmp/yggdrasil-test/A\t(revision 3)
52
+ +++ tmp/yggdrasil-test/A\t(working copy)
53
+ @@ -1,2 +1,3 @@
54
+ hoge
55
+ hoge
56
+ +HOGE
57
+ Index: tmp/yggdrasil-test/B
58
+ ===================================================================
59
+ --- tmp/yggdrasil-test/B\t(revision 3)
60
+ +++ tmp/yggdrasil-test/B\t(working copy)
61
+ @@ -1,2 +1,3 @@
62
+ foo
63
+ foo
64
+ +FOO
65
+ EOS
66
+ end
67
+
68
+ it 'should success (-r)' do
69
+ puts "---- should success (-r)"
70
+ out = catch_stdout do
71
+ FileUtils.cd "/tmp/yggdrasil-test" do
72
+ Yggdrasil.command %w{diff -r 2:3 A --username hoge --password foo}
73
+ end
74
+ end
75
+ out.should == <<"EOS"
76
+ Index: tmp/yggdrasil-test/A
77
+ ===================================================================
78
+ --- tmp/yggdrasil-test/A (revision 2)
79
+ +++ tmp/yggdrasil-test/A (revision 3)
80
+ @@ -1 +1,2 @@
81
+ hoge
82
+ +hoge
83
+ EOS
84
+ end
85
+
86
+ it 'should success (--revision)' do
87
+ puts "---- should success (--revision)"
88
+ out = catch_stdout do
89
+ FileUtils.cd "/tmp/yggdrasil-test" do
90
+ Yggdrasil.command %w{diff --revision 3 A --username hoge --password foo}
91
+ end
92
+ end
93
+ out.should == <<"EOS"
94
+ Index: tmp/yggdrasil-test/A
95
+ ===================================================================
96
+ --- tmp/yggdrasil-test/A (revision 3)
97
+ +++ tmp/yggdrasil-test/A (working copy)
98
+ @@ -1,2 +1,3 @@
99
+ hoge
100
+ hoge
101
+ +HOGE
102
+ EOS
103
+ end
104
+ end