1 //
2 // ====================================================================
3 // Copyright (c) 2003-2009 Barry A Scott.  All rights reserved.
4 //
5 // This software is licensed as described in the file LICENSE.txt,
6 // which you should have received as part of this distribution.
7 //
8 // ====================================================================
9 //
10 
11 #if defined( _MSC_VER )
12 // disable warning C4786: symbol greater than 255 character,
13 // nessesary to ignore as <map> causes lots of warning
14 #pragma warning(disable: 4786)
15 #endif
16 
17 #include "pysvn.hpp"
18 #include "svn_config.h"
19 #include "svn_time.h"
20 
21 #include "pysvn_static_strings.hpp"
22 
23 static const std::string str_URL( "URL" );
24 static const std::string str_action( "action" );
25 static const std::string str_author( "author" );
26 static const std::string str_base_abspath( "base_abspath" );
27 static const std::string str_changed_author( "changed_author" );
28 static const std::string str_changed_date( "changed_date" );
29 static const std::string str_changed_revision( "changed_revision" );
30 static const std::string str_changelist( "changelist" );
31 static const std::string str_checksum( "checksum" );
32 static const std::string str_comment( "comment" );
33 static const std::string str_commit_author( "commit_author" );
34 static const std::string str_commit_revision( "commit_revision" );
35 static const std::string str_commit_time( "commit_time" );
36 static const std::string str_conflict_new( "conflict_new" );
37 static const std::string str_conflict_old( "conflict_old" );
38 static const std::string str_conflict_work( "conflict_work" );
39 static const std::string str_conflicts( "conflicts" );
40 static const std::string str_copy_from_revision( "copy_from_revision" );
41 static const std::string str_copy_from_url( "copy_from_url" );
42 static const std::string str_copyfrom_rev( "copyfrom_rev" );
43 static const std::string str_copyfrom_url( "copyfrom_url" );
44 static const std::string str_creation_date( "creation_date" );
45 static const std::string str_date( "date" );
46 static const std::string str_depth( "depth" );
47 static const std::string str_entry( "entry" );
48 static const std::string str_expiration_date( "expiration_date" );
49 static const std::string str_filesize( "filesize" );
50 static const std::string str_is_absent( "is_absent" );
51 static const std::string str_is_binary( "is_binary" );
52 static const std::string str_is_conflicted( "is_copied" );
53 static const std::string str_is_copied( "is_copied" );
54 static const std::string str_is_dav_comment( "is_dav_comment" );
55 static const std::string str_is_deleted( "is_deleted" );
56 static const std::string str_is_file_external( "is_file_external" );
57 static const std::string str_is_locked( "is_locked" );
58 static const std::string str_is_switched( "is_switched" );
59 static const std::string str_is_versioned( "is_versioned" );
60 static const std::string str_kind( "kind" );
61 static const std::string str_last_changed_author( "last_changed_author" );
62 static const std::string str_last_changed_date( "last_changed_date" );
63 static const std::string str_last_changed_rev( "last_changed_rev" );
64 static const std::string str_local_abspath( "local_abspath" );
65 static const std::string str_lock( "lock" );
66 static const std::string str_lock_comment( "lock_comment" );
67 static const std::string str_lock_creation_date( "lock_creation_date" );
68 static const std::string str_lock_owner( "lock_owner" );
69 static const std::string str_lock_token( "lock_token" );
70 static const std::string str_merged_file( "merged_file" );
71 static const std::string str_mime_type( "mime_type" );
72 static const std::string str_moved_from_abspath( "moved_from_abspath" );
73 static const std::string str_moved_to_abspath( "moved_to_abspath" );
74 static const std::string str_my_abspath( "my_abspath" );
75 static const std::string str_name( "name" );
76 static const std::string str_node_kind( "node_kind" );
77 static const std::string str_node_status( "node_status" );
78 static const std::string str_ood_changed_author( "ood_changed_author" );
79 static const std::string str_ood_changed_date( "ood_changed_date" );
80 static const std::string str_ood_changed_rev( "ood_changed_rev" );
81 static const std::string str_ood_kind( "ood_kind" );
82 static const std::string str_operation( "operation" );
83 static const std::string str_owner( "owner" );
84 static const std::string str_path( "path" );
85 static const std::string str_path_in_repos( "path_in_repos" );
86 static const std::string str_peg_rev( "peg_rev" );
87 static const std::string str_post_commit_err( "post_commit_err" );
88 static const std::string str_prejfile( "prejfile" );
89 static const std::string str_prop_status( "prop_status" );
90 static const std::string str_prop_time( "prop_time" );
91 static const std::string str_properties_time( "properties_time" );
92 static const std::string str_property_name( "property_name" );
93 static const std::string str_property_reject_file( "property_reject_file" );
94 static const std::string str_reason( "reason" );
95 static const std::string str_recorded_size( "recorded_size" );
96 static const std::string str_recorded_time( "recorded_time" );
97 static const std::string str_repos( "repos" );
98 static const std::string str_repos_UUID( "repos_UUID" );
99 static const std::string str_repos_lock( "repos_lock" );
100 static const std::string str_repos_node_status( "repos_node_status" );
101 static const std::string str_repos_prop_status( "repos_prop_status" );
102 static const std::string str_repos_relpath( "repos_relpath" );
103 static const std::string str_repos_root_URL( "repos_root_URL" );
104 static const std::string str_repos_text_status( "repos_text_status" );
105 static const std::string str_repos_url( "repos_url" );
106 static const std::string str_rev( "rev" );
107 static const std::string str_revision( "revision" );
108 static const std::string str_schedule( "schedule" );
109 static const std::string str_size( "size" );
110 static const std::string str_src_left_version( "src_left_version" );
111 static const std::string str_src_right_version( "src_right_version" );
112 static const std::string str_text_status( "text_status" );
113 static const std::string str_text_time( "text_time" );
114 static const std::string str_their_abspath( "their_abspath" );
115 static const std::string str_token( "token" );
116 static const std::string str_url( "url" );
117 static const std::string str_uuid( "uuid" );
118 static const std::string str_wc_info( "wc_info" );
119 static const std::string str_wc_is_locked( "wc_is_locked" );
120 static const std::string str_wcroot_abspath( "wcroot_abspath" );
121 static const std::string str_working_size( "working_size" );
122 
123 #include <iostream>
124 
125 //------------------------------------------------------------
126 //
127 //  DictWrapper
128 //
129 //------------------------------------------------------------
DictWrapper(Py::Dict result_wrappers,const std::string & wrapper_name)130 DictWrapper::DictWrapper( Py::Dict result_wrappers, const std::string &wrapper_name )
131 : m_wrapper_name( wrapper_name )
132 , m_have_wrapper( false )
133 , m_wrapper()
134 {
135     if( result_wrappers.hasKey( wrapper_name ) )
136     {
137         m_wrapper = result_wrappers[ wrapper_name ];
138         m_have_wrapper = true;
139     }
140 }
141 
~DictWrapper()142 DictWrapper::~DictWrapper()
143 {
144 }
145 
wrapDict(Py::Dict result) const146 Py::Object DictWrapper::wrapDict( Py::Dict result ) const
147 {
148     if( m_have_wrapper )
149     {
150         Py::Tuple args( 1 );
151         args[0] = result;
152 
153         try
154         {
155             return m_wrapper.apply( args );
156         }
157         catch( Py::Exception &e )
158         {
159             std::cerr << "pysvn: unhandled exception calling " << m_wrapper_name << std::endl;
160             PyErr_Print();
161             e.clear();
162 
163             return Py::None();
164         }
165     }
166     else
167     {
168         return result;
169     }
170 }
171 
172 //------------------------------------------------------------
173 //
174 //  Converters
175 //
176 //------------------------------------------------------------
utf8_string_or_none(const char * str)177 Py::Object utf8_string_or_none( const char *str )
178 {
179     if( str == NULL )
180         return Py::None();
181     else
182         return Py::String( str, name_utf8 );
183 }
184 
utf8_string_or_none(const std::string & str)185 Py::Object utf8_string_or_none( const std::string &str )
186 {
187     if( str.empty() )
188         return Py::None();
189     else
190         return Py::String( str, name_utf8 );
191 }
192 
path_string_or_none(const char * str,SvnPool & pool)193 Py::Object path_string_or_none( const char *str, SvnPool &pool )
194 {
195     if( str == NULL )
196         return Py::None();
197     else
198         return Py::String( osNormalisedPath( str, pool ), name_utf8 );
199 }
200 
path_string_or_none(const std::string & str,SvnPool & pool)201 Py::Object path_string_or_none( const std::string &str, SvnPool &pool )
202 {
203     if( str.empty() )
204         return Py::None();
205     else
206         return Py::String( osNormalisedPath( str, pool ), name_utf8 );
207 }
208 
convertStringToTime(const std::string & text,apr_time_t now,SvnPool & pool)209 apr_time_t convertStringToTime( const std::string &text, apr_time_t now, SvnPool &pool )
210 {
211     svn_boolean_t matched = 0;
212     apr_time_t result = 0;
213 
214     svn_error_t *error = svn_parse_date
215         (
216         &matched,
217         &result,
218         text.c_str(),
219         now,
220         pool
221         );
222     if( error != NULL || !matched )
223     {
224         return 0;
225     }
226 
227     return result;
228 }
229 
toSvnRevNum(svn_revnum_t rev)230 Py::Object toSvnRevNum( svn_revnum_t rev )
231 {
232     return Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, rev ) );
233 }
234 
toObject(apr_time_t t)235 Py::Object toObject( apr_time_t t )
236 {
237     return Py::Float( double( t )/1000000 );
238 }
239 
240 #if defined( PYSVN_HAS_SVN_COMMIT_INFO_T )
toObject(pysvn_commit_info_t * commit_info,int commit_style)241 Py::Object toObject( pysvn_commit_info_t *commit_info, int commit_style )
242 {
243     if( commit_info == NULL )
244         return Py::None();
245 
246     if( commit_style == 0 )
247     {
248         if( !SVN_IS_VALID_REVNUM( commit_info->revision ) )
249             return Py::None();
250 
251         return toSvnRevNum( commit_info->revision );
252     }
253     else
254     if( commit_style == 1 )
255     {
256         Py::Dict commit_info_dict;
257 
258         commit_info_dict[ str_date ] = utf8_string_or_none( commit_info->date );
259         commit_info_dict[ str_author ] = utf8_string_or_none( commit_info->author );
260         commit_info_dict[ str_post_commit_err ] = utf8_string_or_none( commit_info->post_commit_err );
261         if( SVN_IS_VALID_REVNUM( commit_info->revision ) )
262         {
263             commit_info_dict[ str_revision ] = toSvnRevNum( commit_info->revision );
264         }
265         else
266         {
267             commit_info_dict[ str_revision ] = Py::None();
268         }
269 
270         return commit_info_dict;
271     }
272     else
273     {
274         throw Py::RuntimeError( "commit_style value invalid" );
275     }
276 }
277 #else
toObject(pysvn_commit_info_t * commit_info)278 Py::Object toObject( pysvn_commit_info_t *commit_info )
279 {
280     if( commit_info == NULL || !SVN_IS_VALID_REVNUM( commit_info->revision ) )
281         return Py::None();
282 
283     return toSvnRevNum( commit_info->revision );
284 }
285 #endif
286 
toFilesize(svn_filesize_t filesize)287 Py::Object toFilesize( svn_filesize_t filesize )
288 {
289     if( filesize == SVN_INVALID_FILESIZE )
290     {
291         return Py::None();
292     }
293     else
294     {
295 #ifdef HAVE_LONG_LONG
296         return Py::LongLong( static_cast<PY_LONG_LONG>( filesize ) );
297 #else
298         return Py::Int( static_cast<int>( filesize ) );
299 #endif
300     }
301 }
302 
303 
304 #if defined( PYSVN_HAS_CLIENT_STATUS_T )
toObject(Py::String path,svn_client_status_t & svn_status,SvnPool & pool,const DictWrapper & wrapper_status2,const DictWrapper & wrapper_lock)305 Py::Object toObject
306     (
307     Py::String path,
308     svn_client_status_t &svn_status,
309     SvnPool &pool,
310     const DictWrapper &wrapper_status2,
311     const DictWrapper &wrapper_lock
312     )
313 {
314     Py::Dict status;
315 
316     status[ str_path ] = path;
317     status[ str_local_abspath ] = path_string_or_none( svn_status.local_abspath, pool );
318     status[ str_kind ] = toEnumValue( svn_status.kind );
319     status[ str_filesize ] = toFilesize( svn_status.filesize );
320     status[ str_is_versioned] = Py::Boolean( svn_status.versioned );
321     status[ str_is_conflicted ] = Py::Boolean( svn_status.conflicted );
322     status[ str_node_status ] = toEnumValue( svn_status.node_status );
323     status[ str_text_status ] = toEnumValue( svn_status.text_status );
324     status[ str_prop_status ] = toEnumValue( svn_status.prop_status );
325     status[ str_wc_is_locked ] = Py::Boolean( svn_status.wc_is_locked );
326     status[ str_is_copied ] = Py::Boolean( svn_status.copied );
327     status[ str_repos_root_URL ] = utf8_string_or_none( svn_status.repos_root_url );
328     status[ str_repos_UUID ] = utf8_string_or_none( svn_status.repos_uuid );
329     status[ str_repos_relpath ] = utf8_string_or_none( svn_status.repos_relpath );
330     status[ str_revision ] = toSvnRevNum( svn_status.revision );
331     status[ str_changed_revision ] = toSvnRevNum( svn_status.changed_rev );
332     status[ str_changed_date ] = toObject( svn_status.changed_date );
333     status[ str_changed_author ] = utf8_string_or_none( svn_status.changed_author );
334     status[ str_is_switched ] = Py::Boolean( svn_status.switched );
335     status[ str_is_file_external ] = Py::Boolean( svn_status.file_external );
336     if( svn_status.lock == NULL )
337     {
338         status[ str_lock ] = Py::None();
339     }
340     else
341     {
342         status[ str_lock ] = toObject( *svn_status.lock, wrapper_lock );
343     }
344     status[ str_changelist ] = utf8_string_or_none( svn_status.changelist );
345     status[ str_depth ] = toEnumValue( svn_status.depth );
346     status[ str_ood_kind ] = toEnumValue( svn_status.ood_kind );
347     status[ str_repos_node_status ] = toEnumValue( svn_status.repos_node_status );
348     status[ str_repos_text_status ]  = toEnumValue( svn_status.repos_text_status );
349     status[ str_repos_prop_status ]  = toEnumValue( svn_status.repos_prop_status );
350     if( svn_status.repos_lock == NULL )
351     {
352         status[ str_repos_lock ] = Py::None();
353     }
354     else
355     {
356         status[ str_repos_lock ] = toObject( *svn_status.repos_lock, wrapper_lock );
357     }
358     status[ str_ood_changed_rev ] = toSvnRevNum( svn_status.ood_changed_rev );
359     status[ str_ood_changed_date ] = toObject( svn_status.ood_changed_date );
360     status[ str_ood_changed_author ] = utf8_string_or_none( svn_status.ood_changed_author );
361 #if defined(PYSVN_HAS_SVN_1_9)
362     status[ str_moved_from_abspath ] = utf8_string_or_none( svn_status.moved_from_abspath );
363     status[ str_moved_to_abspath ] = utf8_string_or_none( svn_status.moved_to_abspath );
364 #endif
365 
366     return wrapper_status2.wrapDict( status );
367 }
368 #endif
369 
toObject(Py::String path,pysvn_wc_status_t & svn_status,SvnPool & pool,const DictWrapper & wrapper_status,const DictWrapper & wrapper_entry,const DictWrapper & wrapper_lock)370 Py::Object toObject
371     (
372     Py::String path,
373     pysvn_wc_status_t &svn_status,
374     SvnPool &pool,
375     const DictWrapper &wrapper_status,
376     const DictWrapper &wrapper_entry,
377     const DictWrapper &wrapper_lock
378     )
379 {
380     Py::Dict status;
381 
382     status[ str_path ] = path;
383     if( svn_status.entry == NULL )
384     {
385         status[ str_entry ] = Py::None();
386     }
387     else
388     {
389         status[ str_entry ] = toObject( *svn_status.entry, pool, wrapper_entry );
390     }
391     if( svn_status.repos_lock == NULL )
392     {
393         status[ str_repos_lock ] = Py::None();
394     }
395 #if defined( PYSVN_HAS_CLIENT_STATUS2 )
396     else
397     {
398         status[ str_repos_lock ] = toObject( *svn_status.repos_lock, wrapper_lock );
399     }
400 #endif
401 
402     long is_versioned;
403     switch( svn_status.text_status )
404     {
405     // exists, but uninteresting
406     case svn_wc_status_normal:
407     // is scheduled for addition
408     case svn_wc_status_added:
409     // under v.c., but is missing
410     case svn_wc_status_missing:
411     // scheduled for deletion
412     case svn_wc_status_deleted:
413     // was deleted and then re-added
414     case svn_wc_status_replaced:
415     // text or props have been modified
416     case svn_wc_status_modified:
417     // local mods received repos mods (### unused)
418     case svn_wc_status_merged:
419     // local mods received conflicting repos mods
420     case svn_wc_status_conflicted:
421         is_versioned = 1;
422         break;
423 
424     // an unversioned resource is in the way of the versioned resource
425     case svn_wc_status_obstructed:
426     // does not exist
427     case svn_wc_status_none:
428     // is not a versioned thing in this wc
429     case svn_wc_status_unversioned:
430     // is unversioned but configured to be ignored
431     case svn_wc_status_ignored:
432     // an unversioned directory path populated by an svn:externals
433     // property; this status is not used for file externals
434     case svn_wc_status_external:
435     // a directory doesn't contain a complete entries list
436     case svn_wc_status_incomplete:
437     // assume any new status not versioned
438     default:
439         is_versioned = 0;
440     }
441 
442     status[ str_is_versioned ] = Py::Int( is_versioned );
443     status[ str_is_locked ] = Py::Int( svn_status.locked );
444     status[ str_is_copied ] = Py::Int( svn_status.copied );
445     status[ str_is_switched ] = Py::Int( svn_status.switched );
446     status[ str_prop_status ] = toEnumValue( svn_status.prop_status );
447     status[ str_text_status ] = toEnumValue( svn_status.text_status );
448     status[ str_repos_prop_status ] = toEnumValue( svn_status.repos_prop_status );
449     status[ str_repos_text_status ] = toEnumValue( svn_status.repos_text_status );
450 
451     return wrapper_status.wrapDict( status );
452 }
453 
toObject(const svn_wc_entry_t & svn_entry,SvnPool & pool,const DictWrapper & wrapper_entry)454 Py::Object toObject
455     (
456     const svn_wc_entry_t &svn_entry,
457     SvnPool &pool,
458     const DictWrapper &wrapper_entry
459     )
460 {
461     Py::Dict entry;
462 
463     entry[ str_checksum ] = utf8_string_or_none( svn_entry.checksum );
464     entry[ str_commit_author ] = utf8_string_or_none( svn_entry.cmt_author );
465     entry[ str_commit_revision ] = toSvnRevNum( svn_entry.cmt_rev );
466     entry[ str_commit_time ] = toObject( svn_entry.cmt_date );
467     entry[ str_conflict_new ] = path_string_or_none( svn_entry.conflict_new, pool );
468     entry[ str_conflict_old ] = path_string_or_none( svn_entry.conflict_old, pool );
469     entry[ str_conflict_work ] = path_string_or_none( svn_entry.conflict_wrk, pool );
470     entry[ str_copy_from_revision ] = toSvnRevNum( svn_entry.copyfrom_rev );
471     entry[ str_copy_from_url ] = utf8_string_or_none( svn_entry.copyfrom_url );
472     entry[ str_is_absent ] = Py::Int( svn_entry.absent );
473     entry[ str_is_copied ] = Py::Int( svn_entry.copied );
474     entry[ str_is_deleted ] = Py::Int( svn_entry.deleted );
475     entry[ str_kind ] = toEnumValue( svn_entry.kind );
476     entry[ str_name ] = path_string_or_none( svn_entry.name, pool );
477     entry[ str_properties_time ] = toObject( svn_entry.prop_time );
478     entry[ str_property_reject_file ] = path_string_or_none( svn_entry.prejfile, pool );
479     entry[ str_repos ] = utf8_string_or_none( svn_entry.repos );
480     entry[ str_revision ] = toSvnRevNum( svn_entry.revision );
481     entry[ str_schedule ] = toEnumValue( svn_entry.schedule );
482     entry[ str_text_time ] = toObject( svn_entry.text_time );
483     entry[ str_url ] = utf8_string_or_none( svn_entry.url );
484     entry[ str_uuid ] = utf8_string_or_none( svn_entry.uuid );
485 #if defined( PYSVN_HAS_CLIENT_STATUS2 )
486     entry[ str_lock_token ] = utf8_string_or_none( svn_entry.lock_token );
487     entry[ str_lock_owner ] = utf8_string_or_none( svn_entry.lock_owner );
488     entry[ str_lock_comment ] = utf8_string_or_none( svn_entry.lock_comment );
489     entry[ str_lock_creation_date ] = toObject( svn_entry.lock_creation_date );
490 #endif
491 
492     return wrapper_entry.wrapDict( entry );
493 }
494 
495 #if defined( PYSVN_HAS_CLIENT_INFO )
toObject(const svn_info_t & info,const DictWrapper & wrapper_info,const DictWrapper & wrapper_lock,const DictWrapper & wrapper_wc_info)496 Py::Object toObject
497     (
498     const svn_info_t &info,
499     const DictWrapper &wrapper_info,
500     const DictWrapper &wrapper_lock,
501     const DictWrapper &wrapper_wc_info
502     )
503 {
504     Py::Dict py_info;
505 
506     // Where the item lives in the repository.
507     py_info[str_URL] = utf8_string_or_none( info.URL );
508 
509     // The revision of the object.  If path_or_url is a working-copy
510     // path, then this is its current working revnum.  If path_or_url
511     // is a URL, then this is the repos revision that path_or_url lives in.
512     py_info[str_rev] = toSvnRevNum( info.rev );
513 
514     // The node's kind.
515     py_info[str_kind] = toEnumValue( info.kind );
516 
517     // The root URL of the repository.
518     py_info[str_repos_root_URL] = utf8_string_or_none( info.repos_root_URL );
519 
520     // The repository's UUID.
521     py_info[str_repos_UUID] = utf8_string_or_none( info.repos_UUID );
522 
523     // The last revision in which this object changed.
524     py_info[str_last_changed_rev] = toSvnRevNum( info.last_changed_rev );
525 
526     // The date of the last_changed_rev.
527     py_info[str_last_changed_date] = toObject( info.last_changed_date );
528 
529     // The author of the last_changed_rev.
530     py_info[str_last_changed_author] = utf8_string_or_none( info.last_changed_author );
531 
532 #if defined( PYSVN_HAS_CLIENT_LOCK )
533     // An exclusive lock, if present.  Could be either local or remote.
534     if( info.lock == NULL )
535     {
536         py_info[str_lock] = Py::None();
537     }
538     else
539     {
540         py_info[str_lock] = toObject( *info.lock, wrapper_lock );
541     }
542 #endif
543 
544     // Whether or not to ignore the next 10 wc-specific fields.
545     if( info.has_wc_info == 0 )
546     {
547         py_info[str_wc_info] = Py::None();
548     }
549     else
550     {
551         Py::Dict py_wc_info;
552 
553         py_wc_info[str_schedule] = toEnumValue( info.schedule );
554         py_wc_info[str_copyfrom_url] = utf8_string_or_none( info.copyfrom_url );
555         py_wc_info[str_copyfrom_rev] = toSvnRevNum( info.copyfrom_rev );
556         py_wc_info[str_text_time] = toObject( info.text_time );
557         py_wc_info[str_prop_time] = toObject( info.prop_time );
558         py_wc_info[str_checksum] = utf8_string_or_none( info.checksum );
559         py_wc_info[str_conflict_old] = utf8_string_or_none( info.conflict_old );
560         py_wc_info[str_conflict_new] = utf8_string_or_none( info.conflict_new );
561         py_wc_info[str_conflict_work] = utf8_string_or_none( info.conflict_wrk );
562         py_wc_info[str_prejfile] = utf8_string_or_none( info.prejfile );
563 #ifdef PYSVN_HAS_SVN_INFO_T__CHANGELIST
564         py_wc_info[str_changelist] = utf8_string_or_none( info.changelist );
565         py_wc_info[str_depth] = toEnumValue( info.depth );
566 #endif
567 #ifdef PYSVN_HAS_SVN_INFO_T__SIZES
568 #ifdef HAVE_LONG_LONG
569         if( info.working_size == SVN_INFO_SIZE_UNKNOWN )
570             py_wc_info[str_working_size] = Py::None();
571         else
572             py_wc_info[str_working_size] = Py::LongLong( static_cast<PY_LONG_LONG>( info.working_size ) );
573         if( info.size == SVN_INFO_SIZE_UNKNOWN )
574             py_wc_info[str_size] = Py::None();
575         else
576             py_wc_info[str_size] = Py::LongLong( static_cast<PY_LONG_LONG>( info.size ) );
577 #else
578         if( info.working_size == SVN_INFO_SIZE_UNKNOWN )
579             py_wc_info[str_working_size] = Py::None();
580         else
581             py_wc_info[str_working_size] = Py::Int( static_cast<int>( info.working_size ) );
582         if( info.size == SVN_INFO_SIZE_UNKNOWN )
583             py_wc_info[str_size] = Py::None();
584         else
585             py_wc_info[str_size] = Py::Int( static_cast<int>( info.size ) );
586 #endif
587 #endif
588         py_info[str_wc_info] = wrapper_wc_info.wrapDict( py_wc_info );
589     }
590 
591     return wrapper_info.wrapDict( py_info );
592 }
593 #endif
594 
595 #if defined( PYSVN_HAS_CLIENT_INFO3 )
toObject(const svn_wc_conflict_version_t * info)596 Py::Object toObject
597     (
598     const svn_wc_conflict_version_t *info
599     )
600 {
601     if( info == NULL )
602     {
603         return Py::None();
604     }
605 
606     Py::Dict d;
607 
608     d[str_repos_url] = utf8_string_or_none( info->repos_url );
609     d[str_peg_rev] = toSvnRevNum( info->peg_rev );
610     d[str_path_in_repos] = utf8_string_or_none( info->path_in_repos );
611     d[str_node_kind] = toEnumValue( info->node_kind );
612 #if defined( PYSVN_HAS_SVN_1_8 )
613     d[str_repos_UUID] = utf8_string_or_none( info->repos_uuid );
614 #endif
615 
616     return d;
617 }
618 
toHex(const unsigned char * bytes,size_t length)619 Py::String toHex( const unsigned char *bytes, size_t length )
620 {
621     static char hex_digits[] = "0123456789abcdef";
622     std::string human;
623 
624 
625     for( size_t index=0; index < length; ++index )
626     {
627         human += hex_digits[ bytes[index] >> 4 ];
628         human += hex_digits[ bytes[index] & 0x0f ];
629     }
630 
631     return Py::String( human );
632 }
633 
toObject(const svn_client_info2_t & info,SvnPool & pool,const DictWrapper & wrapper_info,const DictWrapper & wrapper_lock,const DictWrapper & wrapper_wc_info)634 Py::Object toObject
635     (
636     const svn_client_info2_t &info,
637     SvnPool &pool,
638     const DictWrapper &wrapper_info,
639     const DictWrapper &wrapper_lock,
640     const DictWrapper &wrapper_wc_info
641     )
642 {
643     Py::Dict py_info;
644 
645     // Where the item lives in the repository.
646     py_info[str_URL] = utf8_string_or_none( info.URL );
647 
648     // The revision of the object.  If path_or_url is a working-copy
649     // path, then this is its current working revnum.  If path_or_url
650     // is a URL, then this is the repos revision that path_or_url lives in.
651     py_info[str_rev] = toSvnRevNum( info.rev );
652 
653     // The root URL of the repository.
654     py_info[str_repos_root_URL] = utf8_string_or_none( info.repos_root_URL );
655 
656     // The repository's UUID.
657     py_info[str_repos_UUID] = utf8_string_or_none( info.repos_UUID );
658 
659     // The node's kind.
660     py_info[str_kind] = toEnumValue( info.kind );
661 
662     py_info[str_size] = toFilesize( info.size );
663 
664     // The last revision in which this object changed.
665     py_info[str_last_changed_rev] = toSvnRevNum( info.last_changed_rev );
666 
667     // The date of the last_changed_rev.
668     py_info[str_last_changed_date] = toObject( info.last_changed_date );
669 
670     // The author of the last_changed_rev.
671     py_info[str_last_changed_author] = utf8_string_or_none( info.last_changed_author );
672 
673     // An exclusive lock, if present.  Could be either local or remote.
674     if( info.lock == NULL )
675     {
676         py_info[str_lock] = Py::None();
677     }
678     else
679     {
680         py_info[str_lock] = toObject( *info.lock, wrapper_lock );
681     }
682 
683     // Whether or not to ignore the next 10 wc-specific fields.
684     if( info.wc_info == NULL )
685     {
686         py_info[str_wc_info] = Py::None();
687     }
688     else
689     {
690         Py::Dict py_wc_info;
691 
692         py_wc_info[str_schedule] = toEnumValue( info.wc_info->schedule );
693         py_wc_info[str_copyfrom_url] = utf8_string_or_none( info.wc_info->copyfrom_url );
694         py_wc_info[str_copyfrom_rev] = toSvnRevNum( info.wc_info->copyfrom_rev );
695         if( info.wc_info->checksum != NULL )
696         {
697             switch( info.wc_info->checksum->kind )
698             {
699             case svn_checksum_md5:
700                 py_wc_info[str_checksum] = toHex( info.wc_info->checksum->digest, 16 );
701                 break;
702             case svn_checksum_sha1:
703                 py_wc_info[str_checksum] = toHex( info.wc_info->checksum->digest, 20 );
704                 break;
705             default:
706                 py_wc_info[str_checksum] = Py::None();
707             }
708         }
709         else
710         {
711             py_wc_info[str_checksum] = Py::None();
712         }
713 
714         py_wc_info[str_changelist] = utf8_string_or_none( info.wc_info->changelist );
715         py_wc_info[str_depth] = toEnumValue( info.wc_info->depth );
716 #ifdef HAVE_LONG_LONG
717         if( info.wc_info->recorded_size == SVN_INFO_SIZE_UNKNOWN )
718             py_wc_info[str_recorded_size] = Py::None();
719         else
720             py_wc_info[str_recorded_size] = Py::LongLong( static_cast<PY_LONG_LONG>( info.wc_info->recorded_size ) );
721 #else
722         if( info.wc_info->recorded_size == SVN_INFO_SIZE_UNKNOWN )
723             py_wc_info[str_recorded_size] = Py::None();
724         else
725             py_wc_info[str_recorded_size] = Py::Int( static_cast<int>( info.wc_info->recorded_size ) );
726 #endif
727         py_wc_info[str_recorded_time] = toObject( info.wc_info->recorded_time );
728 
729         // I'm guesses that recorded size is what used to be size and working_size
730         py_wc_info[str_size] = py_wc_info[str_recorded_size];
731         py_wc_info[str_working_size] = py_wc_info[str_recorded_size];
732 
733         // I'm guesses that recorded date is what used to be text_time and prop_time
734         py_wc_info[str_text_time] = py_wc_info[str_recorded_time];
735         py_wc_info[str_prop_time] = py_wc_info[str_recorded_time];
736 
737         // conflicts
738         int count = 0;
739         if( info.wc_info->conflicts != NULL )
740         {
741             count = info.wc_info->conflicts->nelts;
742         }
743 
744         switch( count )
745         {
746         case 0:
747             py_wc_info[str_conflict_old] = Py::None();
748             py_wc_info[str_conflict_new] = Py::None();
749             py_wc_info[str_conflict_work] = Py::None();
750             py_wc_info[str_prejfile] = Py::None();
751             break;
752 
753         case 1:
754             // map for backwards compatibility
755             {
756             svn_wc_conflict_description2_t *item = ((svn_wc_conflict_description2_t **)info.wc_info->conflicts->elts)[0];
757 
758             py_wc_info[str_conflict_old] = utf8_string_or_none( item->my_abspath );
759             py_wc_info[str_conflict_new] = utf8_string_or_none( item->their_abspath );
760             py_wc_info[str_conflict_work] = utf8_string_or_none( item->merged_file );
761             py_wc_info[str_prejfile] = utf8_string_or_none( item->their_abspath );
762             }
763             break;
764 
765         default:
766             {
767             Py::List all_conflicts;
768 
769             for( int i=0; i<count; ++i )
770             {
771                 Py::Dict conflict;
772 
773                 svn_wc_conflict_description2_t *item = ((svn_wc_conflict_description2_t **)info.wc_info->conflicts->elts)[i];
774 
775                 conflict[str_local_abspath] = path_string_or_none( item->local_abspath, pool );
776                 conflict[str_node_kind] = toEnumValue( item->node_kind );
777                 conflict[str_kind] = toEnumValue( item->kind );
778                 if( item->kind == svn_wc_conflict_kind_property )
779                 {
780                     conflict[str_property_name] = utf8_string_or_none( item->property_name );
781                 }
782                 else
783                 {
784                     conflict[str_property_name] = Py::None();
785                 }
786 
787                 if( item->kind == svn_wc_conflict_kind_text )
788                 {
789                     conflict[str_is_binary] = Py::Boolean( item->is_binary );
790                     conflict[str_mime_type] = utf8_string_or_none( item->mime_type );
791                 }
792                 else
793                 {
794                     conflict[str_is_binary] = Py::None();
795                     conflict[str_mime_type] = Py::None();
796                 }
797                 conflict[str_action] = toEnumValue( item->action );
798                 conflict[str_reason] = toEnumValue( item->reason );
799                 conflict[str_base_abspath] = path_string_or_none( item->base_abspath, pool );
800                 conflict[str_their_abspath] = path_string_or_none( item->their_abspath, pool );
801                 conflict[str_my_abspath] = path_string_or_none( item->my_abspath, pool );
802                 conflict[str_merged_file] = path_string_or_none( item->merged_file, pool );
803 
804                 conflict[str_operation] = toEnumValue( item->operation );
805                 conflict[str_src_left_version] = toObject( item->src_left_version );
806                 conflict[str_src_right_version] = toObject( item->src_right_version );
807 
808                 all_conflicts.append( conflict );
809             }
810 
811             py_wc_info[str_conflicts] = all_conflicts;
812             }
813             break;
814         }
815 
816         py_wc_info[str_wcroot_abspath] = utf8_string_or_none( info.wc_info->wcroot_abspath );
817 #if defined(PYSVN_HAS_SVN_1_8)
818         py_wc_info[str_moved_to_abspath] = utf8_string_or_none( info.wc_info->moved_to_abspath );
819         py_wc_info[str_moved_from_abspath] = utf8_string_or_none( info.wc_info->moved_from_abspath );
820 #endif
821         py_info[str_wc_info] = wrapper_wc_info.wrapDict( py_wc_info );
822     }
823 
824     return wrapper_info.wrapDict( py_info );
825 }
826 #endif
827 
828 #if defined( PYSVN_HAS_CLIENT_LOCK )
toObject(const svn_lock_t & lock,const DictWrapper & wrapper_lock)829 Py::Object toObject
830     (
831     const svn_lock_t &lock,
832     const DictWrapper &wrapper_lock
833     )
834 {
835 
836     Py::Dict py_lock;
837 
838     py_lock[str_path] = utf8_string_or_none( lock.path );
839     py_lock[str_token] = utf8_string_or_none( lock.token );
840     py_lock[str_owner] = utf8_string_or_none( lock.owner );
841     py_lock[str_comment] = utf8_string_or_none( lock.comment );
842     py_lock[str_is_dav_comment] = Py::Boolean( lock.is_dav_comment != 0 );
843     if( lock.creation_date == 0 )
844     {
845         py_lock[str_creation_date] = Py::None();
846     }
847     else
848     {
849         py_lock[str_creation_date] = toObject( lock.creation_date );
850     }
851     if( lock.expiration_date == 0 )
852     {
853         py_lock[str_expiration_date] = Py::None();
854     }
855     else
856     {
857         py_lock[str_expiration_date] = toObject( lock.expiration_date );
858     }
859 
860     return wrapper_lock.wrapDict( py_lock );
861 }
862 #endif
863 
propsToObject(apr_hash_t * props,SvnPool & pool)864 Py::Object propsToObject( apr_hash_t *props, SvnPool &pool )
865 {
866     Py::Dict py_prop_dict;
867 
868     for( apr_hash_index_t *hi = apr_hash_first( pool, props ); hi; hi = apr_hash_next( hi ) )
869     {
870         const void *key = NULL;
871         void *val = NULL;
872 
873         apr_hash_this (hi, &key, NULL, &val);
874         const svn_string_t *propval = (const svn_string_t *)val;
875 
876         py_prop_dict[ Py::String( (const char *)key ) ] = Py::String( propval->data, (int)propval->len );
877     }
878 
879     return py_prop_dict;
880 }
881 
882 #if defined( PYSVN_HAS_CLIENT_PROPGET5 )
inheritedPropsToObject(apr_array_header_t * inherited_props,SvnPool & pool)883 Py::Object inheritedPropsToObject( apr_array_header_t *inherited_props, SvnPool &pool )
884 {
885     Py::Dict all_inherited_props;
886 
887     for (int j = 0; j < inherited_props->nelts; ++j)
888     {
889         svn_prop_inherited_item_t *item = ((svn_prop_inherited_item_t **)inherited_props->elts)[j];
890 
891         Py::String path_or_url = utf8_string_or_none( item->path_or_url );
892         Py::Dict py_prop_dict = propsToObject( item->prop_hash, pool );
893 
894         all_inherited_props[ path_or_url ] = py_prop_dict;
895     }
896 
897     return all_inherited_props;
898 }
899 #endif
900 
proplistToObject(Py::List & py_path_propmap_list,apr_array_header_t * props,SvnPool & pool)901 void proplistToObject( Py::List &py_path_propmap_list, apr_array_header_t *props, SvnPool &pool )
902 {
903     for (int j = 0; j < props->nelts; ++j)
904     {
905         svn_client_proplist_item_t *item = ((svn_client_proplist_item_t **)props->elts)[j];
906 
907         Py::Object py_prop_dict( propsToObject( item->prop_hash, pool ) );
908 
909         std::string node_name( item->node_name->data, item->node_name->len );
910 
911         Py::Tuple py_path_proplist( 2 );
912         py_path_proplist[0] = Py::String( osNormalisedPath( node_name, pool ) );
913         py_path_proplist[1] = py_prop_dict;
914 
915         py_path_propmap_list.append( py_path_proplist );
916     }
917 }
918 
revnumListToObject(apr_array_header_t * revs,SvnPool & pool)919 Py::Object revnumListToObject( apr_array_header_t *revs, SvnPool &pool )
920 {
921     Py::List py_list;
922 
923     for (int j = 0; j < revs->nelts; ++j)
924     {
925         svn_revnum_t revnum = ((svn_revnum_t *)revs->elts)[j];
926         py_list.append( toSvnRevNum( revnum ) );
927     }
928 
929     return py_list;
930 }
931 
asUtf8Bytes(Py::Object obj)932 Py::Bytes asUtf8Bytes( Py::Object obj )
933 {
934     Py::String any( obj );
935     Py::Bytes utf8( any.encode( name_utf8 ) );
936     return utf8;
937 }
938 
targetsFromStringOrList(Py::Object arg,SvnPool & pool)939 apr_array_header_t *targetsFromStringOrList( Py::Object arg, SvnPool &pool )
940 {
941     int num_targets = 1;
942     if( arg.isList() )
943     {
944         Py::List paths( arg );
945         num_targets = paths.length();
946     }
947 
948     apr_array_header_t *targets = apr_array_make( pool, num_targets, sizeof( const char * ) );
949 
950     std::string type_error_message;
951     try
952     {
953         if( arg.isList() )
954         {
955             Py::List path_list( arg );
956 
957             for( Py::List::size_type i=0; i<path_list.length(); i++ )
958             {
959                 type_error_message = "expecting path list members to be strings (arg 1)";
960 
961                 Py::Bytes path_str( asUtf8Bytes( path_list[i] ) );
962                 std::string norm_path( svnNormalisedIfPath( path_str.as_std_string(), pool ) );
963 
964                 *(char **)apr_array_push( targets ) = apr_pstrdup( pool, norm_path.c_str() );
965             }
966         }
967         else
968         {
969             type_error_message = "expecting path to be a string (arg 1)";
970             Py::Bytes path_str( asUtf8Bytes( arg ) );
971 
972             std::string norm_path( svnNormalisedIfPath( path_str.as_std_string(), pool ) );
973 
974             *(char **)apr_array_push( targets ) = apr_pstrdup( pool, norm_path.c_str() );
975         }
976     }
977     catch( Py::TypeError & )
978     {
979         throw Py::TypeError( type_error_message );
980     }
981 
982     return targets;
983 }
984 
arrayOfStringsFromListOfStrings(Py::Object arg,SvnPool & pool)985 apr_array_header_t *arrayOfStringsFromListOfStrings( Py::Object arg, SvnPool &pool )
986 {
987     apr_array_header_t *array = NULL;
988 
989     std::string type_error_message;
990     try
991     {
992         type_error_message = "expecting list of strings";
993         Py::List path_list( arg );
994         Py::List::size_type num_targets = path_list.length();
995 
996         array = apr_array_make( pool, num_targets, sizeof( const char * ) );
997 
998         for( Py::List::size_type i=0; i<num_targets; i++ )
999         {
1000             type_error_message = "expecting list members to be strings";
1001 
1002             Py::Bytes str( asUtf8Bytes( path_list[i] ) );
1003             *(char **)apr_array_push( array ) = apr_pstrdup( pool, str.as_std_string().c_str() );
1004         }
1005     }
1006     catch( Py::TypeError & )
1007     {
1008         throw Py::TypeError( type_error_message );
1009     }
1010 
1011     return array;
1012 }
1013 
toListOfStrings(Py::Object obj)1014 Py::List toListOfStrings( Py::Object obj )
1015 {
1016     Py::List list;
1017     if( obj.isList() )
1018         list = obj;
1019     else
1020         list.append( obj );
1021 
1022     // check all members of the list are strings
1023     for( Py::List::size_type i=0; i<list.length(); i++ )
1024     {
1025         Py::String path_str( list[i] );
1026     }
1027 
1028     return list;
1029 }
1030 
hashOfStringsFromDictOfStrings(Py::Object arg,SvnPool & pool)1031 apr_hash_t *hashOfStringsFromDictOfStrings( Py::Object arg, SvnPool &pool )
1032 {
1033     Py::Dict dict( arg );
1034 
1035     apr_hash_t *hash = apr_hash_make( pool );
1036 
1037     std::string type_error_message;
1038     try
1039     {
1040         Py::List all_keys( dict.keys() );
1041 
1042         for( Py::List::size_type i=0; i<all_keys.length(); i++ )
1043         {
1044             type_error_message = "expecting string key in dict";
1045             Py::Bytes key( asUtf8Bytes( all_keys[i] ) );
1046 
1047             type_error_message = "expecting string value in dict";
1048             Py::Bytes value( asUtf8Bytes( dict[ key ] ) );
1049 
1050             char *hash_key = apr_pstrdup( pool, key.as_std_string().c_str() );
1051             svn_string_t *hash_value = svn_string_create( value.as_std_string().c_str(), pool );
1052 
1053             apr_hash_set( hash, hash_key, APR_HASH_KEY_STRING, hash_value );
1054         }
1055     }
1056     catch( Py::TypeError & )
1057     {
1058         throw Py::TypeError( type_error_message );
1059     }
1060 
1061     return hash;
1062 }
1063 
direntsToObject(apr_hash_t * dirents,SvnPool & pool)1064 Py::Object direntsToObject( apr_hash_t *dirents, SvnPool &pool )
1065 {
1066     Py::Dict py_dirents_dict;
1067 
1068     for( apr_hash_index_t *hi = apr_hash_first( pool, dirents ); hi; hi = apr_hash_next( hi ) )
1069     {
1070         const void *key = NULL;
1071         void *val = NULL;
1072 
1073         apr_hash_this (hi, &key, NULL, &val);
1074         const svn_fs_dirent_t *dirent = (const svn_fs_dirent_t *)val;
1075 
1076         py_dirents_dict[ Py::String( (const char *)key ) ] = toEnumValue( dirent->kind );
1077     }
1078 
1079     return py_dirents_dict;
1080 }
1081 
1082 //------------------------------------------------------------
1083 //
1084 //
1085 //
1086 //------------------------------------------------------------
1087 #if defined( PYSVN_HAS_COMMIT_CALLBACK2_T )
1088 
CommitInfoResult(SvnPool & pool)1089 CommitInfoResult::CommitInfoResult( SvnPool &pool )
1090 : m_all_results( apr_array_make( pool, 16, sizeof( svn_commit_info_t * ) ) )
1091 , m_pool( pool )
1092 {
1093 }
1094 
~CommitInfoResult()1095 CommitInfoResult::~CommitInfoResult()
1096 {
1097 }
1098 
count()1099 int CommitInfoResult::count()
1100 {
1101     if( m_all_results == NULL )
1102         return 0;
1103 
1104     return m_all_results->nelts;
1105 }
1106 
result(int index)1107 const svn_commit_info_t *CommitInfoResult::result( int index )
1108 {
1109     assert( m_all_results != NULL );
1110     assert( index >= 0 && index < m_all_results->nelts );
1111     return APR_ARRAY_IDX( m_all_results, index, const svn_commit_info_t * );
1112 }
1113 
CommitInfoResult_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t *)1114 extern "C" svn_error_t *CommitInfoResult_callback( const svn_commit_info_t *commit_info, void *baton, apr_pool_t * )
1115 {
1116     CommitInfoResult *result = CommitInfoResult::castBaton( baton );
1117 
1118     if( result->m_all_results == NULL )
1119     {
1120         return svn_error_create( APR_ENOMEM, NULL, "no memory for commit info results" );
1121     }
1122 
1123     svn_commit_info_t *copy = svn_commit_info_dup( commit_info, result->m_pool );
1124     if( copy == NULL )
1125     {
1126         return svn_error_create( APR_ENOMEM, NULL, "no memory for commit info results" );
1127     }
1128 
1129     APR_ARRAY_PUSH( result->m_all_results, const svn_commit_info_t * ) = copy;
1130 
1131     return SVN_NO_ERROR;
1132 }
1133 
1134 
toObject(const svn_commit_info_t * commit_info)1135 Py::Object toObject( const svn_commit_info_t *commit_info )
1136 {
1137     Py::Dict commit_info_dict;
1138 
1139     commit_info_dict[ str_date ] = utf8_string_or_none( commit_info->date );
1140     commit_info_dict[ str_author ] = utf8_string_or_none( commit_info->author );
1141     if( commit_info->post_commit_err == NULL )
1142     {
1143         commit_info_dict[ str_post_commit_err ] = Py::None();
1144     }
1145     else
1146     {
1147         // this field is sometimes a pointer to a none UTF8 string
1148         commit_info_dict[ str_post_commit_err ] = utf8_string_or_none( commit_info->post_commit_err );
1149     }
1150 
1151     if( SVN_IS_VALID_REVNUM( commit_info->revision ) )
1152     {
1153         commit_info_dict[ str_revision ] = toSvnRevNum( commit_info->revision );
1154     }
1155     else
1156     {
1157         commit_info_dict[ str_revision ] = Py::None();
1158     }
1159 
1160     return commit_info_dict;
1161 }
1162 
toObject(CommitInfoResult & commit_info,const DictWrapper & wrapper_commit_info,int commit_style)1163 Py::Object toObject( CommitInfoResult &commit_info, const DictWrapper &wrapper_commit_info, int commit_style )
1164 {
1165     if( commit_info.count() == 0 )
1166     {
1167         Py::Dict commit_info_dict;
1168 
1169         commit_info_dict[ str_date ] = Py::None();
1170         commit_info_dict[ str_author ] = Py::None();
1171         commit_info_dict[ str_post_commit_err ] = Py::None();
1172         commit_info_dict[ str_revision ] = Py::None();
1173 
1174         return commit_info_dict;
1175     }
1176 
1177     if( commit_style == 0 )
1178     {
1179         // assume the last commit revision is the best to return
1180         const svn_commit_info_t *last = commit_info.result( commit_info.count()-1 );
1181 
1182         if( !SVN_IS_VALID_REVNUM( last->revision ) )
1183         {
1184             return Py::None();
1185         }
1186 
1187         return toSvnRevNum( last->revision );
1188     }
1189     else
1190     if( commit_style == 1 )
1191     {
1192         const svn_commit_info_t *last = commit_info.result( commit_info.count()-1 );
1193         return toObject( last );
1194     }
1195     else
1196     if( commit_style == 2 )
1197     {
1198         Py::List all_results;
1199 
1200         for( int index=0; index<commit_info.count(); ++index )
1201         {
1202             Py::Dict py_commit_info( toObject( commit_info.result( index ) ) );
1203             all_results.append( wrapper_commit_info.wrapDict( py_commit_info ) );
1204         }
1205 
1206         return all_results;
1207     }
1208     else
1209     {
1210         throw Py::RuntimeError( "commit_style value invalid" );
1211     }
1212 }
1213 #endif
1214