munkey 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +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
|