multi_git 0.0.1.alpha2 → 0.0.1.beta1

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.
@@ -4,6 +4,8 @@ require 'multi_git/jgit_backend/blob'
4
4
  require 'multi_git/jgit_backend/tree'
5
5
  require 'multi_git/jgit_backend/commit'
6
6
  require 'multi_git/jgit_backend/ref'
7
+ require 'multi_git/jgit_backend/config'
8
+ require 'multi_git/jgit_backend/remote'
7
9
  module MultiGit::JGitBackend
8
10
  class Repository < MultiGit::Repository
9
11
 
@@ -114,6 +116,10 @@ module MultiGit::JGitBackend
114
116
  Ref.new(self, name)
115
117
  end
116
118
 
119
+ def config
120
+ @config ||= Config.new(@git.config)
121
+ end
122
+
117
123
  private
118
124
  ALL_FILTER = %r{\Arefs/(?:heads|remotes)}
119
125
  LOCAL_FILTER = %r{\Arefs/heads}
@@ -212,6 +218,15 @@ module MultiGit::JGitBackend
212
218
  return Java::OrgEclipseJgitLib::ObjectId.toString(parse_java(ref))
213
219
  end
214
220
 
221
+ def remote( name_or_url )
222
+ if looks_like_remote_url? name_or_url
223
+ remote = Remote.new(self, name_or_url)
224
+ else
225
+ remote = Remote::Persistent.new(self, name_or_url)
226
+ end
227
+ return remote
228
+ end
229
+
215
230
  # @visibility private
216
231
  # @api private
217
232
  def parse_java(oidish)
data/lib/multi_git/ref.rb CHANGED
@@ -157,7 +157,6 @@ module MultiGit
157
157
  def destroy!
158
158
  release_lock(@lock)
159
159
  end
160
-
161
160
  end
162
161
 
163
162
  # @api developer
@@ -197,9 +196,17 @@ module MultiGit
197
196
  release_lock( lock ) if lock
198
197
  end
199
198
  end
199
+ end
200
200
 
201
- private
202
-
201
+ class RecklessUpdater < Updater
202
+ def update( new )
203
+ pu = PessimisticFileUpdater.new( ref )
204
+ begin
205
+ pu.update( new )
206
+ ensure
207
+ pu.destroy!
208
+ end
209
+ end
203
210
  end
204
211
 
205
212
  extend MultiGit::Utils::AbstractMethods
@@ -254,12 +261,6 @@ module MultiGit
254
261
 
255
262
  alias / []
256
263
 
257
- def []=(path, options = {}, value)
258
- resolve.update(options.fetch(:lock, :pessimistic) ) do |commit|
259
-
260
- end
261
- end
262
-
263
264
  # @!endgroup
264
265
 
265
266
  # @!group Utility methods
@@ -272,6 +273,10 @@ module MultiGit
272
273
  !direct?
273
274
  end
274
275
 
276
+ def detached?
277
+ symbolic? && !target.kind_of?(Ref)
278
+ end
279
+
275
280
  def exists?
276
281
  !target.nil?
277
282
  end
@@ -285,22 +290,31 @@ module MultiGit
285
290
  # The new target of this reference is the result of the passed block. If
286
291
  # you return nil, the ref will be deleted.
287
292
  #
288
- # By using the lock param you can control the isolation:
293
+ # @overload update( lock = :optimistic )
294
+ # By using the lock param you can control the isolation:
295
+ #
296
+ # [:reckless] Updates the reference the hard way. Only locks enough
297
+ # to ensure the integrity of the repository and simply
298
+ # overwrites concurrent changes.
299
+ # [:optimistic] If the target is altered during the execution of the
300
+ # block, a {MultiGit::Error::ConcurrentRefUpdate} is
301
+ # raised. This is the default as it holds hard locks
302
+ # only as long as necessary while providing pointfull
303
+ # isolation.
304
+ # [:pessimistic] A lock is acquired and held during the execution of the
305
+ # block. Concurrent updates will wait or fail. This is
306
+ # good if the block is not retry-able or very small.
289
307
  #
