dandelion 0.2.3 → 0.3.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.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
|