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 CHANGED
@@ -1,5 +1,6 @@
1
- dandelion
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 Git repository
19
- is searched for a file named `dandelion.yml` by default). Example:
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: `host`, `username`, `password`
42
- Optional: `path`, `exclude`
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: `host`, `username`, `password`
47
- Optional: `path`, `exclude`, `passive` (defaults to true)
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: `access_key_id`, `secret_access_key`, `bucket_name`
52
- Optional: `path`, `exclude`
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
- Or:
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
- $ dandelion deploy path/to/config.yml
63
-
64
- This will deploy the local `HEAD` revision to the server specified in the config
65
- file. Dandelion keeps track of the most recently deployed revision so that only
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] [[command] [options]] [config_file]
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
- Or:
81
-
82
- $ dandelion COMMAND -h
83
-
84
- To see the options for a given command.
112
+ $ dandelion <command> -h
@@ -2,6 +2,6 @@
2
2
 
3
3
  $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
4
 
5
- require 'dandelion/cli'
5
+ require 'dandelion/application'
6
6
 
7
- Dandelion::Cli::Main.execute(ARGV.dup)
7
+ Dandelion::Application.execute(ARGV.dup)
@@ -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
@@ -13,7 +13,7 @@ module Dandelion
13
13
  end
14
14
  end
15
15
 
16
- class Backend
16
+ class Base
17
17
  class << self
18
18
  @@backends = {}
19
19
 
@@ -2,7 +2,7 @@ require 'dandelion/backend'
2
2
 
3
3
  module Dandelion
4
4
  module Backend
5
- class FTP < Backend
5
+ class FTP < Backend::Base
6
6
  scheme 'ftp'
7
7
 
8
8
  def initialize(config)
@@ -2,7 +2,7 @@ require 'dandelion/backend'
2
2
 
3
3
  module Dandelion
4
4
  module Backend
5
- class S3 < Backend
5
+ class S3 < Backend::Base
6
6
  scheme 's3'
7
7
  gems 'aws-s3'
8
8
 
@@ -3,7 +3,7 @@ require 'pathname'
3
3
 
4
4
  module Dandelion
5
5
  module Backend
6
- class SFTP < Backend
6
+ class SFTP < Backend::Base
7
7
  scheme 'sftp'
8
8
  gems 'net-sftp'
9
9
 
@@ -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
@@ -7,20 +7,26 @@ module Dandelion
7
7
 
8
8
  class Deployment
9
9
  class << self
10
- def create(repo, backend, exclude = nil, revision = 'HEAD')
10
+ def create(repo, backend, options)
11
11
  begin
12
- DiffDeployment.new(repo, backend, exclude, revision)
12
+ DiffDeployment.new(repo, backend, options)
13
13
  rescue RemoteRevisionError
14
- FullDeployment.new(repo, backend, exclude, revision)
14
+ FullDeployment.new(repo, backend, options)
15
15
  end
16
16
  end
17
17
  end
18
18
 
19
- def initialize(repo, backend, exclude = nil, revision = 'HEAD')
19
+ def initialize(repo, backend, options = {})
20
20
  @repo = repo
21
21
  @backend = backend
22
- @exclude = exclude || []
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 validate_state(remote = nil)
44
+ def validate
39
45
  begin
40
- if remote and fast_forwardable(remote)
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, exclude = nil, revision = 'HEAD')
66
- super(repo, backend, exclude, revision)
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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Dandelion
2
- VERSION = '0.2.3'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -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, [], @head_revision)
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
- version: 0.2.3
5
- prerelease:
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
- date: 2011-06-08 00:00:00.000000000 -04:00
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
- requirement: &2153656700 !ruby/object:Gem::Requirement
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
- prerelease: false
25
- version_requirements: *2153656700
26
- - !ruby/object:Gem::Dependency
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
27
36
  name: mocha
28
- requirement: &2153656200 !ruby/object:Gem::Requirement
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
- prerelease: false
36
- version_requirements: *2153656200
37
- - !ruby/object:Gem::Dependency
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
38
51
  name: net-sftp
39
- requirement: &2153683620 !ruby/object:Gem::Requirement
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
- prerelease: false
47
- version_requirements: *2153683620
48
- - !ruby/object:Gem::Dependency
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
49
66
  name: aws-s3
50
- requirement: &2153683160 !ruby/object:Gem::Requirement
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
- prerelease: false
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
- files:
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/cli.rb
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
- require_paths:
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
- version: '0'
127
- required_rubygems_version: !ruby/object:Gem::Requirement
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
- version: '0'
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.6.2
168
+ rubygems_version: 1.3.7
136
169
  signing_key:
137
170
  specification_version: 3
138
- summary: dandelion-0.2.3
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
@@ -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