290
- # [:optimistic] If the target is altered during the execution of the
291
- # block, a {MultiGit::Error::ConcurrentRefUpdate} is
292
- # raised. This is the default as it holds hard locks
293
- # only as long as necessary while providing pointfull
294
- # isolation.
295
- # [:pessimistic] A lock is acquired and held during the execution of the
296
- # block. Concurrent updates will wait or fail. This is
297
- # good if the block is not retry-able or very small.
308
+ # @param lock [:reckless, :optimistic, :pessimistic]
309
+ # @yield [current_target] Yields the current target and expects the block to return the new target
310
+ # @yieldparam current_target [MultiGit::Ref, MultiGit::Object, nil] current target
311
+ # @yieldreturn [MultiGit::Ref, MultiGit::Object, nil] new target
312
+ # @return [MultiGit::Ref] The altered ref
298
313
  #
299
- # @param lock [:optimistic, :pessimistic]
300
- # @yield [current_target] Yields the current target and expects the block to return the new target
301
- # @yieldparam current_target [MultiGit::Ref, MultiGit::Object, nil] current target
302
- # @yieldreturn [MultiGit::Ref, MultiGit::Object, nil] new target
303
- # @return [MultiGit::Ref] The altered ref
314
+ # @overload update( value )
315
+ #
316
+ # @param value [Commit, Ref, nil] new target for this ref
317
+ # @return [MultiGit::Ref] The altered ref
304
318
  #
305
319
  # @example
306
320
  # # setup:
@@ -320,26 +334,40 @@ module MultiGit
320
334
  # repository.ref('refs/heads/master').target #=> eql commit
321
335
  # # teardown:
322
336
  # `rm -rf #{dir}`
323
- def update( lock = :optimistic )
324
- updater_class = case lock
325
- when :optimistic then optimistic_updater
326
- when :pessimistic then pessimistic_updater
327
- end
328
- begin
329
- updater = updater_class.new(self)
330
- updater.update( yield(updater.target) )
331
- return reload
332
- ensure
333
- updater.destroy! if updater
334
- end
337
+ def update( value_or_lock = :optimistic )
338
+ updater = updater_class(block_given?, value_or_lock).new(self)
339
+ updater.update( block_given? ? yield(updater.target) : value_or_lock )
340
+ return reload
341
+ ensure
342
+ updater.destroy! if updater
335
343
  end
336
344
 
345
+ # Shorthand for deleting this ref.
346
+ # @return [Ref]
337
347
  def delete
338
- update(:pessimistic){ nil }
348
+ update( nil )
339
349
  end
340
350
 
341
- def commit(&block)
342
- resolve.update do |current|
351
+ # Shorthand method to directly create a commit and update the given ref.
352
+ #
353
+ # @example
354
+ # # setup:
355
+ # dir = `mktemp -d`
356
+ # repository = MultiGit.open(dir, init: true)
357
+ # # insert a commit:
358
+ # repository.head.commit do
359
+ # tree['a_file'] = 'some_content'
360
+ # end
361
+ # # check result:
362
+ # repository.head['a_file'].content #=> eql 'some_content'
363
+ # # teardown:
364
+ # `rm -rf #{dir}`
365
+ #
366
+ # @option options :lock [:optimistic, :pessimistic] How to lock during the commit.
367
+ # @yield
368
+ # @return [Ref]
369
+ def commit(options = {}, &block)
370
+ resolve.update(options.fetch(:lock, :optimistic)) do |current|
343
371
  Commit::Builder.new(current, &block)
344
372
  end
345
373
  return reload
@@ -382,6 +410,22 @@ module MultiGit
382
410
  PessimisticFileUpdater
383
411
  end
384
412
 
413
+ def reckless_updater
414
+ RecklessUpdater
415
+ end
416
+
417
+ def updater_class( block_given, lock )
418
+ if block_given
419
+ case lock
420
+ when :optimistic then optimistic_updater
421
+ when :pessimistic then pessimistic_updater
422
+ when :reckless then reckless_updater
423
+ end
424
+ else
425
+ pessimistic_updater
426
+ end
427
+ end
428
+
385
429
  end
386
430
 
387
431
  end
