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,381 @@
1
+ require 'multi_git/utils'
2
+ require 'fileutils'
3
+ module MultiGit
4
+
5
+ # A reference is something that points eihter to another reference or to
6
+ # an {MultiGit::Object}. So the most noteable method of this class is
7
+ # {#target}.
8
+ #
9
+ # Instances of this classe are immuteable and reuseable. All writing methods
10
+ # return new instances.
11
+ #
12
+ # @abstract
13
+ module Ref
14
+
15
+ # Updates a reference. Different subclasses use different mechanisms
16
+ # for locking and storing the references. Locks may be acquired in the
17
+ # constructor and must be released in #destroy! .
18
+ #
19
+ # Updaters are inherently neither threadsafe nor reuseable. This should
20
+ # , however, not be a problem since they are not allocated by users.
21
+ #
22
+ # @api developer
23
+ # @abstract
24
+ class Updater
25
+
26
+ # @return [MultiGit::Object, MultiGit::Ref, nil]
27
+ attr :target
28
+
29
+ # @return [MultiGit::Ref]
30
+ attr :ref
31
+
32
+ # @return [MultiGit::Repository]
33
+ def repository
34
+ @ref.repository
35
+ end
36
+
37
+ # @return [String]
38
+ def name
39
+ @ref.name
40
+ end
41
+
42
+ # @param ref [Ref]
43
+ def initialize(ref)
44
+ self.ref = ref
45
+ end
46
+
47
+ # Finally carry out the update.
48
+ #
49
+ # @param new [MultiGit::Object, MultiGit::Ref, nil]
50
+ # @return [MultiGit::Object, MultiGit::Ref, nil]
51
+ # @abstract
52
+ def update(new)
53
+ nx = case new
54
+ when Ref, nil then new
55
+ when Object, Builder then repository.write(new)
56
+ else raise
57
+ end
58
+ @target = nx
59
+ return nx
60
+ end
61
+
62
+ # Release all resources used by this updater.
63
+ def destroy!
64
+ end
65
+
66
+ # @param ref [Ref]
67
+ def ref=(ref)
68
+ @ref = ref
69
+ @target = ref.target
70
+ return ref
71
+ end
72
+
73
+ end
74
+
75
+ # @api developer
76
+ class FileUpdater < Updater
77
+
78
+ protected
79
+
80
+ def open_file(exists)
81
+ mode = ::File::WRONLY | ::File::TRUNC
82
+ if !exists
83
+ begin
84
+ return ::File.open(file_path, mode | ::File::CREAT)
85
+ rescue Errno::EEXIST
86
+ raise Error::ConcurrentRefUpdate
87
+ end
88
+ else
89
+ begin
90
+ return ::File.open(file_path, mode)
91
+ rescue Errno::ENOENT
92
+ raise Error::ConcurrentRefUpdate
93
+ end
94
+ end
95
+ end
96
+
97
+ def object_to_ref_str(object)
98
+ case(object)
99
+ when nil then ''
100
+ when MultiGit::Object then object.oid
101
+ when Ref then "ref: #{object.name}"
102
+ end
103
+ end
104
+
105
+ def file_path
106
+ ::File.join(repository.git_dir, name)
107
+ end
108
+
109
+ def lock_file_path
110
+ ::File.join(repository.git_dir, name + '.lock')
111
+ end
112
+
113
+ def acquire_lock
114
+ ::File.open(lock_file_path, ::File::CREAT | ::File::RDWR | ::File::EXCL )
115
+ end
116
+
117
+ def release_lock(lock)
118
+ ::File.unlink(lock.path)
119
+ lock.flock(::File::LOCK_UN)
120
+ end
121
+
122
+ end
123
+
124
+ # @api developer
125
+ class PessimisticFileUpdater < FileUpdater
126
+
127
+ def initialize(*_)
128
+ super
129
+ @lock = acquire_lock
130
+ # safe now
131
+ @ref = @ref.reload
132
+ end
133
+
134
+ def update(new)
135
+ old = target
136
+ nx = super
137
+ if nx
138
+ str = object_to_ref_str(nx)
139
+ begin
140
+ file = open_file(!old.nil?)
141
+ file.puts(str)
142
+ file.flush
143
+ ensure
144
+ file.close if file
145
+ end
146
+ else
147
+ ::File.unlink(file_path)
148
+ end
149
+ return nx
150
+ end
151
+
152
+ def destroy!
153
+ release_lock(@lock)
154
+ end
155
+
156
+ end
157
+
158
+ # @api developer
159
+ class OptimisticFileUpdater < FileUpdater
160
+
161
+ def update(new)
162
+ begin
163
+ lock = acquire_lock
164
+ if ::File.exists?(file_path)
165
+ content = ::File.read(file_path).chomp
166
+ if content != object_to_ref_str(target)
167
+ raise Error::ConcurrentRefUpdate
168
+ end
169
+ elsif !target.nil?
170
+ raise Error::ConcurrentRefUpdate
171
+ end
172
+ old = target
173
+ nx = super
174
+ if nx.nil?
175
+ if !old
176
+ return nx
177
+ end
178
+ ::File.unlink(file_path)
179
+ else
180
+ begin
181
+ file = open_file( !old.nil? )
182
+ str = object_to_ref_str(nx)
183
+ file.puts( str )
184
+ file.flush
185
+ ensure
186
+ file.close if file
187
+ end
188
+ end
189
+ return nx
190
+ ensure
191
+ release_lock( lock )
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ end
198
+
199
+ extend MultiGit::Utils::AbstractMethods
200
+
201
+ # The full name of this ref e.g. refs/heads/master for the master branch.
202
+ # @return [String]
203
+ attr :name
204
+
205
+ # @!attribute [r] target
206
+ # The target of this ref.
207
+ # @return [MultiGit::Ref, MultiGit::Object, nil]
208
+ # @abstract
209
+ abstract :target
210
+
211
+ # @return [MultiGit::Repository]
212
+ attr :repository
213
+
214
+ # @visibility private
215
+ def initialize(repository, name)
216
+ @repository = repository
217
+ @name = name
218
+ end
219
+
220
+ # Rereads this reference from the repository.
221
+ #
222
+ # @return [MultiGit::Ref]
223
+ def reload
224
+ repository.ref(name)
225
+ end
226
+
227
+ # Resolves symbolic references and returns the final reference.
228
+ #
229
+ # @return [MultGit::Ref]
230
+ def resolve
231
+ @leaf = begin
232
+ ref = self
233
+ loop do
234
+ break ref unless ref.symbolic?
235
+ ref = ref.target
236
+ end
237
+ end
238
+ end
239
+
240
+ # @!group Treeish methods
241
+
242
+ def [](name)
243
+ t = resolve.target
244
+ if t
245
+ return t[name]
246
+ end
247
+ end
248
+
249
+ alias / []
250
+
251
+ def []=(path, options = {}, value)
252
+ resolve.update(options.fetch(:lock, :pessimistic) ) do |commit|
253
+
254
+ end
255
+ end
256
+
257
+ # @!endgroup
258
+
259
+ # @!group Utility methods
260
+
261
+ def direct?
262
+ name.include?('/')
263
+ end
264
+
265
+ def symbolic?
266
+ !direct?
267
+ end
268
+
269
+ def exists?
270
+ !target.nil?
271
+ end
272
+
273
+ # @!endgroup
274
+
275
+ # @!group Writing methods
276
+
277
+ # Updates the target of this reference.
278
+ #
279
+ # The new target of this reference is the result of the passed block. If
280
+ # you return nil, the ref will be deleted.
281
+ #
282
+ # By using the lock param you can control the isolation:
283
+ #
284
+ # [:optimistic] If the target is altered during the execution of the
285
+ # block, a {MultiGit::Error::ConcurrentRefUpdate} is
286
+ # raised. This is the default as it holds hard locks
287
+ # only as long as necessary while providing pointfull
288
+ # isolation.
289
+ # [:pessimistic] A lock is acquired and held during the execution of the
290
+ # block. Concurrent updates will wait or fail. This is
291
+ # good if the block is not retry-able or very small.
292
+ #
293
+ # @param lock [:optimistic, :pessimistic]
294
+ # @yield [current_target] Yields the current target and expects the block to return the new target
295
+ # @yieldparam current_target [MultiGit::Ref, MultiGit::Object, nil] current target
296
+ # @yieldreturn [MultiGit::Ref, MultiGit::Object, nil] new target
297
+ # @return [MultiGit::Ref] The altered ref
298
+ #
299
+ # @example
300
+ # # setup:
301
+ # dir = `mktemp -d`
302
+ # repository = MultiGit.open(dir, init: true)
303
+ # # insert a commit:
304
+ # builder = MultiGit::Commit::Builder.new
305
+ # builder.tree['a_file'] = 'some_content'
306
+ # commit = repository.write(builder)
307
+ # # update the ref:
308
+ # ref = repository.ref('refs/heads/master') #=> be_a MultiGit::Ref
309
+ # ref.update do |current_target|
310
+ # current_target #=> be_nil
311
+ # commit
312
+ # end
313
+ # # check result:
314
+ # repository.ref('refs/heads/master').target #=> eql commit
315
+ # # teardown:
316
+ # `rm -rf #{dir}`
317
+ def update( lock = :optimistic )
318
+ updater_class = case lock
319
+ when :optimistic then optimistic_updater
320
+ when :pessimistic then pessimistic_updater
321
+ end
322
+ begin
323
+ updater = updater_class.new(self)
324
+ updater.update( yield(updater.target) )
325
+ return reload
326
+ ensure
327
+ updater.destroy! if updater
328
+ end
329
+ end
330
+
331
+ def delete
332
+ update(:pessimistic){ nil }
333
+ end
334
+
335
+ def commit(&block)
336
+ resolve.update do |current|
337
+ Commit::Builder.new(current, &block)
338
+ end
339
+ return reload
340
+ end
341
+
342
+ #@!endgroup
343
+
344
+ # @api private
345
+ # @visibility private
346
+ def hash
347
+ name.hash ^ repository.hash
348
+ end
349
+
350
+ # @api private
351
+ # @visibility private
352
+ def eql?(other)
353
+ return false unless other.kind_of? Ref
354
+ name == other.name && repository.eql?(other.repository)
355
+ end
356
+
357
+ # @api private
358
+ # @visibility private
359
+ def ==(other)
360
+ eql?(other) && target == other.target
361
+ end
362
+
363
+ # @api private
364
+ # @visibility private
365
+ def inspect
366
+ ['<',self.class,' ',repository.inspect,':', name, ' -> ', target.inspect,' >'].join
367
+ end
368
+
369
+ private
370
+
371
+ def optimistic_updater
372
+ OptimisticFileUpdater
373
+ end
374
+
375
+ def pessimistic_updater
376
+ PessimisticFileUpdater
377
+ end
378
+
379
+ end
380
+
381
+ end
@@ -0,0 +1,190 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+ require 'multi_git/utils'
4
+ require 'multi_git/error'
5
+ require 'multi_git/tree_entry'
6
+ require 'multi_git/symlink'
7
+ require 'multi_git/directory'
8
+ require 'multi_git/file'
9
+ require 'multi_git/executeable'
10
+ require 'multi_git/submodule'
11
+
12
+ # Abstract base class for all repository implementations.
13
+ # @abstract
14
+ class MultiGit::Repository
15
+
16
+ protected
17
+ Utils = MultiGit::Utils
18
+ Error = MultiGit::Error
19
+
20
+ VALID_TYPES = Set[:blob, :tree, :commit, :tag]
21
+ public
22
+ extend Utils::AbstractMethods
23
+
24
+ # @!method git_dir
25
+ # @abstract
26
+ # Return the repository base directory
27
+ # @return [String]
28
+ abstract :git_dir
29
+
30
+ # @!method bare?
31
+ # @abstract
32
+ # Is this repository bare?
33
+ abstract :bare?
34
+
35
+ # @!method initialize(directory, options = {})
36
+ # @param directory [String] a directory
37
+ # @option options [Boolean] :init init the repository if it doesn't exist
38
+ # @option options [Boolean] :bare open/init the repository bare
39
+
40
+ # @!method read(ref)
41
+ # Reads a reference.
42
+ #
43
+ # @abstract
44
+ #
45
+ # @raise [MultiGit::Error::InvalidReference] if ref is not a valid reference
46
+ # @raise [MultiGit::Error::AmbiguousReference] if ref refers to multiple objects
47
+ # @raise [MultiGit::Error::BadRevisionSyntax] if ref does not contain a valid ref-syntax
48
+ # @param [String] ref
49
+ # @return [MultiGit::Object] object
50
+ abstract :read
51
+
52
+ # @!method include?(oid)
53
+ # Checks whether this repository contains a given oid.
54
+ # @abstract
55
+ # @param [String] oid
56
+ # @return [Boolean]
57
+ abstract :include?
58
+
59
+ # @!method parse(ref)
60
+ # Resolves a reference into an oid.
61
+ # @abstract
62
+ # @param [String] rev
63
+ # @raise [MultiGit::Error::InvalidReference] if ref is not a valid reference
64
+ # @raise [MultiGit::Error::AmbiguousReference] if ref refers to multiple objects
65
+ # @raise [MultiGit::Error::BadRevisionSyntax] if ref does not contain a valid ref-syntax
66
+ # @return [String] oid
67
+ abstract :parse
68
+
69
+ # @!method write(content)
70
+ # Writes something to the repository.
71
+ #
72
+ # If called with a String or an IO, this method creates a {MultiGit::Blob} with the
73
+ # given content. This is the easiest way to create blobs.
74
+ #
75
+ # If called with a {MultiGit::Object}, this method determines if the object does already exist
76
+ # and writes it otherwise.
77
+ #
78
+ # If called with a {MultiGit::Builder}, this method inserts the content of the builder to the
79
+ # repository. This is the easiest way to create trees/commits.
80
+ #
81
+ # @abstract
82
+ # @param [String, IO, MultiGit::Object, MultiGit::Builder] content
83
+ # @return [MultiGit::Object] the resulting object
84
+ abstract :write
85
+
86
+ # @!method ref(name)
87
+ # Opens a reference.
88
+ # @abstract
89
+ # @param [String] name
90
+ # @return [MultiGit::Ref] ref
91
+ abstract :ref
92
+
93
+ def branch(name)
94
+ if name.include? '/'
95
+ ref('refs/remotes/'+name)
96
+ else
97
+ ref('refs/heads/'+name)
98
+ end
99
+ end
100
+
101
+ def tag(name)
102
+ ref('refs/tags/'+name)
103
+ end
104
+
105
+ # @!parse alias_method :[], :ref
106
+ def [](name)
107
+ ref(name)
108
+ end
109
+
110
+ # @!parse alias_method :<<, :write
111
+ def <<(*args,&block)
112
+ write(*args,&block)
113
+ end
114
+
115
+ # @visibility private
116
+ def inspect
117
+ if bare?
118
+ ["#<",self.class.name," ",git_dir,">"].join
119
+ else
120
+ ["#<",self.class.name," ",git_dir," checked out at:",git_work_tree,">"].join
121
+ end
122
+ end
123
+
124
+ # @visibility private
125
+ EC = {
126
+ Utils::MODE_EXECUTEABLE => MultiGit::Executeable,
127
+ Utils::MODE_FILE => MultiGit::File,
128
+ Utils::MODE_SYMLINK => MultiGit::Symlink,
129
+ Utils::MODE_DIRECTORY => MultiGit::Directory
130
+ }
131
+
132
+ # @visibility private
133
+ def read_entry(parent = nil, name, mode, oidish)
134
+ obj = read(oidish)
135
+ EC[mode].new(parent, name, obj)
136
+ end
137
+ protected
138
+
139
+ def initialize_options(path, options)
140
+ options = options.dup
141
+ options[:expected_bare] = options[:bare]
142
+ looks_bare = Utils.looks_bare?(path)
143
+ case(options[:bare])
144
+ when nil then
145
+ options[:bare] = looks_bare
146
+ when false then
147
+ raise Error::RepositoryBare, path if looks_bare
148
+ end
149
+ if !::File.exists?(path)
150
+ if options[:init]
151
+ FileUtils.mkdir_p(path)
152
+ else
153
+ raise Error::NotARepository, path
154
+ end
155
+ end
156
+ if options[:bare]
157
+ if looks_bare || options[:init]
158
+ options[:repository] ||= path
159
+ else
160
+ options[:repository] ||= ::File.join(path, '.git')
161
+ end
162
+ options.delete(:working_directory)
163
+ else
164
+ options[:working_directory] = path
165
+ options[:repository] ||= ::File.join(path, '.git')
166
+ end
167
+ options[:index] ||= ::File.join(options[:repository],'index')
168
+ return options
169
+ end
170
+
171
+ def verify_bareness(path, options)
172
+ bareness = options[:expected_bare]
173
+ return if bareness.nil?
174
+ if !bareness && bare?
175
+ raise Error::RepositoryBare, path
176
+ end
177
+ end
178
+
179
+ def verify_type_for_mode(type, mode)
180
+ expected = Utils.type_from_mode(mode)
181
+ unless type == expected
182
+ raise Error::WrongTypeForMode.new(expected, type)
183
+ end
184
+ end
185
+
186
+ def validate_type(type)
187
+ raise Error::InvalidObjectType, type.inspect unless VALID_TYPES.include?(type)
188
+ end
189
+
190
+ end
@@ -0,0 +1,8 @@
1
+ require 'stringio'
2
+ require 'multi_git/blob'
3
+ require 'multi_git/rugged_backend/object'
4
+ module MultiGit::RuggedBackend
5
+ class Blob < Object
6
+ include MultiGit::Blob
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'multi_git/commit'
2
+ require 'multi_git/rugged_backend/object'
3
+ module MultiGit
4
+ module RuggedBackend
5
+ class Commit < Object
6
+ include MultiGit::Commit
7
+
8
+ def tree
9
+ @tree ||= repository.read(rugged_object.tree_oid)
10
+ end
11
+
12
+ def parents
13
+ @parents ||= rugged_object.parent_oids.map{|oid| repository.read(oid) }
14
+ end
15
+
16
+ def author
17
+ MultiGit::Handle.new(rugged_object.author[:name],rugged_object.author[:email])
18
+ end
19
+
20
+ def time
21
+ rugged_object.author[:time]
22
+ end
23
+
24
+ def committer
25
+ MultiGit::Handle.new(rugged_object.committer[:name],rugged_object.committer[:email])
26
+ end
27
+
28
+ def commit_time
29
+ rugged_object.committer[:time]
30
+ end
31
+
32
+ def message
33
+ rugged_object.message
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require 'forwardable'
2
+ require 'multi_git/object'
3
+ class MultiGit::RuggedBackend::Object
4
+
5
+ include MultiGit::Object
6
+
7
+ extend Forwardable
8
+
9
+ def initialize( repository, oid, object = nil )
10
+ @repository = repository
11
+ @git = repository.__backend__
12
+ @oid = oid
13
+ @rugged_object = object
14
+ end
15
+
16
+ def to_io
17
+ StringIO.new(content)
18
+ end
19
+
20
+ def bytesize
21
+ rugged_odb.len
22
+ end
23
+
24
+ def content
25
+ @content ||= rugged_odb.data.freeze
26
+ end
27
+
28
+ protected
29
+
30
+ def rugged_object
31
+ @rugged_object ||= @git.lookup(@oid)
32
+ end
33
+
34
+ def rugged_odb
35
+ @rugged_odb ||= @git.read(@oid)
36
+ end
37
+
38
+ end
@@ -0,0 +1,32 @@
1
+ require 'multi_git/ref'
2
+ module MultiGit
3
+
4
+ module RuggedBackend
5
+
6
+ class Ref
7
+
8
+ include MultiGit::Ref
9
+
10
+ def initialize(repository, name)
11
+ super(repository, name)
12
+ @rugged_ref = Rugged::Reference.lookup(repository.__backend__, name)
13
+ end
14
+
15
+ def target
16
+ return nil unless rugged_ref
17
+ @target ||= begin
18
+ if rugged_ref.type == :symbolic
19
+ repository.ref(rugged_ref.target)
20
+ else
21
+ repository.read(rugged_ref.target)
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+ attr :rugged_ref
28
+ end
29
+
30
+ end
31
+
32
+ end