dandelion 0.3.15 → 0.4.0.beta2

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE +1 -1
  6. data/README.md +15 -26
  7. data/Rakefile +4 -8
  8. data/bin/dandelion +4 -3
  9. data/dandelion.gemspec +23 -20
  10. data/lib/dandelion.rb +26 -10
  11. data/lib/dandelion/adapter.rb +45 -0
  12. data/lib/dandelion/{backend → adapter}/ftp.rb +22 -17
  13. data/lib/dandelion/adapter/noop.rb +14 -0
  14. data/lib/dandelion/{backend → adapter}/s3.rb +14 -15
  15. data/lib/dandelion/{backend → adapter}/sftp.rb +29 -16
  16. data/lib/dandelion/change.rb +11 -0
  17. data/lib/dandelion/changeset.rb +49 -0
  18. data/lib/dandelion/cli.rb +127 -0
  19. data/lib/dandelion/command.rb +26 -92
  20. data/lib/dandelion/command/deploy.rb +54 -41
  21. data/lib/dandelion/command/status.rb +14 -13
  22. data/lib/dandelion/config.rb +36 -0
  23. data/lib/dandelion/deployer.rb +53 -0
  24. data/lib/dandelion/diff.rb +62 -0
  25. data/lib/dandelion/tree.rb +22 -0
  26. data/lib/dandelion/utils.rb +13 -0
  27. data/lib/dandelion/version.rb +2 -2
  28. data/lib/dandelion/workspace.rb +71 -0
  29. data/spec/dandelion/adapter_spec.rb +42 -0
  30. data/spec/dandelion/change_spec.rb +17 -0
  31. data/spec/dandelion/changeset_spec.rb +73 -0
  32. data/spec/dandelion/command/deploy_spec.rb +111 -0
  33. data/spec/dandelion/command/status_spec.rb +4 -0
  34. data/spec/dandelion/command_spec.rb +70 -0
  35. data/spec/dandelion/config_spec.rb +15 -0
  36. data/spec/dandelion/deployer_spec.rb +65 -0
  37. data/spec/dandelion/diff_spec.rb +54 -0
  38. data/spec/dandelion/tree_spec.rb +15 -0
  39. data/spec/dandelion/workspace_spec.rb +77 -0
  40. data/{test/test_git.git → spec/fixtures/repo.git}/HEAD +0 -0
  41. data/{test/test_git.git → spec/fixtures/repo.git}/config +1 -0
  42. data/{test/test_git.git → spec/fixtures/repo.git}/description +0 -0
  43. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/applypatch-msg.sample +0 -0
  44. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/commit-msg.sample +0 -0
  45. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/post-update.sample +0 -0
  46. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-applypatch.sample +0 -0
  47. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-commit.sample +6 -2
  48. data/spec/fixtures/repo.git/hooks/pre-push.sample +53 -0
  49. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-rebase.sample +0 -0
  50. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/prepare-commit-msg.sample +0 -0
  51. data/{test/test_git.git → spec/fixtures/repo.git}/hooks/update.sample +1 -1
  52. data/{test/test_git.git → spec/fixtures/repo.git}/info/exclude +0 -0
  53. data/spec/fixtures/repo.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 +0 -0
  54. data/spec/fixtures/repo.git/objects/34/e64fe350f3ea80c989cdac7a99c2adb8574fca +0 -0
  55. data/spec/fixtures/repo.git/objects/3d/9b743acb4a84dd99002d2c6f3fcf1a47e9f06b +0 -0
  56. data/spec/fixtures/repo.git/objects/4e/44973d41d33bf5342037f56497efe0a9604d25 +0 -0
  57. data/{test/test_git.git → spec/fixtures/repo.git}/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 +0 -0
  58. data/spec/fixtures/repo.git/objects/6c/a0f54491390579ce9438ec89c64c6b3499683a +0 -0
  59. data/spec/fixtures/repo.git/objects/8a/e33865a630c5d141c8a498f0c0166ff240b433 +0 -0
  60. data/{test/test_git.git → spec/fixtures/repo.git}/objects/90/2dce0535b19f0c15ac8407fc4468256ad672d7 +0 -0
  61. data/spec/fixtures/repo.git/objects/c3/9af82404cc4267b1ba5f4b4437a511e0776abb +0 -0
  62. data/spec/fixtures/repo.git/objects/c8/85b3f693ed6e2926971ef75680f41b318072ae +0 -0
  63. data/spec/fixtures/repo.git/objects/d8/7cbcba0e2ede0752bdafc5938da35546803ba5 +0 -0
  64. data/spec/fixtures/repo.git/objects/e2/89ff1e2729839759dbd6fe99b6e35880910c7c +0 -0
  65. data/{test/test_git.git → spec/fixtures/repo.git}/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  66. data/{test/test_git.git → spec/fixtures/repo.git}/objects/ea/41dba10b54a794284e0be009a11f0ff3716a28 +0 -0
  67. data/spec/fixtures/repo.git/objects/ee/314a31b622b027c10981acaed7903a3607dbd4 +0 -0
  68. data/spec/fixtures/repo.git/objects/f6/66b137794d56880bab05e8fd256713a8fccf92 +0 -0
  69. data/spec/fixtures/repo.git/refs/heads/master +1 -0
  70. data/spec/spec_helper.rb +28 -0
  71. metadata +119 -127
  72. data/lib/dandelion/application.rb +0 -73
  73. data/lib/dandelion/backend.rb +0 -54
  74. data/lib/dandelion/deployment.rb +0 -173
  75. data/lib/dandelion/git.rb +0 -123
  76. data/test/fixtures/diff +0 -3
  77. data/test/fixtures/ls_tree +0 -5
  78. data/test/test_diff_deployment.rb +0 -122
  79. data/test/test_ftp.rb +0 -49
  80. data/test/test_git.git/hooks/post-commit.sample +0 -8
  81. data/test/test_git.git/hooks/post-receive.sample +0 -15
  82. data/test/test_git.git/objects/0c/a605e9f0f1d42ce8193ac36db11ec3cc9efc08 +0 -0
  83. data/test/test_git.git/objects/11/bada4e36fd065c8d1d3ca97b8dffa496c8e021 +0 -0
  84. data/test/test_git.git/objects/88/d4480861346093048e08ce8dcc577d8aa69379 +0 -1
  85. data/test/test_git.git/objects/a6/394b3e8a82b76b0dd5b6b317f489dfe22426a6 +0 -0
  86. data/test/test_git.git/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 +0 -0
  87. data/test/test_git.git/objects/f5/5f3c44c89e5d215fbaaef9d33563117fe0b61b +0 -1
  88. data/test/test_git.git/objects/ff/1f1d4bd0c99e1c9cca047c46b2194accf89504 +0 -4
  89. data/test/test_git.git/refs/heads/master +0 -1
  90. data/test/test_git.rb +0 -50
  91. data/test/test_sftp.rb +0 -54