@@ -0,0 +1,118 @@
1
+ module MultiGit
2
+
3
+ # A RefSpec describes which and how references are updated during
4
+ # push and pull.
5
+ #
6
+ # It basically says: set the "to" ref to the target of "from"
7
+ #
8
+ # @example
9
+ # refspecs = MultiGit::RefSpec.parse('master')
10
+ #
11
+ class RefSpec < Struct.new(:from,:to,:forced)
12
+
13
+ extend Utils::AbstractMethods
14
+
15
+ # @!attribute from
16
+ # @return [String]
17
+
18
+ # @!attribute to
19
+ # @return [String]
20
+
21
+ # @!attribute forced
22
+ # @return [Boolean]
23
+
24
+ alias forced? forced
25
+
26
+ # @param from [String]
27
+ # @param to [String]
28
+ # @param forced [Boolean]
29
+ def initialize(from,to,forced = false)
30
+ super
31
+ end
32
+
33
+ def inspect
34
+ ['#<',self.class,' ',forced ? '+':'',from,':',to,'>'].join
35
+ end
36
+
37
+ def to_s
38
+ [forced ? '+':'',from,':',to].join
39
+ end
40
+
41
+ class Parser
42
+
43
+ REF = %r{\A(\+?)([a-zA-Z/0-9_*]+)?(?:(:)([a-zA-Z/0-9_*]+)?)?\z}
44
+
45
+ attr :from_base, :to_base
46
+
47
+ def initialize(from_base = 'refs/heads/', to_base )
48
+ @from_base = from_base
49
+ @to_base = to_base
50
+ end
51
+
52
+ #
53
+ # @param args [RefSpec, String, Hash, Range, ...]
54
+ # @return [Array<RefSpec>]
55
+ def [](*args)
56
+ args.collect_concat do |arg|
57
+ if arg.kind_of? RefSpec
58
+ [arg]
59
+ elsif arg.kind_of? String
60
+ [parse_string(arg)]
61
+ elsif arg.kind_of? Hash
62
+ arg.map{|k,v| parse_pair(k,v) }
63
+ elsif arg.kind_of? Range
64
+ [parse_pair(arg.begin, arg.end)]
65
+ else
66
+ raise ArgumentError, "Expected a String, Hash or Range. Got #{arg.inspect}"
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+ def parse_string(string)
73
+ if ma = REF.match(string)
74
+ if ma[2]
75
+ from = normalize(from_base, ma[2])
76
+ end
77
+ if ma[3]
78
+ to = normalize(to_base, ma[4])
79
+ else
80
+ to = normalize(to_base, ma[2])
81
+ end
82
+ RefSpec.new( from, to, ma[1] == '+' )
83
+ end
84
+ end
85
+
86
+ def parse_pair(a,b)
87
+ RefSpec.new( normalize(from_base,a.to_s), normalize(to_base,b.to_s) )
88
+ end
89
+
90
+ def normalize(base, name)
91
+ ns = name.split(SLASH, -1)
92
+ bs = base.split(SLASH, -1)
93
+ fill_reverse(bs, ns)
94
+ return bs.join(SLASH)
95
+ end
96
+
97
+ def fill_reverse(a,b)
98
+ s = a.size - b.size
99
+ b.each_with_index do |e, i|
100
+ a[s+i] = e
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ DEFAULT_PARSER = Parser.new('refs/remotes/origin/')
107
+
108
+ class << self
109
+
110
+ def parse(arg)
111
+ DEFAULT_PARSER[arg].first
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,66 @@
1
+ require 'multi_git/refspec'
2
+ module MultiGit
3
+
4
+ module Remote
5
+
6
+ extend Utils::AbstractMethods
7
+
8
+ # @!attribute repository
9
+ # @return [Repository]
10
+ abstract :repository
11
+
12
+ # @!attribute fetch_urls
13
+ # @return [Enumerable<URI>]
14
+ abstract :fetch_urls
15
+
16
+ # @!attribute push_urls
17
+ # @return [Enumerable<URI>]
18
+ abstract :push_urls
19
+
20
+ # @!method fetch( *refspecs )
21
+ # @param refspecs [RefSpec, String, Range, Hash, ...]
22
+ abstract :fetch
23
+
24
+ # @!method push( *refspecs )
25
+ # @param refspecs [RefSpec, String, Range, Hash, ...]
26
+ abstract :push
27
+
28
+ # @!method save( name )
29
+ # @param name [String]
30
+ # @return [Persistent]
31
+ abstract :save
32
+
33
+ module Persistent
34
+ include Remote
35
+ extend Utils::AbstractMethods
36
+
37
+ # @!attribute name
38
+ # @return [String, nil]
39
+ abstract :name
40
+
41
+ # @!method save( name = name )
42
+ # @param name [String]
43
+ # @return [Persistent]
44
+ abstract :save
45
+
46
+ protected
47
+
48
+ def refspec_parser
49
+ RefSpec::Parser.new("refs/remotes/#{name}/")
50
+ end
51
+
52
+ end
53
+
54
+ protected
55
+
56
+ def parse_fetch_refspec(*refspecs)
57
+ refspec_parser[*refspecs]
58
+ end
59
+
60
+ def refspec_parser
61
+ RefSpec::Parser.new("refs/remotes//")
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -21,6 +21,12 @@ protected
21
21
  public
