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