svn 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module Svn #:nodoc:
|
6
|
+
|
7
|
+
LOG_PROP_NAME = 'svn:log'
|
8
|
+
AUTHOR_PROP_NAME = 'svn:author'
|
9
|
+
TIMESTAMP_PROP_NAME = 'svn:date'
|
10
|
+
|
11
|
+
class Revision < Root
|
12
|
+
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
attr_reader :num
|
16
|
+
attr_reader :repo
|
17
|
+
|
18
|
+
def initialize( ptr, repo, pool )
|
19
|
+
super( ptr )
|
20
|
+
@repo = repo
|
21
|
+
@pool = pool
|
22
|
+
@num = revnum
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_i
|
26
|
+
@num
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>( other )
|
30
|
+
to_i <=> other.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
module C
|
34
|
+
extend FFI::Library
|
35
|
+
ffi_lib 'libsvn_fs-1.so.1'
|
36
|
+
|
37
|
+
typedef :pointer, :out_pointer
|
38
|
+
typedef Pool, :pool
|
39
|
+
typedef CError.by_ref, :error
|
40
|
+
typedef Root, :root
|
41
|
+
typedef :long, :revnum
|
42
|
+
typedef :string, :name
|
43
|
+
typedef Repo::FileSystem, :fs
|
44
|
+
typedef CountedString, :counted_string
|
45
|
+
|
46
|
+
attach_function :revnum,
|
47
|
+
:svn_fs_revision_root_revision,
|
48
|
+
[ :root ],
|
49
|
+
:revnum
|
50
|
+
|
51
|
+
attach_function :prop,
|
52
|
+
:svn_fs_revision_prop,
|
53
|
+
[ :out_pointer, :fs, :revnum, :name, :pool ],
|
54
|
+
:error
|
55
|
+
|
56
|
+
attach_function :proplist,
|
57
|
+
:svn_fs_revision_proplist,
|
58
|
+
[ :out_pointer, :fs, :revnum, :pool ],
|
59
|
+
:error
|
60
|
+
end
|
61
|
+
|
62
|
+
# use the C module for all bound methods
|
63
|
+
bind_to C
|
64
|
+
|
65
|
+
# gets the numeric identifier for this revision
|
66
|
+
bind :revnum
|
67
|
+
private :revnum
|
68
|
+
|
69
|
+
# returns the revision property +name+
|
70
|
+
bind( :prop,
|
71
|
+
:returning => CountedString,
|
72
|
+
:before_return => :to_s,
|
73
|
+
:validate => Error.return_check
|
74
|
+
) { |out, this, name| [ out, repo.fs, num, name, pool ] }
|
75
|
+
|
76
|
+
# returns a hash of revision properties
|
77
|
+
bind( :props, :to => :proplist,
|
78
|
+
:returning => AprHash.factory( :string, [:pointer, :string] ),
|
79
|
+
:before_return => :to_h,
|
80
|
+
:validate => Error.return_check
|
81
|
+
) { |out, this| [ out, repo.fs, num, pool ] }
|
82
|
+
|
83
|
+
def message
|
84
|
+
prop( LOG_PROP_NAME )
|
85
|
+
end
|
86
|
+
alias_method :log, :message
|
87
|
+
|
88
|
+
def author
|
89
|
+
prop( AUTHOR_PROP_NAME )
|
90
|
+
end
|
91
|
+
|
92
|
+
def timestamp
|
93
|
+
Time.parse( prop( TIMESTAMP_PROP_NAME ) )
|
94
|
+
end
|
95
|
+
|
96
|
+
# diffs +path+ with another revision. if no revision is specified, the
|
97
|
+
# previous revision is used.
|
98
|
+
def diff( path, options={} )
|
99
|
+
raise Svn::Error, "cannot diff directory #{path}@#{to_s}" if dir? path
|
100
|
+
|
101
|
+
other = options[:with] if options[:with].is_a? Root
|
102
|
+
other = repo.revision(options[:with]) if options[:with].is_a? Numeric
|
103
|
+
other ||= repo.revision(to_i - 1)
|
104
|
+
|
105
|
+
return other.diff( path, :with => self ) if other < self
|
106
|
+
|
107
|
+
content = ""
|
108
|
+
begin
|
109
|
+
content = file_content_stream( path ).to_counted_string
|
110
|
+
rescue Svn::Error => err
|
111
|
+
raise if options[:raise_errors]
|
112
|
+
end
|
113
|
+
|
114
|
+
other_content = ""
|
115
|
+
begin
|
116
|
+
other_content= other.file_content_stream( path ).to_counted_string
|
117
|
+
rescue Svn::Error => err
|
118
|
+
raise if options[:raise_errors]
|
119
|
+
end
|
120
|
+
|
121
|
+
Diff.string_diff( content, other_content ).unified(
|
122
|
+
:original_header => "#{path}@#{to_s}",
|
123
|
+
:modified_header => "#{path}@#{other}"
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
"r#{to_i}"
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
data/lib/svn/roots.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module Svn #:nodoc:
|
5
|
+
|
6
|
+
class Root < FFI::AutoPointer
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def release( ptr )
|
10
|
+
C.close_root( ptr )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module C
|
15
|
+
extend FFI::Library
|
16
|
+
ffi_lib 'libsvn_fs-1.so.1'
|
17
|
+
|
18
|
+
typedef :pointer, :out_pointer
|
19
|
+
typedef Pool, :pool
|
20
|
+
typedef CError.by_ref, :error
|
21
|
+
typedef Root, :root
|
22
|
+
typedef :string, :path
|
23
|
+
typedef :string, :name
|
24
|
+
|
25
|
+
# lifecycle functions
|
26
|
+
attach_function :close_root,
|
27
|
+
:svn_fs_close_root,
|
28
|
+
[ :root ],
|
29
|
+
:void
|
30
|
+
|
31
|
+
# node metadata
|
32
|
+
attach_function :is_dir,
|
33
|
+
:svn_fs_is_dir,
|
34
|
+
[ :out_pointer, :root, :path, :pool ],
|
35
|
+
:error
|
36
|
+
attach_function :is_file,
|
37
|
+
:svn_fs_is_file,
|
38
|
+
[ :out_pointer, :root, :path, :pool ],
|
39
|
+
:error
|
40
|
+
attach_function :created_rev,
|
41
|
+
:svn_fs_node_created_rev,
|
42
|
+
[ :out_pointer, :root, :path, :pool ],
|
43
|
+
:error
|
44
|
+
attach_function :created_path,
|
45
|
+
:svn_fs_node_created_path,
|
46
|
+
[ :out_pointer, :root, :path, :pool ],
|
47
|
+
:error
|
48
|
+
|
49
|
+
# props
|
50
|
+
attach_function :node_prop,
|
51
|
+
:svn_fs_node_prop,
|
52
|
+
[ :out_pointer, :root, :path, :name, :pool ],
|
53
|
+
:error
|
54
|
+
attach_function :node_proplist,
|
55
|
+
:svn_fs_node_proplist,
|
56
|
+
[ :out_pointer, :root, :path, :pool ],
|
57
|
+
:error
|
58
|
+
|
59
|
+
# files
|
60
|
+
attach_function :file_size,
|
61
|
+
:svn_fs_file_length,
|
62
|
+
[ :out_pointer, :root, :path, :pool ],
|
63
|
+
:error
|
64
|
+
attach_function :file_content,
|
65
|
+
:svn_fs_file_contents,
|
66
|
+
[ :out_pointer, :root, :path, :pool ],
|
67
|
+
:error
|
68
|
+
|
69
|
+
# dirs
|
70
|
+
attach_function :dir_content,
|
71
|
+
:svn_fs_dir_entries,
|
72
|
+
[ :out_pointer, :root, :path, :pool ],
|
73
|
+
:error
|
74
|
+
|
75
|
+
# changes
|
76
|
+
attach_function :changes,
|
77
|
+
:svn_fs_paths_changed2,
|
78
|
+
[ :out_pointer, :root, :pool ],
|
79
|
+
:error
|
80
|
+
end
|
81
|
+
|
82
|
+
def pool
|
83
|
+
@pool ||= RootPool
|
84
|
+
end
|
85
|
+
|
86
|
+
# helper procs for method binding
|
87
|
+
test_c_true = Proc.new { |i| i == 1 }
|
88
|
+
add_pool = Proc.new { |out, this, *args| [ out, this, *args, pool ] }
|
89
|
+
|
90
|
+
# use the above C module for the source of bound functions
|
91
|
+
bind_to C
|
92
|
+
|
93
|
+
# bound method definitions
|
94
|
+
bind :dir?, :to => :is_dir,
|
95
|
+
:returning => :int,
|
96
|
+
:before_return => test_c_true,
|
97
|
+
:validate => Error.return_check,
|
98
|
+
&add_pool
|
99
|
+
|
100
|
+
bind :dir_content,
|
101
|
+
:returning => AprHash.factory( :string, :pointer ),
|
102
|
+
:before_return => Proc.new { |h| h.to_h.keys },
|
103
|
+
:validate => Error.return_check,
|
104
|
+
&add_pool
|
105
|
+
|
106
|
+
bind :file?, :to => :is_file,
|
107
|
+
:returning => :int,
|
108
|
+
:before_return => test_c_true,
|
109
|
+
:validate => Error.return_check,
|
110
|
+
&add_pool
|
111
|
+
|
112
|
+
bind :file_size,
|
113
|
+
:returning => :int64,
|
114
|
+
:validate => Error.return_check,
|
115
|
+
&add_pool
|
116
|
+
|
117
|
+
bind :file_content,
|
118
|
+
:returning => Stream,
|
119
|
+
:before_return => :to_string_io,
|
120
|
+
:validate => Error.return_check,
|
121
|
+
&add_pool
|
122
|
+
|
123
|
+
bind :file_content_stream, :to => :file_content,
|
124
|
+
:returning => Stream,
|
125
|
+
:validate => Error.return_check,
|
126
|
+
&add_pool
|
127
|
+
|
128
|
+
bind :created_rev,
|
129
|
+
:returning => :long,
|
130
|
+
:validate => Error.return_check,
|
131
|
+
&add_pool
|
132
|
+
|
133
|
+
bind :created_path,
|
134
|
+
:returning => :string,
|
135
|
+
:validate => Error.return_check,
|
136
|
+
&add_pool
|
137
|
+
|
138
|
+
# returns the +path+'s property value for +name+
|
139
|
+
bind :prop_for, :to => :node_prop,
|
140
|
+
:returning => CountedString,
|
141
|
+
:before_return => :to_s,
|
142
|
+
:validate => Error.return_check,
|
143
|
+
&add_pool
|
144
|
+
|
145
|
+
# returns a hash of name to property values for +path+
|
146
|
+
bind :props_for, :to => :node_proplist,
|
147
|
+
:returning => AprHash.factory( :string, [:pointer, :string] ),
|
148
|
+
:before_return => :to_h,
|
149
|
+
:validate => Error.return_check,
|
150
|
+
&add_pool
|
151
|
+
|
152
|
+
# return the changes in this revision or transaction
|
153
|
+
bind :changes,
|
154
|
+
:returning => AprHash.factory( :string, ChangedPath ),
|
155
|
+
:before_return => :to_h,
|
156
|
+
:validate => Error.return_check,
|
157
|
+
&add_pool
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
data/lib/svn/streams.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module Svn
|
6
|
+
|
7
|
+
class Stream < FFI::AutoPointer
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Wraps an IO object to be used as a subversion stream
|
11
|
+
def wrap_io( io, pool=RootPool )
|
12
|
+
stream = new( C.create( Svn::Utils.wrap(io), pool ) )
|
13
|
+
C.set_write( stream, C::WriteToIO )
|
14
|
+
C.set_read( stream, C::ReadFromIO )
|
15
|
+
return stream
|
16
|
+
end
|
17
|
+
|
18
|
+
def release( ptr )
|
19
|
+
Error.check_and_raise(
|
20
|
+
C.close( ptr )
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module C
|
26
|
+
extend FFI::Library
|
27
|
+
ffi_lib 'libsvn_subr-1.so.1'
|
28
|
+
|
29
|
+
typedef :pointer, :in_out_len
|
30
|
+
typedef CError.by_ref, :error
|
31
|
+
typedef Pool, :pool
|
32
|
+
typedef Stream, :stream
|
33
|
+
typedef :size_t, :apr_size
|
34
|
+
|
35
|
+
callback :read_function, [:pointer, :pointer, :pointer], :error
|
36
|
+
callback :write_function, [:pointer, :string, :pointer], :error
|
37
|
+
|
38
|
+
# lifecycle functions
|
39
|
+
attach_function :create,
|
40
|
+
:svn_stream_create,
|
41
|
+
[:pointer, :pool], :pointer
|
42
|
+
attach_function :set_read,
|
43
|
+
:svn_stream_set_read,
|
44
|
+
[:stream, :read_function], :void
|
45
|
+
attach_function :set_write,
|
46
|
+
:svn_stream_set_write,
|
47
|
+
[:stream, :write_function], :void
|
48
|
+
attach_function :close,
|
49
|
+
:svn_stream_close,
|
50
|
+
[:stream], :error
|
51
|
+
|
52
|
+
# note: the SVN docs say that short reads indicate the end of the stream
|
53
|
+
# and short writes indicate an error. this means that we cannot use
|
54
|
+
# read_nonblock and write_nonblock, since they do not guarantee anything
|
55
|
+
# will happen.
|
56
|
+
|
57
|
+
ReadFromIO = FFI::Function.new(
|
58
|
+
:pointer, [:pointer, :pointer, :pointer]
|
59
|
+
) do |io_ptr, out_buffer, in_out_len|
|
60
|
+
|
61
|
+
# read the number of bytes requested and unwrap the io object
|
62
|
+
bytes_to_read = in_out_len.read_int
|
63
|
+
io = Svn::Utils.unwrap(io_ptr)
|
64
|
+
|
65
|
+
# read the bytes from IO and write them to the pointer object
|
66
|
+
bytes_read = io.read( bytes_to_read )
|
67
|
+
out_buffer.write_string( bytes_read )
|
68
|
+
|
69
|
+
# write the number of bytes read from io
|
70
|
+
in_out_len.write_int( bytes_read.length )
|
71
|
+
|
72
|
+
nil # return no error
|
73
|
+
end
|
74
|
+
|
75
|
+
WriteToIO = FFI::Function.new(
|
76
|
+
:pointer, [:pointer, :string, :pointer]
|
77
|
+
) do |io_ptr, in_string, in_out_len|
|
78
|
+
|
79
|
+
# read the size of in_string and unwrap the io object
|
80
|
+
bytes_to_write = in_out_len.read_int
|
81
|
+
io = Svn::Utils.unwrap(io_ptr)
|
82
|
+
|
83
|
+
# should we check that in_string isn't longer than in_out_len?
|
84
|
+
bytes_written = io.write( in_string )
|
85
|
+
|
86
|
+
# write the actual number of bytes written to io
|
87
|
+
in_out_len.write_int( bytes_written )
|
88
|
+
|
89
|
+
nil # return no error
|
90
|
+
end
|
91
|
+
|
92
|
+
# accessor functions
|
93
|
+
attach_function :read,
|
94
|
+
:svn_stream_read,
|
95
|
+
[ :stream, :buffer_out, :in_out_len ],
|
96
|
+
:error
|
97
|
+
end
|
98
|
+
|
99
|
+
def read( size=8192 )
|
100
|
+
# setup the pointers
|
101
|
+
@in_out_len ||= FFI::MemoryPointer.new( :size_t )
|
102
|
+
@in_out_len.write_ulong( size )
|
103
|
+
|
104
|
+
# make sure a buffer for reading exists and save it for reuse
|
105
|
+
if @read_buf.nil? or @read_buf.size < size
|
106
|
+
@read_buf = FFI::Buffer.alloc_out( size )
|
107
|
+
end
|
108
|
+
|
109
|
+
# call read to fill the buffer
|
110
|
+
Error.check_and_raise(
|
111
|
+
C.read( self, @read_buf, @in_out_len )
|
112
|
+
)
|
113
|
+
|
114
|
+
@read_buf.read_bytes( @in_out_len.read_ulong )
|
115
|
+
end
|
116
|
+
|
117
|
+
# reads the stream contents into a String object
|
118
|
+
def read_all
|
119
|
+
content = String.new
|
120
|
+
while bytes = read and !bytes.empty?
|
121
|
+
content << bytes
|
122
|
+
end
|
123
|
+
content
|
124
|
+
end
|
125
|
+
alias_method :to_s, :read_all
|
126
|
+
|
127
|
+
# reads the entire stream and creates a CountedString from the contents
|
128
|
+
#
|
129
|
+
# Note that this function copies the entire stream into Ruby memory and
|
130
|
+
# then copies it again into C memory. There is probably a more efficient
|
131
|
+
# way to do this, by allocating a big string and re-allocing when the size
|
132
|
+
# required overruns that memory
|
133
|
+
def to_counted_string
|
134
|
+
CountedString.from_string( read_all )
|
135
|
+
end
|
136
|
+
|
137
|
+
# reads the stream contents into a StringIO object
|
138
|
+
def to_string_io
|
139
|
+
content = StringIO.new
|
140
|
+
while bytes = read and !bytes.empty?
|
141
|
+
content.write( bytes )
|
142
|
+
end
|
143
|
+
content.rewind
|
144
|
+
content
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module Svn #:nodoc:
|
5
|
+
|
6
|
+
class Transaction < Root
|
7
|
+
|
8
|
+
def initialize( txn_ptr, pool )
|
9
|
+
# using the transaction object, get it's root
|
10
|
+
out_ptr = FFI::MemoryPointer.new(:pointer)
|
11
|
+
Error.check_and_raise(
|
12
|
+
C.root( out_ptr, txn_ptr, pool )
|
13
|
+
)
|
14
|
+
|
15
|
+
# call super with the root pointer, rather than the transaction pointer.
|
16
|
+
# this is so Transactions can be used as Roots in external calls:
|
17
|
+
super( out_ptr.read_pointer )
|
18
|
+
|
19
|
+
# save off the others
|
20
|
+
@txn_ptr = txn_ptr
|
21
|
+
@pool = pool
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def release
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module C
|
30
|
+
extend FFI::Library
|
31
|
+
ffi_lib 'libsvn_fs-1.so.1'
|
32
|
+
|
33
|
+
typedef :pointer, :out_pointer
|
34
|
+
typedef Pool, :pool
|
35
|
+
typedef CError.by_ref, :error
|
36
|
+
typedef :pointer, :transaction
|
37
|
+
typedef :long, :revnum
|
38
|
+
|
39
|
+
attach_function :cancel_transaction,
|
40
|
+
:svn_fs_abort_txn,
|
41
|
+
[ :transaction, :pool ],
|
42
|
+
:error
|
43
|
+
attach_function :root,
|
44
|
+
:svn_fs_txn_root,
|
45
|
+
[ :out_pointer, :pointer, :pool ],
|
46
|
+
:error
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|