22
22
  extend Utils::AbstractMethods
23
23
 
24
+ # @!method config
25
+ # @abstract
26
+ # Returns the config
27
+ # @return [Config]
28
+ abstract :config
29
+
24
30
  # @!method git_dir
25
31
  # @abstract
26
32
  # Return the repository base directory
@@ -101,6 +107,12 @@ public
101
107
  # @return [MultiGit::Ref] ref
102
108
  abstract :ref
103
109
 
110
+ # Gets the ref
111
+ # @return [Ref] head
112
+ def head
113
+ return ref('HEAD')
114
+ end
115
+
104
116
  # Opens a branch
105
117
  #
106
118
  # @param name [String] branch name
@@ -236,4 +248,9 @@ protected
236
248
  raise Error::InvalidObjectType, type.inspect unless VALID_TYPES.include?(type)
237
249
  end
238
250
 
251
+ def looks_like_remote_url?(string)
252
+ # poor but efficient
253
+ string.include? '/'
254
+ end
255
+
239
256
  end
@@ -0,0 +1,43 @@
1
+ require 'forwardable'
2
+ require 'multi_git/config'
3
+ module MultiGit
4
+ module RuggedBackend
5
+ class Config
6
+
7
+ include MultiGit::Config
8
+
9
+ def initialize(rugged_config)
10
+ @rugged_config = rugged_config
11
+ @config = Hash.new([])
12
+ rugged_config.each_pair do |qk, value|
13
+ key = split_key(qk)
14
+ @config.fetch(key){ @config[key] = [] } << value
15
+ end
16
+ end
17
+
18
+ def get(section, subsection, key)
19
+ s = schema_for(section, subsection, key)
20
+ v = @config[ [section, subsection,key] ]
21
+ if v.size == 0
22
+ s.default
23
+ elsif s.list?
24
+ s.convert(v)
25
+ else
26
+ s.convert(v.last)
27
+ end
28
+ end
29
+
30
+ def each_explicit_key
31
+ return to_enum(:each_explicit_key) unless block_given?
32
+ @config.each_key do |k|
33
+ yield *k
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr :rugged_config
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,66 @@
1
+ require 'multi_git/remote'
2
+ require 'forwardable'
3
+ module MultiGit
4
+ module RuggedBackend
5
+ class Remote
6
+
7
+ include MultiGit::Remote
8
+ extend Forwardable
9
+
10
+ attr :repository
11
+
12
+ attr :rugged_remote
13
+ protected :rugged_remote
14
+
15
+ def fetch_urls
16
+ [rugged_remote.url]
17
+ end
18
+
19
+ # :nocov:
20
+ if Rugged::Remote.instance_methods.include? :push_url
21
+ def push_urls
22
+ [rugged_remote.push_url || rugged_remote.url]
23
+ end
24
+ else
25
+ def push_urls
26
+ raise Error::NotYetImplemented, 'Rugged::Remote#push_urls is only available in bleeding edge rugged'
27
+ end
28
+ end
29
+ # :nocov:
30
+
31
+ def initialize( repository, remote )
32
+ @repository = repository
33
+ @rugged_remote = remote
34
+ end
35
+
36
+ class Persistent < self
37
+
38
+ include MultiGit::Remote::Persistent
39
+ extend Forwardable
40
+
41
+ def_instance_delegator :rugged_remote, :name
42
+
43
+ end
44
+
45
+ if Rugged::Remote.instance_methods.include? :clear_refspecs
46
+ def fetch(*refspecs)
47
+ rs = parse_fetch_refspec(*refspecs)
48
+ cl = Rugged::Remote.new(repository.__backend__, fetch_urls.first)
49
+ cl.clear_refspecs
50
+ rs.each do |spec|
51
+ cl.add_fetch(spec.to_s)
52
+ end
53
+ cl.connect(:fetch) do |r|
54
+ r.download
55
+ end
56
+ cl.update_tips!
57
+ end
58
+ else
59
+ def fetch(*_)
60
+ raise Error::NotYetImplemented, 'This rugged version doesn\'t seem to support fetching. You may want to install from HEAD.'
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -4,6 +4,8 @@ require 'multi_git/rugged_backend/blob'
4
4
  require 'multi_git/rugged_backend/tree'
5
5
  require 'multi_git/rugged_backend/commit'
6
6
  require 'multi_git/rugged_backend/ref'
