multi_git 0.0.1.alpha1

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 (45) hide show
  1. data/lib/multi_git/backend.rb +100 -0
  2. data/lib/multi_git/backend_set.rb +42 -0
  3. data/lib/multi_git/blob.rb +52 -0
  4. data/lib/multi_git/builder.rb +19 -0
  5. data/lib/multi_git/commit.rb +185 -0
  6. data/lib/multi_git/directory.rb +67 -0
  7. data/lib/multi_git/error.rb +67 -0
  8. data/lib/multi_git/executeable.rb +12 -0
  9. data/lib/multi_git/file.rb +35 -0
  10. data/lib/multi_git/git_backend/blob.rb +11 -0
  11. data/lib/multi_git/git_backend/cmd.rb +117 -0
  12. data/lib/multi_git/git_backend/commit.rb +75 -0
  13. data/lib/multi_git/git_backend/object.rb +34 -0
  14. data/lib/multi_git/git_backend/ref.rb +36 -0
  15. data/lib/multi_git/git_backend/repository.rb +162 -0
  16. data/lib/multi_git/git_backend/tree.rb +22 -0
  17. data/lib/multi_git/git_backend.rb +19 -0
  18. data/lib/multi_git/handle.rb +33 -0
  19. data/lib/multi_git/jgit_backend/blob.rb +10 -0
  20. data/lib/multi_git/jgit_backend/commit.rb +45 -0
  21. data/lib/multi_git/jgit_backend/object.rb +48 -0
  22. data/lib/multi_git/jgit_backend/ref.rb +117 -0
  23. data/lib/multi_git/jgit_backend/repository.rb +223 -0
  24. data/lib/multi_git/jgit_backend/rewindeable_io.rb +33 -0
  25. data/lib/multi_git/jgit_backend/tree.rb +28 -0
  26. data/lib/multi_git/jgit_backend.rb +15 -0
  27. data/lib/multi_git/object.rb +59 -0
  28. data/lib/multi_git/ref.rb +381 -0
  29. data/lib/multi_git/repository.rb +190 -0
  30. data/lib/multi_git/rugged_backend/blob.rb +8 -0
  31. data/lib/multi_git/rugged_backend/commit.rb +38 -0
  32. data/lib/multi_git/rugged_backend/object.rb +38 -0
  33. data/lib/multi_git/rugged_backend/ref.rb +32 -0
  34. data/lib/multi_git/rugged_backend/repository.rb +160 -0
  35. data/lib/multi_git/rugged_backend/tree.rb +18 -0
  36. data/lib/multi_git/rugged_backend.rb +16 -0
  37. data/lib/multi_git/submodule.rb +7 -0
  38. data/lib/multi_git/symlink.rb +42 -0
  39. data/lib/multi_git/tree/builder.rb +184 -0
  40. data/lib/multi_git/tree.rb +144 -0
  41. data/lib/multi_git/tree_entry.rb +86 -0
  42. data/lib/multi_git/utils.rb +57 -0
  43. data/lib/multi_git/version.rb +3 -0
  44. data/lib/multi_git.rb +44 -0
  45. metadata +138 -0
