svn 0.1.0

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.
data/README ADDED
@@ -0,0 +1,39 @@
1
+ == About ==
2
+
3
+ This is a library for interacting with Subverison repositories. It is a
4
+ new set of ruby bindings that are simple to use and compatible with RVM
5
+ because they are dynamically linked to libsvn and do not require any native
6
+ extensions.
7
+
8
+ In addition, these new bindings expose a ruby-style API and try to hide the
9
+ underlying SVN details---for example, SVN separates repositories from their
10
+ inner filesystems, but this library hides that distinction.
11
+
12
+ == Getting Started ==
13
+
14
+ Example code:
15
+
16
+ require 'svn'
17
+
18
+ repo = Svn::Repo.open('/path/to/repo')
19
+ r4 = repo.revision(4)
20
+ puts "#{r}, by #{r.author} (on #{r.timestamp.strftime('%Y-%m-%d %H:%M')})\nLog: #{r.log}"
21
+
22
+ r4.changes.each_pair { |path, changes| ... }
23
+ puts r4.props.inspect
24
+ # => {"svn:log"=>"...", "svn:author"=>"blue", "svn:date"=>"2011-10-14T23:51:58.705026Z"}
25
+ puts r4.diff('/some/repo/file') # by default, against the previous revision
26
+
27
+ == Current Status ==
28
+
29
+ Version 0.1.0 supports read-only operations to inspect a subversion repository,
30
+ its files, and file properties. Transactions are planned and will be included
31
+ in a future version.
32
+
33
+ The current release, 0.1.0 is an experimental release and the API included is
34
+ subject to change. Rubygems version number conventions will be respected, so it
35
+ is recommended that callers specify the version of this library using the "~>"
36
+ operator:
37
+ spec.add_dependency 'ffi', '~> 0.1'
38
+
39
+ == Contributing ==
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+
4
+ require 'svn/utils'
5
+ require 'svn/misc'
6
+ require 'svn/errors'
7
+ require 'svn/pools'
8
+ require 'svn/apr_utils'
9
+ require 'svn/counted_strings'
10
+
11
+ # General Svn docs here!
12
+ module Svn
13
+ autoload :Stream, 'svn/streams'
14
+ autoload :Log, 'svn/logs'
15
+ autoload :Repo, 'svn/repos'
16
+ autoload :Root, 'svn/roots'
17
+ autoload :Revision, 'svn/revisions'
18
+ autoload :Diff, 'svn/diffs'
19
+ end
@@ -0,0 +1,313 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+
4
+ module Svn #:nodoc:
5
+
6
+ class AprHash < FFI::AutoPointer
7
+
8
+ # when used as key length, indicates that string length should be used
9
+ HASH_KEY_STRING = -1
10
+
11
+ include Enumerable
12
+
13
+ attr_accessor :keys_null_terminated
14
+ alias_method :keys_null_terminated?, :keys_null_terminated
15
+
16
+ def initialize( ptr, key_type, val_type, keys_null_terminated=true)
17
+ super( ptr )
18
+ @key_type = key_type
19
+ @val_type = val_type
20
+ @pointers = []
21
+ @keys_null_terminated = keys_null_terminated
22
+ end
23
+
24
+ class << self
25
+ # creates a new apr_hash_t that contains +contents+, if given
26
+ def create( key_type, val_type, pool=RootPool )
27
+ ptr = C.create( pool )
28
+ new( ptr, key_type, val_type )
29
+ end
30
+
31
+ def create_from( rb_hash, key_type, val_type, pool=RootPool )
32
+ create( key_type, val_type, pool ).copy_from( rb_hash )
33
+ end
34
+
35
+ def release( ptr )
36
+ # memory will be released with the allocation pool
37
+ end
38
+ end
39
+
40
+ module C
41
+ extend FFI::Library
42
+ ffi_lib 'libapr-1.so.0'
43
+
44
+ typedef :pointer, :index
45
+ typedef :pointer, :out_pointer
46
+ typedef :long, :apr_ssize
47
+ typedef Pool, :pool
48
+ typedef AprHash, :hash
49
+
50
+ # lifecycle functions
51
+ # returns a :pointer instead of a :hash because AprHash#create needs to
52
+ # add extra args to the ruby instantiation
53
+ attach_function :create,
54
+ :apr_hash_make,
55
+ [ :pool ],
56
+ :pointer
57
+
58
+ # pool accessor -- returns a Pointer instead of a Pool because Pool
59
+ # objects will destroy the pool when they are garbage collected. If the
60
+ # pool was created in SVN, we should never destroy it and if the pool was
61
+ # created elsewhere, then we should let the original instance destroy it.
62
+ # This means that the function here should not actually return a Pool.
63
+ attach_function :pool,
64
+ :apr_hash_pool_get,
65
+ [ :hash ],
66
+ :pointer
67
+
68
+ # size
69
+ attach_function :count,
70
+ :apr_hash_count,
71
+ [ :hash ],
72
+ :uint
73
+
74
+ # getter/setter methods
75
+ attach_function :get,
76
+ :apr_hash_get,
77
+ # hash key klen
78
+ [ :hash, :pointer, :apr_ssize ],
79
+ :pointer
80
+ attach_function :set,
81
+ :apr_hash_set,
82
+ # hash key klen val (NULL = delete entry)
83
+ [ :hash, :pointer, :apr_ssize, :pointer ],
84
+ :void
85
+
86
+ # iteration functions
87
+ attach_function :first,
88
+ :apr_hash_first,
89
+ [ :pool, :hash ],
90
+ :index
91
+ attach_function :next,
92
+ :apr_hash_next,
93
+ [ :index ],
94
+ :index
95
+ attach_function :this,
96
+ :apr_hash_this,
97
+ [ :index, :out_pointer, :out_pointer, :out_pointer ],
98
+ :void
99
+ end
100
+
101
+ # use the above C module for the source of bound functions
102
+ bind_to C
103
+
104
+ # bound method definitions
105
+ bind :size, :to => :count
106
+
107
+ # because pool returns a Pointer and not a Pool as is expected, make it
108
+ # private so others can't use it.
109
+ bind :pool
110
+ private :pool
111
+
112
+ def each_pair
113
+ # outgoing pointers for keys and values
114
+ key_ptr = FFI::MemoryPointer.new( :pointer )
115
+ # apr_ssize_t => ssize_t => platform-specific long
116
+ len_ptr = FFI::MemoryPointer.new( :long )
117
+ val_ptr = FFI::MemoryPointer.new( :pointer )
118
+
119
+ # initialize a hash index to the first entry
120
+ iter = C.first( pool, self )
121
+
122
+ while !iter.null?
123
+ # get the key, key length, and val
124
+ C.this( iter, key_ptr, len_ptr, val_ptr )
125
+
126
+ # read the key
127
+ key_len = len_ptr.read_long
128
+ key = Utils.content_for( key_ptr.read_pointer, @key_type, key_len )
129
+
130
+ # yield the key and value
131
+ yield key, Utils.content_for( val_ptr.read_pointer, @val_type ) if block_given?
132
+
133
+ # advance to the next iteration
134
+ iter = C.next( iter )
135
+ end
136
+ end
137
+
138
+ def []( key )
139
+ val = nil # keep val in scope
140
+
141
+ if key.is_a?( String ) && keys_null_terminated?
142
+ val = C.get( self, key, HASH_KEY_STRING )
143
+ elsif key.respond_to? :size
144
+ val = C.get( self, key, key.size )
145
+ elsif key.respond_to? :length
146
+ val = C.get( self, key, key.length )
147
+ else
148
+ raise ArgumentError, "Invalid key #{key}: cannot determine length"
149
+ end
150
+
151
+ Utils.content_for( val, @val_type )
152
+ end
153
+
154
+ def []=( key, val )
155
+ val_ptr = Utils.pointer_for( val, @val_type )
156
+
157
+ # because the pointers passed in are referenced in native code, keep
158
+ # track of the pointers so they aren't garbage collected until this hash
159
+ # is destroyed
160
+ @pointers << val_ptr
161
+
162
+ if key.is_a?( String ) && keys_null_terminated?
163
+ C.set( self, key, HASH_KEY_STRING, val_ptr )
164
+ elsif key.respond_to? :size
165
+ C.set( self, key, key.size, val_ptr )
166
+ elsif key.respond_to? :length
167
+ C.set( self, key, key.length, val_ptr )
168
+ else
169
+ raise ArgumentError, "Invalid key #{key}: cannot determine length"
170
+ end
171
+
172
+ val
173
+ end
174
+
175
+ def copy_from( rb_hash )
176
+ rb_hash.each_pair do |key, val|
177
+ self[ key ] = val
178
+ end
179
+
180
+ self
181
+ end
182
+
183
+ def to_h
184
+ rb_hash = {}
185
+ each_pair do |key, val|
186
+ rb_hash[ key ] = val
187
+ end
188
+ rb_hash
189
+ end
190
+
191
+ end
192
+
193
+ class AprArray < FFI::AutoPointer
194
+
195
+ include Enumerable
196
+
197
+ def initialize( ptr, type )
198
+ super( ptr )
199
+ @type = type
200
+ @pointers = []
201
+ end
202
+
203
+ class << self
204
+ # creates a new AprArray of +nelts+ elements of +type+; allocation is done
205
+ # in +pool+, which defaults to Svn::RootPool
206
+ def create( type, nelts, pool=RootPool )
207
+ ptr = C.create( pool, nelts, FFI::Pointer.size )
208
+ new( ptr, type )
209
+ end
210
+
211
+ def create_from( rb_arr, type, pool=RootPool )
212
+ create( type, rb_arr.size ).copy_from( rb_arr )
213
+ end
214
+
215
+ def release
216
+ # memory will be released with the allocation pool
217
+ end
218
+ end
219
+
220
+ module C
221
+ extend FFI::Library
222
+ ffi_lib 'libapr-1.so.0'
223
+
224
+ typedef :pointer, :index
225
+ typedef :pointer, :out_pointer
226
+ typedef :long, :apr_ssize
227
+ typedef Pool, :pool
228
+ typedef AprArray, :array
229
+
230
+ # allocation
231
+ # returns a :pointer instead of a :array because AprArray#create needs to
232
+ # add extra args to the ruby instantiation
233
+ attach_function :create,
234
+ :apr_array_make,
235
+ [ :pool, :int, :int ],
236
+ :pointer
237
+
238
+ # empty?
239
+ attach_function :is_empty,
240
+ :apr_is_empty_array,
241
+ [ :array ],
242
+ :int
243
+
244
+ # modifier methods
245
+ attach_function :push,
246
+ :apr_array_push,
247
+ [ :array ],
248
+ :pointer
249
+
250
+ attach_function :pop,
251
+ :apr_array_pop,
252
+ [ :array ],
253
+ :pointer
254
+ end
255
+
256
+ # helper procs for method binding
257
+ test_c_true = Proc.new { |i| i == 1 }
258
+
259
+ # use the above C module for the source of bound functions
260
+ bind_to C
261
+
262
+ # bound method definitions
263
+ bind :empty?, :to => :is_empty,
264
+ :before_return => test_c_true
265
+
266
+ def push( el )
267
+ # turn element into a pointer
268
+ ptr = Utils.pointer_for( el, @type )
269
+ # get the array element location and write ptr there
270
+ location = C.push( self )
271
+ location.write_pointer( ptr )
272
+
273
+ # because the pointers passed in are referenced in native code, keep
274
+ # track of the pointers so they aren't garbage collected until this hash
275
+ # is destroyed
276
+ @pointers << ptr
277
+
278
+ el
279
+ end
280
+ alias_method :add, :push
281
+ alias_method :<<, :push
282
+
283
+ def copy_from( arr )
284
+ arr.each do |el|
285
+ self << el
286
+ end
287
+ self
288
+ end
289
+
290
+ def pop
291
+ location = C.pop( self )
292
+ Utils.content_for( location.read_pointer, @type )
293
+ end
294
+
295
+ # these are commented out because they don't work. it doesn't look like APR
296
+ # array accesses happen this way.
297
+ # def []( num )
298
+ # num = num.to_i
299
+ # Utils.content_for( get_pointer( num * FFI::Pointer.size ), @type )
300
+ # end
301
+ #
302
+ # def []=( num, val )
303
+ # num = num.to_i
304
+ #
305
+ # ptr = Utils.pointer_for( val, @type )
306
+ # put_pointer( num * FFI::Pointer.size, ptr )
307
+ #
308
+ # val
309
+ # end
310
+
311
+ end
312
+
313
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'ffi'
3
+
4
+ module Svn #:nodoc:
5
+
6
+ # a struct for interacting with svn_string_t values
7
+ class CountedStringStruct < FFI::Struct
8
+
9
+ layout(
10
+ # because the data may not be NULL-terminated, treat it as a pointer
11
+ # and always read the string contents with FFI::Pointer#read_string
12
+ :data, :pointer,
13
+ :length, :size_t
14
+ )
15
+
16
+ # returns a new ruby String with the CountedString's contents.
17
+ def to_s
18
+ self[:data].read_string( self[:length] )
19
+ end
20
+
21
+ def inspect
22
+ to_s.inspect
23
+ end
24
+
25
+ end
26
+
27
+ # the svn_string_t pointer type, which is the one used externally
28
+ CountedString = CountedStringStruct.by_ref
29
+
30
+ def CountedString.from_string( content )
31
+ return content if content.is_a? CountedStringStruct
32
+ cstr = CountedStringStruct.new
33
+ cstr[:data] = FFI::MemoryPointer.from_string( content )
34
+ cstr[:length] = content.size
35
+ cstr
36
+ end
37
+
38
+ end
@@ -0,0 +1,209 @@
1
+ require 'rubygems'
2
+ require 'stringio'
3
+ require 'ffi'
4
+
5
+ module Svn #:nodoc:
6
+
7
+ class Diff < FFI::AutoPointer
8
+
9
+ class << self
10
+ def string_diff( original, modified, options={}, pool=RootPool )
11
+ options = FileOptions.from_hash( options ) if options.is_a? Hash
12
+ original = CountedString.from_string( original )
13
+ modified = CountedString.from_string( modified )
14
+
15
+ out = FFI::MemoryPointer.new( Diff )
16
+
17
+ Error.check_and_raise(
18
+ C.string_diff( out, original, modified, options, pool )
19
+ )
20
+
21
+ d = new( out.read_pointer )
22
+
23
+ return nil if d.null?
24
+
25
+ d.type = :string
26
+ d.original = original
27
+ d.modified = modified
28
+ d.options = options
29
+ d.pool = pool
30
+
31
+ return d
32
+ end
33
+
34
+ def file_diff( original_path, modified_path, options={}, pool=RootPool )
35
+ options = FileOptions.from_hash( options ) if options.is_a? Hash
36
+
37
+ out = FFI::MemoryPointer.new( Diff )
38
+
39
+ Error.check_and_raise(
40
+ C.file_diff( out, original_path, modified_path, options, pool )
41
+ )
42
+
43
+ d = new( out.read_pointer )
44
+
45
+ return nil if d.null?
46
+
47
+ d.type = :file
48
+ d.original_path = original_path
49
+ d.modified_path = modified_path
50
+ d.options = options
51
+ d.pool = pool
52
+
53
+ return d
54
+ end
55
+
56
+ def release( ptr )
57
+ # diff objects will probably need to keep track of the pool in which
58
+ # they are allocated so they can be freed in that pool
59
+ end
60
+ end
61
+
62
+ attr_accessor :type
63
+ attr_accessor :original
64
+ attr_accessor :original_path
65
+ attr_accessor :modified
66
+ attr_accessor :modified_path
67
+ attr_accessor :options
68
+ attr_accessor :pool
69
+
70
+ def changed?
71
+ ( C.is_changed( self ) == 1 )
72
+ end
73
+
74
+ def conflicts?
75
+ ( C.has_conflicts( self ) == 1 )
76
+ end
77
+
78
+ def unified( *args )
79
+ # keep these in scope
80
+ out_stream = nil
81
+ options = nil
82
+ pool = nil
83
+
84
+ case args.size
85
+ when 0
86
+ # use all defaults
87
+ when 1
88
+ if args.first.is_a? Hash
89
+ options = args.first
90
+ elsif args.first.is_a? IO or args.first.is_a? StringIO
91
+ out_stream = args.first
92
+ elsif args.first.is_a? Pool
93
+ pool = args.first
94
+ end
95
+ when 2, 3
96
+ out_stream, options, pool = args
97
+ else
98
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 3)"
99
+ end
100
+
101
+ # defaults
102
+ out_stream ||= StringIO.new
103
+ options ||= {}
104
+ pool ||= RootPool
105
+
106
+ # get common options
107
+ encoding = options[:encoding] || 'utf-8'
108
+ original_header = options[:original_header]
109
+ modified_header = options[:modified_header]
110
+
111
+ case type
112
+ when :string
113
+ with_diff_header = ( options[:with_diff_header] ? 1 : 0 )
114
+
115
+ Error.check_and_raise( C.string_output_unified(
116
+ Svn::Stream.wrap_io( out_stream ), self,
117
+ original_header, modified_header, encoding,
118
+ original, modified, pool
119
+ ) )
120
+
121
+ when :file
122
+ path_strip = options[:path_strip]
123
+ show_c_function = ( options[:show_c_function] ? 1 : 0 )
124
+
125
+ Error.check_and_raise( C.file_output_unified(
126
+ Svn::Stream.wrap_io( out_stream ), self,
127
+ original_path, modified_path,
128
+ original_header, modified_header, encoding, path_strip,
129
+ show_c_function, pool
130
+ ) )
131
+ end
132
+
133
+ out_stream.rewind if out_stream.is_a? StringIO
134
+
135
+ out_stream
136
+ end
137
+
138
+ class FileOptionsStruct < FFI::Struct
139
+ layout(
140
+ :ignore_whitespace, :int, # :whitespace,
141
+ :ignore_eol_style, :int,
142
+ :show_c_function, :int
143
+ )
144
+ end
145
+
146
+ # create a mapped type for use elsewhere
147
+ FileOptions = FileOptionsStruct.by_ref
148
+
149
+ def FileOptions.from_hash( hash )
150
+ options = FileOptionsStruct.new
151
+ hash.each_pair do |key, val|
152
+ # add a check if key is in members?
153
+ options[key] = val
154
+ end
155
+ options
156
+ end
157
+
158
+ module C
159
+
160
+ extend FFI::Library
161
+ ffi_lib 'libsvn_diff-1.so.1'
162
+
163
+ typedef :pointer, :out_pointer
164
+ typedef Pool, :pool
165
+ typedef CError.by_ref, :error
166
+ typedef Stream, :stream
167
+ typedef FileOptions, :file_options
168
+ typedef CountedString, :counted_string
169
+ typedef Diff, :diff
170
+
171
+ enum :whitespace, [
172
+ :none, # do not ignore whitespace
173
+ :change, # treat as a single char
174
+ :all # ignore all whitespace chars
175
+ ]
176
+
177
+ # diff functions
178
+ attach_function :file_diff,
179
+ :svn_diff_file_diff_2,
180
+ [ :out_pointer, :string, :string, :file_options, :pool ],
181
+ :error
182
+
183
+ attach_function :string_diff,
184
+ :svn_diff_mem_string_diff,
185
+ [ :out_pointer, :counted_string, :counted_string, :file_options,
186
+ :pool ],
187
+ :error
188
+
189
+ # diff inspection
190
+ attach_function :is_changed, :svn_diff_contains_diffs, [:diff], :int
191
+ attach_function :has_conflicts, :svn_diff_contains_conflicts, [:diff], :int
192
+
193
+ # output functions
194
+ attach_function :file_output_unified,
195
+ :svn_diff_file_output_unified3,
196
+ [ :stream, :diff, :string, :string, :string, :string, :string,
197
+ :string, :int, :pool ],
198
+ :error
199
+
200
+ attach_function :string_output_unified,
201
+ :svn_diff_mem_string_output_unified,
202
+ [ :stream, :diff, :string, :string, :string,
203
+ :counted_string, :counted_string, :pool ],
204
+ :error
205
+ end
206
+
207
+ end
208
+
209
+ end