multi_git 0.0.1.alpha2 → 0.0.1.beta1

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