7
+ require 'multi_git/rugged_backend/config'
8
+ require 'multi_git/rugged_backend/remote'
7
9
  module MultiGit::RuggedBackend
8
10
 
9
11
  class Repository < MultiGit::Repository
@@ -45,6 +47,7 @@ module MultiGit::RuggedBackend
45
47
  raise MultiGit::Error::NotARepository, path
46
48
  end
47
49
  end
50
+ @git.config = Rugged::Config.new(::File.join(@git.path, 'config'))
48
51
  verify_bareness(path, options)
49
52
  end
50
53
 
@@ -115,6 +118,10 @@ module MultiGit::RuggedBackend
115
118
  @git.include?(oid)
116
119
  end
117
120
 
121
+ def config
122
+ @config ||= Config.new(@git.config)
123
+ end
124
+
118
125
  TRUE_LAMBDA = proc{ true }
119
126
 
120
127
  def each_branch(filter = :all)
@@ -181,6 +188,24 @@ module MultiGit::RuggedBackend
181
188
  return read(oid)
182
189
  end
183
190
 
191
+ #
192
+ def remote( name_or_url )
193
+ if looks_like_remote_url? name_or_url
194
+ remote = Rugged::Remote.new(__backend__, name_or_url)
195
+ else
196
+ remote = Rugged::Remote.lookup(__backend__, name_or_url)
197
+ end
198
+ if remote
199
+ if remote.name
200
+ return Remote::Persistent.new(self, remote)
201
+ else
202
+ return Remote.new(self, remote)
203
+ end
204
+ else
205
+ return nil
206
+ end
207
+ end
208
+
184
209
  private
185
210
 
186
211
  def strip_slash(path)
@@ -94,14 +94,37 @@ module MultiGit
94
94
  alias / traverse
95
95
  alias [] traverse
96
96
 
97
+ # Works like the builtin Dir.glob
98
+ #
99
+ # @param pattern [String] A glob pattern
100
+ # @param flags [Integer] glob flags
101
+ # @yield [MultiGit::TreeEntry]
102
+ # @return [Enumerator] if called without a block
103
+ # @return self if called with a block
104
+ #
105
+ # @example
106
+ # bld = MultiGit::Tree::Builder.new do
107
+ # file "file"
108
+ # directory "folder" do
109
+ # file "file"
110
+ # end
111
+ # end
112
+ # bld.glob( '**/file' ).map(&:path) #=> eq ['file','folder/file']
113
+ #
114
+ # @see http://ruby-doc.org/core/Dir.html#method-c-glob
97
115
  def glob( pattern, flags = 0 )
98
116
  return to_enum(:glob, pattern, flags) unless block_given?
99
- l = path.size
117
+ l = respond_to?(:path) ? path.size : 0
100
118
  flags |= ::File::FNM_PATHNAME
101
- walk_pre do |object|
102
- if ::File.fnmatch(pattern, object.path[l..-1], flags)
103
- yield object
104
- false
119
+ if ::File.fnmatch(pattern, '.', flags)
120
+ yield self
121
+ end
122
+ each do |entry|
123
+ entry.walk_pre do |sub_entry|
124
+ if ::File.fnmatch(pattern, sub_entry.path[l..-1], flags)
125
+ yield sub_entry
126
+ false
127
+ end
105
128
  end
106
129
  end
107
130
  return self
@@ -161,10 +184,6 @@ module MultiGit
161
184
  ['#<',self.class.name,' ',oid,' repository:', repository.inspect,'>'].join
162
185
  end
163
186
 
164
- def path
165
- ''
166
- end
167
-
168
187
  protected
169
188
  # @return [Hash<String, MultiGit::TreeEntry>]
170
189
  def entries
@@ -48,7 +48,7 @@ module MultiGit
48
48
 
49
49
  def path
50
50
  @path ||= begin
51
- if parent && parent.path != ''
51
+ if parent.respond_to? :path
52
52
  [parent.path,SLASH, name].join
53
53
  else
54
54
  name
@@ -1,3 +1,3 @@
1
1
  module MultiGit
2
- VERSION = '0.0.1.alpha2'
2
+ VERSION = '0.0.1.beta1'
3
3
  end
data/lib/multi_git.rb CHANGED
@@ -4,6 +4,7 @@ require 'multi_git/backend'
4
4
  require 'multi_git/backend_set'
5
5
  require 'multi_git/error'
6
6
  require 'multi_git/utils'
7
+ require 'multi_git/config'
7
8
  module MultiGit
8
9
 
9
10
  private