@@ -1,21 +1,22 @@
1
1
  module Dandelion
2
2
  module Command
3
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
4
+ command :status
5
+
6
+ def self.parser(options)
7
+ OptionParser.new do |opts|
8
+ opts.banner = 'Usage: dandelion status'
11
9
  end
12
10
  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}")
11
+
12
+ def execute!
13
+ log.info("Connecting to #{adapter.to_s}")
14
+
15
+ local_commit = workspace.local_commit
16
+ remote_commit = workspace.remote_commit
17
+
18
+ log.info("Remote revision: #{remote_commit ? remote_commit.oid : '---'}")
19
+ log.info("Local HEAD revision: #{workspace.local_commit.oid}")
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,36 @@
1
+ require 'yaml'
2
+
3
+ module Dandelion
4
+ class Config
5
+ attr_reader :path, :data
6
+
7
+ def initialize(options = {})
8
+ @path = options[:path]
9
+ @data = @path ? load : (options[:data] || {})
10
+ end
11
+
12
+ def [](key)
13
+ @data[key] || @data[key.to_s]
14
+ end
15
+
16
+ def []=(key, value)
17
+ @data[key.to_s] = value
18
+ end
19
+
20
+ def defaults(values)
21
+ values.each do |k, v|
22
+ if self[k].nil?
23
+ self[k] = v
24
+ end
25
+ end
26
+
27
+ self
28
+ end
29
+
30
+ private
31
+
32
+ def load
33
+ YAML.load_file(path) || {}
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ module Dandelion
2
+ class Deployer
3
+ def initialize(adapter, options = {})
4
+ @adapter = adapter
5
+ @options = options
6
+ end
7
+
8
+ def deploy_changeset!(changeset)
9
+ changeset.each do |change|
10
+ if exclude?(change.path)
11
+ log.debug("Skipping file: #{change.path}")
12
+ else
13
+ deploy_change!(change)
14
+ end
15
+ end
16
+ end
17
+
18
+ def deploy_files!(files)
19
+ files.each do |path|
20
+ local_path = remote_path = path
21
+
22
+ if path.is_a?(Hash)
23
+ local_path, remote_path = path.first
24
+ end
25
+
26
+ log.debug("Writing file: #{local_path} -> #{remote_path}")
27
+ @adapter.write(remote_path, IO.read(local_path))
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def deploy_change!(change)
34
+ case change.type
35
+ when :write
36
+ log.debug("Writing file: #{change.path}")
37
+ @adapter.write(change.path, change.data)
38
+ when :delete
39
+ log.debug("Deleting file: #{change.path}")
40
+ @adapter.delete(change.path)
41
+ end
42
+ end
43
+
44
+ def exclude?(path)
45
+ excluded = @options[:exclude] || []
46
+ excluded.map { |e| path.start_with?(e) }.any?
47
+ end
48
+
49
+ def log
50
+ Dandelion.logger
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ require 'forwardable'
2
+
3
+ module Dandelion
4
+ class Diff
5
+ extend Forwardable
6
+ include Enumerable
7
+
8
+ def_delegator :@target, :empty?
9
+ def_delegator :@target, :each
10
+
11
+ attr_reader :from_commit, :to_commit
12
+
13
+ def initialize(from_commit, to_commit)
14
+ @from_commit = from_commit
15
+ @to_commit = to_commit
16
+
17
+ if from_commit.nil?
18
+ @target = FullDiff.new(to_commit.diff(nil))
19
+ else
20
+ @target = PartialDiff.new(from_commit.diff(to_commit))
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ class PartialDiff
28
+ def initialize(diff)
29
+ @deltas = diff.deltas
30
+ end
31
+
32
+ def empty?
33
+ @deltas.empty?
34
+ end
35
+
36
+ def each
37
+ @deltas.each do |delta|
38
+ if delta.deleted?
39
+ yield Change.new(delta.old_file[:path], :delete)
40
+ else
41
+ yield Change.new(delta.new_file[:path], :write)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class FullDiff
48
+ def initialize(diff)
49
+ @deltas = diff.patches.map(&:delta)
50
+ end
51
+
52
+ def empty?
53
+ @deltas.empty?
54
+ end
55
+
56
+ def each
57
+ @deltas.each do |delta|
58
+ yield Change.new(delta.new_file[:path], :write)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ module Dandelion
2
+ class Tree
3
+ attr_reader :commit
4
+
5
+ def initialize(repo, commit)
6
+ @repo = repo
7
+ @commit = commit
8
+ end
9
+
10
+ def data(path)
11
+ object = @commit.tree
12
+
13
+ path.split('/').each do |name|
14
+ return nil unless object[name]
15
+ object = @repo.lookup(object[name][:oid])
16
+ return nil unless object
17
+ end
18
+
19
+ object.read_raw.data
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 'tempfile'
2
+
3
+ module Dandelion
4
+ module Utils
5
+ def temp(file, data)
6
+ tmp = Tempfile.new(file.gsub('/', '.'))
7
+ tmp << data
8
+ tmp.flush
9
+ yield(tmp.path)
10
+ tmp.close
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module Dandelion
2
- VERSION = '0.3.15'
3
- end
2
+ VERSION = '0.4.0.beta2'
3
+ end
@@ -0,0 +1,71 @@
1
+ module Dandelion
2
+ class RevisionError < StandardError; end
3
+
4
+ class Workspace
5
+ attr_reader :adapter, :config
6
+
7
+ def initialize(repo, adapter, config = nil)
8
+ @repo = repo
9
+ @adapter = adapter
10
+
11
+ if config.is_a?(Hash)
12
+ @config = Config.new(data: config)
13
+ else
14
+ @config = config || Config.new
15
+ end
16
+
17
+ @config.defaults(revision_file: '.revision', local_path: '')
18
+ end
19
+
20
+ def tree
21
+ Tree.new(@repo, local_commit)
22
+ end
23
+
24
+ def changeset
25
+ Changeset.new(tree, remote_commit, @config)
26
+ end
27
+
28
+ def local_commit
29
+ lookup(local_sha)
30
+ end
31
+
32
+ def remote_commit
33
+ sha = remote_sha
34
+ sha ? lookup(remote_sha) : nil
35
+ end
36
+
37
+ def remote_commit=(commit)
38
+ self.remote_sha = commit.oid
39
+ end
40
+
41
+ private
42
+
43
+ def lookup(val)
44
+ begin
45
+ begin
46
+ if ref = @repo.ref(val)
47
+ val = ref.target.to_s
48
+ end
49
+ rescue Rugged::ReferenceError
50
+ end
51
+
52
+ @repo.lookup(val)
53
+ rescue Rugged::OdbError, Rugged::InvalidError
54
+ raise RevisionError.new(val)
55
+ end
56
+ end
57
+
58
+ def local_sha
59
+ @config[:revision] || @repo.head.target
60
+ end
61
+
62
+ def remote_sha
63
+ @remote_sha ||= @adapter.read(@config[:revision_file])
64
+ end
65
+
66
+ def remote_sha=(sha)
67
+ @adapter.write(@config[:revision_file], sha)
68
+ @remote_sha = sha
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dandelion::Adapter::Base do
4
+ describe '#create_adapter' do
5
+ it 'registers adapter classes' do
6
+ class TestAdapter < Dandelion::Adapter::Base
7
+ adapter :test
8
+ end
9
+
10
+ expect(Dandelion::Adapter::Base.create_adapter(:test)).to be_a(TestAdapter)
11
+ end
12
+
13
+ it 'raises error on invalid adapter' do
14
+ expect {
15
+ Dandelion::Adapter::Base.create_adapter(:another)
16
+ }.to raise_error(Dandelion::Adapter::InvalidAdapterError)
17
+ end
18
+
19
+ it 'registers gem list' do
20
+ class TestAdapter < Dandelion::Adapter::Base
21
+ adapter :test
22
+ requires_gems :foo, :bar
23
+ end
24
+
25
+ expect(TestAdapter.required_gems).to eq [:foo, :bar]
26
+ end
27
+
28
+ it 'catches load errors' do
29
+ class TestAdapter < Dandelion::Adapter::Base
30
+ adapter :test
31
+
32
+ def initialize(options)
33
+ raise LoadError
34
+ end
35
+ end
36
+
37
+ expect {
38
+ Dandelion::Adapter::Base.create_adapter(:test)
39
+ }.to raise_error(Dandelion::Adapter::MissingDependencyError)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dandelion::Change do
4
+ let(:change) { Dandelion::Change.new('foo', 'bar', 'baz') }
5
+
6
+ it 'has path' do
7
+ expect(change.path).to eq 'foo'
8
+ end
9
+
10
+ it 'has type' do
11
+ expect(change.type).to eq 'bar'
12
+ end
13
+
14
+ it 'has data' do
15
+ expect(change.data).to eq 'baz'
16
+ end
17
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dandelion::Changeset do
4
+ context 'empty local path' do
5
+ let(:changeset) { test_changeset }
6
+
7
+ describe '#enumerable' do
8
+ let(:changes) { changeset.to_a }
9
+
10
+ it 'returns all changes' do
11
+ expect(changes).to be_a(Array)
12
+ expect(changes.length).to eq 5
13
+ expect(changes.map(&:path)).to eq ['bar', 'baz/bar', 'baz/foo', 'foo', 'qux']
14
+ expect(changes.map(&:type)).to eq [:delete, :write, :delete, :write, :write]
15
+ end
16
+
17
+ it 'returns data for write changes' do
18
+ expect(changes.select { |c| c.type == :write }.map(&:data)).to eq ["bar\n", "foo\n", ""]
19
+ end
20
+ end
21
+
22
+ describe '#empty?' do
23
+ it 'returns false' do
24
+ expect(changeset.empty?).to eq false
25
+ end
26
+ end
27
+ end
28
+
29
+ context 'non-empty local path' do
30
+ let(:changeset) { test_changeset(local_path: 'baz') }
31
+
32
+ describe '#enumerable' do
33
+ let(:changes) { changeset.to_a }
34
+
35
+ it 'returns all changes' do
36
+ expect(changes).to be_a(Array)
37
+ expect(changes.length).to eq 2
38
+ expect(changes.map(&:path)).to eq ['bar', 'foo']
39
+ expect(changes.map(&:type)).to eq [:write, :delete]
40
+ end
41
+
42
+ it 'returns data for write changes' do
43
+ expect(changes.first.data).to eq "bar\n"
44
+ end
45
+ end
46
+
47
+ describe '#empty?' do
48
+ it 'returns false' do
49
+ expect(changeset.empty?).to eq false
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'empty diff' do
55
+ let(:changeset) { test_changeset }
56
+ before(:each) { changeset.stub(:diff).and_return([]) }
57
+
58
+ describe '#enumerable' do
59
+ let(:changes) { changeset.to_a }
60
+
61
+ it 'returns no changes' do
62
+ expect(changes).to be_a(Array)
63
+ expect(changes.length).to eq 0
64
+ end
65
+ end
66
+
67
+ describe '#empty?' do
68
+ it 'returns true' do
69
+ expect(changeset.empty?).to eq true
70
+ end
71
+ end
72
+ end
73
+ end