git_compound 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/Compoundfile +4 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +291 -0
- data/Rakefile +13 -0
- data/bin/console +7 -0
- data/bin/setup +5 -0
- data/exe/gitcompound +5 -0
- data/git_compound.gemspec +33 -0
- data/lib/git_compound/builder.rb +93 -0
- data/lib/git_compound/command/options.rb +55 -0
- data/lib/git_compound/command.rb +113 -0
- data/lib/git_compound/component/destination.rb +45 -0
- data/lib/git_compound/component/source.rb +53 -0
- data/lib/git_compound/component/version/branch.rb +30 -0
- data/lib/git_compound/component/version/gem_version.rb +45 -0
- data/lib/git_compound/component/version/sha.rb +33 -0
- data/lib/git_compound/component/version/tag.rb +30 -0
- data/lib/git_compound/component/version/version_strategy.rb +45 -0
- data/lib/git_compound/component.rb +78 -0
- data/lib/git_compound/dsl/component_dsl.rb +60 -0
- data/lib/git_compound/dsl/manifest_dsl.rb +27 -0
- data/lib/git_compound/exceptions.rb +16 -0
- data/lib/git_compound/lock.rb +64 -0
- data/lib/git_compound/logger/colors.rb +123 -0
- data/lib/git_compound/logger/core_ext/string.rb +5 -0
- data/lib/git_compound/logger.rb +43 -0
- data/lib/git_compound/manifest.rb +39 -0
- data/lib/git_compound/node.rb +17 -0
- data/lib/git_compound/repository/git_command.rb +33 -0
- data/lib/git_compound/repository/git_repository.rb +79 -0
- data/lib/git_compound/repository/git_version.rb +43 -0
- data/lib/git_compound/repository/remote_file/git_archive_strategy.rb +30 -0
- data/lib/git_compound/repository/remote_file/github_strategy.rb +54 -0
- data/lib/git_compound/repository/remote_file/remote_file_strategy.rb +27 -0
- data/lib/git_compound/repository/remote_file.rb +34 -0
- data/lib/git_compound/repository/repository_local.rb +81 -0
- data/lib/git_compound/repository/repository_remote.rb +12 -0
- data/lib/git_compound/repository.rb +19 -0
- data/lib/git_compound/task/task.rb +28 -0
- data/lib/git_compound/task/task_all.rb +27 -0
- data/lib/git_compound/task/task_each.rb +18 -0
- data/lib/git_compound/task/task_single.rb +21 -0
- data/lib/git_compound/task.rb +22 -0
- data/lib/git_compound/version.rb +5 -0
- data/lib/git_compound/worker/circular_dependency_checker.rb +31 -0
- data/lib/git_compound/worker/component_builder.rb +30 -0
- data/lib/git_compound/worker/component_replacer.rb +25 -0
- data/lib/git_compound/worker/component_update_dispatcher.rb +64 -0
- data/lib/git_compound/worker/component_updater.rb +24 -0
- data/lib/git_compound/worker/components_collector.rb +20 -0
- data/lib/git_compound/worker/conflicting_dependency_checker.rb +31 -0
- data/lib/git_compound/worker/local_changes_guard.rb +47 -0
- data/lib/git_compound/worker/name_constraint_checker.rb +20 -0
- data/lib/git_compound/worker/pretty_print.rb +18 -0
- data/lib/git_compound/worker/task_runner.rb +12 -0
- data/lib/git_compound/worker/worker.rb +20 -0
- data/lib/git_compound.rb +112 -0
- metadata +193 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module GitCompound
|
4
|
+
# Manifest
|
5
|
+
#
|
6
|
+
class Manifest < Node
|
7
|
+
attr_accessor :name, :components, :tasks
|
8
|
+
|
9
|
+
FILENAMES = %w(Compoundfile .gitcompound)
|
10
|
+
|
11
|
+
def initialize(contents, parent = nil)
|
12
|
+
@contents = contents
|
13
|
+
@parent = parent
|
14
|
+
@name = ''
|
15
|
+
@components = {}
|
16
|
+
@tasks = {}
|
17
|
+
DSL::ManifestDSL.new(self, contents) if contents
|
18
|
+
end
|
19
|
+
|
20
|
+
def process(*workers)
|
21
|
+
workers.each { |worker| worker.visit_manifest(self) }
|
22
|
+
components.each_value { |component| component.process(*workers) }
|
23
|
+
tasks.each_value { |task| workers.each { |worker| worker.visit_task(task) } }
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
return false unless other.instance_of? Manifest
|
28
|
+
md5sum == other.md5sum
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists?
|
32
|
+
@contents ? true : false
|
33
|
+
end
|
34
|
+
|
35
|
+
def md5sum
|
36
|
+
Digest::MD5.hexdigest(@contents) if exists?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GitCompound
|
2
|
+
# Abstract node class
|
3
|
+
#
|
4
|
+
class Node
|
5
|
+
attr_reader :parent
|
6
|
+
|
7
|
+
def process(*_workers)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def ancestors
|
12
|
+
return [] if @parent.nil? || @parent.parent.nil?
|
13
|
+
ancestor = @parent.parent
|
14
|
+
ancestor.ancestors.dup << ancestor
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'English'
|
2
|
+
|
3
|
+
module GitCompound
|
4
|
+
module Repository
|
5
|
+
# Execute git command
|
6
|
+
#
|
7
|
+
class GitCommand
|
8
|
+
attr_reader :output, :status, :command
|
9
|
+
|
10
|
+
def initialize(cmd, args, workdir = nil)
|
11
|
+
@command = "git #{cmd} #{args} 2>&1"
|
12
|
+
@workdir = workdir
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute!
|
16
|
+
path = @workdir ? @workdir : Dir.pwd
|
17
|
+
Dir.chdir(path) { @output = `#{@command}` }
|
18
|
+
@status = $CHILD_STATUS.exitstatus
|
19
|
+
@output.sub!(/\n\Z/, '')
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute
|
23
|
+
execute!
|
24
|
+
raise GitCommandError, @output unless valid?
|
25
|
+
@output
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
@status == 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
# Git repository base class
|
4
|
+
#
|
5
|
+
class GitRepository
|
6
|
+
def initialize(source)
|
7
|
+
@source = source
|
8
|
+
end
|
9
|
+
|
10
|
+
def clone(destination, options = nil, source = @source)
|
11
|
+
args = "#{source} #{destination}"
|
12
|
+
args.prepend(options + ' ') if options
|
13
|
+
GitCommand.new(:clone, args).execute
|
14
|
+
end
|
15
|
+
|
16
|
+
def versions
|
17
|
+
git_versions = tags.map { |tag, sha| GitVersion.new(tag, sha) }
|
18
|
+
git_versions.select(&:valid?)
|
19
|
+
end
|
20
|
+
|
21
|
+
def refs
|
22
|
+
@refs ||= GitCommand.new('ls-remote', @source).execute
|
23
|
+
@refs.scan(%r{^(\b[0-9a-f]{5,40}\b)\srefs\/(heads|tags)\/(.+)})
|
24
|
+
rescue GitCommandError => e
|
25
|
+
raise RepositoryUnreachableError, "Could not reach repository: #{e.message}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def branches
|
29
|
+
refs_select('heads')
|
30
|
+
end
|
31
|
+
|
32
|
+
def tags
|
33
|
+
all = refs_select('tags')
|
34
|
+
annotated = all.select { |tag, _| tag =~ /\^\{\}$/ }
|
35
|
+
annotated.each_pair do |annotated_tag, annotated_tag_sha|
|
36
|
+
tag = annotated_tag.sub(/\^\{\}$/, '')
|
37
|
+
all.delete(annotated_tag)
|
38
|
+
all[tag] = annotated_tag_sha
|
39
|
+
end
|
40
|
+
all
|
41
|
+
end
|
42
|
+
|
43
|
+
def ref_exists?(ref)
|
44
|
+
matching = refs.select { |refs_a| refs_a.include?(ref.to_s) }
|
45
|
+
matching.any?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns contents of first file found
|
49
|
+
#
|
50
|
+
def files_contents(files, ref)
|
51
|
+
files.each do |file|
|
52
|
+
begin
|
53
|
+
return file_contents(file, ref)
|
54
|
+
rescue FileNotFoundError
|
55
|
+
next
|
56
|
+
end
|
57
|
+
end
|
58
|
+
raise FileNotFoundError,
|
59
|
+
"Couldn't find any of #{files} files"
|
60
|
+
end
|
61
|
+
|
62
|
+
def file_contents(_file, _ref)
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_exists?(_file, _ref)
|
67
|
+
raise NotImplementedError
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def refs_select(name)
|
73
|
+
selected_refs = refs.select { |ref| ref[1] == name }
|
74
|
+
selected_refs.collect! { |r| [r.last, r.first] }
|
75
|
+
Hash[selected_refs]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
# GitVersion represents tagged version inside Git repository
|
4
|
+
#
|
5
|
+
class GitVersion
|
6
|
+
attr_reader :tag, :sha, :version
|
7
|
+
|
8
|
+
def initialize(tag, sha)
|
9
|
+
@tag = tag
|
10
|
+
@sha = sha
|
11
|
+
@version = tag.sub(/^v/, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_gem_version
|
15
|
+
Gem::Version.new(@version)
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
@tag.match(/^v?#{Gem::Version::VERSION_PATTERN}$/)
|
20
|
+
end
|
21
|
+
|
22
|
+
def matches?(requirement)
|
23
|
+
dependency = Gem::Dependency.new('component', requirement)
|
24
|
+
dependency.match?('component', to_gem_version, true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(other)
|
28
|
+
to_gem_version <=> other.to_gem_version
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
case other
|
33
|
+
when String
|
34
|
+
version == other
|
35
|
+
when GitVersion
|
36
|
+
version == other.version
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
class RemoteFile
|
4
|
+
# Git archive strategy
|
5
|
+
#
|
6
|
+
class GitArchiveStrategy < RemoteFileStrategy
|
7
|
+
def initialize(source, ref, file)
|
8
|
+
super
|
9
|
+
opts = "--format=tar --remote=#{@source} #{@ref} -- #{@file} | tar -O -xf -"
|
10
|
+
@command = GitCommand.new(:archive, opts)
|
11
|
+
@command.execute!
|
12
|
+
end
|
13
|
+
|
14
|
+
def contents
|
15
|
+
raise FileUnreachableError unless reachable?
|
16
|
+
raise FileNotFoundError unless exists?
|
17
|
+
@command.output
|
18
|
+
end
|
19
|
+
|
20
|
+
def reachable?
|
21
|
+
@command.valid? || @command.output.include?('did not match any files')
|
22
|
+
end
|
23
|
+
|
24
|
+
def exists?
|
25
|
+
@command.valid?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module GitCompound
|
5
|
+
module Repository
|
6
|
+
class RemoteFile
|
7
|
+
# Git archive strategy
|
8
|
+
#
|
9
|
+
class GithubStrategy < RemoteFileStrategy
|
10
|
+
GITHUB_URI = 'https://raw.githubusercontent.com'
|
11
|
+
GITHUB_PATTERN = 'git@github.com:|https:\/\/github.com'
|
12
|
+
|
13
|
+
def initialize(source, ref, file)
|
14
|
+
super
|
15
|
+
@uri = github_uri
|
16
|
+
end
|
17
|
+
|
18
|
+
def contents
|
19
|
+
raise FileUnreachableError unless reachable?
|
20
|
+
raise FileNotFoundError unless exists?
|
21
|
+
@response.body
|
22
|
+
end
|
23
|
+
|
24
|
+
def reachable?
|
25
|
+
@source.match(/#{GITHUB_PATTERN}/) ? true : false
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?
|
29
|
+
@response ||= http_response(@uri)
|
30
|
+
@response.code == 200.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def github_uri
|
36
|
+
github_location = @source.sub(/^#{GITHUB_PATTERN}/, '')
|
37
|
+
github_location.gsub!(%r{^/|/$}, '')
|
38
|
+
file_uri = "#{GITHUB_URI}/#{github_location}/#{@ref}/#{@file}"
|
39
|
+
URI.parse(file_uri)
|
40
|
+
end
|
41
|
+
|
42
|
+
def http_response(uri)
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
http.use_ssl = true
|
45
|
+
http.open_timeout = 4
|
46
|
+
http.read_timeout = 4
|
47
|
+
params = { 'User-Agent' => 'git_compound' }
|
48
|
+
req = Net::HTTP::Get.new(uri.request_uri, params)
|
49
|
+
http.request(req)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
class RemoteFile
|
4
|
+
# Base interface for strategies
|
5
|
+
#
|
6
|
+
class RemoteFileStrategy
|
7
|
+
def initialize(source, ref, file)
|
8
|
+
@source = source
|
9
|
+
@ref = ref
|
10
|
+
@file = file
|
11
|
+
end
|
12
|
+
|
13
|
+
def contents
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def reachable?
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
# Remote file loader based on strategies
|
4
|
+
#
|
5
|
+
class RemoteFile
|
6
|
+
def initialize(source, ref, file, strategies = nil)
|
7
|
+
@source = source
|
8
|
+
@ref = ref
|
9
|
+
@file = file
|
10
|
+
@strategies = strategies
|
11
|
+
end
|
12
|
+
|
13
|
+
def contents
|
14
|
+
@strategies ||= strategies_available
|
15
|
+
@strategies.each do |remote_file_strategy|
|
16
|
+
remote_file = remote_file_strategy.new(@source, @ref, @file)
|
17
|
+
next unless remote_file.reachable?
|
18
|
+
return remote_file.contents
|
19
|
+
end
|
20
|
+
raise FileUnreachableError,
|
21
|
+
"Couldn't reach file #{@file} after trying #{@strategies.count} stategies"
|
22
|
+
end
|
23
|
+
|
24
|
+
def strategies_available
|
25
|
+
# Strategies ordered by #reachable? method overhead
|
26
|
+
# More general strategies should be placed at lower positions,
|
27
|
+
# but this also depends on #reachable? overhead.
|
28
|
+
#
|
29
|
+
[GithubStrategy,
|
30
|
+
GitArchiveStrategy]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Repository
|
3
|
+
# Local git repository implementation
|
4
|
+
#
|
5
|
+
class RepositoryLocal < GitRepository
|
6
|
+
def initialize(source)
|
7
|
+
super
|
8
|
+
raise RepositoryUnreachableError unless
|
9
|
+
File.directory?("#{@source}/.git")
|
10
|
+
end
|
11
|
+
|
12
|
+
def clone(destination, options = nil)
|
13
|
+
# Prefer ^file:/// instead of ^/ as latter does not work with --depth
|
14
|
+
source = @source.sub(%r{^\/}, 'file:///')
|
15
|
+
super(destination, options, source)
|
16
|
+
end
|
17
|
+
|
18
|
+
def checkout(ref)
|
19
|
+
GitCommand.new(:checkout, ref, @source).execute
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch
|
23
|
+
GitCommand.new(:fetch, '', @source).execute
|
24
|
+
GitCommand.new(:fetch, '--tags', @source).execute
|
25
|
+
end
|
26
|
+
|
27
|
+
def merge(mergeable = 'FETCH_HEAD')
|
28
|
+
GitCommand.new(:merge, mergeable, @source).execute
|
29
|
+
end
|
30
|
+
|
31
|
+
def file_exists?(file, ref)
|
32
|
+
cmd = GitCommand.new(:show, "#{ref}:#{file}", @source)
|
33
|
+
cmd.execute!
|
34
|
+
cmd.valid?
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_contents(file, ref)
|
38
|
+
raise FileNotFoundError unless file_exists?(file, ref)
|
39
|
+
GitCommand.new(:show, "#{ref}:#{file}", @source).execute
|
40
|
+
end
|
41
|
+
|
42
|
+
def origin_remote
|
43
|
+
origin = GitCommand.new(:remote, '-v', @source).execute.match(/origin\t(.*?)\s/)
|
44
|
+
origin.captures.first if origin
|
45
|
+
end
|
46
|
+
|
47
|
+
def untracked_files?(exclude = nil)
|
48
|
+
untracked =
|
49
|
+
GitCommand.new('ls-files', '--exclude-standard --others', @source).execute
|
50
|
+
return (untracked.length > 0) unless exclude
|
51
|
+
|
52
|
+
untracked = untracked.split("\n")
|
53
|
+
untracked.delete_if do |file|
|
54
|
+
exclude.include?(file) || exclude.include?(file.split(File::SEPARATOR).first)
|
55
|
+
end
|
56
|
+
|
57
|
+
untracked.any?
|
58
|
+
end
|
59
|
+
|
60
|
+
def uncommited_changes?
|
61
|
+
GitCommand.new('update-index', '-q --refresh', @source).execute
|
62
|
+
unstaged = GitCommand.new('diff-files', '--quiet', @source)
|
63
|
+
uncommited = GitCommand.new('diff-index', '--cached --quiet HEAD', @source)
|
64
|
+
|
65
|
+
[unstaged, uncommited].any? do |cmd|
|
66
|
+
cmd.execute!
|
67
|
+
!cmd.valid?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def unpushed_commits?
|
72
|
+
unpushed = GitCommand.new('rev-list', '@{u}..', @source)
|
73
|
+
unpushed.execute.length > 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def head_sha
|
77
|
+
GitCommand.new('rev-parse', 'HEAD', @source).execute
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module GitCompound
|
2
|
+
# Git repositories module, also repository factory
|
3
|
+
#
|
4
|
+
module Repository
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def factory(source)
|
8
|
+
if local?(source)
|
9
|
+
RepositoryLocal.new(source)
|
10
|
+
else
|
11
|
+
RepositoryRemote.new(remote = source) # rubocop:disable Lint/UselessAssignment
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def local?(source)
|
16
|
+
source.match(%r{(^\/|file:\/\/).*})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Task
|
3
|
+
# Base abstract class for task
|
4
|
+
#
|
5
|
+
class Task
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name, manifest, &block)
|
9
|
+
raise GitCompoundError,
|
10
|
+
"Block not given for task `#{name}`" unless block
|
11
|
+
|
12
|
+
@name = name
|
13
|
+
@manifest = manifest
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def execute_on(directory, component)
|
24
|
+
@block.call(directory, component)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Task
|
3
|
+
# Task for all descendant components in manifest
|
4
|
+
#
|
5
|
+
class TaskAll < Task
|
6
|
+
def initialize(name, manifest, &block)
|
7
|
+
super
|
8
|
+
@components = components_collect!
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@components.each_value do |component|
|
13
|
+
execute_on(component.destination_path, component)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def components_collect!
|
20
|
+
components = {}
|
21
|
+
@manifest.process(Worker::CircularDependencyChecker.new,
|
22
|
+
Worker::ComponentsCollector.new(components))
|
23
|
+
components
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Task
|
3
|
+
# Task for each component defined in manifest
|
4
|
+
#
|
5
|
+
class TaskEach < Task
|
6
|
+
def initialize(name, manifest, &block)
|
7
|
+
super
|
8
|
+
@components = manifest.components
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@components.each_value do |component|
|
13
|
+
execute_on(component.destination_path, component)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Task
|
3
|
+
# Single task for single component
|
4
|
+
#
|
5
|
+
class TaskSingle < Task
|
6
|
+
def initialize(name, manifest, &block)
|
7
|
+
super
|
8
|
+
@component = @manifest.parent
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
if @component
|
13
|
+
execute_on(@component.destination_path, @component.manifest)
|
14
|
+
else
|
15
|
+
# Root manifest without parent
|
16
|
+
execute_on(Dir.pwd, @manifest)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GitCompound
|
2
|
+
# Task module and factory
|
3
|
+
#
|
4
|
+
module Task
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def factory(name, type, manifest, &block)
|
8
|
+
case
|
9
|
+
# manifest task
|
10
|
+
when type.nil? || type == :manfiest then task_class = TaskSingle
|
11
|
+
# task for each component defined in manifest
|
12
|
+
when type == :each then task_class = TaskEach
|
13
|
+
# task for all descendant components of manifest
|
14
|
+
when type == :all then task_class = TaskAll
|
15
|
+
else
|
16
|
+
raise GitCompoundError, "Unrecognized task type `#{type}`"
|
17
|
+
end
|
18
|
+
|
19
|
+
task_class.new(name, manifest, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Worker
|
3
|
+
# Worker that checks if unwanted circular dependency exists
|
4
|
+
#
|
5
|
+
class CircularDependencyChecker < Worker
|
6
|
+
def visit_component(component)
|
7
|
+
@element = component
|
8
|
+
raise_error if circular_dependency_exists?
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_manifest(manifest)
|
12
|
+
@element = manifest
|
13
|
+
raise_error if circular_dependency_exists?
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def circular_dependency_exists?
|
19
|
+
@element.ancestors.include?(@element)
|
20
|
+
end
|
21
|
+
|
22
|
+
def raise_error
|
23
|
+
name = @element.name
|
24
|
+
type = @element.class.name.downcase
|
25
|
+
|
26
|
+
raise CircularDependencyError,
|
27
|
+
"Circular dependency detected in #{type} `#{name}`!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Worker
|
3
|
+
# Worker that builds components
|
4
|
+
#
|
5
|
+
class ComponentBuilder < Worker
|
6
|
+
def initialize(lock = nil)
|
7
|
+
@lock = lock
|
8
|
+
@print = PrettyPrint.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_component(component)
|
12
|
+
raise GitCompoundError,
|
13
|
+
"Destination directory `#{component.destination_path}` " \
|
14
|
+
'already exists !' if component.destination_exists?
|
15
|
+
|
16
|
+
Logger.inline 'Building: '
|
17
|
+
@print.visit_component(component)
|
18
|
+
|
19
|
+
component.build
|
20
|
+
|
21
|
+
raise GitCompoundError,
|
22
|
+
"Destination `#{component.destination_path}` " \
|
23
|
+
'verification failed !' unless component.destination_exists?
|
24
|
+
|
25
|
+
return unless @lock
|
26
|
+
@lock.lock_component(component) unless @lock.find(component)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module GitCompound
|
2
|
+
module Worker
|
3
|
+
# Worker that replaces components if necessary
|
4
|
+
#
|
5
|
+
class ComponentReplacer < Worker
|
6
|
+
def initialize(lock)
|
7
|
+
@lock = lock
|
8
|
+
@print = PrettyPrint.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_component(component)
|
12
|
+
raise "Component `#{component.name}` is not built !" unless
|
13
|
+
component.destination_exists?
|
14
|
+
|
15
|
+
Logger.inline 'Replacing: '
|
16
|
+
@print.visit_component(component)
|
17
|
+
|
18
|
+
component.remove!
|
19
|
+
component.build
|
20
|
+
|
21
|
+
@lock.lock_component(component)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|