girror 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.cvsignore +3 -0
- data/.document +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +123 -0
- data/Rakefile +43 -0
- data/TODO.md +8 -0
- data/VERSION +1 -0
- data/bin/girror +78 -0
- data/examples/_girror/config.rb +18 -0
- data/gem_graph.png +0 -0
- data/girror.gemspec +92 -0
- data/lib/girror.rb +275 -0
- metadata +238 -0
data/.cvsignore
ADDED
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.1"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
14
|
+
|
15
|
+
gem "net-sftp"
|
16
|
+
gem "git"
|
17
|
+
gem "highline"
|
18
|
+
gem "syslog-logger"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
highline (1.6.1)
|
6
|
+
jeweler (1.5.1)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
net-sftp (2.0.5)
|
11
|
+
net-ssh (>= 2.0.9)
|
12
|
+
net-ssh (2.0.23)
|
13
|
+
rake (0.8.7)
|
14
|
+
rcov (0.9.9)
|
15
|
+
shoulda (2.11.3)
|
16
|
+
syslog-logger (1.6.4)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
x86-mingw32
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
bundler (~> 1.0.0)
|
24
|
+
git
|
25
|
+
highline
|
26
|
+
jeweler (~> 1.5.1)
|
27
|
+
net-sftp
|
28
|
+
rcov
|
29
|
+
shoulda
|
30
|
+
syslog-logger
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
== MIT license
|
2
|
+
|
3
|
+
Copyright (c) 2010-2011 Pavel Argentov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
= girror
|
2
|
+
|
3
|
+
Girror == [G]it the M[irror]
|
4
|
+
|
5
|
+
A CLI remote-to-local directory mirroring tool, the poor man's "filezilla" which
|
6
|
+
I couldn't google up, so decided to make it myself.
|
7
|
+
|
8
|
+
What does it do?
|
9
|
+
|
10
|
+
- Logs into the remote sftp server;
|
11
|
+
- Changes to the dir indicated in the CLI parameter;
|
12
|
+
- Gets the directory tree from there;
|
13
|
+
- Compares the tree to the local one;
|
14
|
+
- Downloads all the files/directories which are:
|
15
|
+
- don't exist in the local tree,
|
16
|
+
- newer than local ones (mtime comparison),
|
17
|
+
- have the mode/owner changed (this feature is available only if the local
|
18
|
+
system is Unix);
|
19
|
+
- Removes all the removed-on-remote files;
|
20
|
+
- Wraps all the operations into git transactions (creation/deletion
|
21
|
+
of files/dirs);
|
22
|
+
- Commits the changes to the local git repository.
|
23
|
+
|
24
|
+
Girror doesn't need any special server software on the remote side
|
25
|
+
besides the ssh/sftp.
|
26
|
+
|
27
|
+
== CLI reference
|
28
|
+
|
29
|
+
=== girror -h
|
30
|
+
Girror cli tool, version 0.0.1.
|
31
|
+
|
32
|
+
Mirrors the remote sftp site and stores the changes in a local git repo.
|
33
|
+
|
34
|
+
Command Line Usage:
|
35
|
+
|
36
|
+
girror [options] remote_uri
|
37
|
+
|
38
|
+
remote_uri: a complete sftp uri of a remote location;
|
39
|
+
|
40
|
+
remote_uri may be in the form of either "user:pass@host:path" or
|
41
|
+
"host:path". In the latter case username is taken from the environment
|
42
|
+
variable $USERNAME.
|
43
|
+
|
44
|
+
Options:
|
45
|
+
-l, --log place place = ['syslog' or a filename]: Logging destination (default is STDERR).
|
46
|
+
--renc encoding Remote filename encoding; defaults to 'utf-8'.
|
47
|
+
--lenc encoding Local filename encoding; defaults to 'utf-8'.
|
48
|
+
-o, --output path Output directory, should be a git repo workdir.
|
49
|
+
-v, --verbose Be verbose: log tons of debugging. THOUSANDS OF THEM!
|
50
|
+
-h, --help Display this help message.
|
51
|
+
|
52
|
+
== Configuration file
|
53
|
+
|
54
|
+
Some runtime options may be specified in _girror/config.rb file in local "mirror" directory.
|
55
|
+
Here's the example with the tested options:
|
56
|
+
|
57
|
+
=== _girror/config.rb
|
58
|
+
#
|
59
|
+
# girror config for current mirror
|
60
|
+
#
|
61
|
+
module Config
|
62
|
+
|
63
|
+
# Program options for the current instance
|
64
|
+
OPTIONS = {
|
65
|
+
# custom commit message
|
66
|
+
:commit_msg => Proc.new {"State at #{Time.now}"},
|
67
|
+
|
68
|
+
# local filename encoding
|
69
|
+
:lenc => 'cp1251',
|
70
|
+
|
71
|
+
# remote filename encoding
|
72
|
+
:renc => 'koi8-r'
|
73
|
+
}
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
== Usage example
|
78
|
+
|
79
|
+
[paul@paul site]$ git init mirror
|
80
|
+
Initialized empty Git repository in /usr/home/paul/work/Krotov/site/mirror/.git/
|
81
|
+
[paul@paul site]$ girror -o mirror paul@paul:/home/paul/develop/ruby/girror
|
82
|
+
I, [2010-12-31T13:43:35.843256 #2341] INFO -- : Starting
|
83
|
+
I, [2010-12-31T13:43:35.843515 #2341] INFO -- : Opening local git repo at mirror
|
84
|
+
I, [2010-12-31T13:43:35.843848 #2341] INFO -- : Changed to /usr/home/paul/work/Krotov/site/mirror
|
85
|
+
I, [2010-12-31T13:43:35.860511 #2341] INFO -- : Not using stored config: no such file to load -- config
|
86
|
+
Enter passphrase for /home/paul/.ssh/id_dsa:
|
87
|
+
I, [2010-12-31T13:43:40.856664 #2341] INFO -- : Connected to remote paul as paul
|
88
|
+
I, [2010-12-31T13:43:40.863612 #2341] INFO -- : Fetching file /home/paul/develop/ruby/girror/Rakefile -> ./Rakefile (1565 bytes)
|
89
|
+
I, [2010-12-31T13:43:40.866610 #2341] INFO -- : Setting mode: ./Rakefile => 100644
|
90
|
+
I, [2010-12-31T13:43:40.866853 #2341] INFO -- : Setting mtime: ./Rakefile => ["2010-12-31 13:39:07", "2010-12-31 13:28:11"]
|
91
|
+
I, [2010-12-31T13:43:40.882452 #2341] INFO -- : Fetching file /home/paul/develop/ruby/girror/Gemfile -> ./Gemfile (423 bytes)
|
92
|
+
I, [2010-12-31T13:43:40.885195 #2341] INFO -- : Setting mode: ./Gemfile => 100644
|
93
|
+
I, [2010-12-31T13:43:40.885317 #2341] INFO -- : Setting mtime: ./Gemfile => ["2010-12-31 13:39:08", "2010-12-28 16:26:55"]
|
94
|
+
|
95
|
+
...
|
96
|
+
|
97
|
+
I, [2010-12-31T13:43:40.974504 #2341] INFO -- : Fetching directory /home/paul/develop/ruby/girror/pkg -> ./pkg | [2010-12-31 13:39:07 +0300, 2010-12-31 13:39:08 +0300, 0, 0, "40755"]
|
98
|
+
I, [2010-12-31T13:43:40.980814 #2341] INFO -- : Fetching file /home/paul/develop/ruby/girror/pkg/girror-0.0.0.gem -> ./pkg/girror-0.0.0.gem (61440 bytes)
|
99
|
+
I, [2010-12-31T13:43:40.995837 #2341] INFO -- : Setting mode: ./pkg/girror-0.0.0.gem => 100644
|
100
|
+
I, [2010-12-31T13:43:40.996319 #2341] INFO -- : Setting mtime: ./pkg/girror-0.0.0.gem => ["2010-12-31 13:39:10", "2010-12-31 13:39:07"]
|
101
|
+
I, [2010-12-31T13:43:40.996829 #2341] INFO -- : Setting mode: ./pkg => 40755
|
102
|
+
I, [2010-12-31T13:43:40.997039 #2341] INFO -- : Setting mtime: ./pkg => ["2010-12-31 13:39:08", "2010-12-31 13:39:07"]
|
103
|
+
I, [2010-12-31T13:43:40.997403 #2341] INFO -- : Disconnected from remote paul
|
104
|
+
I, [2010-12-31T13:43:40.997466 #2341] INFO -- : Committing changes to local git repo
|
105
|
+
I, [2010-12-31T13:43:41.061205 #2341] INFO -- : Finishing
|
106
|
+
|
107
|
+
Note that remote git-related files aren't got.
|
108
|
+
|
109
|
+
== Contributing to girror
|
110
|
+
|
111
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
112
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
113
|
+
* Fork the project
|
114
|
+
* Start a feature/bugfix branch
|
115
|
+
* Commit and push until you are happy with your contribution
|
116
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
117
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
118
|
+
|
119
|
+
== Copyright
|
120
|
+
|
121
|
+
Copyright (c) 2010 Pavel Argentov. See LICENSE.txt for
|
122
|
+
further details.
|
123
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
16
|
+
gem.name = "girror"
|
17
|
+
gem.homepage = "http://github.com/argent-smith/girror"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = %Q{Remote -> local directory 'mirror' using SFTP transport and Git storage.}
|
20
|
+
gem.description = %Q{Retrieves remote directory via SFTP and stores it in local Git repository.}
|
21
|
+
gem.email = "argentoff@gmail.com"
|
22
|
+
gem.authors = ["Pavel Argentov"]
|
23
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
24
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
25
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
26
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
27
|
+
gem.add_runtime_dependency 'net-sftp'
|
28
|
+
gem.add_runtime_dependency 'git'
|
29
|
+
gem.add_runtime_dependency 'highline'
|
30
|
+
gem.add_runtime_dependency 'syslog-logger'
|
31
|
+
end
|
32
|
+
Jeweler::RubygemsDotOrgTasks.new
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
37
|
+
|
38
|
+
rdoc.rdoc_dir = 'rdoc'
|
39
|
+
rdoc.title = "girror #{version}"
|
40
|
+
rdoc.rdoc_files.include('README*')
|
41
|
+
rdoc.rdoc_files.include('LICENSE*')
|
42
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
|
+
end
|
data/TODO.md
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/girror
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# -*- Ruby -*-
|
4
|
+
#
|
5
|
+
# Author:: Pavel Argentov <argentoff@gmail.com>
|
6
|
+
#
|
7
|
+
# Mirror the remote sftp site and store the changes in a local git repo.
|
8
|
+
|
9
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
10
|
+
|
11
|
+
require 'optparse'
|
12
|
+
require 'girror'
|
13
|
+
|
14
|
+
help = <<HELP
|
15
|
+
Girror cli tool, version #{Girror::VERSION}.
|
16
|
+
|
17
|
+
Mirrors the remote sftp site and stores the changes in a local git repo.
|
18
|
+
|
19
|
+
Command Line Usage:
|
20
|
+
|
21
|
+
girror [options] remote_uri
|
22
|
+
|
23
|
+
remote_uri: a complete sftp uri of a remote location;
|
24
|
+
|
25
|
+
remote_uri may be in the form of either "user:pass@host:path" or
|
26
|
+
"host:path". In the latter case username is taken from the environment
|
27
|
+
variable $USERNAME.
|
28
|
+
|
29
|
+
Options:
|
30
|
+
HELP
|
31
|
+
|
32
|
+
options = {}
|
33
|
+
opts = OptionParser.new do |opts|
|
34
|
+
opts.banner = help
|
35
|
+
|
36
|
+
opts.on("-l place", "--log place", "place = ['syslog' or a filename]: Logging destination (default is STDERR).") do |log|
|
37
|
+
options[:log] = log
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--renc encoding", "Remote filename encoding; defaults to 'utf-8'.") do |enc|
|
41
|
+
options[:renc] = enc
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("--lenc encoding", "Local filename encoding; defaults to 'utf-8'.") do |enc|
|
45
|
+
options[:lenc] = enc
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-o path", "--output path", "Output directory, should be a git repo workdir.") do |path|
|
49
|
+
options[:to] = path
|
50
|
+
end
|
51
|
+
|
52
|
+
# opts.on("--dr", "Dry run: only logs what's to be done") do |addr|
|
53
|
+
# options[:dr] = true
|
54
|
+
# end
|
55
|
+
|
56
|
+
opts.on("-v", "--verbose", "Be verbose: log tons of debugging. THOUSANDS OF THEM!") do
|
57
|
+
options[:verbose] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-h", "--help", "Display this help message.") do
|
61
|
+
puts opts.help
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Read command line options into `options` hash
|
67
|
+
opts.parse!
|
68
|
+
|
69
|
+
case ARGV.size
|
70
|
+
when 1
|
71
|
+
options[:from] = ARGV[0]
|
72
|
+
options[:to] = "." if options[:to].nil?
|
73
|
+
else
|
74
|
+
puts "Incorrect arguments; use -h to see help."
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
Girror::Application.run options
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#
|
2
|
+
# girror config for current mirror
|
3
|
+
#
|
4
|
+
module Config
|
5
|
+
|
6
|
+
# Program options for the current instance
|
7
|
+
OPTIONS = {
|
8
|
+
# custom commit message
|
9
|
+
:commit_msg => Proc.new {"State at #{Time.now}"},
|
10
|
+
|
11
|
+
# local filename encoding
|
12
|
+
:lenc => 'cp1251',
|
13
|
+
|
14
|
+
# remote filename encoding
|
15
|
+
:renc => 'koi8-r'
|
16
|
+
}
|
17
|
+
|
18
|
+
end
|
data/gem_graph.png
ADDED
Binary file
|
data/girror.gemspec
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{girror}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Pavel Argentov"]
|
12
|
+
s.date = %q{2010-12-31}
|
13
|
+
s.default_executable = %q{girror}
|
14
|
+
s.description = %q{Retrieves remote directory via SFTP and stores it in local Git repository.}
|
15
|
+
s.email = %q{argentoff@gmail.com}
|
16
|
+
s.executables = ["girror"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".cvsignore",
|
23
|
+
".document",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"TODO.md",
|
30
|
+
"VERSION",
|
31
|
+
"bin/girror",
|
32
|
+
"examples/_girror/config.rb",
|
33
|
+
"gem_graph.png",
|
34
|
+
"girror.gemspec",
|
35
|
+
"lib/girror.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/argent-smith/girror}
|
38
|
+
s.licenses = ["MIT"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.7}
|
41
|
+
s.summary = %q{Remote -> local directory 'mirror' using SFTP transport and Git storage.}
|
42
|
+
s.test_files = [
|
43
|
+
"examples/_girror/config.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<net-sftp>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<git>, [">= 0"])
|
53
|
+
s.add_runtime_dependency(%q<highline>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<syslog-logger>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
s.add_runtime_dependency(%q<net-sftp>, [">= 0"])
|
60
|
+
s.add_runtime_dependency(%q<git>, [">= 0"])
|
61
|
+
s.add_runtime_dependency(%q<highline>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<syslog-logger>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<net-sftp>, [">= 0"])
|
65
|
+
s.add_dependency(%q<git>, [">= 0"])
|
66
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
67
|
+
s.add_dependency(%q<syslog-logger>, [">= 0"])
|
68
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
69
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
70
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
71
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
72
|
+
s.add_dependency(%q<net-sftp>, [">= 0"])
|
73
|
+
s.add_dependency(%q<git>, [">= 0"])
|
74
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
75
|
+
s.add_dependency(%q<syslog-logger>, [">= 0"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<net-sftp>, [">= 0"])
|
79
|
+
s.add_dependency(%q<git>, [">= 0"])
|
80
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
81
|
+
s.add_dependency(%q<syslog-logger>, [">= 0"])
|
82
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
83
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
84
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
85
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
86
|
+
s.add_dependency(%q<net-sftp>, [">= 0"])
|
87
|
+
s.add_dependency(%q<git>, [">= 0"])
|
88
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
89
|
+
s.add_dependency(%q<syslog-logger>, [">= 0"])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
data/lib/girror.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# Author:: Pavel Argentov <argentoff@gmail.com>
|
2
|
+
# Copyright:: (c) 2010-2011 Pavel Argentov
|
3
|
+
# License:: see LICENSE.txt
|
4
|
+
#
|
5
|
+
# Girror library code, the internals. Since the whole app is a CLI utility,
|
6
|
+
# there's not a lot to document here.
|
7
|
+
#
|
8
|
+
# The library now contains only one module Girror which contains class Application
|
9
|
+
# which is the app logic container. The methods have some documentation in the corresponding
|
10
|
+
# section (Girror::Application).
|
11
|
+
#
|
12
|
+
# == Disclaimer
|
13
|
+
#
|
14
|
+
# This documentation is written as an aid to further development of the program.
|
15
|
+
# For more user-friendly documentation see README.rdoc file.
|
16
|
+
#
|
17
|
+
|
18
|
+
######## Utility
|
19
|
+
|
20
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
21
|
+
|
22
|
+
# Require all of the Ruby files in the given directory.
|
23
|
+
#
|
24
|
+
# path - The String relative path from here to the directory.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def require_all(path) # :nodoc:
|
28
|
+
glob = File.join(File.dirname(__FILE__), path, '*.rb')
|
29
|
+
Dir[glob].each do |f|
|
30
|
+
require f
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
######## Requires
|
35
|
+
require 'singleton'
|
36
|
+
require 'net/sftp'
|
37
|
+
require 'fileutils'
|
38
|
+
require 'git'
|
39
|
+
require 'iconv'
|
40
|
+
|
41
|
+
# This module encapsulates *girror*'s namespace.
|
42
|
+
module Girror
|
43
|
+
# Version of the library.
|
44
|
+
VERSION = "0.0.1"
|
45
|
+
|
46
|
+
# Regexp to filter out 'technical' files which aren't normally a part of
|
47
|
+
# the mirrored tree and therefore should be _ignored_.
|
48
|
+
FILTER_RE = /^((\.((\.{0,1})|((git)(ignore)?)))|(_girror))$/
|
49
|
+
|
50
|
+
# Application logic container.
|
51
|
+
class Application
|
52
|
+
include Singleton
|
53
|
+
|
54
|
+
class << self # class things
|
55
|
+
include FileUtils
|
56
|
+
|
57
|
+
# Runs the app. Much like the C's main().
|
58
|
+
def run ops
|
59
|
+
# Logging setup
|
60
|
+
@log = case ops[:log]
|
61
|
+
when 'syslog'
|
62
|
+
unless ENV['OS'] == 'Windows_NT'
|
63
|
+
require 'syslog_logger'
|
64
|
+
SyslogLogger.new('girror')
|
65
|
+
else
|
66
|
+
Logger.new STDERR
|
67
|
+
end
|
68
|
+
when nil
|
69
|
+
Logger.new STDERR
|
70
|
+
else
|
71
|
+
Logger.new ops[:log]
|
72
|
+
end
|
73
|
+
@log.datetime_format = "%Y-%m-%d %H:%M:%S " if Logger.class == Logger
|
74
|
+
log "Starting"
|
75
|
+
@debug = true if ops[:verbose]
|
76
|
+
debug "Current options are: #{ops.inspect}"
|
77
|
+
|
78
|
+
# check the validity of a local directory
|
79
|
+
@lpath = ops[:to] # local save path
|
80
|
+
log "Opening local git repo at #{@lpath}"
|
81
|
+
@git = Git.open(@lpath) # local git repo
|
82
|
+
|
83
|
+
cd ops[:to]; log "Changed to #{pwd}"
|
84
|
+
|
85
|
+
# read the config and use CLI ops to override it
|
86
|
+
$:.unshift(File.join(".", "_girror"))
|
87
|
+
begin
|
88
|
+
require 'config'
|
89
|
+
ops = Config::OPTIONS.merge ops
|
90
|
+
|
91
|
+
begin
|
92
|
+
debug "Program options:"
|
93
|
+
ops.each do |pair|
|
94
|
+
debug pair.inspect
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
rescue LoadError => d
|
99
|
+
log "Not using stored config: #{d.message}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# set commit message for git
|
103
|
+
ops[:commit_msg].nil? ? @commit_msg = Proc.new { Time.now.to_s } : @commit_msg = ops[:commit_msg]
|
104
|
+
|
105
|
+
# name conversion encodings for Iconv
|
106
|
+
ops[:renc].nil? ? @renc = "utf-8" : @renc = ops[:renc]
|
107
|
+
ops[:lenc].nil? ? @lenc = "utf-8" : @lenc = ops[:lenc]
|
108
|
+
|
109
|
+
# Check the validity of a remote url and run the remote connection
|
110
|
+
if ops[:from] =~ /^((\w+)(:(\w+))?@)?(.+):(.*)$/
|
111
|
+
$2.nil? ? @user = ENV["USERNAME"] : @user = $2
|
112
|
+
@pass = $4
|
113
|
+
@host = $5
|
114
|
+
@path = $6
|
115
|
+
|
116
|
+
debug "Remote data specified as: login: #{@user}; pass: #{@pass.inspect}; host: #{@host}; path: #{@path}"
|
117
|
+
Net::SFTP.start(@host, @user, :password => @pass) do |s|
|
118
|
+
@sftp = s
|
119
|
+
log "Connected to remote #{@host} as #{@user}"
|
120
|
+
|
121
|
+
dl_if_needed @path
|
122
|
+
|
123
|
+
log "Disconnected from remote #{@host}"
|
124
|
+
|
125
|
+
# fix the local tree in the git repo
|
126
|
+
begin
|
127
|
+
log "Committing changes to local git repo"
|
128
|
+
@git.add
|
129
|
+
msg = if @commit_msg.class == Proc then @commit_msg.call
|
130
|
+
else @commit_msg
|
131
|
+
end . to_s
|
132
|
+
@git.commit msg, :add_all => true
|
133
|
+
rescue Git::GitExecuteError => detail
|
134
|
+
case detail.message
|
135
|
+
when /nothing to commit/
|
136
|
+
log "Nothing to commit"
|
137
|
+
else
|
138
|
+
log detail.message
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
else
|
145
|
+
raise "Bad remote specification!"
|
146
|
+
end
|
147
|
+
|
148
|
+
log "Finishing"
|
149
|
+
end
|
150
|
+
|
151
|
+
# Writes a debug message to @log.
|
152
|
+
def debug string
|
153
|
+
@log.debug string if @debug
|
154
|
+
end
|
155
|
+
|
156
|
+
# Writes a log message to @log.
|
157
|
+
def log string
|
158
|
+
@log.info string
|
159
|
+
end
|
160
|
+
|
161
|
+
# On-demand fetcher. Recursively fetches a directory entry 'name' (String)
|
162
|
+
# if there's no local copy of it or the remote mtime is newer or the
|
163
|
+
# attributes are to be updated.
|
164
|
+
def dl_if_needed name
|
165
|
+
debug "RNA: #{name}"
|
166
|
+
lname = econv(File.join '.', name.gsub(/^#{@path}/,'')); debug "LNA: #{lname}"
|
167
|
+
|
168
|
+
# get and hold the current direntry's stat in here
|
169
|
+
begin
|
170
|
+
rs = @sftp.stat!(name); s_rs = [Time.at(rs.mtime), Time.at(rs.atime), rs.uid, rs.gid, "%o" % rs.permissions].inspect
|
171
|
+
rescue Net::SFTP::StatusException => detail
|
172
|
+
return if detail.code == 2 # silently ignore the broken remote link
|
173
|
+
end
|
174
|
+
debug "Remote stat for #{name} => #{s_rs}"
|
175
|
+
|
176
|
+
# remote type filter: we only work with types 1..2 (regular, dir)
|
177
|
+
begin
|
178
|
+
debug "Remote file type #{rs.type} isn't supported, ignoring."
|
179
|
+
return
|
180
|
+
end if rs.type > 2
|
181
|
+
|
182
|
+
# remove the local entry if local/remote entry type differ;
|
183
|
+
# else compare remote/local owner/mode and schedule the update.
|
184
|
+
if File.exist? lname
|
185
|
+
if (
|
186
|
+
rs.type != case File.ftype lname
|
187
|
+
when "file" then 1
|
188
|
+
when "directory" then 2
|
189
|
+
end
|
190
|
+
)
|
191
|
+
remove_entry_secure lname, :force => true
|
192
|
+
else
|
193
|
+
lrs = File.stat(lname)
|
194
|
+
# we do mode comparison on Unices only,
|
195
|
+
# and owner compaison only if we are root
|
196
|
+
unless ENV['OS'] == "Windows_NT"
|
197
|
+
set_attrs = true unless (
|
198
|
+
if ENV['EUID'] == 0
|
199
|
+
debug "Comparing: #{[rs.permissions, rs.uid, rs.gid].inspect} <=> #{[lrs.mode, lrs.uid, lrs.gid].inspect}"
|
200
|
+
[lrs.mode, lrs.uid, lrs.gid] == [rs.permissions, rs.uid, rs.gid]
|
201
|
+
else
|
202
|
+
debug "Comparing: #{rs.permissions} <=> #{lrs.mode}"
|
203
|
+
lrs.mode == rs.permissions
|
204
|
+
end
|
205
|
+
)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# do the type-specific fetch operations
|
211
|
+
case rs.type
|
212
|
+
when 1
|
213
|
+
if (lrs.nil? or (lrs.mtime.to_i < rs.mtime))
|
214
|
+
log "Fetching file #{name} -> #{lname.force_encoding("BINARY")} (#{rs.size} bytes)"
|
215
|
+
@sftp.download! name, lname
|
216
|
+
set_attrs = true
|
217
|
+
end
|
218
|
+
when 2
|
219
|
+
# here we've got a dir
|
220
|
+
# create the dir locally if needed
|
221
|
+
unless File.exist?(lname)
|
222
|
+
log "Fetching directory #{name} -> #{lname.force_encoding("BINARY")} | #{s_rs}"
|
223
|
+
mkdir lname
|
224
|
+
set_attrs = true
|
225
|
+
end
|
226
|
+
# recurse into the dir; get the remote list
|
227
|
+
rlist = @sftp.dir.entries(name).map do |e|
|
228
|
+
unless e.name =~ FILTER_RE
|
229
|
+
dl_if_needed(File.join(name, e.name))
|
230
|
+
Iconv.conv("utf-8", @renc, e.name)
|
231
|
+
end
|
232
|
+
end . compact
|
233
|
+
|
234
|
+
# get the local list
|
235
|
+
llist = Dir.entries(lname).map do |n|
|
236
|
+
Iconv.conv("utf-8", @lenc, n) unless n =~ FILTER_RE
|
237
|
+
end . compact
|
238
|
+
|
239
|
+
# differentiate the lists; remove what's needed from local repo
|
240
|
+
diff = llist - rlist
|
241
|
+
diff.each do |n|
|
242
|
+
n = File.join(lname, n)
|
243
|
+
log "Removing #{n}"
|
244
|
+
@git.remove n, :recursive => true
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
# do the common after-fetch tasks (chown, chmod, utime)
|
250
|
+
unless lname == "./"
|
251
|
+
unless ENV['OS'] == "Windows_NT" # chmod/chown issues on that platform
|
252
|
+
if ENV['EUID'] == 0
|
253
|
+
log "Setting owner: #{lname} => #{[rs.uid, rs.gid].inspect}"
|
254
|
+
File.chown rs.uid, rs.gid, lname
|
255
|
+
end
|
256
|
+
log "Setting mode: #{lname} => #{"%o" % rs.permissions}"
|
257
|
+
File.chmod rs.permissions, lname
|
258
|
+
end
|
259
|
+
log "Setting mtime: #{lname} => #{[rs.atime, rs.mtime].map{|t| Time.at(t).strftime("%Y-%m-%d %H:%M:%S")}.inspect}"
|
260
|
+
File.utime rs.atime, rs.mtime, lname
|
261
|
+
end if set_attrs
|
262
|
+
end
|
263
|
+
|
264
|
+
# Converts the String str from @renc to @lenc if both @renc and @lenc are
|
265
|
+
# set and aren't equal.
|
266
|
+
#
|
267
|
+
# Returns the converted String.
|
268
|
+
def econv str
|
269
|
+
((@lenc == @renc) or (@lenc.nil? or @renc.nil?)) ?
|
270
|
+
str : Iconv.conv(@lenc, @renc, str)
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
metadata
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: girror
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Pavel Argentov
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-31 00:00:00 +03:00
|
18
|
+
default_executable: girror
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: net-sftp
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
prerelease: false
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: git
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: highline
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: syslog-logger
|
61
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: shoulda
|
74
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: *id005
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: bundler
|
87
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 1
|
94
|
+
- 0
|
95
|
+
- 0
|
96
|
+
version: 1.0.0
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: *id006
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: jeweler
|
102
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ~>
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 1
|
109
|
+
- 5
|
110
|
+
- 1
|
111
|
+
version: 1.5.1
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: *id007
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: rcov
|
117
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: *id008
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: net-sftp
|
130
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
version: "0"
|
138
|
+
type: :runtime
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: *id009
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: git
|
143
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
type: :runtime
|
152
|
+
prerelease: false
|
153
|
+
version_requirements: *id010
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: highline
|
156
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
type: :runtime
|
165
|
+
prerelease: false
|
166
|
+
version_requirements: *id011
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: syslog-logger
|
169
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
segments:
|
175
|
+
- 0
|
176
|
+
version: "0"
|
177
|
+
type: :runtime
|
178
|
+
prerelease: false
|
179
|
+
version_requirements: *id012
|
180
|
+
description: Retrieves remote directory via SFTP and stores it in local Git repository.
|
181
|
+
email: argentoff@gmail.com
|
182
|
+
executables:
|
183
|
+
- girror
|
184
|
+
extensions: []
|
185
|
+
|
186
|
+
extra_rdoc_files:
|
187
|
+
- LICENSE.txt
|
188
|
+
- README.rdoc
|
189
|
+
files:
|
190
|
+
- .cvsignore
|
191
|
+
- .document
|
192
|
+
- Gemfile
|
193
|
+
- Gemfile.lock
|
194
|
+
- LICENSE.txt
|
195
|
+
- README.rdoc
|
196
|
+
- Rakefile
|
197
|
+
- TODO.md
|
198
|
+
- VERSION
|
199
|
+
- bin/girror
|
200
|
+
- examples/_girror/config.rb
|
201
|
+
- gem_graph.png
|
202
|
+
- girror.gemspec
|
203
|
+
- lib/girror.rb
|
204
|
+
has_rdoc: true
|
205
|
+
homepage: http://github.com/argent-smith/girror
|
206
|
+
licenses:
|
207
|
+
- MIT
|
208
|
+
post_install_message:
|
209
|
+
rdoc_options: []
|
210
|
+
|
211
|
+
require_paths:
|
212
|
+
- lib
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
none: false
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
hash: 452461409
|
219
|
+
segments:
|
220
|
+
- 0
|
221
|
+
version: "0"
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
none: false
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
segments:
|
228
|
+
- 0
|
229
|
+
version: "0"
|
230
|
+
requirements: []
|
231
|
+
|
232
|
+
rubyforge_project:
|
233
|
+
rubygems_version: 1.3.7
|
234
|
+
signing_key:
|
235
|
+
specification_version: 3
|
236
|
+
summary: Remote -> local directory 'mirror' using SFTP transport and Git storage.
|
237
|
+
test_files:
|
238
|
+
- examples/_girror/config.rb
|