git_compound 0.0.9
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.
- 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
|