@@ -0,0 +1,160 @@
1
+ require 'multi_git/tree_entry'
2
+ require 'multi_git/repository'
3
+ require 'multi_git/rugged_backend/blob'
4
+ require 'multi_git/rugged_backend/tree'
5
+ require 'multi_git/rugged_backend/commit'
6
+ require 'multi_git/rugged_backend/ref'
7
+ module MultiGit::RuggedBackend
8
+
9
+ class Repository < MultiGit::Repository
10
+
11
+ extend Forwardable
12
+
13
+ private
14
+ OBJECT_CLASSES = {
15
+ :blob => Blob,
16
+ :tree => Tree,
17
+ :commit => Commit
18
+ }
19
+
20
+ public
21
+
22
+ # {include:MultiGit::Repository#bare?}
23
+ delegate "bare?" => "@git"
24
+
25
+ # {include:MultiGit::Repository#git_dir}
26
+ def git_dir
27
+ strip_slash @git.path
28
+ end
29
+
30
+ def git_work_tree
31
+ strip_slash @git.workdir
32
+ end
33
+
34
+ def initialize(path, options = {})
35
+ options = initialize_options(path,options)
36
+ begin
37
+ @git = Rugged::Repository.new(options[:repository])
38
+ if options[:working_directory]
39
+ @git.workdir = options[:working_directory]
40
+ end
41
+ rescue Rugged::RepositoryError, Rugged::OSError
42
+ if options[:init]
43
+ @git = Rugged::Repository.init_at(path, !!options[:bare])
44
+ else
45
+ raise MultiGit::Error::NotARepository, path
46
+ end
47
+ end
48
+ verify_bareness(path, options)
49
+ end
50
+
51
+ # {include:MultiGit::Repository#write}
52
+ # @param (see MultiGit::Repository#write)
53
+ # @raise (see MultiGit::Repository#write)
54
+ # @return (see MultiGit::Repository#write)
55
+ def write(content, type = :blob)
56
+ if content.kind_of? MultiGit::Builder
57
+ return content >> self
58
+ end
59
+ validate_type(type)
60
+ if content.kind_of? MultiGit::Object
61
+ if include?(content.oid)
62
+ return read(content.oid)
63
+ end
64
+ content = content.to_io
65
+ end
66
+ #if content.respond_to? :path
67
+ # file duck-type
68
+ # oid = @git.hash_file(content.path, type)
69
+ # return OBJECT_CLASSES[type].new(@git, oid)
70
+ #els
71
+ if content.respond_to? :read
72
+ # IO duck-type
73
+ content = content.read
74
+ end
75
+ oid = @git.write(content.to_s, type)
76
+ return OBJECT_CLASSES[type].new(self, oid)
77
+ end
78
+
79
+ # {include:MultiGit::Repository#read}
80
+ # @param (see MultiGit::Repository#read)
81
+ # @raise (see MultiGit::Repository#read)
82
+ # @return (see MultiGit::Repository#read)
83
+ def read(ref)
84
+ oid = parse(ref)
85
+ object = @git.lookup(oid)
86
+ return OBJECT_CLASSES[object.type].new(self, oid, object)
87
+ end
88
+
89
+ def ref(ref)
90
+ Ref.new(self, ref)
91
+ end
92
+
93
+ # {include:MultiGit::Repository#parse}
94
+ # @param (see MultiGit::Repository#parse)
95
+ # @raise (see MultiGit::Repository#parse)
96
+ # @return (see MultiGit::Repository#parse)
97
+ def parse(oidish)
98
+ begin
99
+ return Rugged::Object.rev_parse_oid(@git, oidish)
100
+ rescue Rugged::ReferenceError => e
101
+ raise MultiGit::Error::InvalidReference, e
102
+ end
103
+ end
104
+
105
+ # {include:MultiGit::Repository#include?}
106
+ # @param (see MultiGit::Repository#include?)
107
+ # @raise (see MultiGit::Repository#include?)
108
+ # @return (see MultiGit::Repository#include?)
109
+ def include?(oid)
110
+ @git.include?(oid)
111
+ end
112
+
113
+ # @api private
114
+ # @visibility private
115
+ def __backend__
116
+ @git
117
+ end
118
+
119
+ # @api private
120
+ # @visibility private
121
+ def make_tree(entries)
122
+ builder = Rugged::Tree::Builder.new
123
+ entries.each do |name, mode, oid|
124
+ builder << { name: name, oid: oid, filemode: mode}
125
+ end
126
+ oid = builder.write(@git)
127
+ return read(oid)
128
+ end
129
+
130
+ # @api private
131
+ # @visibility private
132
+ def make_commit(options)
133
+ rugged_options = {
134
+ tree: options[:tree],
135
+ message: options[:message],
136
+ parents: options[:parents],
137
+ author: {
138
+ name: options[:author].name,
139
+ email: options[:author].email,
140
+ time: options[:time]
141
+ },
142
+ committer: {
143
+ name: options[:committer].name,
144
+ email: options[:committer].email,
145
+ time: options[:commit_time]
146
+ }
147
+ }
148
+ oid = Rugged::Commit.create(@git, rugged_options)
149
+ return read(oid)
150
+ end
151
+
152
+ private
153
+
154
+ def strip_slash(path)
155
+ return nil if path.nil?
156
+ return path[0..-2]
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,18 @@
1
+ require 'multi_git/tree'
2
+ require 'multi_git/rugged_backend/object'
3
+ module MultiGit::RuggedBackend
4
+ class Tree < Object
5
+ include MultiGit::Tree
6
+
7
+ def size
8
+ rugged_object.count
9
+ end
10
+
11
+ def raw_entries
12
+ @raw_entries ||= rugged_object.map do |entry|
13
+ [entry[:name], entry[:filemode], entry[:oid]]
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'rugged'
2
+ module MultiGit
3
+ module RuggedBackend
4
+ class << self
5
+
6
+ def load!
7
+ end
8
+
9
+ def open(path, options = {})
10
+ Repository.new(path, options)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ require 'multi_git/rugged_backend/repository'
@@ -0,0 +1,7 @@
1
+ require 'multi_git/tree_entry'
2
+ module MultiGit
3
+
4
+ class Submodule < TreeEntry
5
+ end
6
+
7
+ end
@@ -0,0 +1,42 @@
1
+ require 'multi_git/tree_entry'
2
+ module MultiGit
3
+
4
+ class Symlink < TreeEntry
5
+
6
+ module Base
7
+
8
+ def target
9
+ object.content
10
+ end
11
+
12
+ def resolve
13
+ parent.traverse(target, :follow => :path)
14
+ end
15
+ end
16
+
17
+ class Builder < TreeEntry::Builder
18
+
19
+ include Base
20
+
21
+ def make_inner(*args)
22
+ if args.any?
23
+ if args[0].kind_of? Blob::Builder
24
+ return args[0]
25
+ elsif args[0].kind_of? Blob
26
+ return args[0].to_builder
27
+ end
28
+ end
29
+ Blob::Builder.new(*args)
30
+ end
31
+
32
+ def target=(t)
33
+ object.content = t
34
+ end
35
+
36
+ end
37
+
38
+ include Base
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,184 @@
1
+ require 'set'
2
+ require 'multi_git/tree'
3
+ require 'multi_git/builder'
4
+ module MultiGit
5
+ class Tree::Builder
6
+
7
+ include MultiGit::Builder
8
+ include Tree::Base
9
+
10
+ attr :entries
11
+
12
+ def initialize(from = nil, &block)
13
+ @entries = {}
14
+ @from = from
15
+ instance_eval(&block) if block
16
+ end
17
+
18
+ def entry(key)
19
+ if @from
20
+ @entries.fetch(key) do
21
+ e = @from.entry(key)
22
+ if e
23
+ @entries[key] = e.to_builder.with_parent(self)
24
+ end
25
+ end
26
+ else
27
+ @entries[key]
28
+ end
29
+ end
30
+
31
+ def each
32
+ return to_enum unless block_given?
33
+ names.each do |name|
34
+ yield entry(name)
35
+ end
36
+ end
37
+
38
+ # TODO: cache
39
+ def names
40
+ names = @from ? @from.names.dup : []
41
+ @entries.each do |k,v|
42
+ if v
43
+ unless names.include? k
44
+ names << k
45
+ end
46
+ else
47
+ names.delete(k)
48
+ end
49
+ end
50
+ return names
51
+ end
52
+
53
+ def size
54
+ names.size
55
+ end
56
+
57
+ def >>(repository)
58
+ ent = []
59
+ @entries.each do |name, entry|
60
+ if entry
61
+ object = repository.write(entry)
62
+ ent << [name, object.mode, object.oid]
63
+ end
64
+ end
65
+ if @from
66
+ @from.each do |entry|
67
+ unless @entries.key? entry.name
68
+ ent << [entry.name, entry.mode, entry.oid]
69
+ end
70
+ end
71
+ end
72
+ return repository.make_tree(ent)
73
+ end
74
+
75
+ module DSL
76
+
77
+ def set(key, *args, &block)
78
+ case(args.size)
79
+ when 0
80
+ raise ArgumentError, "Expected a value or a block" unless block
81
+ value = block
82
+ when 1
83
+ if block
84
+ options = args[0]
85
+ value = block
86
+ else
87
+ value = args[0]
88
+ end
89
+ when 2
90
+ raise ArgumentError, "Expected either a value or a block, got both" if block
91
+ options = args[0]
92
+ value = args[1]
93
+ else
94
+ raise ArgumentError, "Expected 1-3 arguments, got #{args.size}"
95
+ end
96
+ # okay, a bit simple here for now
97
+ parts = key.split('/').reject{|k| k == '' || k == '.' }
98
+ if parts.any?{|p| p == ".." }
99
+ raise MultiGit::Error::InvalidTraversal, "Traversal to parent directories is currently not supported while setting."
100
+ end
101
+ return traverse_set( self, parts, value, true)
102
+ end
103
+
104
+ alias []= set
105
+
106
+ def entry_set(key, value)
107
+ entries[key] = make_entry(key, value)
108
+ end
109
+
110
+ def make_entry(key, value)
111
+ if value.kind_of? Proc
112
+ value = value.call(self, key)
113
+ end
114
+ if value.nil?
115
+ return value
116
+ elsif value.kind_of? String
117
+ return MultiGit::File::Builder.new(self, key, value)
118
+ elsif value.kind_of? MultiGit::Builder
119
+ return value.with_parent(self)
120
+ else
121
+ raise ArgumentError, "No idea what to do with #{value.inspect}"
122
+ end
123
+ end
124
+
125
+ def traverse_set(current, parts, value, create)
126
+ if parts.none?
127
+ raise
128
+ end
129
+ if parts.size == 1
130
+ current.entry_set(parts[0], value)
131
+ return current
132
+ end
133
+ part, *rest = parts
134
+ if !current.respond_to? :entry
135
+ raise MultiGit::Error::InvalidTraversal, "Can't traverse to #{path} from #{self.inspect}: #{current.inspect} doesn't contain an entry named #{part.inspect}"
136
+ end
137
+ entry = current.entry(part)
138
+ if !entry.kind_of? MultiGit::Directory::Builder
139
+ # fine
140
+ if entry.kind_of? MultiGit::Tree
141
+ entry = entry.to_builder
142
+ elsif create == :overwrite || ( entry.nil? && create )
143
+ entry = MultiGit::Directory::Builder.new(current, part)
144
+ else
145
+ raise MultiGit::Error::InvalidTraversal, "Can't traverse to #{path} from #{self.inspect}: #{current.inspect} doesn't contain an entry named #{part.inspect}" unless entry
146
+ end
147
+ end
148
+ current.entry_set(part, traverse_set(entry, rest, value, create))
149
+ return current
150
+ end
151
+
152
+ def file(name, content = nil, &block)
153
+ set(name){|parent, name|
154
+ File::Builder.new(parent, name, content, &block)
155
+ }
156
+ end
157
+
158
+ def directory(name, &block)
159
+ set(name){|parent, name|
160
+ Directory::Builder.new(parent, name, &block)
161
+ }
162
+ end
163
+
164
+ def link(name, target)
165
+ set(name){|parent, name|
166
+ Symlink::Builder.new(parent, name, target)
167
+ }
168
+ end
169
+
170
+ def delete(name)
171
+ set(name){ nil }
172
+ end
173
+
174
+ def to_builder
175
+ self
176
+ end
177
+
178
+ end
179
+
180
+ include DSL
181
+
182
+ end
183
+ end
184
+ require 'multi_git/directory'
@@ -0,0 +1,144 @@
1
+ require 'multi_git/object'
2
+ require 'forwardable'
3
+ module MultiGit
4
+ module Tree
5
+
6
+ # @visibility protected
7
+ SLASH = '/'.freeze
8
+
9
+ module Base
10
+
11
+ include Enumerable
12
+
13
+ def type
14
+ :tree
15
+ end
16
+
17
+ def parent?
18
+ false
19
+ end
20
+
21
+ def key?(key)
22
+ if key.kind_of? String
23
+ return entries.key?(key)
24
+ else
25
+ raise ArgumentError, "Expected a String, got #{key.inspect}"
26
+ end
27
+ end
28
+
29
+ # @param [String] key
30
+ # @return [MultiGit::TreeEntry, nil]
31
+ def entry(key)
32
+ entries[key]
33
+ end
34
+
35
+ # Traverses to path
36
+ # @param [String] path
37
+ # @param [Hash] options
38
+ # @option options [Boolean] :follow follow sylinks ( default: true )
39
+ # @raise [MultiGit::Error::InvalidTraversal] if the path is not reacheable
40
+ # @raise [MultiGit::Error::CyclicSymlink] if a cyclic symlink is found
41
+ # @return [MultiGit::TreeEntry]
42
+ def traverse(path, options = {})
43
+ unless path.kind_of? String
44
+ raise ArgumentError, "Expected a String, got #{path.inspect}"
45
+ end
46
+ parts = path.split('/').reverse!
47
+ current = self
48
+ follow = options.fetch(:follow){true}
49
+ symlinks = Set.new
50
+ while parts.any?
51
+ part = parts.pop
52
+ if part == '..'
53
+ unless current.parent?
54
+ raise MultiGit::Error::InvalidTraversal, "Can't traverse to parent of #{current.inspect} since I don't know where it is."
55
+ end
56
+ current = current.parent
57
+ elsif part == '.' || part == ''
58
+ # do nothing
59
+ else
60
+ if !current.respond_to? :entry
61
+ raise MultiGit::Error::InvalidTraversal, "Can't traverse to #{path} from #{self.inspect}: #{current.inspect} doesn't contain an entry named #{part.inspect}"
62
+ end
63
+ entry = current.entry(part)
64
+ raise MultiGit::Error::InvalidTraversal, "Can't traverse to #{path} from #{self.inspect}: #{current.inspect} doesn't contain an entry named #{part.inspect}" unless entry
65
+ # may be a symlink
66
+ if entry.respond_to? :target
67
+ # this is a symlink
68
+ if symlinks.include? entry
69
+ # We have already seen this symlink
70
+ #TODO: it's okay to see a symlink twice if requested
71
+ raise MultiGit::Error::CyclicSymlink, "Cyclic symlink detected while traversing #{path} from #{self.inspect}."
72
+ else
73
+ symlinks << entry
74
+ end
75
+ if follow
76
+ parts.push(*entry.target.split(SLASH))
77
+ else
78
+ if parts.none?
79
+ return entry
80
+ else
81
+ raise ArgumentError, "Can't follow symlink #{entry.inspect} since you didn't allow me to"
82
+ end
83
+ end
84
+ else
85
+ current = entry
86
+ end
87
+ end
88
+ end
89
+ return current
90
+ end
91
+
92
+ alias / traverse
93
+ alias [] traverse
94
+
95
+ # @yield [MultiGit::TreeEntry]
96
+ def each
97
+ return to_enum unless block_given?
98
+ entries.each do |name, entry|
99
+ yield entry
100
+ end
101
+ return self
102
+ end
103
+
104
+ # @return [Integer] number of entries
105
+ def size
106
+ entries.size
107
+ end
108
+
109
+ # @return [Array<String>] names of all entries
110
+ def names
111
+ entries.keys
112
+ end
113
+
114
+ end
115
+
116
+ include Base
117
+ include Object
118
+
119
+ def to_builder
120
+ Builder.new(self)
121
+ end
122
+
123
+ # @visibility private
124
+ def inspect
125
+ ['#<',self.class.name,' ',oid,' repository:', repository.inspect,'>'].join
126
+ end
127
+
128
+ protected
129
+ # @return [Hash<String, MultiGit::TreeEntry>]
130
+ def entries
131
+ @entries ||= Hash[ raw_entries.map{|name, mode, oid| [name, make_entry(name, mode, oid) ] } ]
132
+ end
133
+
134
+ def raw_entries
135
+ raise Error::NotYetImplemented, "#{self.class}#each_entry"
136
+ end
137
+
138
+ def make_entry(name, mode, oid)
139
+ repository.read_entry(self, name,mode,oid)
140
+ end
141
+
142
+ end
143
+ end
144
+ require 'multi_git/tree/builder'
@@ -0,0 +1,86 @@
1
+ require 'multi_git/utils'
2
+ require 'multi_git/object'
3
+ require 'multi_git/builder'
4
+ module MultiGit
5
+
6
+ base = Class.new
7
+
8
+ # @!parse
9
+ # class TreeEntry < TreeEntry::Base
10
+ # end
11
+ class TreeEntry < base
12
+ Base = superclass
13
+ end
14
+
15
+ # A tree entry is like a {MultiGit::Object} or a {MultiGit::Builder} but it
16
+ # also has knows it's parent tree.
17
+ class TreeEntry
18
+
19
+ class Base
20
+
21
+ # @return [String]
22
+ attr :name
23
+ # @return [MultiGit::Tree::Base]
24
+ attr :parent
25
+
26
+ extend MultiGit::Utils::AbstractMethods
27
+
28
+ # @!method mode
29
+ # @abstract
30
+ # @return [Integer] the git-internal entry mode
31
+ abstract :mode
32
+
33
+ # @visibility private
34
+ def initialize(parent, name, inner)
35
+ @parent = parent
36
+ @name = name
37
+ @object = inner
38
+ end
39
+
40
+ # @param [MultiGit::Object] parent
41
+ # @return [MultiGit::TreeEntry]
42
+ def with_parent(parent, name = self.name)
43
+ self.class.new(parent, name, @object)
44
+ end
45
+
46
+ end
47
+
48
+ class Builder < Base
49
+
50
+ include MultiGit::Builder
51
+
52
+ # @return [MultiGit::Builder]
53
+ attr :object
54
+
55
+ # {include:MultiGit::Builder#>>}
56
+ # @param (see MultiGit::Builder#>>)
57
+ # @return [MultiGit::TreeEntry]
58
+ def >>(repository)
59
+ result = object >> repository
60
+ return repository.read_entry(parent, name, mode, result.oid)
61
+ end
62
+
63
+ # @visibility private
64
+ def initialize(parent, name, *args, &block)
65
+ super( parent, name , make_inner(*args) )
66
+ instance_eval(&block) if block
67
+ end
68
+
69
+ end
70
+
71
+ # @return [MultiGit::Object]
72
+ attr :object
73
+
74
+ include MultiGit::Object
75
+
76
+ extend Forwardable
77
+
78
+ delegate (MultiGit::Object.instance_methods - ::Object.instance_methods) => :object
79
+
80
+ def to_builder
81
+ self.class::Builder.new(parent, name, object)
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,57 @@
1
+ module MultiGit
2
+ module Utils
3
+
4
+ module AbstractMethods
5
+
6
+ def abstract(name)
7
+ class_eval <<RUBY
8
+ def #{name}(*args, &block)
9
+ raise NotImplementedError, "Please implement #{name} for \#{self}"
10
+ end
11
+ RUBY
12
+ end
13
+
14
+ end
15
+
16
+ NULL_OID = '0'*40
17
+
18
+ MODE_SYMLINK = 0120000
19
+ MODE_SUBMODULE = 0160000
20
+ MODE_DIRECTORY = 0040000
21
+ MODE_FILE = 0100644
22
+ MODE_EXECUTEABLE = 0100755
23
+
24
+ MODE_TYPES = {
25
+ MODE_SYMLINK => :blob,
26
+ MODE_SUBMODULE => :commit,
27
+ MODE_DIRECTORY => :tree,
28
+ MODE_FILE => :blob,
29
+ MODE_EXECUTEABLE => :blob
30
+ }
31
+
32
+ # @api private
33
+ DOTS = { '.' => true, '..' => true }
34
+
35
+ def empty_dir?(path)
36
+ Dir.new(path).reject{|path| DOTS[path] }.none?
37
+ end
38
+
39
+ # A
40
+ def looks_bare?(path)
41
+ return nil unless ::File.exists?(path)
42
+ return !::File.exists?(::File.join(path,'.git')) &&
43
+ ::File.exists?(::File.join(path,'refs'))
44
+ end
45
+
46
+ # @api private
47
+ def file_loadeable?(file)
48
+ $LOAD_PATH.any?{|path| File.exists?( File.join(path, file) ) }
49
+ end
50
+
51
+ def type_from_mode(mode)
52
+ MODE_TYPES.fetch(mode.to_i){ raise "Unknown file mode #{mode}" }
53
+ end
54
+
55
+ extend self
56
+ end
57
+ end