munkey 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +28 -0
- data/bin/munkey +64 -0
- data/lib/gitignore_parser.rb +48 -0
- data/lib/munkey.rb +179 -0
- data/test/gitignore_test.rb +116 -0
- data/test/munkey_test.rb +286 -0
- metadata +92 -0
data/README.rdoc
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
= Munkey
|
2
|
+
|
3
|
+
Uses git to mirror an ftp server and track local changes. These can then be
|
4
|
+
pushed back up to the ftp server or additional changes pulled down from the
|
5
|
+
ftp server.
|
6
|
+
|
7
|
+
Solves my problem of working on a website locally but needing to interact
|
8
|
+
with other developers editing directly the server via ftp.
|
9
|
+
|
10
|
+
This is not very efficient yet since it downloads all the (non-.gitignore'd)
|
11
|
+
files each time.
|
12
|
+
|
13
|
+
Note: Depends on net-ftp-list gem so be sure to install that as well
|
14
|
+
|
15
|
+
== Usage
|
16
|
+
|
17
|
+
munkey clone [--ignore=ignoresfile] ftp://usr:pwd@host/dir [dst]
|
18
|
+
|
19
|
+
munkey pull [--no-merge] [--full-download] - pulls in new changes from ftp
|
20
|
+
|
21
|
+
munkey push - pushes changes back into ftp server. Important to have pulled
|
22
|
+
recent changes from ftp since munkey will overwrite locally
|
23
|
+
modified files.
|
24
|
+
|
25
|
+
== Caveats
|
26
|
+
|
27
|
+
* Gitignore support doesn't understand inverse (!...) lines -- patches welcome
|
28
|
+
* Probably doesn't work on Windows (or any non-unix platform)
|
data/bin/munkey
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'munkey'
|
4
|
+
|
5
|
+
class MunkeyBin
|
6
|
+
def initialize(args)
|
7
|
+
@options = {}
|
8
|
+
@options[:verbose] = args.delete('--quiet') ? false : true
|
9
|
+
end
|
10
|
+
|
11
|
+
def clone(args)
|
12
|
+
ignores = read_ignores(args.shift) if /^--ignore=/ =~ args.first
|
13
|
+
src = args.shift
|
14
|
+
dst = args.shift
|
15
|
+
dst ||= join_to_pwd(src.split('/').last) if src
|
16
|
+
dst = join_to_pwd(dst) unless dst.slice(0, 1) == '/'
|
17
|
+
show_help unless src && dst
|
18
|
+
Munkey::clone src, dst, @options.merge(:ignores => ignores)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pull(args)
|
22
|
+
merge = args.delete('--no-merge') ? false : true
|
23
|
+
quick = args.delete('--full-download') ? false : true
|
24
|
+
Munkey.new(Dir.pwd, @options).pull(:merge => merge, :quick => quick)
|
25
|
+
end
|
26
|
+
|
27
|
+
def push(args)
|
28
|
+
dryrun = args.delete('--dry-run') ? true : false
|
29
|
+
Munkey.new(Dir.pwd, @options).push(dryrun)
|
30
|
+
end
|
31
|
+
|
32
|
+
def show_help
|
33
|
+
puts "Usage: munkey <command> [options] [args]"
|
34
|
+
puts
|
35
|
+
puts "munkey clone [--quiet] [--ignore=ignoresfile] ftp://usr:pwd@host/dir [dst]"
|
36
|
+
puts "\t- Clone from FTP source"
|
37
|
+
puts "munkey pull [--quiet] [--no-merge] [--full-download]"
|
38
|
+
puts "\t- Pull in changes from FTP source"
|
39
|
+
puts "munkey push [--quiet] [--dry-run]"
|
40
|
+
puts "\t- Push your changes back to FTP source"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
def join_to_pwd(path)
|
46
|
+
/^\// =~ path ? path : File.join(Dir.pwd, path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read_ignores(arg)
|
50
|
+
ignore_file = arg.gsub(/^--ignore=/, '')
|
51
|
+
File.read join_to_pwd(ignore_file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
munkey = MunkeyBin.new(ARGV)
|
56
|
+
if !ARGV.empty? and munkey.respond_to?(cmd = ARGV.shift)
|
57
|
+
begin
|
58
|
+
munkey.send cmd, ARGV
|
59
|
+
rescue NoMethodError
|
60
|
+
puts "WARNING: Invalid FTP Source"
|
61
|
+
end
|
62
|
+
else
|
63
|
+
munkey.show_help
|
64
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class GitignoreParser
|
2
|
+
DEFAULT_IGNORES = ".git\n"
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def parse(gitpath)
|
6
|
+
new(gitpath)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(gitpath)
|
11
|
+
@gitpath = gitpath
|
12
|
+
|
13
|
+
ignore_file = File.join(@gitpath, '.gitignore')
|
14
|
+
gitignore = DEFAULT_IGNORES
|
15
|
+
gitignore += File.read(ignore_file) if File.exist?(ignore_file)
|
16
|
+
|
17
|
+
@globs = []
|
18
|
+
rx = gitignore.split("\n").map do |i|
|
19
|
+
i.strip!
|
20
|
+
if i == '' or i.slice(0,1) == '#'
|
21
|
+
nil
|
22
|
+
elsif not i.include?('*')
|
23
|
+
if i.slice(-1,1) == '/'
|
24
|
+
i
|
25
|
+
else
|
26
|
+
"^#{i}|\/#{i}"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
@globs << i
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end.compact.join("|")
|
33
|
+
@regex = (rx == '' ? nil : Regexp.new(rx))
|
34
|
+
end
|
35
|
+
|
36
|
+
def ignore?(path)
|
37
|
+
raise NotAbsolutePathError unless path.slice(0, 1) == '/'
|
38
|
+
relpath = path.gsub %r{^#{Regexp.escape(@gitpath)}\/}, ''
|
39
|
+
@regex =~ relpath || @globs.any? {|g| File.fnmatch(g, relpath) }
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class NotAbsolutePathError < StandardError
|
45
|
+
def initialize
|
46
|
+
super("Supplied path is not an absolute path")
|
47
|
+
end
|
48
|
+
end
|
data/lib/munkey.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uri'
|
3
|
+
require 'ftp_sync'
|
4
|
+
require 'yaml'
|
5
|
+
require 'gitignore_parser'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
class Munkey
|
9
|
+
DEFAULT_BRANCH = 'munkey'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def clone(ftpsrc, repo_path, options = {})
|
13
|
+
src = URI::parse(ftpsrc)
|
14
|
+
raise InvalidSource unless src.is_a?(URI::FTP)
|
15
|
+
|
16
|
+
repo = create_repo(repo_path, options)
|
17
|
+
repo.save_ftp_details(src)
|
18
|
+
repo.pull_ftp_files
|
19
|
+
repo.commit_changes
|
20
|
+
repo.create_branch
|
21
|
+
repo
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def create_repo(repo_path, options = {})
|
26
|
+
return false unless system("git init #{repo_path}")
|
27
|
+
if options[:ignores]
|
28
|
+
File.open(File.join(repo_path, '.gitignore'), 'w') do |f|
|
29
|
+
f.write options[:ignores]
|
30
|
+
end
|
31
|
+
system("cd #{repo_path} && git add .gitignore")
|
32
|
+
end
|
33
|
+
new(repo_path, options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(gitpath, options)
|
38
|
+
@gitpath = gitpath
|
39
|
+
munkey_file = File.join(gitpath, '.git', 'munkey.yml')
|
40
|
+
@ftpdetails = YAML.load_file(munkey_file) if File.exist?(munkey_file)
|
41
|
+
@verbose = options[:verbose] || false
|
42
|
+
end
|
43
|
+
|
44
|
+
def pull(options = {})
|
45
|
+
default_options = { :merge => true, :quick => false }
|
46
|
+
default_options.merge! options
|
47
|
+
|
48
|
+
tmp_repo = clone_to_tmp
|
49
|
+
pull_ftp_files(tmp_repo, default_options[:quick])
|
50
|
+
commit_changes(tmp_repo)
|
51
|
+
push_into_base_repo(tmp_repo)
|
52
|
+
FileUtils.rm_rf(tmp_repo)
|
53
|
+
merge_foreign_changes if default_options[:merge]
|
54
|
+
end
|
55
|
+
|
56
|
+
def push(dryrun = false)
|
57
|
+
changes = files_changed_between_branches
|
58
|
+
|
59
|
+
list_ftp_changes(changes) && return if dryrun
|
60
|
+
|
61
|
+
update_ftp_server(changes)
|
62
|
+
tmp_repo = clone_to_tmp
|
63
|
+
merge_pushed_changes(tmp_repo)
|
64
|
+
push_into_base_repo(tmp_repo)
|
65
|
+
FileUtils.rm_rf(tmp_repo)
|
66
|
+
end
|
67
|
+
|
68
|
+
def save_ftp_details(ftp_uri)
|
69
|
+
@ftpdetails = { :host => ftp_uri.host, :path => "/#{ftp_uri.path}", :user => ftp_uri.user, :password => ftp_uri.password }
|
70
|
+
File.open File.join(@gitpath, '.git', 'munkey.yml'), 'w' do |f|
|
71
|
+
f.write @ftpdetails.to_yaml
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def pull_ftp_files(dst = nil, quick = false)
|
76
|
+
if quick
|
77
|
+
since = last_pull_date
|
78
|
+
end
|
79
|
+
|
80
|
+
dst ||= @gitpath
|
81
|
+
gitignore = GitignoreParser.parse(dst)
|
82
|
+
ftp = FtpSync.new(@ftpdetails[:host], @ftpdetails[:user], @ftpdetails[:password], :ignore => gitignore, :verbose => @verbose)
|
83
|
+
ftp.pull_dir(dst, @ftpdetails[:path], { :delete => true, :since => (since || nil) }) do |p|
|
84
|
+
Dir.chdir(dst) do
|
85
|
+
relpath = p.gsub %r{^#{Regexp.escape(dst)}\/}, ''
|
86
|
+
system("git rm -r#{git_quiet} '#{relpath}'")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def commit_changes(dst = nil)
|
92
|
+
Dir.chdir(dst || @gitpath) do
|
93
|
+
system("git add .") && system("git commit -m 'Pull from ftp://#{@ftpdetails[:host]}#{@ftpdetails[:path]} at #{Time.now.to_s}'")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_branch(branch_name = DEFAULT_BRANCH)
|
98
|
+
Dir.chdir(@gitpath) do
|
99
|
+
system("git branch #{branch_name}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def clone_to_tmp(branch = DEFAULT_BRANCH)
|
104
|
+
tmp_repo = File.join ENV['TMPDIR'], create_tmpname
|
105
|
+
system("git clone#{git_quiet} -b #{branch} #{@gitpath} #{tmp_repo}")
|
106
|
+
tmp_repo
|
107
|
+
end
|
108
|
+
|
109
|
+
def push_into_base_repo(tmp_repo, branch = DEFAULT_BRANCH)
|
110
|
+
Dir.chdir(tmp_repo) do
|
111
|
+
system("git push#{git_quiet} origin #{branch}:#{branch}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def merge_foreign_changes(branch = DEFAULT_BRANCH)
|
116
|
+
Dir.chdir(@gitpath) do
|
117
|
+
system("git merge #{branch}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def merge_pushed_changes(tmp_repo)
|
122
|
+
Dir.chdir(tmp_repo) do
|
123
|
+
system("git pull#{git_quiet} origin master")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def files_changed_between_branches(branch = DEFAULT_BRANCH)
|
128
|
+
changes = { :changed => [], :removed => [] }
|
129
|
+
Dir.chdir(@gitpath) do
|
130
|
+
`git diff --name-status #{branch} master`.strip.split("\n").each do |f|
|
131
|
+
status, name = f.split(/\s+/, 2)
|
132
|
+
if status == "D"
|
133
|
+
changes[:removed] << name
|
134
|
+
else
|
135
|
+
changes[:changed] << name unless name == '.gitignore'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
changes
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_ftp_server(changes)
|
144
|
+
ftp = FtpSync.new(@ftpdetails[:host], @ftpdetails[:user], @ftpdetails[:password], :verbose => @verbose)
|
145
|
+
|
146
|
+
unless changes[:changed].size == 0
|
147
|
+
ftp.push_files @gitpath, @ftpdetails[:path], changes[:changed]
|
148
|
+
end
|
149
|
+
|
150
|
+
unless changes[:removed].size == 0
|
151
|
+
ftp.remove_files @ftpdetails[:path], changes[:removed]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def list_ftp_changes(changes)
|
156
|
+
changes[:changed].each {|f| puts "WILL UPLOAD #{f}" }
|
157
|
+
changes[:removed].each {|f| puts "WILL REMOVE #{f}" }
|
158
|
+
end
|
159
|
+
|
160
|
+
def last_pull_date(branch = DEFAULT_BRANCH)
|
161
|
+
Dir.chdir(@gitpath) do
|
162
|
+
commits = `git log --format=oneline #{branch}`.strip.split("\n")
|
163
|
+
commits.find {|c| c =~ /^[a-f\d]{40} Pull from ftp:\/\/.* at ([\w +:]+)$/ } ? Time.parse($1) : nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def git_quiet
|
170
|
+
@verbose ? '' : ' -q'
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_tmpname
|
174
|
+
tmpname = ''
|
175
|
+
char_list = ("a".."z").to_a + ("0".."9").to_a
|
176
|
+
1.upto(20) { |i| tmpname << char_list[rand(char_list.size)] }
|
177
|
+
return tmpname
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
test_path = File.expand_path(File.dirname(__FILE__))
|
2
|
+
lib_path = File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
3
|
+
|
4
|
+
$:.unshift test_path unless $:.include?(test_path)
|
5
|
+
$:.unshift lib_path unless $:.include?(lib_path)
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'gitignore_parser'
|
9
|
+
require 'tmpdir'
|
10
|
+
|
11
|
+
class GitignoreTest < Test::Unit::TestCase
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@gitdir = File.join Dir.tmpdir, create_tmpname
|
15
|
+
FileUtils.mkdir_p @gitdir
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
FileUtils.rm_rf @gitdir
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_skips_blank_lines_in_gitignore
|
23
|
+
create_git_ignore "\nfoo.txt\n\n"
|
24
|
+
assert GitignoreParser::parse(@gitdir).ignore?(File.join(@gitdir, 'foo.txt'))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_skips_commented_lines_in_gitignore
|
28
|
+
create_git_ignore "foo.txt\n#bar.txt\n"
|
29
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
30
|
+
assert gitignore.ignore?(File.join(@gitdir, 'foo.txt'))
|
31
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'bar.txt'))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_filename_ignore
|
35
|
+
create_git_ignore 'foo.txt'
|
36
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
37
|
+
assert gitignore.ignore?(File.join(@gitdir, 'foo.txt'))
|
38
|
+
assert gitignore.ignore?(File.join(@gitdir, 'foo/foo.txt'))
|
39
|
+
assert gitignore.ignore?(File.join(@gitdir, 'foo/bar/foo.txt'))
|
40
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'bar.txt'))
|
41
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'foo/bar.txt'))
|
42
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'barfoo.txt'))
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_simple_ignore
|
46
|
+
create_git_ignore "*.txt\n"
|
47
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
48
|
+
assert gitignore.ignore?(File.join(@gitdir, 'foo.txt'))
|
49
|
+
assert gitignore.ignore?(File.join(@gitdir, 'nested/foo.txt'))
|
50
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'foo.jpg'))
|
51
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'foo_txt'))
|
52
|
+
assert !gitignore.ignore?(File.join(@gitdir, 'foo.txt.jpg'))
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_combined_ignore
|
56
|
+
create_git_ignore "*.txt\n*.jpg\n"
|
57
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
58
|
+
assert gitignore.ignore?(File.join(@gitdir, "foo.txt"))
|
59
|
+
assert gitignore.ignore?(File.join(@gitdir, "foo.jpg"))
|
60
|
+
assert gitignore.ignore?(File.join(@gitdir, "foo.jpg.txt"))
|
61
|
+
assert !gitignore.ignore?(File.join(@gitdir, "foo.doc"))
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_path_only_ignore
|
65
|
+
create_git_ignore "doc/"
|
66
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
67
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/foo.txt"))
|
68
|
+
assert gitignore.ignore?(File.join(@gitdir, "src/doc/foo.txt"))
|
69
|
+
assert !gitignore.ignore?(File.join(@gitdir, "src/foo.txt"))
|
70
|
+
assert !gitignore.ignore?(File.join(@gitdir, "doc"))
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_simple_glob_ignore
|
74
|
+
create_git_ignore "doc/*.txt"
|
75
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
76
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/foo.txt"))
|
77
|
+
assert !gitignore.ignore?(File.join(@gitdir, "src/doc/foo.txt"))
|
78
|
+
assert !gitignore.ignore?(File.join(@gitdir, "src/foo.txt"))
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_path_only_glob_ignore
|
82
|
+
create_git_ignore "doc/*"
|
83
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
84
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/html/index.html"))
|
85
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/html"))
|
86
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/pdf"))
|
87
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/pdf/index.pdf"))
|
88
|
+
assert gitignore.ignore?(File.join(@gitdir, "doc/index.html"))
|
89
|
+
assert !gitignore.ignore?(File.join(@gitdir, "src/index.html"))
|
90
|
+
assert !gitignore.ignore?(File.join(@gitdir, "src/doc/index.html"))
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_path_and_file_glob
|
94
|
+
create_git_ignore "foo/**/*.txt"
|
95
|
+
gitignore = GitignoreParser::parse(@gitdir)
|
96
|
+
assert gitignore.ignore?(File.join(@gitdir, "foo/bar/hello.txt"))
|
97
|
+
assert !gitignore.ignore?(File.join(@gitdir, "foo/bar/hello.html"))
|
98
|
+
assert !gitignore.ignore?(File.join(@gitdir, "foo/bar"))
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def create_tmpname
|
104
|
+
tmpname = ''
|
105
|
+
char_list = ("a".."z").to_a + ("0".."9").to_a
|
106
|
+
1.upto(20) { |i| tmpname << char_list[rand(char_list.size)] }
|
107
|
+
return tmpname
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_git_ignore(ignore_content)
|
111
|
+
File.open File.join(@gitdir, '.gitignore'), 'w' do |f|
|
112
|
+
f.write ignore_content
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/test/munkey_test.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
test_path = File.expand_path(File.dirname(__FILE__))
|
2
|
+
lib_path = File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
3
|
+
|
4
|
+
$:.unshift test_path unless $:.include?(test_path)
|
5
|
+
$:.unshift lib_path unless $:.include?(lib_path)
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'tmpdir'
|
9
|
+
require 'net/ftp'
|
10
|
+
require 'munkey'
|
11
|
+
require 'fileutils'
|
12
|
+
|
13
|
+
class MunkeyTest < Test::Unit::TestCase
|
14
|
+
|
15
|
+
def setup
|
16
|
+
Net::FTP.create_ftp_src
|
17
|
+
Net::FTP.listing_overrides = {}
|
18
|
+
@gitdir = File.join Dir.tmpdir, create_tmpname
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
FileUtils.rm_rf @gitdir
|
23
|
+
FileUtils.rm_rf Net::FTP.ftp_src
|
24
|
+
FileUtils.rm_rf Net::FTP.ftp_dst if File.exist?(Net::FTP.ftp_dst)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_clone_creates_supplied_target_dir
|
28
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
29
|
+
assert File.exist?(@gitdir)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_clone_initialises_git_in_target_dir
|
33
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
34
|
+
assert File.exist?(File.join(@gitdir, '.git'))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_clone_creates_git_ignore_with_supplied_ignores
|
38
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir, :ignores => '*.txt')
|
39
|
+
assert File.exist?(File.join(@gitdir, '.gitignore'))
|
40
|
+
assert_equal "*.txt", File.read(File.join(@gitdir, '.gitignore'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_clone_saves_ftp_details
|
44
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
45
|
+
assert File.exist?(File.join(@gitdir, '.git', 'munkey.yml'))
|
46
|
+
ftp_details = YAML.load(File.read(File.join(@gitdir, '.git', 'munkey.yml')))
|
47
|
+
assert_equal 'test.server', ftp_details[:host]
|
48
|
+
assert_equal 'user', ftp_details[:user]
|
49
|
+
assert_equal 'pass', ftp_details[:password]
|
50
|
+
assert_equal '/', ftp_details[:path]
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_clone_pulls_in_files_from_ftp
|
54
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
55
|
+
assert File.exist?(File.join(@gitdir, 'README'))
|
56
|
+
assert File.exist?(File.join(@gitdir, 'dirA'))
|
57
|
+
assert File.exist?(File.join(@gitdir, 'dirA', 'fileAA'))
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_clone_adds_files_to_git
|
61
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
62
|
+
Dir.chdir(@gitdir) do
|
63
|
+
status = `git status`
|
64
|
+
assert_no_match /untracked files present/, status
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_clone_creates_a_munkey_branch
|
69
|
+
Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
70
|
+
Dir.chdir(@gitdir) do
|
71
|
+
branches = `git branch`.split("\n").map {|b| b.gsub(/^\*/, '').strip }
|
72
|
+
assert branches.include?('master')
|
73
|
+
assert branches.include?('munkey')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_pull_adds_new_files
|
78
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
79
|
+
FileUtils.touch File.join(Net::FTP.ftp_src, 'missing')
|
80
|
+
assert !File.exist?(File.join(@gitdir, 'missing'))
|
81
|
+
munkey.pull
|
82
|
+
assert File.exist?(File.join(@gitdir, 'missing'))
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_pull_removes_missing_files
|
86
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
87
|
+
File.unlink File.join(Net::FTP.ftp_src, 'README')
|
88
|
+
assert File.exist?(File.join(@gitdir, 'README'))
|
89
|
+
munkey.pull
|
90
|
+
assert !File.exist?(File.join(@gitdir, 'README'))
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_pull_doesnt_change_locally_removed_files
|
94
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
95
|
+
readme = File.join(@gitdir, 'README')
|
96
|
+
File.unlink(readme)
|
97
|
+
assert !File.exist?(readme)
|
98
|
+
munkey.pull
|
99
|
+
assert !File.exist?(readme)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_pull_adds_a_commit
|
103
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
104
|
+
FileUtils.touch File.join(Net::FTP.ftp_src, 'missing')
|
105
|
+
munkey.pull
|
106
|
+
Dir.chdir(@gitdir) do
|
107
|
+
commits = `git log --format=oneline`.strip.split("\n")
|
108
|
+
assert_equal 2, commits.size
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_pull_with_no_merge_commits_to_munkey_branch_but_not_master
|
113
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
114
|
+
Dir.chdir(@gitdir) do
|
115
|
+
munkey_commits = `git log --format=oneline munkey`.strip.split("\n").size
|
116
|
+
master_commits = `git log --format=oneline master`.strip.split("\n").size
|
117
|
+
FileUtils.touch File.join(Net::FTP.ftp_src, 'missing')
|
118
|
+
munkey.pull(:merge => false)
|
119
|
+
assert_equal munkey_commits + 1, `git log --format=oneline munkey`.strip.split("\n").size
|
120
|
+
assert_equal master_commits, `git log --format=oneline master`.strip.split("\n").size
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_quick_pull_includes_changed_files
|
125
|
+
Net::FTP.listing_overrides['/'] = ["-rw-r--r-- 1 root other 0 #{(Time.now + 90).strftime('%b %d %H:%M')} README"]
|
126
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
127
|
+
File.open(File.join(Net::FTP.ftp_src, 'README'), 'w') {|f| f.write 'quicktest' }
|
128
|
+
Dir.chdir(@gitdir) do
|
129
|
+
munkey.pull(:quick => true)
|
130
|
+
assert_equal 2, `git log --format=oneline munkey`.strip.split("\n").size
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_quick_pull_excludes_unmodified_files
|
135
|
+
Net::FTP.listing_overrides['/'] = ["-rw-r--r-- 1 root other 0 #{(Time.now - 90).strftime('%b %d %H:%M')} README"]
|
136
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
137
|
+
File.open(File.join(Net::FTP.ftp_src, 'README'), 'w') {|f| f.write 'quicktest' }
|
138
|
+
Dir.chdir(@gitdir) do
|
139
|
+
munkey.pull(:quick => true)
|
140
|
+
assert_equal 1, `git log --format=oneline munkey`.strip.split("\n").size
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_push_includes_newly_added_files
|
145
|
+
Net::FTP.create_ftp_dst
|
146
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
147
|
+
add_file_to_git 'newfile'
|
148
|
+
munkey.push
|
149
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'newfile'))
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_push_includes_files_from_multiple_commits
|
153
|
+
Net::FTP.create_ftp_dst
|
154
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
155
|
+
add_file_to_git('newfile')
|
156
|
+
add_file_to_git('secondfile')
|
157
|
+
munkey.push
|
158
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'newfile'))
|
159
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'secondfile'))
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_push_excludes_remote_added_files
|
163
|
+
Net::FTP.create_ftp_dst
|
164
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
165
|
+
FileUtils.touch File.join(Net::FTP.ftp_src, 'another')
|
166
|
+
munkey.pull
|
167
|
+
munkey.push
|
168
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, 'another'))
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_push_excludes_existing_files
|
172
|
+
Net::FTP.create_ftp_dst
|
173
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
174
|
+
add_file_to_git 'newfile'
|
175
|
+
munkey.push
|
176
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'newfile'))
|
177
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_push_excludes_gitignore
|
181
|
+
Net::FTP.create_ftp_dst
|
182
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
183
|
+
add_file_to_git '.gitignore'
|
184
|
+
munkey.push
|
185
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, '.gitignore'))
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_push_includes_files_changed_on_both_local_and_remote
|
189
|
+
Net::FTP.create_ftp_dst
|
190
|
+
File.open(File.join(Net::FTP.ftp_src, 'README'), 'w') do |f|
|
191
|
+
f.write "line 1\nline 2\nline 3\nline 4\n"
|
192
|
+
end
|
193
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
194
|
+
File.open(File.join(Net::FTP.ftp_src, 'README'), 'w') do |f|
|
195
|
+
f.write "line 1\nline 2\nline 3\nline 4\nline 5\n"
|
196
|
+
end
|
197
|
+
File.open(File.join(@gitdir, 'README'), 'w') do |f|
|
198
|
+
f.write "line 1\nline two\nline 3\nline 4\n"
|
199
|
+
end
|
200
|
+
munkey.pull
|
201
|
+
munkey.push
|
202
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_push_removes_locally_removed_files
|
206
|
+
FileUtils.cp_r Net::FTP.ftp_src, Net::FTP.ftp_dst
|
207
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
208
|
+
Dir.chdir(@gitdir) do
|
209
|
+
system("git rm README && git commit -m 'removed README'")
|
210
|
+
end
|
211
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
212
|
+
munkey.push
|
213
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_push_excludes_remotely_removed_files
|
217
|
+
FileUtils.cp_r Net::FTP.ftp_src, Net::FTP.ftp_dst
|
218
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
219
|
+
File.unlink File.join(Net::FTP.ftp_src, 'README')
|
220
|
+
munkey.pull
|
221
|
+
munkey.push
|
222
|
+
assert File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_push_excludes_files_with_same_change_local_and_remote
|
226
|
+
Net::FTP.create_ftp_dst
|
227
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
228
|
+
File.open(File.join(Net::FTP.ftp_src, 'README'), 'w') {|f| f.write 'munkey' }
|
229
|
+
Dir.chdir(@gitdir) do
|
230
|
+
File.open('README', 'w') {|f| f.write 'munkey' }
|
231
|
+
system("git add . && git commit -m 'add README CHANGES'")
|
232
|
+
end
|
233
|
+
munkey.pull
|
234
|
+
munkey.push
|
235
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, 'README'))
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_push_adds_a_commit_to_the_munkey_branch
|
239
|
+
Net::FTP.create_ftp_dst
|
240
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
241
|
+
add_file_to_git('newfile')
|
242
|
+
add_file_to_git('secondfile')
|
243
|
+
munkey.push
|
244
|
+
Dir.chdir(@gitdir) do
|
245
|
+
commits = `git log --format=oneline munkey`.strip.split("\n")
|
246
|
+
assert_equal 3, commits.size
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_dryrun_push_doesnt_create_commit_in_munkey_branch
|
251
|
+
Net::FTP.create_ftp_dst
|
252
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
253
|
+
last_commit = File.read File.join(@gitdir, '.git', 'refs', 'heads', 'munkey')
|
254
|
+
add_file_to_git 'newfile'
|
255
|
+
munkey.push(true)
|
256
|
+
assert_equal last_commit, File.read(File.join(@gitdir, '.git', 'refs', 'heads', 'munkey'))
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_dryrun_push_doesnt_upload_files
|
260
|
+
Net::FTP.create_ftp_dst
|
261
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
262
|
+
add_file_to_git 'newfile'
|
263
|
+
munkey.push(true)
|
264
|
+
assert !File.exist?(File.join(Net::FTP.ftp_dst, 'newfile'))
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_last_pull_date
|
268
|
+
munkey = Munkey.clone('ftp://user:pass@test.server/', @gitdir)
|
269
|
+
assert_equal Time.now.to_s, munkey.last_pull_date.to_s
|
270
|
+
end
|
271
|
+
|
272
|
+
protected
|
273
|
+
def create_tmpname
|
274
|
+
tmpname = ''
|
275
|
+
char_list = ("a".."z").to_a + ("0".."9").to_a
|
276
|
+
1.upto(20) { |i| tmpname << char_list[rand(char_list.size)] }
|
277
|
+
return tmpname
|
278
|
+
end
|
279
|
+
|
280
|
+
def add_file_to_git(filename, content = nil)
|
281
|
+
Dir.chdir(@gitdir) do
|
282
|
+
File.open(filename, 'a') {|f| f.write content }
|
283
|
+
system("git add . && git commit -m 'Added file #{filename}'")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: munkey
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- jebw
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-12 00:00:00 +01:00
|
19
|
+
default_executable: munkey
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: ftp_sync
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 4
|
33
|
+
- 1
|
34
|
+
version: 0.4.1
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Tool for using git to push and pull from ftp servers
|
38
|
+
email: jeb@jdwilkins.co.uk
|
39
|
+
executables:
|
40
|
+
- munkey
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README.rdoc
|
45
|
+
files:
|
46
|
+
- lib/gitignore_parser.rb
|
47
|
+
- lib/munkey.rb
|
48
|
+
- bin/munkey
|
49
|
+
- README.rdoc
|
50
|
+
- test/gitignore_test.rb
|
51
|
+
- test/munkey_test.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/jebw/munkey
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --line-numbers
|
59
|
+
- --title
|
60
|
+
- Tool for using git to push and pull from ftp servers
|
61
|
+
- --main
|
62
|
+
- README.rdoc
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.3.7
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Tool for using git to push and pull from ftp servers
|
90
|
+
test_files:
|
91
|
+
- test/gitignore_test.rb
|
92
|
+
- test/munkey_test.rb
|