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
@@ -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
|