dandelion 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +49 -21
- data/bin/dandelion +2 -2
- data/lib/dandelion/application.rb +73 -0
- data/lib/dandelion/backend.rb +1 -1
- data/lib/dandelion/backend/ftp.rb +1 -1
- data/lib/dandelion/backend/s3.rb +1 -1
- data/lib/dandelion/backend/sftp.rb +1 -1
- data/lib/dandelion/command.rb +118 -0
- data/lib/dandelion/command/deploy.rb +55 -0
- data/lib/dandelion/command/status.rb +22 -0
- data/lib/dandelion/deployment.rb +20 -14
- data/lib/dandelion/git.rb +2 -0
- data/lib/dandelion/version.rb +1 -1
- data/test/test_diff_deployment.rb +1 -1
- metadata +86 -53
- data/lib/dandelion/cli.rb +0 -201
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
Dandelion
|
2
2
|
=========
|
3
|
+
Incremental Git repository deployment.
|
3
4
|
|
4
5
|
Install
|
5
6
|
-------
|
@@ -15,8 +16,8 @@ Alternatively, you can build the gem yourself:
|
|
15
16
|
|
16
17
|
Config
|
17
18
|
------
|
18
|
-
Configuration options are specified in a YAML file (the root of your
|
19
|
-
is searched for a file named `dandelion.yml`
|
19
|
+
Configuration options are specified in a YAML file (by default, the root of your
|
20
|
+
Git repository is searched for a file named `dandelion.yml`). Example:
|
20
21
|
|
21
22
|
# Required
|
22
23
|
scheme: sftp
|
@@ -38,18 +39,43 @@ by the given scheme.
|
|
38
39
|
|
39
40
|
**SFTP**: `scheme: sftp`
|
40
41
|
|
41
|
-
Required:
|
42
|
-
|
42
|
+
Required:
|
43
|
+
|
44
|
+
* `host`
|
45
|
+
* `username`
|
46
|
+
* `password`
|
47
|
+
|
48
|
+
Optional:
|
49
|
+
|
50
|
+
* `path`
|
51
|
+
* `exclude`
|
43
52
|
|
44
53
|
**FTP**: `scheme: ftp`
|
45
54
|
|
46
|
-
Required:
|
47
|
-
|
55
|
+
Required:
|
56
|
+
|
57
|
+
* `host`
|
58
|
+
* `username`
|
59
|
+
* `password`
|
60
|
+
|
61
|
+
Optional:
|
62
|
+
|
63
|
+
* `path`
|
64
|
+
* `exclude`
|
65
|
+
* `passive` (defaults to true)
|
48
66
|
|
49
67
|
**Amazon S3**: `scheme: s3`
|
50
68
|
|
51
|
-
Required:
|
52
|
-
|
69
|
+
Required:
|
70
|
+
|
71
|
+
* `access_key_id`
|
72
|
+
* `secret_access_key`
|
73
|
+
* `bucket_name`
|
74
|
+
|
75
|
+
Optional:
|
76
|
+
|
77
|
+
* `path`
|
78
|
+
* `exclude`
|
53
79
|
|
54
80
|
Usage
|
55
81
|
-----
|
@@ -57,28 +83,30 @@ From within your Git repository, run:
|
|
57
83
|
|
58
84
|
$ dandelion deploy
|
59
85
|
|
60
|
-
|
86
|
+
This will deploy the local `HEAD` revision to the location specified in the config
|
87
|
+
file. Dandelion keeps track of the currently deployed revision so that only files
|
88
|
+
which have been added/changed/deleted need to be transferred.
|
61
89
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
files which have changed since the last deployment need to be transferred.
|
90
|
+
You can specify the revision you wish to deploy and Dandelion will determine which
|
91
|
+
files need to be transferred:
|
92
|
+
|
93
|
+
$ dandelion deploy <revision>
|
67
94
|
|
68
95
|
For a more complete summary of usage options, run:
|
69
96
|
|
70
97
|
$ dandelion -h
|
71
|
-
Usage: dandelion [options]
|
98
|
+
Usage: dandelion [options] <command> [<args>]
|
72
99
|
-v, --version Display the current version
|
73
100
|
-h, --help Display this screen
|
74
101
|
--repo=[REPO] Use the given repository
|
102
|
+
--config=[CONFIG] Use the given configuration file
|
75
103
|
|
76
104
|
Available commands:
|
77
105
|
deploy
|
78
106
|
status
|
107
|
+
|
108
|
+
Note that when specifying the repository or configuration file, the given paths
|
109
|
+
are relative to the current working directory (not the repository root). To see
|
110
|
+
the options for a particular command, run:
|
79
111
|
|
80
|
-
|
81
|
-
|
82
|
-
$ dandelion COMMAND -h
|
83
|
-
|
84
|
-
To see the options for a given command.
|
112
|
+
$ dandelion <command> -h
|
data/bin/dandelion
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'dandelion/command'
|
2
|
+
|
3
|
+
module Dandelion
|
4
|
+
class Application
|
5
|
+
class << self
|
6
|
+
def execute(args)
|
7
|
+
new(args).execute
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
@args = args
|
13
|
+
@options = {}
|
14
|
+
@parser = OptionParser.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
global = Command::Base.parser(@options)
|
19
|
+
parse(global)
|
20
|
+
|
21
|
+
begin
|
22
|
+
name = @args.shift
|
23
|
+
command = Command::Base.create(name)
|
24
|
+
parse(command.parser(@options))
|
25
|
+
rescue Command::InvalidCommandError
|
26
|
+
log.fatal("Invalid command: #{name}")
|
27
|
+
log.fatal(global.help)
|
28
|
+
log.fatal("Available commands:")
|
29
|
+
log.fatal(Command::Base.commands.map { |name| " #{name}" }.join("\n"))
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
|
33
|
+
prepare
|
34
|
+
validate
|
35
|
+
|
36
|
+
command.new(@options) do |cmd|
|
37
|
+
cmd.setup(@args) if cmd.respond_to?(:setup)
|
38
|
+
cmd.execute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def log
|
43
|
+
Dandelion.logger
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse(parser)
|
49
|
+
begin
|
50
|
+
parser.order!(@args)
|
51
|
+
rescue OptionParser::InvalidOption => e
|
52
|
+
log.fatal(e.to_s.capitalize)
|
53
|
+
log.fatal(parser.help)
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def prepare
|
59
|
+
@options[:config] ||= File.join(@options[:repo], 'dandelion.yml')
|
60
|
+
end
|
61
|
+
|
62
|
+
def validate
|
63
|
+
unless File.exists?(File.join(@options[:repo], '.git'))
|
64
|
+
log.fatal("Not a git repository: #{@options[:repo]}")
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
unless File.exists?(@options[:config])
|
68
|
+
log.fatal("Could not find file: #{@options[:config]}")
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/dandelion/backend.rb
CHANGED
data/lib/dandelion/backend/s3.rb
CHANGED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'dandelion'
|
2
|
+
require 'dandelion/backend'
|
3
|
+
require 'dandelion/deployment'
|
4
|
+
require 'dandelion/git'
|
5
|
+
require 'dandelion/version'
|
6
|
+
require 'optparse'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module Dandelion
|
10
|
+
module Command
|
11
|
+
class InvalidCommandError < StandardError; end
|
12
|
+
|
13
|
+
class Base
|
14
|
+
class << self
|
15
|
+
@@commands = {}
|
16
|
+
|
17
|
+
def command(name)
|
18
|
+
@@commands[name] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(name)
|
22
|
+
require_commands
|
23
|
+
raise InvalidCommandError unless @@commands.include?(name)
|
24
|
+
@@commands[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def commands
|
28
|
+
@@commands.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def require_commands
|
32
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'command', '*.rb')) { |file| require file }
|
33
|
+
end
|
34
|
+
|
35
|
+
def parser(options)
|
36
|
+
OptionParser.new do |opts|
|
37
|
+
opts.banner = 'Usage: dandelion [options] <command> [<args>]'
|
38
|
+
|
39
|
+
opts.on('-v', '--version', 'Display the current version') do
|
40
|
+
puts "Dandelion #{Dandelion::VERSION}"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-h', '--help', 'Display this screen') do
|
45
|
+
require_commands
|
46
|
+
puts opts
|
47
|
+
puts "\nAvailable commands:"
|
48
|
+
puts commands.map { |name| " #{name}" }.join("\n")
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
options[:repo] = closest_repo(File.expand_path('.'))
|
53
|
+
opts.on('--repo=[REPO]', 'Use the given repository') do |repo|
|
54
|
+
options[:repo] = File.expand_path(repo)
|
55
|
+
end
|
56
|
+
|
57
|
+
options[:config] = nil
|
58
|
+
opts.on('--config=[CONFIG]', 'Use the given configuration file') do |config|
|
59
|
+
options[:config] = File.expand_path(config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def closest_repo(dir)
|
67
|
+
if File.exists?(File.join(dir, '.git'))
|
68
|
+
dir
|
69
|
+
else
|
70
|
+
File.dirname(dir) != dir && closest_repo(File.dirname(dir)) || File.expand_path('.')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(options)
|
76
|
+
@options = options
|
77
|
+
@config = YAML.load_file(@options[:config])
|
78
|
+
@repo = Git::Repo.new(@options[:repo])
|
79
|
+
|
80
|
+
yield(self) if block_given?
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def log
|
86
|
+
Dandelion.logger
|
87
|
+
end
|
88
|
+
|
89
|
+
def backend
|
90
|
+
begin
|
91
|
+
backend = Backend::Base.create(@config)
|
92
|
+
log.info("Connecting to #{backend}")
|
93
|
+
backend
|
94
|
+
rescue Backend::MissingDependencyError => e
|
95
|
+
log.fatal("The '#{@config['scheme']}' scheme requires additional gems:")
|
96
|
+
log.fatal(e.gems.map { |name| " #{name}" }.join("\n"))
|
97
|
+
log.fatal("Please install the gems: gem install #{e.gems.join(' ')}")
|
98
|
+
exit 1
|
99
|
+
rescue Backend::UnsupportedSchemeError
|
100
|
+
log.fatal("Unsupported scheme: #{@config['scheme']}")
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def deployment(revision, backend = nil)
|
106
|
+
begin
|
107
|
+
backend ||= backend()
|
108
|
+
options = { :excldue => @config['exclude'], :revision => revision, :dry => @options[:dry] }
|
109
|
+
Deployment::Deployment.create(@repo, backend, options)
|
110
|
+
rescue Git::DiffError
|
111
|
+
log.fatal('Error: could not generate diff')
|
112
|
+
log.fatal('Try merging remote changes before running dandelion again')
|
113
|
+
exit 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dandelion
|
2
|
+
module Command
|
3
|
+
class Deploy < Command::Base
|
4
|
+
command 'deploy'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def parser(options)
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = 'Usage: deploy [options] [<revision>]'
|
10
|
+
|
11
|
+
options[:force] = false
|
12
|
+
opts.on('-f', '--force', 'Force deployment') do
|
13
|
+
options[:force] = true
|
14
|
+
end
|
15
|
+
|
16
|
+
options[:dry] = false
|
17
|
+
opts.on('--dry-run', 'Show what would have been deployed') do
|
18
|
+
options[:dry] = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup(args)
|
25
|
+
@revision = args.shift || 'HEAD'
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute
|
29
|
+
begin
|
30
|
+
@deployment = deployment(@revision)
|
31
|
+
rescue Git::RevisionError
|
32
|
+
log.fatal("Invalid revision: #{@revision}")
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
log.info("Remote revision: #{@deployment.remote_revision || '---'}")
|
37
|
+
log.info("Deploying revision: #{@deployment.local_revision}")
|
38
|
+
|
39
|
+
begin
|
40
|
+
@deployment.validate
|
41
|
+
rescue Deployment::FastForwardError
|
42
|
+
if !@options[:force]
|
43
|
+
log.warn('Warning: you are trying to deploy unpushed commits')
|
44
|
+
log.warn('This could potentially prevent others from being able to deploy')
|
45
|
+
log.warn('If you are sure you want to this, use the -f option to force deployment')
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@deployment.deploy
|
51
|
+
log.info("Deployment complete")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Dandelion
|
2
|
+
module Command
|
3
|
+
class Status < Command::Base
|
4
|
+
command 'status'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def parser(options)
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = 'Usage: dandelion status'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
@deployment = deployment('HEAD')
|
16
|
+
|
17
|
+
log.info("Remote revision: #{@deployment.remote_revision || '---'}")
|
18
|
+
log.info("Local HEAD revision: #{@deployment.local_revision}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/dandelion/deployment.rb
CHANGED
@@ -7,20 +7,26 @@ module Dandelion
|
|
7
7
|
|
8
8
|
class Deployment
|
9
9
|
class << self
|
10
|
-
def create(repo, backend,
|
10
|
+
def create(repo, backend, options)
|
11
11
|
begin
|
12
|
-
DiffDeployment.new(repo, backend,
|
12
|
+
DiffDeployment.new(repo, backend, options)
|
13
13
|
rescue RemoteRevisionError
|
14
|
-
FullDeployment.new(repo, backend,
|
14
|
+
FullDeployment.new(repo, backend, options)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def initialize(repo, backend,
|
19
|
+
def initialize(repo, backend, options = {})
|
20
20
|
@repo = repo
|
21
21
|
@backend = backend
|
22
|
-
@
|
23
|
-
@tree = Git::Tree.new(@repo, revision)
|
22
|
+
@options = { :exclude => [], :revision => 'HEAD' }.merge(options)
|
23
|
+
@tree = Git::Tree.new(@repo, @options[:revision])
|
24
|
+
|
25
|
+
if @options[:dry]
|
26
|
+
# Stub out the destructive backend methods
|
27
|
+
def @backend.write(file, data); end
|
28
|
+
def @beckend.delete(file); end
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
def local_revision
|
@@ -35,10 +41,10 @@ module Dandelion
|
|
35
41
|
@backend.write('.revision', local_revision)
|
36
42
|
end
|
37
43
|
|
38
|
-
def
|
44
|
+
def validate
|
39
45
|
begin
|
40
|
-
|
41
|
-
raise FastForwardError
|
46
|
+
@repo.remote_list.each do |remote|
|
47
|
+
raise FastForwardError if fast_forwardable(remote)
|
42
48
|
end
|
43
49
|
rescue Grit::Git::CommandFailed
|
44
50
|
end
|
@@ -51,20 +57,20 @@ module Dandelion
|
|
51
57
|
protected
|
52
58
|
|
53
59
|
def exclude_file?(file)
|
54
|
-
return @exclude.map { |e| file.start_with?(e) }.any?
|
60
|
+
return @options[:exclude].map { |e| file.start_with?(e) }.any?
|
55
61
|
end
|
56
62
|
|
57
63
|
private
|
58
64
|
|
59
65
|
def fast_forwardable(remote)
|
60
|
-
@repo.git.native(:remote, {:raise => true}, 'show', remote) =~ /fast-forward/i
|
66
|
+
!(@repo.git.native(:remote, {:raise => true}, 'show', remote) =~ /fast-forward/i).nil?
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
70
|
class DiffDeployment < Deployment
|
65
|
-
def initialize(repo, backend,
|
66
|
-
super(repo, backend,
|
67
|
-
@diff = Git::Diff.new(@repo, read_remote_revision, revision)
|
71
|
+
def initialize(repo, backend, options = {})
|
72
|
+
super(repo, backend, options)
|
73
|
+
@diff = Git::Diff.new(@repo, read_remote_revision, @options[:revision])
|
68
74
|
end
|
69
75
|
|
70
76
|
def remote_revision
|
data/lib/dandelion/git.rb
CHANGED
@@ -3,6 +3,7 @@ require 'grit'
|
|
3
3
|
module Dandelion
|
4
4
|
module Git
|
5
5
|
class DiffError < StandardError; end
|
6
|
+
class RevisionError < StandardError; end
|
6
7
|
|
7
8
|
class Repo < Grit::Repo
|
8
9
|
def initialize(dir)
|
@@ -54,6 +55,7 @@ module Dandelion
|
|
54
55
|
def initialize(repo, revision)
|
55
56
|
@repo = repo
|
56
57
|
@commit = @repo.commit(revision)
|
58
|
+
raise RevisionError if @commit.nil?
|
57
59
|
@tree = @commit.tree
|
58
60
|
end
|
59
61
|
|
data/lib/dandelion/version.rb
CHANGED
@@ -87,7 +87,7 @@ class TestDiffDeployment < Test::Unit::TestCase
|
|
87
87
|
@remote_revision = 'ff1f1d4bd0c99e1c9cca047c46b2194accf89504'
|
88
88
|
@repo = MockRepo.new
|
89
89
|
@backend = MockBackend.new(@remote_revision)
|
90
|
-
@diff_deployment = Dandelion::Deployment::DiffDeployment.new(@repo, @backend,
|
90
|
+
@diff_deployment = Dandelion::Deployment::DiffDeployment.new(@repo, @backend, :revision => @head_revision)
|
91
91
|
end
|
92
92
|
|
93
93
|
def test_diff_deployment_local_revision
|
metadata
CHANGED
@@ -1,69 +1,92 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: dandelion
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
6
10
|
platform: ruby
|
7
|
-
authors:
|
11
|
+
authors:
|
8
12
|
- Scott Nelson
|
9
13
|
autorequire:
|
10
14
|
bindir: bin
|
11
15
|
cert_chain: []
|
12
|
-
|
16
|
+
|
17
|
+
date: 2011-06-28 00:00:00 -04:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
- !ruby/object:Gem::Dependency
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
16
21
|
name: grit
|
17
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
24
|
none: false
|
19
|
-
requirements:
|
20
|
-
- -
|
21
|
-
- !ruby/object:Gem::Version
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 4
|
31
|
+
- 1
|
22
32
|
version: 2.4.1
|
23
33
|
type: :runtime
|
24
|
-
|
25
|
-
|
26
|
-
- !ruby/object:Gem::Dependency
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
27
36
|
name: mocha
|
28
|
-
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
39
|
none: false
|
30
|
-
requirements:
|
31
|
-
- -
|
32
|
-
- !ruby/object:Gem::Version
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 9
|
46
|
+
- 12
|
33
47
|
version: 0.9.12
|
34
48
|
type: :development
|
35
|
-
|
36
|
-
|
37
|
-
- !ruby/object:Gem::Dependency
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
38
51
|
name: net-sftp
|
39
|
-
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
54
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 2
|
60
|
+
- 0
|
61
|
+
- 5
|
44
62
|
version: 2.0.5
|
45
63
|
type: :development
|
46
|
-
|
47
|
-
|
48
|
-
- !ruby/object:Gem::Dependency
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
49
66
|
name: aws-s3
|
50
|
-
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
69
|
none: false
|
52
|
-
requirements:
|
53
|
-
- -
|
54
|
-
- !ruby/object:Gem::Version
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
- 6
|
76
|
+
- 0
|
55
77
|
version: 0.6.0
|
56
78
|
type: :development
|
57
|
-
|
58
|
-
version_requirements: *2153683160
|
79
|
+
version_requirements: *id004
|
59
80
|
description: Incremental Git repository deployment
|
60
|
-
email:
|
81
|
+
email:
|
61
82
|
- scottbnel@gmail.com
|
62
|
-
executables:
|
83
|
+
executables:
|
63
84
|
- dandelion
|
64
85
|
extensions: []
|
86
|
+
|
65
87
|
extra_rdoc_files: []
|
66
|
-
|
88
|
+
|
89
|
+
files:
|
67
90
|
- .gitignore
|
68
91
|
- Gemfile
|
69
92
|
- README.md
|
@@ -71,11 +94,14 @@ files:
|
|
71
94
|
- bin/dandelion
|
72
95
|
- dandelion.gemspec
|
73
96
|
- lib/dandelion.rb
|
97
|
+
- lib/dandelion/application.rb
|
74
98
|
- lib/dandelion/backend.rb
|
75
99
|
- lib/dandelion/backend/ftp.rb
|
76
100
|
- lib/dandelion/backend/s3.rb
|
77
101
|
- lib/dandelion/backend/sftp.rb
|
78
|
-
- lib/dandelion/
|
102
|
+
- lib/dandelion/command.rb
|
103
|
+
- lib/dandelion/command/deploy.rb
|
104
|
+
- lib/dandelion/command/status.rb
|
79
105
|
- lib/dandelion/deployment.rb
|
80
106
|
- lib/dandelion/git.rb
|
81
107
|
- lib/dandelion/version.rb
|
@@ -114,29 +140,36 @@ files:
|
|
114
140
|
has_rdoc: true
|
115
141
|
homepage: http://github.com/scttnlsn/dandelion
|
116
142
|
licenses: []
|
143
|
+
|
117
144
|
post_install_message:
|
118
145
|
rdoc_options: []
|
119
|
-
|
146
|
+
|
147
|
+
require_paths:
|
120
148
|
- lib
|
121
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
150
|
none: false
|
123
|
-
requirements:
|
124
|
-
- -
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
|
127
|
-
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
version: "0"
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
158
|
none: false
|
129
|
-
requirements:
|
130
|
-
- -
|
131
|
-
- !ruby/object:Gem::Version
|
132
|
-
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
segments:
|
163
|
+
- 0
|
164
|
+
version: "0"
|
133
165
|
requirements: []
|
166
|
+
|
134
167
|
rubyforge_project:
|
135
|
-
rubygems_version: 1.
|
168
|
+
rubygems_version: 1.3.7
|
136
169
|
signing_key:
|
137
170
|
specification_version: 3
|
138
|
-
summary: dandelion-0.
|
139
|
-
test_files:
|
171
|
+
summary: dandelion-0.3.0
|
172
|
+
test_files:
|
140
173
|
- test/fixtures/diff
|
141
174
|
- test/fixtures/ls_tree
|
142
175
|
- test/test_diff_deployment.rb
|
data/lib/dandelion/cli.rb
DELETED
@@ -1,201 +0,0 @@
|
|
1
|
-
require 'dandelion'
|
2
|
-
require 'dandelion/backend'
|
3
|
-
require 'dandelion/deployment'
|
4
|
-
require 'dandelion/git'
|
5
|
-
require 'dandelion/version'
|
6
|
-
require 'optparse'
|
7
|
-
require 'yaml'
|
8
|
-
|
9
|
-
module Dandelion
|
10
|
-
module Cli
|
11
|
-
class Options
|
12
|
-
def initialize
|
13
|
-
@options = {}
|
14
|
-
@config_file = nil
|
15
|
-
@global = global_parser
|
16
|
-
@commands = { 'deploy' => deploy_parser, 'status' => status_parser }
|
17
|
-
@commands_help = "\nAvailable commands:\n #{@commands.keys.join("\n ")}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def parse(args)
|
21
|
-
order(@global, args)
|
22
|
-
command = args.shift
|
23
|
-
if command and @commands[command]
|
24
|
-
order(@commands[command], args)
|
25
|
-
end
|
26
|
-
|
27
|
-
if @commands.key? command
|
28
|
-
@config_file = args.shift.strip if args[0]
|
29
|
-
command
|
30
|
-
else
|
31
|
-
if not @command.nil?
|
32
|
-
puts "Invalid command: #{command}"
|
33
|
-
end
|
34
|
-
puts @global.help
|
35
|
-
puts @commands_help
|
36
|
-
exit
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def [](key)
|
41
|
-
@options[key]
|
42
|
-
end
|
43
|
-
|
44
|
-
def []=(key, value)
|
45
|
-
@options[key] = value
|
46
|
-
end
|
47
|
-
|
48
|
-
def config_file
|
49
|
-
@config_file || File.join(@options[:repo], 'dandelion.yml')
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def closest_repo(dir)
|
55
|
-
if File.exists?(File.join(dir, '.git'))
|
56
|
-
dir
|
57
|
-
else
|
58
|
-
File.dirname(dir) != dir && closest_repo(File.dirname(dir))
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def global_parser
|
63
|
-
OptionParser.new do |opts|
|
64
|
-
opts.banner = 'Usage: dandelion [options] [[command] [options]]'
|
65
|
-
|
66
|
-
opts.on('-v', '--version', 'Display the current version') do
|
67
|
-
puts "Dandelion v#{Dandelion::VERSION}"
|
68
|
-
exit
|
69
|
-
end
|
70
|
-
|
71
|
-
opts.on('-h', '--help', 'Display this screen') do
|
72
|
-
puts opts
|
73
|
-
puts @commands_help
|
74
|
-
exit
|
75
|
-
end
|
76
|
-
|
77
|
-
@options[:repo] = closest_repo(File.expand_path('.'))
|
78
|
-
opts.on('--repo=[REPO]', 'Use the given repository') do |repo|
|
79
|
-
@options[:repo] = repo
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def deploy_parser
|
85
|
-
OptionParser.new do |opts|
|
86
|
-
opts.banner = 'Usage: dandelion deploy [options]'
|
87
|
-
|
88
|
-
@options[:force] = false
|
89
|
-
opts.on('-f', '--force', 'Force deployment') do
|
90
|
-
@options[:force] = true
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def status_parser
|
96
|
-
OptionParser.new do |opts|
|
97
|
-
opts.banner = 'Usage: dandelion status'
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def order(parser, args)
|
102
|
-
begin
|
103
|
-
parser.order!(args)
|
104
|
-
rescue OptionParser::InvalidOption => e
|
105
|
-
puts e.to_s.capitalize
|
106
|
-
puts parser.help
|
107
|
-
exit
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
class Main
|
113
|
-
class << self
|
114
|
-
def execute(args)
|
115
|
-
new(args).execute
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def initialize(args)
|
120
|
-
@options = Options.new
|
121
|
-
@command = @options.parse(args)
|
122
|
-
|
123
|
-
validate_files
|
124
|
-
@config = YAML.load_file(File.expand_path(@options.config_file))
|
125
|
-
@repo = Git::Repo.new(File.expand_path(@options[:repo]))
|
126
|
-
end
|
127
|
-
|
128
|
-
def log
|
129
|
-
Dandelion.logger
|
130
|
-
end
|
131
|
-
|
132
|
-
def execute
|
133
|
-
deployment = deployment()
|
134
|
-
log.info("Remote revision: #{deployment.remote_revision || '---'}")
|
135
|
-
log.info("Local revision: #{deployment.local_revision}")
|
136
|
-
|
137
|
-
if @command == 'status'
|
138
|
-
exit
|
139
|
-
elsif @command == 'deploy'
|
140
|
-
validate_deployment(deployment)
|
141
|
-
deployment.deploy
|
142
|
-
log.info("Deployment complete")
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
def backend
|
149
|
-
begin
|
150
|
-
backend = Backend::Backend.create(@config)
|
151
|
-
log.info("Connecting to: #{backend}")
|
152
|
-
backend
|
153
|
-
rescue Backend::MissingDependencyError => e
|
154
|
-
log.fatal("The '#{@config['scheme']}' scheme requires additional gems:")
|
155
|
-
log.fatal(' ' + e.gems.join("\n ") + "\n")
|
156
|
-
log.fatal("Please install the gems: gem install #{e.gems.join(' ')}")
|
157
|
-
exit
|
158
|
-
rescue Backend::UnsupportedSchemeError
|
159
|
-
log.fatal("Unsupported scheme: #{@config['scheme']}")
|
160
|
-
exit
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def deployment
|
165
|
-
begin
|
166
|
-
Deployment::Deployment.create(@repo, backend, @config['exclude'])
|
167
|
-
rescue Git::DiffError
|
168
|
-
log.fatal('Error: could not generate diff')
|
169
|
-
log.fatal('Try merging remote changes before running dandelion again')
|
170
|
-
exit
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def validate_deployment(deployment)
|
175
|
-
begin
|
176
|
-
@repo.remote_list.each do |remote|
|
177
|
-
deployment.validate_state(remote)
|
178
|
-
end
|
179
|
-
rescue Deployment::FastForwardError
|
180
|
-
if !@options[:force]
|
181
|
-
log.warn('Warning: you are trying to deploy unpushed commits')
|
182
|
-
log.warn('This could potentially prevent others from being able to deploy')
|
183
|
-
log.warn('If you are sure you want to this, use the -f option to force deployment')
|
184
|
-
exit
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def validate_files
|
190
|
-
unless File.exists?(File.expand_path(File.join(@options[:repo], '.git')))
|
191
|
-
log.fatal("Not a git repository: #{@options[:repo]}")
|
192
|
-
exit
|
193
|
-
end
|
194
|
-
unless File.exists?(File.expand_path(@options.config_file))
|
195
|
-
log.fatal("Could not find file: #{@options.config_file}")
|
196
|
-
exit
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|