dandelion 0.1.1
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/.gitignore +4 -0
- data/Gemfile +2 -0
- data/Rakefile +3 -0
- data/bin/dandelion +7 -0
- data/dandelion.gemspec +22 -0
- data/lib/dandelion.rb +50 -0
- data/lib/dandelion/deployment.rb +79 -0
- data/lib/dandelion/git.rb +51 -0
- data/lib/dandelion/service.rb +79 -0
- data/lib/dandelion/version.rb +3 -0
- metadata +103 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/dandelion
ADDED
data/dandelion.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'dandelion/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'dandelion'
|
7
|
+
s.version = Dandelion::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Scott Nelson']
|
10
|
+
s.email = ['scottbnel@gmail.com']
|
11
|
+
s.homepage = 'http://github.com/scottbnel/dandelion'
|
12
|
+
s.summary = "dandelion-#{s.version}"
|
13
|
+
s.description = 'Git repository deployment via SFTP'
|
14
|
+
|
15
|
+
s.add_dependency 'net-sftp', '>= 2.0.5'
|
16
|
+
s.add_dependency 'grit', '>= 2.4.1'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
end
|
data/lib/dandelion.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'dandelion/deployment'
|
2
|
+
require 'dandelion/service'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Dandelion
|
6
|
+
class << self
|
7
|
+
def run
|
8
|
+
unless File.exists? '.git'
|
9
|
+
puts 'Not a git repository: .git'
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
unless File.exists? 'deploy.yml'
|
14
|
+
puts 'Could not find file: deploy.yml'
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
config = YAML.load_file 'deploy.yml'
|
19
|
+
|
20
|
+
if config['scheme'] == 'sftp'
|
21
|
+
service = Service::SFTP.new(config['host'], config['username'], config['password'], config['path'])
|
22
|
+
else
|
23
|
+
puts "Unsupported scheme: #{config['scheme']}"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "Connecting to: #{service.uri}"
|
28
|
+
|
29
|
+
begin
|
30
|
+
# Deploy changes since remote revision
|
31
|
+
deployment = Deployment::DiffDeployment.new('.', service, config['exclude'])
|
32
|
+
|
33
|
+
puts "Remote revision: #{deployment.remote_revision}"
|
34
|
+
puts "Local revision: #{deployment.local_revision}"
|
35
|
+
|
36
|
+
deployment.deploy
|
37
|
+
rescue Deployment::RemoteRevisionError
|
38
|
+
# No remote revision, deploy everything
|
39
|
+
deployment = Deployment::FullDeployment.new('.', service, config['exclude'])
|
40
|
+
|
41
|
+
puts "Remote revision: ---"
|
42
|
+
puts "Local revision: #{deployment.local_revision}"
|
43
|
+
|
44
|
+
deployment.deploy
|
45
|
+
end
|
46
|
+
|
47
|
+
puts "Deployment complete"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'dandelion/git'
|
2
|
+
|
3
|
+
module Deployment
|
4
|
+
class RemoteRevisionError < StandardError; end
|
5
|
+
|
6
|
+
class Deployment
|
7
|
+
def initialize(dir, service, exclude = nil, revision = 'HEAD')
|
8
|
+
@service = service
|
9
|
+
@exclude = exclude || []
|
10
|
+
@tree = Git::Tree.new(dir, revision)
|
11
|
+
end
|
12
|
+
|
13
|
+
def local_revision
|
14
|
+
@tree.revision
|
15
|
+
end
|
16
|
+
|
17
|
+
def remote_uri
|
18
|
+
@service.uri
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_revision
|
22
|
+
@service.write('.revision', local_revision)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class DiffDeployment < Deployment
|
27
|
+
def initialize(dir, service, exclude = nil, revision = 'HEAD')
|
28
|
+
super(dir, service, exclude, revision)
|
29
|
+
@diff = Git::Diff.new(dir, read_revision)
|
30
|
+
end
|
31
|
+
|
32
|
+
def remote_revision
|
33
|
+
@diff.revision
|
34
|
+
end
|
35
|
+
|
36
|
+
def deploy
|
37
|
+
if remote_revision != local_revision
|
38
|
+
@diff.changed.each do |file|
|
39
|
+
unless @exclude.include?(file)
|
40
|
+
puts "Uploading file: #{file}"
|
41
|
+
@service.write(file, @tree.show(file))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@diff.deleted.each do |file|
|
45
|
+
unless @exclude.include?(file)
|
46
|
+
puts "Deleting file: #{file}"
|
47
|
+
@service.delete(file)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
write_revision
|
51
|
+
else
|
52
|
+
puts "Nothing to deploy"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def read_revision
|
59
|
+
begin
|
60
|
+
@service.read('.revision').chomp
|
61
|
+
rescue Net::SFTP::StatusException => e
|
62
|
+
raise unless e.code == 2
|
63
|
+
raise RemoteRevisionError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class FullDeployment < Deployment
|
69
|
+
def deploy
|
70
|
+
@tree.files.each do |file|
|
71
|
+
unless @exclude.include?(file)
|
72
|
+
puts "Uploading file: #{file}"
|
73
|
+
@service.write(file, @tree.show(file))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
write_revision
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'grit'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
class Diff
|
5
|
+
attr_reader :revision
|
6
|
+
|
7
|
+
def initialize(dir, revision)
|
8
|
+
@revision = revision
|
9
|
+
@raw = `cd #{dir}; git diff --name-status #{@revision} HEAD`
|
10
|
+
end
|
11
|
+
|
12
|
+
def changed
|
13
|
+
files_flagged ['A', 'C', 'M']
|
14
|
+
end
|
15
|
+
|
16
|
+
def deleted
|
17
|
+
files_flagged ['D']
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def files_flagged(statuses)
|
23
|
+
items = []
|
24
|
+
@raw.split("\n").each do |line|
|
25
|
+
status, file = line.split("\t")
|
26
|
+
items << file if statuses.include? status
|
27
|
+
end
|
28
|
+
items
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Tree
|
33
|
+
def initialize(dir, revision)
|
34
|
+
@dir = dir
|
35
|
+
@commit = Grit::Repo.new(dir).commit(revision)
|
36
|
+
@tree = @commit.tree
|
37
|
+
end
|
38
|
+
|
39
|
+
def files
|
40
|
+
`cd #{@dir}; git ls-tree --name-only -r #{revision}`.split("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
def show(file)
|
44
|
+
(@tree / file).data
|
45
|
+
end
|
46
|
+
|
47
|
+
def revision
|
48
|
+
@commit.sha
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Service
|
5
|
+
class Service
|
6
|
+
def initialize(host, username, path)
|
7
|
+
@host = host
|
8
|
+
@username = username
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
def uri
|
13
|
+
"#{@scheme}://#{@username}@#{@host}/#{@path}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class SFTP < Service
|
18
|
+
def initialize(host, username, password, path)
|
19
|
+
super(host, username, path)
|
20
|
+
@scheme = 'sftp'
|
21
|
+
@sftp = Net::SFTP.start(host, username, :password => password)
|
22
|
+
end
|
23
|
+
|
24
|
+
def read(file)
|
25
|
+
@sftp.file.open(File.join(@path, file), 'r') do |f|
|
26
|
+
f.gets
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(file, data)
|
31
|
+
path = File.join(@path, file)
|
32
|
+
begin
|
33
|
+
dir = File.dirname(path)
|
34
|
+
@sftp.stat!(dir)
|
35
|
+
rescue Net::SFTP::StatusException => e
|
36
|
+
raise unless e.code == 2
|
37
|
+
mkdir_p(dir)
|
38
|
+
end
|
39
|
+
tmp = Tempfile.new(file.gsub('/', '.'))
|
40
|
+
tmp << data
|
41
|
+
tmp.flush
|
42
|
+
@sftp.upload!(tmp.path, path)
|
43
|
+
tmp.close
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(file)
|
47
|
+
path = File.join(@path, file)
|
48
|
+
@sftp.remove!(path)
|
49
|
+
cleanup(File.dirname(path))
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def cleanup(dir)
|
55
|
+
unless File.identical?(dir, @path)
|
56
|
+
if empty?(dir)
|
57
|
+
@sftp.rmdir!(dir)
|
58
|
+
cleanup(File.dirname(dir))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def empty?(dir)
|
64
|
+
@sftp.dir.entries(dir).map do |entry|
|
65
|
+
entry.name unless entry.name == '.' or entry.name == '..'
|
66
|
+
end.compact.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
def mkdir_p(dir)
|
70
|
+
begin
|
71
|
+
@sftp.mkdir!(dir)
|
72
|
+
rescue Net::SFTP::StatusException => e
|
73
|
+
raise unless e.code == 2
|
74
|
+
mkdir_p(File.dirname(dir))
|
75
|
+
mkdir_p(dir)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dandelion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Scott Nelson
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-21 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: net-sftp
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 0
|
31
|
+
- 5
|
32
|
+
version: 2.0.5
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: grit
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 2
|
45
|
+
- 4
|
46
|
+
- 1
|
47
|
+
version: 2.4.1
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Git repository deployment via SFTP
|
51
|
+
email:
|
52
|
+
- scottbnel@gmail.com
|
53
|
+
executables:
|
54
|
+
- dandelion
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- .gitignore
|
61
|
+
- Gemfile
|
62
|
+
- Rakefile
|
63
|
+
- bin/dandelion
|
64
|
+
- dandelion.gemspec
|
65
|
+
- lib/dandelion.rb
|
66
|
+
- lib/dandelion/deployment.rb
|
67
|
+
- lib/dandelion/git.rb
|
68
|
+
- lib/dandelion/service.rb
|
69
|
+
- lib/dandelion/version.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/scottbnel/dandelion
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.3.7
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: dandelion-0.1.1
|
102
|
+
test_files: []
|
103
|
+
|