git_compound 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/Compoundfile +4 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +21 -0
  8. data/README.md +291 -0
  9. data/Rakefile +13 -0
  10. data/bin/console +7 -0
  11. data/bin/setup +5 -0
  12. data/exe/gitcompound +5 -0
  13. data/git_compound.gemspec +33 -0
  14. data/lib/git_compound/builder.rb +93 -0
  15. data/lib/git_compound/command/options.rb +55 -0
  16. data/lib/git_compound/command.rb +113 -0
  17. data/lib/git_compound/component/destination.rb +45 -0
  18. data/lib/git_compound/component/source.rb +53 -0
  19. data/lib/git_compound/component/version/branch.rb +30 -0
  20. data/lib/git_compound/component/version/gem_version.rb +45 -0
  21. data/lib/git_compound/component/version/sha.rb +33 -0
  22. data/lib/git_compound/component/version/tag.rb +30 -0
  23. data/lib/git_compound/component/version/version_strategy.rb +45 -0
  24. data/lib/git_compound/component.rb +78 -0
  25. data/lib/git_compound/dsl/component_dsl.rb +60 -0
  26. data/lib/git_compound/dsl/manifest_dsl.rb +27 -0
  27. data/lib/git_compound/exceptions.rb +16 -0
  28. data/lib/git_compound/lock.rb +64 -0
  29. data/lib/git_compound/logger/colors.rb +123 -0
  30. data/lib/git_compound/logger/core_ext/string.rb +5 -0
  31. data/lib/git_compound/logger.rb +43 -0
  32. data/lib/git_compound/manifest.rb +39 -0
  33. data/lib/git_compound/node.rb +17 -0
  34. data/lib/git_compound/repository/git_command.rb +33 -0
  35. data/lib/git_compound/repository/git_repository.rb +79 -0
  36. data/lib/git_compound/repository/git_version.rb +43 -0
  37. data/lib/git_compound/repository/remote_file/git_archive_strategy.rb +30 -0
  38. data/lib/git_compound/repository/remote_file/github_strategy.rb +54 -0
  39. data/lib/git_compound/repository/remote_file/remote_file_strategy.rb +27 -0
  40. data/lib/git_compound/repository/remote_file.rb +34 -0
  41. data/lib/git_compound/repository/repository_local.rb +81 -0
  42. data/lib/git_compound/repository/repository_remote.rb +12 -0
  43. data/lib/git_compound/repository.rb +19 -0
  44. data/lib/git_compound/task/task.rb +28 -0
  45. data/lib/git_compound/task/task_all.rb +27 -0
  46. data/lib/git_compound/task/task_each.rb +18 -0
  47. data/lib/git_compound/task/task_single.rb +21 -0
  48. data/lib/git_compound/task.rb +22 -0
  49. data/lib/git_compound/version.rb +5 -0
  50. data/lib/git_compound/worker/circular_dependency_checker.rb +31 -0
  51. data/lib/git_compound/worker/component_builder.rb +30 -0
  52. data/lib/git_compound/worker/component_replacer.rb +25 -0
  53. data/lib/git_compound/worker/component_update_dispatcher.rb +64 -0
  54. data/lib/git_compound/worker/component_updater.rb +24 -0
  55. data/lib/git_compound/worker/components_collector.rb +20 -0
  56. data/lib/git_compound/worker/conflicting_dependency_checker.rb +31 -0
  57. data/lib/git_compound/worker/local_changes_guard.rb +47 -0
  58. data/lib/git_compound/worker/name_constraint_checker.rb +20 -0
  59. data/lib/git_compound/worker/pretty_print.rb +18 -0
  60. data/lib/git_compound/worker/task_runner.rb +12 -0
  61. data/lib/git_compound/worker/worker.rb +20 -0
  62. data/lib/git_compound.rb +112 -0
  63. metadata +193 -0
