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