multi_git 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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