@@ -0,0 +1,45 @@
1
+ require 'pathname'
2
+
3
+ module GitCompound
4
+ class Component
5
+ # Component destination
6
+ #
7
+ class Destination
8
+ attr_reader :path
9
+
10
+ def initialize(path, component)
11
+ raise CompoundSyntaxError, 'Destination cannot be empty' if
12
+ path.nil? || path.empty?
13
+
14
+ raise CompoundSyntaxError,
15
+ 'Destination should contain at least one directory' unless
16
+ Pathname.new(path).each_filename.count > 0
17
+
18
+ @path = path
19
+ @component = component
20
+ end
21
+
22
+ def expanded_path
23
+ pathname = Pathname.new(@path)
24
+
25
+ unless pathname.absolute?
26
+ ancestor_paths = @component.ancestors.map(&:destination_path)
27
+ pathname = Pathname.new('.').join(*ancestor_paths) + pathname
28
+ end
29
+
30
+ Pathname.new("./#{pathname}").cleanpath.to_s + '/'
31
+ end
32
+
33
+ def exists?
34
+ FileTest.exist?(expanded_path)
35
+ end
36
+
37
+ def repository
38
+ destination_repository =
39
+ Repository::RepositoryLocal.new(expanded_path)
40
+ yield destination_repository if block_given?
41
+ destination_repository
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ require 'forwardable'
2
+
3
+ module GitCompound
4
+ class Component
5
+ # Component source
6
+ #
7
+ class Source
8
+ extend Forwardable
9
+ delegate [:sha, :ref] => :@version
10
+ attr_reader :origin, :repository, :version, :options
11
+
12
+ def initialize(origin, version, strategy, options, component)
13
+ raise CompoundSyntaxError, 'Source cannot be empty' if
14
+ origin.nil? || origin.empty?
15
+
16
+ @origin = origin
17
+ @strategy = strategy
18
+ @options = options
19
+ @component = component
20
+ @repository = Repository.factory(@origin)
21
+ @version = strategy.new(@repository, version)
22
+ end
23
+
24
+ # Loads manifest from source repository
25
+ #
26
+ def manifest
27
+ raise DependencyError,
28
+ "Version #{@version} unreachable" unless @version.reachable?
29
+
30
+ contents = @repository.files_contents(Manifest::FILENAMES, @version.ref)
31
+ Manifest.new(contents, @component)
32
+ rescue FileNotFoundError
33
+ Manifest.new(nil, @component)
34
+ end
35
+
36
+ def clone(destination)
37
+ @repository.clone(destination, clone_args)
38
+ end
39
+
40
+ private
41
+
42
+ def clone_args
43
+ raise CompoundSyntaxError,
44
+ '`shallow` keyword not available for sha version strategy' if
45
+ @options.include?(:shallow) && @strategy == Component::Version::SHA
46
+
47
+ opts = []
48
+ opts << "--branch '#{@version.ref}' --depth 1" if @options.include? :shallow
49
+ opts.join(' ')
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ module GitCompound
2
+ class Component
3
+ module Version
4
+ # Component version indicated by branch (head of branch)
5
+ #
6
+ class Branch < VersionStrategy
7
+ def initialize(repository, branch)
8
+ @repository = repository
9
+ @branch = branch
10
+ end
11
+
12
+ def ref
13
+ @branch
14
+ end
15
+
16
+ def sha
17
+ @repository.branches[@branch]
18
+ end
19
+
20
+ def reachable?
21
+ @repository.branches.key?(@branch)
22
+ end
23
+
24
+ def to_s
25
+ "branch: #{@branch}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ module GitCompound
2
+ class Component
3
+ module Version
4
+ # Component Gem-like version
5
+ #
6
+ class GemVersion < VersionStrategy
7
+ attr_reader :requirement
8
+
9
+ def initialize(repository, requirement)
10
+ raise CompoundSyntaxError, 'Malformed version requirement string' unless
11
+ requirement =~ Gem::Requirement::PATTERN
12
+
13
+ @repository = repository
14
+ @requirement = requirement
15
+ end
16
+
17
+ def lastest_version
18
+ matches.first
19
+ end
20
+
21
+ def ref
22
+ lastest_version.tag
23
+ end
24
+
25
+ def sha
26
+ lastest_version.sha
27
+ end
28
+
29
+ def matches
30
+ versions = @repository.versions
31
+ versions.select! { |version| version.matches?(@requirement) }
32
+ versions.sort.reverse
33
+ end
34
+
35
+ def reachable?
36
+ matches.any?
37
+ end
38
+
39
+ def to_s
40
+ "gem version: #{@requirement}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ module GitCompound
2
+ class Component
3
+ module Version
4
+ # Component version indicated by SHA hash
5
+ #
6
+ class SHA < VersionStrategy
7
+ def initialize(repository, sha)
8
+ @repository = repository
9
+ @sha = sha
10
+ end
11
+
12
+ def ref
13
+ @sha
14
+ end
15
+
16
+ def sha # rubocop:disable Style/TrivialAccessors
17
+ @sha
18
+ end
19
+
20
+ def reachable?
21
+ # We assume that SHA is always available as we do not want
22
+ # to clone repository and check it -- this probably needs
23
+ # to be changed, so -- TODO
24
+ true
25
+ end
26
+
27
+ def to_s
28
+ "sha: #{@sha[0..7]}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module GitCompound
2
+ class Component
3
+ module Version
4
+ # Component version as tag
5
+ #
6
+ class Tag < VersionStrategy
7
+ def initialize(repository, tag)
8
+ @repository = repository
9
+ @tag = tag
10
+ end
11
+
12
+ def ref
13
+ @tag
14
+ end
15
+
16
+ def sha
17
+ @repository.tags[@tag]
18
+ end
19
+
20
+ def reachable?
21
+ @repository.tags.key?(@tag)
22
+ end
23
+
24
+ def to_s
25
+ "tag: #{@tag}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ module GitCompound
2
+ class Component
3
+ module Version
4
+ # Abstraction for component versions like
5
+ # gem version, sha and branch
6
+ #
7
+ class VersionStrategy
8
+ def initialize
9
+ raise NotImplementedError
10
+ end
11
+
12
+ # Should return git reference (ex branch, tag or sha)
13
+ # This should not raise exception if unreachable
14
+ #
15
+ def ref
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Should return sha for specified reference
20
+ # (ex tagged commit sha or head of specified branch)
21
+ #
22
+ def sha
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Should return true if this reference in source repository
27
+ # is reachable
28
+ #
29
+ def reachable?
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # String representation of this version strategy
34
+ #
35
+ def to_s
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def ==(other)
40
+ to_s == other.to_s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,78 @@
1
+ require 'forwardable'
2
+ require 'fileutils'
3
+
4
+ module GitCompound
5
+ # Component
6
+ #
7
+ class Component < Node
8
+ extend Forwardable
9
+
10
+ attr_reader :name
11
+ attr_accessor :source, :destination
12
+
13
+ def initialize(name, parent = nil, &block)
14
+ @name = name
15
+ @parent = parent
16
+ DSL::ComponentDSL.new(self, &block) if block
17
+ raise CompoundSyntaxError, "No block given for component `#{@name}`" unless block
18
+ raise GitCompoundError, "Component `#{@name}` invalid" unless valid?
19
+ end
20
+
21
+ def valid?
22
+ [@name, @source, @destination].all?
23
+ end
24
+
25
+ def process(*workers)
26
+ workers.each { |worker| worker.visit_component(self) }
27
+ @manifest.process(*workers) if manifest
28
+ end
29
+
30
+ def manifest
31
+ @manifest ||= @source.manifest
32
+ end
33
+
34
+ def build
35
+ @source.clone(destination_path)
36
+ @destination.repository do |repo|
37
+ repo.checkout(@source.ref)
38
+ end
39
+ end
40
+
41
+ def update
42
+ @destination.repository do |repo|
43
+ repo.fetch
44
+ repo.checkout(@source.ref)
45
+ repo.merge
46
+ end
47
+ end
48
+
49
+ def remove!
50
+ path = destination_path
51
+ raise GitCompoundError, 'Risky directory !' if
52
+ path.start_with?('/') || path.include?('..')
53
+ raise GitCompoundError, 'Not a directory !' unless
54
+ File.directory?(path)
55
+
56
+ FileUtils.remove_entry_secure(path)
57
+ end
58
+
59
+ def ==(other)
60
+ origin == other.origin || manifest == other.manifest
61
+ end
62
+
63
+ def to_hash
64
+ { name: @name,
65
+ sha: @source.sha,
66
+ source: @source.origin,
67
+ destination: @destination.expanded_path
68
+ }
69
+ end
70
+
71
+ # delegators
72
+
73
+ delegate [:sha, :origin, :repository, :version] => :@source
74
+ def_delegator :@destination, :expanded_path, :destination_path
75
+ def_delegator :@destination, :exists?, :destination_exists?
76
+ def_delegator :@destination, :repository, :destination_repository
77
+ end
78
+ end
@@ -0,0 +1,60 @@
1
+ module GitCompound
2
+ # Compound Domain Specific Language
3
+ #
4
+ module DSL
5
+ # DSL for Component
6
+ #
7
+ class ComponentDSL
8
+ def initialize(component, &block)
9
+ @component = component
10
+ instance_eval(&block)
11
+ end
12
+
13
+ # Custom version strategy, also available via DSL
14
+ #
15
+ def version_strategy(version, strategy)
16
+ raise CompoundSyntaxError,
17
+ 'Version strategy already set !' if @version_strategy
18
+
19
+ @version = version
20
+ @version_strategy = strategy
21
+ end
22
+
23
+ def version(component_version)
24
+ version_strategy(component_version, Component::Version::GemVersion)
25
+ end
26
+
27
+ def branch(component_branch)
28
+ version_strategy(component_branch, Component::Version::Branch)
29
+ end
30
+
31
+ def tag(component_tag)
32
+ version_strategy(component_tag, Component::Version::Tag)
33
+ end
34
+
35
+ def sha(component_sha)
36
+ raise CompoundSyntaxError, 'Invalid SHA1 format' unless
37
+ component_sha.match(/[0-9a-f]{5,40}/)
38
+
39
+ version_strategy(component_sha, Component::Version::SHA)
40
+ end
41
+
42
+ def source(component_source, *source_options)
43
+ raise CompoundSyntaxError,
44
+ 'Version/branch/tag/sha needed first' unless @version
45
+
46
+ @component.source =
47
+ Component::Source.new(component_source,
48
+ @version,
49
+ @version_strategy,
50
+ source_options,
51
+ @component)
52
+ end
53
+
54
+ def destination(component_path)
55
+ @component.destination =
56
+ Component::Destination.new(component_path, @component)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ module GitCompound
2
+ # Compound Domain Specific Language
3
+ #
4
+ module DSL
5
+ # DSL for Manifest
6
+ #
7
+ class ManifestDSL
8
+ def initialize(manifest, contents)
9
+ @manifest = manifest
10
+ instance_eval(contents)
11
+ end
12
+
13
+ def name(component_name)
14
+ @manifest.name = component_name.to_sym
15
+ end
16
+
17
+ def component(name, &block)
18
+ @manifest.components.store(name.to_sym, Component.new(name, @manifest, &block))
19
+ end
20
+
21
+ def task(name, type = nil, &block)
22
+ new_task = Task.factory(name, type, @manifest, &block)
23
+ @manifest.tasks.store(name.to_sym, new_task) if new_task
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module GitCompound
2
+ class GitCompoundError < StandardError; end
3
+
4
+ class CompoundLoadError < GitCompoundError; end
5
+ class CompoundSyntaxError < GitCompoundError; end
6
+ class FileNotFoundError < GitCompoundError; end
7
+ class FileUnreachableError < GitCompoundError; end
8
+ class RepositoryUnreachableError < GitCompoundError; end
9
+ class GitCommandError < GitCompoundError; end
10
+ class LocalRepositoryNotFoundError < GitCompoundError; end
11
+ class DependencyError < GitCompoundError; end
12
+ class CircularDependencyError < GitCompoundError; end
13
+ class ConflictingDependencyError < GitCompoundError; end
14
+ class NameConstraintError < GitCompoundError; end
15
+ class LocalChangesError < GitCompoundError; end
16
+ end
@@ -0,0 +1,64 @@
1
+ require 'yaml'
2
+
3
+ module GitCompound
4
+ # Class that represents lock file
5
+ #
6
+ class Lock
7
+ FILENAME = '.gitcompound.lock'
8
+
9
+ def self.exist?
10
+ File.exist?(FILENAME)
11
+ end
12
+
13
+ def initialize(file = FILENAME)
14
+ @file = file
15
+ @locked = YAML.load(File.read(file)) if File.exist?(file)
16
+ clean unless @locked.is_a? Hash
17
+ end
18
+
19
+ def clean
20
+ @locked = { manifest: '', components: [] }
21
+ self
22
+ end
23
+
24
+ def lock_manifest(manifest)
25
+ @locked[:manifest] = manifest.md5sum
26
+ end
27
+
28
+ def lock_component(component)
29
+ @locked[:components] << component.to_hash
30
+ end
31
+
32
+ def manifest
33
+ @locked[:manifest]
34
+ end
35
+
36
+ def components
37
+ @locked[:components].to_a.map do |locked|
38
+ Component.new(locked[:name].to_sym) do
39
+ sha locked[:sha]
40
+ source locked[:source]
41
+ destination locked[:destination]
42
+ end
43
+ end
44
+ end
45
+
46
+ def contents
47
+ @locked
48
+ end
49
+
50
+ def find(component)
51
+ components.find do |locked_component|
52
+ locked_component.destination_path == component.destination_path
53
+ end
54
+ end
55
+
56
+ def process(worker)
57
+ components.each { |component| worker.visit_component(component) }
58
+ end
59
+
60
+ def write
61
+ File.open(@file, 'w') { |f| f.puts @locked.to_yaml }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,123 @@
1
+ module GitCompound
2
+ module Logger
3
+ # Colors module
4
+ #
5
+ module Colors
6
+ def self.included(parent_class)
7
+ parent_class.extend ClassMethods
8
+ parent_class.class_eval { create_color_methods }
9
+ end
10
+
11
+ def colorize(params)
12
+ return self if colors_unavailable?
13
+ scan_colors.inject('') do |str, match|
14
+ set_colors(match, params)
15
+ str << "\033[#{match[0]};#{match[1]};#{match[2]}m#{match[3]}\033[0m"
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def colors_unavailable?
22
+ self.class.disable_colors || RUBY_VERSION < '2.0.0' || RUBY_PLATFORM =~ /win32/
23
+ end
24
+
25
+ def scan_colors
26
+ scan(/\033\[([0-9;]+)m(.+?)\033\[0m|([^\033]+)/m).map do |match|
27
+ colors = (match[0] || '').split(';')
28
+ Array.new(4).tap do |array|
29
+ array[0], array[1], array[2] = colors if colors.length == 3
30
+ array[3] = match[1] || match[2]
31
+ end
32
+ end
33
+ end
34
+
35
+ def set_colors(match, params)
36
+ default_colors(match)
37
+
38
+ mode = mode(params[:mode])
39
+ color = color(params[:color])
40
+ bgcolor = bgcolor(params[:bgcolor])
41
+
42
+ match[0] = mode if mode
43
+ match[1] = color if color
44
+ match[2] = bgcolor if bgcolor
45
+ end
46
+
47
+ def default_colors(match)
48
+ match[0] ||= mode(:default)
49
+ match[1] ||= color(:default)
50
+ match[2] ||= bgcolor(:default)
51
+ end
52
+
53
+ def color(color)
54
+ self.class.colors[color] + 30 if self.class.colors[color]
55
+ end
56
+
57
+ def bgcolor(color)
58
+ self.class.colors[color] + 40 if self.class.colors[color]
59
+ end
60
+
61
+ def mode(mode)
62
+ self.class.modes[mode] if self.class.modes[mode]
63
+ end
64
+ end
65
+
66
+ # Class methods core ext for String
67
+ #
68
+ module ClassMethods
69
+ def disable_colors=(value)
70
+ @disable_colors = value && true
71
+ end
72
+
73
+ def disable_colors
74
+ @disable_colors ||= false
75
+ end
76
+
77
+ def colors
78
+ {
79
+ black: 0,
80
+ red: 1,
81
+ green: 2,
82
+ yellow: 3,
83
+ blue: 4,
84
+ magenta: 5,
85
+ cyan: 6,
86
+ white: 7,
87
+ default: 9
88
+ }
89
+ end
90
+
91
+ def modes
92
+ {
93
+ default: 0,
94
+ bold: 1
95
+ }
96
+ end
97
+
98
+ private
99
+
100
+ def create_color_methods
101
+ colors.keys.each do |key|
102
+ next if key == :default
103
+
104
+ define_method key do
105
+ colorize(color: key)
106
+ end
107
+
108
+ define_method "on_#{key}" do
109
+ colorize(bgcolor: key)
110
+ end
111
+ end
112
+
113
+ modes.keys.each do |key|
114
+ next if key == :default
115
+
116
+ define_method key do
117
+ colorize(mode: key)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,5 @@
1
+ # rubocop:disable Style/Documentation
2
+ class String
3
+ include GitCompound::Logger::Colors
4
+ end
5
+ # rubocop:enable Style/Documentation
@@ -0,0 +1,43 @@
1
+ module GitCompound
2
+ # Logger class
3
+ #
4
+ module Logger
5
+ extend self
6
+
7
+ def verbose=(value)
8
+ @verbose = value && true
9
+ end
10
+
11
+ def verbose
12
+ @verbose ||= false
13
+ end
14
+
15
+ def inline(inline_message)
16
+ print inline_message
17
+ inline_message
18
+ end
19
+
20
+ def debug(debug_message, *params)
21
+ log(debug_message.cyan, *params) if verbose
22
+ end
23
+
24
+ def info(information_message, *params)
25
+ log(information_message, *params)
26
+ end
27
+
28
+ def warn(warning_message, *params)
29
+ log(warning_message.red.bold, *params)
30
+ end
31
+
32
+ def error(error_message, *params)
33
+ log(error_message.on_red.white.bold, *params)
34
+ end
35
+
36
+ private
37
+
38
+ def log(message, *params)
39
+ puts message unless params.include?(:quiet)
40
+ message
41
+ end
42
+ end
43
+ end