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 +39 -0
- data/lib/svn.rb +19 -0
- data/lib/svn/apr_utils.rb +313 -0
- data/lib/svn/counted_strings.rb +38 -0
- data/lib/svn/diffs.rb +209 -0
- data/lib/svn/errors.rb +83 -0
- data/lib/svn/logs.rb +120 -0
- data/lib/svn/misc.rb +68 -0
- data/lib/svn/pools.rb +67 -0
- data/lib/svn/repos.rb +333 -0
- data/lib/svn/revisions.rb +133 -0
- data/lib/svn/roots.rb +161 -0
- data/lib/svn/streams.rb +149 -0
- data/lib/svn/transactions.rb +51 -0
- data/lib/svn/utils.rb +249 -0
- metadata +71 -0
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 ==
|
data/lib/svn.rb
ADDED
@@ -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
|
data/lib/svn/diffs.rb
ADDED
@@ -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
|